输入文件
输入.obj文件,在解析obj文件时,若一行不是以V、VT、VN、F开头,则可将改行忽略:
- V:代表顶点,格式为V X Y Z;
- VT:表示纹理坐标,每个三角形的三个顶点都要指定一个纹理坐标,格式为VT TU TV;
- VN:法向量,每个三角形的三个顶点都要指定一个法向量,格式为VN NX NY NZ;
- F:面,面后面跟着的整型数表示属于这个面的顶点、纹理、法向量的索引,格式为f Vertex1/Texture1/Normal1 Vertex2/Texture2/Normal2 Vertex3/Texture3/Normal3,f中顶点的序号以1为起始;
区域扫描线zbuffer
背景
区域扫描线z-buffer算法是由z-buffer算法及扫描线z-buffer算法一步步优化得来的:
- z-buffer算法:创建与像素个数相同的z-buffer存储单元和帧缓冲器,将带显示对象的每个面上各点的z坐标与z-buffer中相应单元的z值比较,只有前者大于后者时才改变帧缓冲器对应单元的值,最终以帧缓冲器中的值绘制图像。
- 扫描线z-buffer算法:z-buffer算法需要很大的z缓冲器,工作量较大,因此采用将平面分区域处理的方式,z-buffer的单元数取成和一行上像素数目相同,通过检查每个多边形的投影与当前扫描线的相交情况,比较每对交点之间的像素所在多边形的z值,获得消隐的图像;
- 区域扫描线z-buffer算法:由于每条扫描线被各多边形的投影分割成多个区间,因此只要在区间上任一点处找到该处z值最大的一个面,该区间上每个像素的都用该面的颜色来显示。
区域扫描线算法基本思想
1.开两个一维数组分别作为当前扫描线的z缓冲器和帧缓冲器;
2.处理当前扫描线时;
- 帧缓冲器的初值为背景颜色;
- z缓冲器的初值为最小z值;
3.求出扫描线与场景中各多边形的二维投影之间的交点,一条扫描线分解为一些列区间扫描线段;
4.每一个区间扫描线段位于多边形“内部”和“外部”
- “外部”扫描线段:直接着背景色;
- “内部”扫描线段:位于一个或多个多边形内部
- 位于一个多边形内部,该线段上的像素着多边形的颜色;
- 位于多个多边形内部,比较扫描线段端点的z值,以确定该线段上像素的颜色;
5.当场景中所有多边形处理完毕时,扫描线帧缓冲器中的内容为画面在此扫描线的消隐结果;
扫描线与单个多边形内外关系
1.当扫描线与多边形的一条边相交时:
- 第一个交点表示扫描线段进入多边形;
- 第二个交点表示扫描线段离开多边形;
2.对每一个多边形,用一个“in/out”标记符flag跟踪记录当前扫描线段的状态:
- 初始:flag=0;
- 进入多边形:flag=1;
- 离开多边形:flag=0;
3.对于一条扫描线上一个给定的区间,可能有多个多边形的flag值为1;
4.在当前扫描线上,跟踪flag为1的多边形
- 如果多于一个多边形flag为1,需要比较扫描线区间的z值确定颜色;
区域扫描线算法的数据结构
1.分类多边形表
根据多边形最大y坐标ymax将多边形放入相应类中:
- a,b,c,d:多边形所在平面的方程系数,需要根据读入的三角面片坐标点计算获得;
- id:多边形的编号;
- dy:多边形跨越的扫描线的数目;
- color:多边形的颜色,颜色自行定义;
- nxt:指向下一个与扫描线相交的多边形;
2.分类边表
根据边的上端点y坐标ymax将边放入相应的类中:
- x:边的上端点的x坐标;
- dx:相邻两条扫描线交点的x坐标差dx(-1/k);
- dy:边跨越的扫描线数目;
- id:边所属多边形的编号;
- nxt:指向下一条与扫描线相交的边;
3.活化多边形表
记录当前扫描线与多边形在oxy投影面上投影相交的多边形:
- a,b,c,d:多边形所在平面的方程系数;
- id:多边形的编号;
- dy:多边形跨越的剩余扫描线数目;
- color:多边形的颜色;
- flag:记录扫描线与多边形的内外状态;
- nxt:指向下一个与扫描线相交的多边形;
4.活化边表
记录投影多边形边界与扫描线相交的边对:
- x:边与扫描线交点的x坐标值;
- dx:相邻两扫描线交点的x坐标差(-1/k);
- dy:边跨越的扫描线数目;
- id:边所属多边形的编号;
- nxt:指向下一条与扫描线相交的边(边表按x值的大小次序排列);
NOTE:
一开始想要选择从下至上扫描图像以符合glDrawPixel的行为,但是发现我们在建立分类边表时记录的是y的最大值,从而推算在其下方的扫描线与边的交点,因此还是需要从上至下扫描;所以需要进一步考虑存储图像的问题;
区域扫描线实现
1.构建分类多边形表
遍历每一个三角形面片,计算其最大y值,创建分类多边形,将其放入对应其y值的分类多边形表中;
2.构建分类边表
遍历每一个三角形面片,计算它的三条边,创建分类边,将其放入边y最大值对应的分类边表中;
3.构建活化多边形表
由上至下扫描,将新加入的多边形加入到活化多边形表中;由于进入下一条扫描线,因此需要将多边形跨越的剩余扫描线数目减1。
在数据中需要考虑本来多边形横跨扫描线数目就为0的多边形,此处考虑将剩余扫描线数目小于等于0的多边形都从活化多边形表中删去;
4.构建活化边表
遍历分类边表,更新原有边表,加入新的与扫描线相交的边;
5.zbuffer计算
需要注意在程序中要保证整个图形的各个三角面片都在window中,超出window的部分没有进行考虑;
处理记录
1.在这里稍微对dy进行调整,dy表示边占据的扫描线的条数,例如在同一扫描线上的边其dy为1,加入边表时,跨越的剩余扫描线数目则为0,在下一次处理时由于对resDy的更新,其小于0,将从活化边表中删除;
2.遗留问题:似乎在inPolygon中加入三角面片之后,无法合理删除,因为观察获得的颜色结果,相同颜色占据了后面一大段而没有其他的变化,应该是存在问题。
3.程序实现了,剩下需要对输入的图形进行尺寸调整;
OpenGL绘制zBuffer结果
|
|
我们使用framBufferData存储每个像素的颜色值,共需width x height x 3的存储空间。
该函数用于对齐像素字节,默认4字节对齐,即图像数据字节必须是4的整数倍,则读取数据时,读取4的整数倍的字节数据来渲染一行。若为RGB图像,则一行10个像素共30字节,在4字节对齐模式下,会读取32个字节,这将导致读取越界;因此,此处将数据读取方式改为1字节读取形式,这种方式牺牲了存取效率,但是保证了不会出现存取越界的情况;
该函数设置像素点绘制的起始位置,此处是从窗口中心对像素进行一行一行的绘制;若不适用该函数,则之后的像素将从左下角开始绘制;
根据frameBufferData中存储的颜色数据绘制每一个像素点的颜色,width和height控制绘制图像的宽和高,这两个两和窗口的宽和高没有关系;GL_RGB设置的是frameBufferData中存储的颜色值的排列顺序;GL_FLOAT表示数据的类型;
结果图像
程序测试结果