前言
在上一篇<OpenGL之 甜甜圈与背面剔除>中,讲述了如何绘制甜甜圈,以及在绘制甜甜圈的时候,遇到的隐藏面消除的问题。同时,也讲述了如何使用正背面剔除来解决该问题。
但是,当我们把之前的demo跑起来并且左右旋转之后,会发现又出现了一个问题(如下图),甜甜圈感觉缺了一块,这里的话,就要开启深度测试了。
一、问题产生的原因
当甜甜圈旋转到前后两个位置重叠的时候,而前后都是正面或者背面,OpenGL无法确定哪个图层应该在前,哪个图层应该在后,所以也就出现了图示的情况。
二、如何解决问题
2.1 深度缓存区
2.1.2 深度
深度就是OpenGL坐标系中,像素点的z坐标离观察者的距离。观察者是可以放在坐标系的任意位置的,因此并不能简单的说z值越大或者越小就越靠近观察者。
- 如果观察者在z轴的正方向,则z的值越大,像素点就越靠近观察者
- 如果观察者在z轴的负方向,则z的值越小,像素点就越靠近观察者
2.1.2 深度缓存区
深度缓存区本质上是一种GPU缓存区,存储在显存中,专门存储每个像素点的深度值。它的原理就是把距离观察者平面(近裁剪面)的深度值与窗口中的像素点一一对应,进行关联和存储。
如果想禁止写入的话,可以调用以下函数。
//可以调用来禁止写入。
glDepthMask(GL_FALSE)
2.2 深度测试
在之前的demo中的RenderScene()函数,始终有着一行代码。
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
这行代码的目的就是清除颜色缓存区和深度缓存区。当我们清除深度缓存区的时候,底层操作其实是把所有像素点的深度值设为最大值,默认是1.0(深度值的范围是[0,1]之间)。
深度缓冲区和颜⾊缓存区是对应的,颜⾊缓存区存储像素的颜⾊信息,⽽深度 缓冲区存储像素的深度信息。当我们之后要绘制一个物体表面的时候,首先会将表面像素点的深度与当前深度缓存区对应的深度进行比较,如果是大于深度缓存区对应的深度,则丢弃这部分值;否则的话,则使用当前像素点的颜色和深度更新颜色缓存区和深度缓存区。而这个丢弃和比较的过程,就是 深度测试。
深度测试同时也可以解决隐藏面消除的问题。
2.2.1 开启/关闭深度测试
首先,之前的文章中有讲述到main函数的代码编写,里面有一行代码如下
//初始化双缓冲窗口
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
这里主要就是申请颜色缓存区以及深度缓存区。如果不申请深度缓存区,后续在开启深度测试的时候,会没有效果。
而后,我们需要在之前代码的基础上,在RenderScene()方法中先清除颜色、深度缓存区,然后添加开启深度测试以及关闭深度测试的代码。
//清除颜色、深度缓存区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//开启深度测试
glEnable(GL_DEPTH_TEST);
//关闭深度测试
glDisable(GL_DEPTH_TEST);
到这里,我们可以重新跑一遍我们的甜甜圈程序,会得到一个比较完美的甜甜圈了:
2.2.2 深度测试规则修改
可以通过下述方法修改深度测试规则
void glDepthFunc(GLenum func)
参数 | 说明 |
---|---|
GL_ALWAYS | 总是通过测试 |
GL_NEVER | 总是不通过测试 |
GL_LESS | 当前深度值 < 存储的深度值时通过 |
GL_EQUAL | 当前深度值 = 存储的深度值时通过 |
GL_LEQUAL | 当前深度值 <= 存储的深度值时通过 |
GL_GREATER | 当前深度值 > 存储的深度值时通过 |
GL_NOTEQUAL | 当前深度值 != 存储的深度值时通过 |
GL_GEQUAL | 当前深度值 >= 存储的深度值时通过 |
三、解决问题之后
3.1 Z-Fighting闪烁问题
3.1.1 原因
开启深度测试之后,之前遇到的问题都得到了解决。但是由于精度的限制,对于相差非常小的深度值来说(例如在同一个深度进行了两次渲染),OpenGL可能会出现无法正确判断深度值的情况,从而导致测试结果随机出现,造成了画面交替显示,产生闪烁的问题。这就是Z-Fighting闪烁问题。(如下图)
举个例子,如果一个像素点的深度是0.1000000000000000009,另一个是0.10000000000000000000009,对于低精度的设备来说,在他们看来二者可能都是0.10000000000000000。
从这里也可以知道,现在的设备相当于以往的低精度设备来说其实并不容易出现Z-Fighting。
3.1.2 解决方法
目前OpenGL对Z-Fighting闪烁问题提供了一个解决方案,那就是多边形偏移( Polygon Offset)
解决步骤
- 启用多边形偏移
基本原理就是,在深度测试之前,增大重叠或者深度值极为接近的两个图形的深度值间隔。
//启用多边形偏移
glEnable(GL_POLYGON_OFFSET_FILL)
Polygon Offset ⽅式 | 光栅化模式 |
---|---|
GL_POLYGON_OFFSET_POINT | GL_POINT |
GL_POLYGON_OFFSET_LINE | GL_LINE |
GL_POLYGON_OFFSET_FILL | GL_FILL |
上面三张图分别对应着 GL_FILL光栅化模式、GL_LINE光栅化模式、GL_POINT光栅化模式,默认情况下是GL_FILL光栅化模式。
而不同的光栅化模式,在开启多边形偏移的时候,就要设置对应的多边形偏移方式。
- 指定偏移量
指定偏移量的时候,需要2个参数,分别是factor , units。使用下述方法来设置。
void glPolygonOffset(GLFloat factor,GLFloat units)
在计算偏移量的时候,遵循的是一个总偏移计算方程式,如下:
Offset = ( m * factor ) + ( r * units);
m:多边形的深度的斜率的最⼤值,理解⼀个多边形越是与近裁剪⾯平⾏,m 就越接近于0
r : 能产⽣于窗⼝坐标系的深度值中可分辨的差异最⼩值.r 是由具体是由具体OpenGL 平台指定的 ⼀个常量.
在使用glPolygonOffset方法的时候,开发者是无法直接得知m和r具体值的,因此这里不必过多纠结于这两个参数,而对于factor和units来说,一般情况下都设置为-1.0即可。
- 当Offset的结果小于0的时候,将使z值距离观察者更近;
- 当Offset的结果大于0的时候,将使z值距离观察者更远;
关于这部分内容,附上部分网络上资料,感兴趣的朋友可以自行深入了解。
- 关闭多边形偏移
//关闭多边形偏移,参数必须要和开启的时候一样
glDisable(GL_POLYGON_OFFSET_FILL)
3.1.2 如何预防
- 首先应该尽量避免将两个物体靠的太近,避免渲染的时候,三角形叠在一起。
- 尽可能将近裁剪⾯设置得离观察者远⼀些,观察者离近裁剪面越近,则对深度测试的精度要求就越高,因此,可以尽量让近裁剪⾯远⼀些。但是同时,也有可能导致距离观察者较劲的物体被裁剪。因此需要调试好裁剪面参数。
- 使用更高位数的深度缓冲区,通常使⽤的深度缓冲区是24位的,如果能够使⽤32位的缓冲区,则可以使深度测试的精确度得到提⾼。