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)