1、基本图元连接方式
图元primitive,即图形元素,是可以编辑的最小图形单位。图元是图形软件用于操作和组织画面的最基本的素材。一幅画面由图元组成,图元是一组最简单的、最通用的几何图形或字符。
OpenGL的基本图元有点(Point)、线段(Line)、多边形(Ploygon)、三角形(Triangle)、四边形(Quadrangle)。
线段又分为独立线段、不闭合的线(Line Strip)和首尾闭合的线(Line Loop)。
三角形分为独立三角形、三角形链(Triangle Strip)和三角扇形(Triangle Fan)。
四边形则分为独立的四边形和四边形链(Quadrangle Strip)。
多边形Polygon可以看成是三角形带GL_TRIANGLE_TRIPE组成。
图元 | 描述 |
---|---|
GL_POINTS | 每个顶点在屏幕上都是单独点 |
GL_LINES | 每⼀对顶点定义⼀个线段 |
GL_LINE_STRIP | 从第⼀个顶点依次经过各个后续顶点的线段 |
GL_LINE_LOOP | 和GL_LINE_STRIP相同,但是最后⼀个顶点和第⼀个顶点连接起来了 |
GL_POLYGON | 按点的定义顺序依次连接 |
GL_TRIANGLES | 每3个顶点定义⼀个新的三⻆形,三角形之间是独立的 |
GL_TRIANGLE_STRIP | 共⽤⼀个条带(strip)上的顶点的⼀组三⻆形 |
GL_TRIANGLE_FAN | 以⼀个圆点为中⼼呈扇形排列,共⽤相邻顶点的⼀组三⻆形 |
三角形带、三角形扇
相对于GL_TRIANGLES,当我们会需要绘制⼏个相连的三角形时,我们可以使用GL_TRIANGLE_STRIP图元绘制⼀串相连三⻆角形,从⽽节省⼤量的时间。
优点:
用前3个顶点指定第1个三角形之后,对于接下来的每⼀个三角形,只需要再指定1个顶点。需要绘制⼤量的三⻆形时,采用这种⽅方法可以节省⼤量的程序代码和数据存储空间。
提供运算性能和节省带宽。更少的顶点意味着数据从内存传输到图形卡的速度更快,并且顶点着⾊器需要处理的次数也更少了。
2、正背面剔除(Face Culling)
在绘制一个3D场景的时候,处于对性能和实际情况的考虑,我们是不是应该决定哪些部分是对观察者可见的部分,哪些是观察者不可见的部分,而不可见的部分,我们是不是不应该渲染,这样符合实际情况,也节省了性能。这种情况叫做“隐藏面消除”(Hidden surface elimination).例如下面这张图片,有黑色的部分,也有红色的部分,显而易见,黑色的部分不应该让观察者看到。
无论使用平面着色器还是默认光源着色器都会出现“隐藏面消除”问题,只不过使用用平面着色器时没有黑影效果,看不出来而已。
解决办法:油画算法
油画算法:
先绘制场景中的离观察者较远的物体,再绘制较近的物体
下图先绘制红色的部分,在绘制黄色的问题,最后绘制灰色的部分,即可解决隐藏面消除的问题。
油画法的弊端:
油画法虽然解决的隐藏面消除的问题,我们进一步思考在上面的图形绘制中,三个图形有交汇的地方,我们是不是绘制了三遍,影响了性能。还有我们无法绘制图形相互叠加层次混乱的问题。如下图:
解决方案: 正背面剔除(Face Culling)
我们定义一个多边形有两个表面,我们将一个标为正面,一个为背面。通常,后表面总是不可见的,这是因为场景中大多数物体是密封的。例如盒子、圆柱体、箱子等,并且我们也不能把摄相机放入物体的内部。因此摄相机永不可能看到多边形的背面。
看不到的面,我们是可以不绘制来提高渲染性能。OpenGL可以开启正背面剔除做到检查所有正面朝向观察者的面,并渲染它们,从而丢弃背面朝向的⾯,这样可以节约渲染资源,提高渲染效率。
正面背面区分
传递给OpenGL绘制的顶点数据是按照一定顺序传递的,如果我们规定逆时针的连接方向为正面,顺时针的连接为背面,那么OpenGL就知道绘制计算规律,按照正面的原则渲染图形。正面和背面是有三角形的顶点定义顺序和观察者方向共同决定的,随着观察者的角度⽅向的改变,正面背面也会跟着改变。
默认正面: 按照逆时针顶点连接顺序的三角形面
默认背面: 按照顺时针顶点连接顺序的三⻆形面
正背面剔除使用:
开启表面剔除(默认背面剔除)
void glEnable(GL_CULL_FACE);
关闭表面剔除(默认背面剔除)
void glDisable(GL_CULL_FACE);
⽤户选择剔除那个面(正面/背面)
void glCullFace(GLenum mode);
mode参数为: GL_FRONT,GL_BACK,GL_FRONT_AND_BACK ,默GL_BACK
用户指定那个为正面
void glFrontFace(GLenum mode);
mode参数为: GL_CW,GL_CCW,默认值:GL_CCW
例如,剔除正面实现(1)
glCullFace(GL_BACK);
glFrontFace(GL_CW);
例例如,剔除正⾯面实现(2)
glCullFace(GL_FRONT);
正背面剔除无法解决的问题
正背面剔除能解决显示正面,剔除背面的功能,但是无法解决同时都是正面时,那个图层在前,那个图层在后,无法确定显示哪个正面,出现图像显示问题。
3、深度测试
隐藏⾯消除,除了使⽤正背⾯剔除,还可以使⽤深度测试来解决。
深度 :
就是在openGL坐标系中,像素点的Z坐标距离观察者的距离。当观察者可以放在坐标系的任意位置,所以不能简单的说Z数值越⼤或越⼩,观察者就越靠近物体。如果观察者在Z轴的正⽅向,Z值越⼤则靠近观察者;如果观察者在Z轴的负⽅向,Z值越⼩则靠近观察者。
深度缓冲区(DepthBuffer) :
深度缓冲区在显存中。深度缓存区原理就是把距离观察者平⾯(近裁剪⾯)的深度值与窗⼝中每个像素点1对1进⾏关联以及存储,每个像素都需要存储一个深度值。
深度缓冲区(DepthBuffer)和颜⾊缓存区(ColorBuffer)是对应的。颜⾊缓存区存储像素的颜⾊信息,⽽深度缓冲区存储像素的深度信息。深度缓存区的默认值为1.0,表示最⼤的深度值,深度值的范围是[0,1]之间。
- 深度测试 : 在决定是否绘制⼀个物体表⾯时,⾸先要将表⾯对应的像素的深度值与当前深度缓冲区中的值进⾏⽐较,如果⼤于深度缓冲区中的值,则丢弃这部分,否则利⽤这个像素对应的深度值和颜⾊值,分别更新深度缓冲区和颜⾊缓存区,这个过程称为”深度测试”。
// 清空深度缓存区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 开启深度测试
glEnable(GL_DEPTH_TEST);
// 关闭深度测试
glDisable(GL_DEPTH_TEST);
我们可以通过 glDepthFunc(GLenum func)来修改深度测试的测试规则,那么测试的规则主要在于该函数中的枚举值。
深度测试的Z-fighting(Z冲突.闪烁)问题:
开启深度测试后,OpenGL就不会再去绘制模型被遮挡的部分,这样实现的显示更加真实。但是由于深度缓冲区精度的限制对于深度相差⾮常⼩的情况(例如在同⼀平⾯上进⾏2次制)。OpenGL就可能出现不能正确判断两者的深度值,会导致深度测试的结果不可预测,显示出来的现象是前⾯几个画⾯交错出现。
同⼀个位置上出现的图层,且深度值出现精确度很低时,就会容易引起 ZFighting现象。表示几个物体靠的⾮常的近,⽆法确定谁在前,谁在后。 ⽽出现显示歧义。
解决ZFighting问题:
既然是因为靠的太近,⽆法区分图层先后. 那么此时,就可以在2个图层之间加⼊⼀个微妙的间隔. OpenGL提供了"多边形偏移"解决⽅案。
多边形偏移解决⽅法:
让深度值之间产⽣间隔,如果2个图形之间有间隔,是不是意味着就不会产⽣⼲涉。可以理解为在执⾏深度测试前将⽴⽅体的深度值做⼀些细微的增加,于是就能将重叠的2个图形深度值之前有所区分。
// 启⽤多边形偏移
glEnable(GL_POLYGON_OFFSET_FILL)
GL_POLYGON_OFFSET_POINT 对应模式: GL_POINT
GL_POLYGON_OFFSET_LINE 对应模式: GL_LINE
GL_POLYGON_OFFSET_FILL 对应模式: GL_FILL
// 设置参数,每个Fragment的深度值都会增加如下所示的偏移量:
// ⼀般⽽⾔,只需要将factor和units简单赋值为-1就基本可以满⾜需求。
glPolygonOffset(-1, -1)
// 关闭多边形偏移
glDisable(GL_POLYGON_OFFSET_FILL)
// glPolygonOffset(Glfloat factor, Glfloat units);
// 每个Fragment的深度值都会增加的偏移量: Offset = ( m * factor ) + ( r * units);
// m:多边形的深度的斜率的最⼤值,理解⼀个多边形越是与近裁剪⾯平⾏, m 就越接近于0。
// r:能产⽣于窗⼝坐标系的深度值中可分辨的差异最⼩值。r是由OpenGL平台指定的⼀个常量.
// Offset:⼀个⼤于0的Offset会把模型推到离你(摄像机)更远的位置,相应的⼀个⼩于0的Offset会把模型拉近。
// ⼀般⽽⾔,只需要将factor和units简单赋值为-1就基本可以满⾜需求。
如何预防ZFighting闪烁问题
- 不要将两个物体靠的太近,避免渲染时三⻆形叠在⼀起。这种⽅式要求对场景中物体插⼊⼀个少量的偏移,那么就可能避免ZFighting现象。例如上⾯的⽴⽅体和平⾯问题中,将平⾯下移0.001f就可以解决这个问题。当然⼿动去插⼊这个⼩的偏移是要付出代价的。
- 尽可能将近裁剪⾯设置得离观察者远⼀些。上⾯我们看到,在近裁剪平⾯附近,深度的精确度是很⾼的,因此尽可能让近裁剪⾯远⼀些的话,会使整个裁剪范围内的精确度变⾼⼀些。但是这种⽅式会使离观察者较近的物体被裁减掉,因此需要调试好裁剪⾯参数。
- 使⽤更⾼位数的深度缓冲区,通常使⽤的深度缓冲区是24位的,现在有⼀些硬件使⽤使⽤32/64位的缓 冲区,使精确度得到提⾼