1 - 深度测试

在入门阶段,我们使用深度缓冲(Depth Buffer)来让渲染的箱子可以被正确显示。本文将进一步讨论这些存储在深度缓冲/Z-Buffer中的深度值,以及它们是如何确定片段的先后次序的。

深度测试

就像颜色缓冲那样,深度缓冲也存储于每个片段中,精度通常为24位float(也有16、32位的)。当深度测试(Depth Testing)启用时,OpenGL会将一个片段的深度值和当前深度缓冲的内容做对比,如果通过深度测试,深度缓冲就会被更新;反之深度值会被丢弃。

深度测试是在片段着色器运行后和模板测试(Stencil Testing)运行后才在屏幕空间开始的。可以通过GLSL内建变量gl_FragCoord在片段着色器中直接访问深度值,就是与深度缓冲内容所对比的那个值。

现在大多数GPU都提供一个叫做 提前深度测试(Early Depth Testing)的硬件特性,允许深度测试在片段着色器之前运行,以减小片段着色器的开销。

启用深度测试代码如下:

glEnable(GL_DEPTH_TEST);

并且也不要忘记在每次渲染前清除上一次的深度缓冲:

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

如果希望进行深度测试,但不更新深度缓冲,只需设定深度掩码为GL_FALSE

glDepthMask(GL_FALSE);

深度测试函数

深度测试函数决定深度测试以什么方式运行,可以通过glDepthFunc()函数来设定:

glDepthFunc(GL_LESS);

它的参数如下:

参数含义
GL_ALWAYS永远通过深度测试
GL_NEVER永远不通过深度测试
GL_LESS在片段深度值小于缓冲的深度值时通过测试
GL_EQUAL在片段深度值等于缓冲区的深度值时通过测试
GL_LEQUAL在片段深度值小于等于缓冲区的深度值时通过测试
GL_GREATER在片段深度值大于缓冲区的深度值时通过测试
GL_NOTEQUAL在片段深度值不等于缓冲区的深度值时通过测试
GL_GEQUAL在片段深度值大于等于缓冲区的深度值时通过测试

默认情况下参数为GL_LESS

深度值精度

深度值的范围是\([0.0,1.0]\),而物体的z值却没有这个范围,为了方便深度测试,我们得想法儿把z值映射到深度值的范围中。主要有两种方法:线性深度缓冲和非线性深度缓冲。

线性深度缓冲

线性深度缓冲的计算式如下: \[ \nonumber F_{depth}=\frac{z-near}{far-near} \] 其中,near和far对应着视锥体的near和far。该方程将z值线性变换到了\([0,1]\)中:

但这样还是太简单了,从右往左看这个图,我们希望z值较大的时候深度值都看起来差不多,但当z值较小或更小时,深度值的变化较明显,因此计算式得是非线性的。

非线性深度缓冲

非线性深度缓冲的计算式如下: \[ \nonumber F_{depth}=\frac{1/z-1/near}{1/far-1/near} \]

通过让方程与1/z成正比,得到如下的效果:

这样的变换趋势是符合我们要求的。

可视化深度缓冲

前面说过,片段着色器的内建向量gl_FragCoord.z记录了特定片段的深度值,可以将其当作颜色输出:

FragColor = vec4(vec3(gl_FragCoord.z), 1.0);

可以看到,效果是不怎么明显的,得离它很近才能看清效果:

因此可以尝试将非线性深度缓冲变成线性的,让效果更明显一点(首先将非线性深度值变成标准化的z坐标,然后用从投影矩阵推出来的公式去让z值变成线性深度值):

float near = 0.1;
float far = 100.0;
float z = gl_FragCoord.z * 2.0 - 1.0;
float linearDepth = (2.0 * near * far) / (far + near - z * (far - near));

FragColor = vec4(vec3(linearDepth / 10), 1.0);

深度冲突

当两个图元很紧密地排列在一起时,就有可能出现深度冲突。深度缓冲没有足够的精度来决定两个图元的先后次序,导致在交界处出现闪烁等现象。这就是 深度冲突(Z-fighting)

深度冲突不能被完全避免,但做好下面几点可以减轻深度冲突:

  • 不要把多个物体摆放的太靠近。
  • 尽可能将近平面设置远一些。将近平面远离观察者,会对视锥体有着更大的深度精度,但也不要太远,可能会把近处的物体给裁剪掉。
  • 使用高精度深度缓冲,虽然性能会有损耗。

参考资料

  • 深度测试 - LearnOpenGL CN (learnopengl-cn.github.io)