需要深度测试的原因
当我们渲染多个物体时,这多个物体之间存在互相遮挡的关系,被遮挡的物体的部分将不可见,也就是它离相机更远,为了告诉计算机被遮挡的物体不需要渲染,我们就需要对物体上的点做深度测试,检测它是否需要渲染。
为了实现上述的检测,就需要深度缓冲,简单而言就是存储物体上点深度值的数组,假如深度值在0到1之间。这个数组一开始值为1,GPU会把该片元的深度值和已经存在于深度缓冲中的深度值进行比较。当目前渲染的物体的深度值小于缓冲区中存储的深度值时,就将这个值写入缓冲区,同时通过深度测试,如果与此相反,深度值大于缓冲区中的深度值,就是未通过深度测试,就不写入缓冲区,淘汰掉这个点(不做渲染)。
当然,比较函数可以自己定义,接下来会做介绍。
深度测试的时间
深度测试是在片段着色器运行之后(以及模板测试(Stencil Testing)运行之后)运行的
现在大部分的GPU都提供一个叫做提前深度测试(Early Depth Testing)Early Z技术的硬件特性。提前深度测试允许深度测试在片段着色器之前运行。只要我们清楚一个片段永远不会是可见的(它在其他物体之后),我们就能提前丢弃这个片段。
因为片段着色器通常开销都是很大的,如果计算出了深度,但是这个片元却没有通过测试,就是浪费时间,所以我们应该尽可能避免运行它们。当使用提前深度测试时,片段着色器的一个限制是你不能写入片段的深度值。如果一个片段着色器对它的深度值进行了写入,提前深度测试是不可能的。OpenGL不能提前知道深度值。
深度测试函数
深度测试主要有两个步骤,
- 对比缓冲区中的值,判断是否通过测试(Z Test)
- 是否将该值写入缓冲区(Z Write)
深度测试函数默认关闭,开启深度测试函数的方法:
glEnable(GL_DEPTH_TEST);
深度写入函数的控制开关:
可以想象,在某些情况下你会需要对所有片段都执行深度测试并丢弃相应的片段,但不希望更新深度缓冲。基本上来说,你在使用一个只读的(Read-only)深度缓冲。OpenGL允许我们禁用深度缓冲的写入,只需要设置它的深度掩码(Depth Mask)设置为GL_FALSE就可以了:
glDepthMask(GL_FALSE);
glDepthMask(GL_TRUE);
比较函数
通过比较函数,我们可以自定义刷新缓冲区的方式
默认情况下使用的深度函数是GL_LESS,它将会丢弃深度值大于等于当前深度缓冲值的所有片段。
glDepthFunc(GL_ALWAYS);
清除深度缓存
我们每次渲染一遍之后需要将缓存值清空,不然可能会出现画面错误情况
清除方式和清楚颜色缓存一样
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
深度值精度
深度缓冲包含了一个介于0.0和1.0之间的深度值,它将会与观察者视角所看见的场景中所有物体的z值进行比较。观察空间的z值可能是投影平截头体的近平面(Near)和远平面(Far)之间的任何值。我们需要一种方式来将这些观察空间的z值变换到[0, 1]范围之间。
在实践中是几乎永远不会使用这样的线性深度缓冲(Linear Depth Buffer)的。要想有正确的投影性质,需要使用一个非线性的深度方程,它是与 1/z 成正比的。它做的就是在z值很小的时候提供非常高的精度,而在z值很远的时候提供更少的精度。
计算深度值的函数
可以看到,深度值很大一部分是由很小的z值所决定的,这给了近处的物体很大的深度精度。
深度冲突
出现的问题:
- 当大量物体离摄像机极远时,会出现深度值错误的现象,因为太过集中在1这个值处了
- 当大量物体在同一平面时,会出现深度值相同(因为是float,所以可能每次渲染的结果都不同,造成抖动现象)
解决深度冲突
深度冲突不能够被完全避免,但一般会有一些技巧有助于在你的场景中减轻或者完全避免深度冲突、
- 第一个也是最重要的技巧是永远不要把多个物体摆得太靠近,以至于它们的一些三角形会重叠。通过在两个物体之间设置一个用户无法注意到的偏移值,你可以完全避免这两个物体之间的深度冲突。
- 设置相机的远近平面来达到最佳效果
- 使用更高精度的深度缓冲。大部分深度缓冲的精度都是24位的,但现在大部分的显卡都支持32位的深度缓冲,这将会极大地提高精度。