OpenGL 渲染技巧之深度测试
在上篇的 正背面剔除的文章中我们使用正背面剔除的的方式解决了甜甜圈正背面显示错乱的问题,但是呢同时引出了新的问题,当甜甜圈旋转的时候出现了缺口,如下图,那这是为什么呢?又该如何解决呢?
image-20200712095351068从图中可以看出,在甜甜圈旋转过程中,当前后两部分重叠时,对于我们而言,需要显示的是前面部分,后面部分是隐藏面,但是OpenGL中并不能清除的区分,两个图层谁显示在前,谁显示在后,由此导致甜甜圈产生了缺口。
在解决这个问题前,我们先了解几个概念
深度
深度其实就是该像素点在3D世界中距离Z值距离摄像机的距离。
- 如果摄像机在Z的正轴,那么Z值越大像素离摄像机越近。
- 如果摄像机在Z的负轴那么Z值越小,像素离摄像机也就越近。
深度缓冲区
深度缓存区是指一块专门内存区域,存储在显存中,用于存储屏幕上所绘制图形的每个像素点的深度值。
- Z正轴深度值越大,离观察者越近。
- z负轴深度值越小,离观察者越近。
为什么需要深度缓冲区?
在不使用深度测试的时候,如果我们先绘制一个距离比较近的物体,再绘制距离较远的物体,则距离远的物体,因为后绘制,会把距离近的物体覆盖掉,有了深度缓冲区后,绘制物体的顺序就不那么重要了,只要存在深度缓冲区,OpenGL都会把像素的深度值写入到缓冲区中,除非调用glDepthMask(GL_FALSE)来禁止写入。
在前面的文章中,我们的RenderScene
函数中,调用glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
这其中就有清除深度缓冲区,如果我们不清除,那么就会有旧的数据,对新图的绘制造成影响。
深度测试
一个物体在绘制时,像素点新的深度值需要与深度缓存中已经存在的深度值作比较,如果 新值 > 旧值,则丢弃这部分不绘制,反之,将新的深度值更新至深度缓存区,由于深度缓存区与颜色缓存区是一一对应的,同时也需要更新该像素点的颜色值到颜色缓存区,这个过程就是深度测试。
深度值的计算
深度值一般由16位,24位或者32位值表示,通常是24位。位数越高的话,深度的精确度越好。深度值的范围在[0,1]之间,值越小表示越靠近观察者,值越⼤表示远离观察者。 深度缓冲主要是通过计算深度值来⽐较⼤小,在深度缓冲区中包含深度值介于0.0和1.0之间, 从观察者看到其内容与场景中的所有对象的 z 值进行了比较。这些视图空间中的 z 值可以在投影平面截体的近平面和远平面之间的任何值。我们因此需要一些⽅法来转换这些视图空间 z 值 到 [0,1] 的范围内,下面的 (线性) 方程把 z 值转换为 0.0 和 1.0 之间的值:
image-20200712110231334利用深度测试解决问题
现在我们利用深度测试来解决我们甜甜圈缺口的问题。
在之前的RenderScene
函数中增加如下代码,来开启或者关闭深度测试。
//根据设置iDepth标记来判断是否开启深度测试
if(iDepth)
glEnable(GL_DEPTH_TEST);
else
glDisable(GL_DEPTH_TEST);
在右键菜单的回调函数ProcessMenu
中增加如下的开关代码:
void ProcessMenu(int value)
{
switch(value)
{
case 1:
iDepth = !iDepth;
break;
}
glutPostRedisplay();
}
调用glutPostRedisplay
函数手动触发重绘。这时候我们的甜甜圈就符合了我们的现实认知了。
既没有缺口问题,也没有了隐藏面消除的问题。完美!
深度测试应用场景
- 类似于甜甜圈的缺口问题,当旋转时,OpenGL无法区分物体的两部分重叠情况,导致缺口出现。
- 利用深度测试解决隐藏面的消除。
隐藏面消除方案 总结
- 正背面消除:需要根据顶点数据顺序判断用户可见部分与隐藏面,隐藏面直接丢弃,不绘制,只绘制可见部分。
- 深度测试:可以一次性解决隐藏面消除问题,原理是不管有多少图层,只显示可见图层,剩余不可见的都丢弃。
多边形偏移
Z-Fighting(Z冲突,闪烁)
当我们打开了深度测试的时候,就有可能产生Z-Fighting(Z冲突,闪烁)问题,那什么是Z-Fighting(Z冲突,闪烁)呢?它又是如何产生的呢?
因为开启深度测试后,OpenGL就不会再去绘制模型被遮挡的部分,这样实现的显示更加真实,但是由于深度缓冲区对精度的限制,导致对于深度相差非常小的情况下,(例如在同一平面上进行两次绘制),OpenGL就可能出现不能正确判断两者的深度值,会导致深度测试的结果不可预测,显示出来的现象时交错闪烁,前后两个画面交错出现。就像下面这个样子。
image-20200712112354179那么该如何解决这个问题呢?
解决方案
其问题产生的主要原因是由于图形靠的太近,导致无法区分出图层先后次序,针对该问题,OpenGL提供了一种多边形偏移(Polygon Offset)
方案。
主要是让深度值之间产生间隔。如果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 -
指定偏移量
通过
glPolygonOffset
来指定。gPolygonOffset
需要2个参数:factor
,units
每个Fragment的深度值都会增加如下所示的偏移量:Offset= ( m* factor)+( r* units);
void glPolygonOffset(Glfloat factor,Glfloat units); // 应用到片段上总偏移计算方程式: Depth Offset = (DZ * factor)+ (r。units); // DZ:深度值(Z值) // r:使得深度缓冲区产生变化的最小值。负值,将使得z值距离我们更近,而正值,将使得z值距离我们更远,我们设置factor和units设置为-1, -1
-
在绘制完成后,关闭多边形偏移
glDisable(GL_POLYGON_OFFSET_FILL)
预防ZFighting闪烁
- 避免两个物体靠的太近:在绘制时,插入一个小偏移。
- 将近裁剪面(设置透视投影时设置)设置的离观察者远一些:提高裁剪范围内的精确度。
- 使用更高位数的深度缓冲区:提高深度缓冲区的精确度。
参考文献
[Style_月月]https://www.jianshu.com/p/7918c66e3d1d