深度测试

讨论这些储存在深度缓冲(或z缓冲(z-buffer))中的深度值(Depth Value),以及它们是如何确定一个片段是处于其它片段后方的。 深度缓冲就像颜色缓冲(Color Buffer)(储存所有的片段颜色:视觉输出)一样,在每个片段中储存了信息,并且(通常)和颜色缓冲有着一样的宽度和高度。深度缓冲是由窗口系统自动创建的,它会以16、24或32位float的形式储存它的深度值。在大部分的系统中,深度缓冲的精度都是24位的。 1. 工作过程

当深度测试(Depth Testing)被启用的时候,OpenGL会将一个片段的的深度值与深度缓冲的内容进行对比。OpenGL会执行一个深度测试,如果这个测试通过了的话,深度缓冲将会更新为新的深度值。如果深度测试失败了,片段将会被丢弃。

  1. 工作阶段

深度缓冲是在片段着色器运行之后(以及模板测试(Stencil Testing)运行之后,我们将在下一节中讨论)在屏幕空间中运行的。屏幕空间坐标与通过OpenGL的glViewport所定义的视口密切相关,并且可以直接使用GLSL内建变量gl_FragCoord从片段着色器中直接访问。gl_FragCoord的x和y分量代表了片段的屏幕空间坐标(其中(0, 0)位于左下角)。gl_FragCoord中也包含了一个z分量,它包含了片段真正的深度值。z值就是需要与深度缓冲内容所对比的那个值。

  1. 附加阶段

现在大部分的GPU都提供一个叫做提前深度测试(Early Depth Testing)的硬件特性。提前深度测试允许深度测试在片段着色器之前运行。只要我们清楚一个片段永远不会是可见的(它在其他物体之后),我们就能提前丢弃这个片段。

  1. 使用 > 深度测试默认是禁用的,所以如果要启用深度测试的话,我们需要用GL_DEPTH_TEST选项来启用它: cpp glEnable(GL_DEPTH_TEST);

    因为储存了深度值,所以每次循环前需要使用GL_DEPTH_BUFFER_BIT来清除深度缓冲,否则你会仍在使用上一次渲染迭代中的写入的深度值 cpp glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    仅进行深度测试,不更新深度值。也就是希望使用一个只读的(Read-only)深度缓冲,则可以设置深度掩码为false cpp glDepthMask(GL_FALSE);

    设置深度测试的运算符 深度测试函数glDepthFunc cpp glDepthFunc(GL_LESS); |函数 | 描述 | |:- | :- | |GL_ALWAYS |永远通过深度测试| |GL_NEVER |永远不通过深度测试| |GL_LESS |在片段深度值小于缓冲的深度值时通过测试| |GL_EQUAL |在片段深度值等于缓冲区的深度值时通过测试| |GL_LEQUAL |在片段深度值小于等于缓冲区的深度值时通过测试| |GL_GREATER |在片段深度值大于缓冲区的深度值时通过测试| |GL_NOTEQUAL|在片段深度值不等于缓冲区的深度值时通过测试| |GL_GEQUAL |在片段深度值大于等于缓冲区的深度值时通过测试|

  2. 深度值精度 > 深度缓冲包含了一个介于0.0和1.0之间的深度值,它将会与观察者视角所看见的场景中所有物体的z值进行比较。观察空间的z值可能是投影平截头体的近平面(Near)和远平面(Far)之间的任何值。我们需要一种方式来将这些观察空间的z值变换到[0, 1]范围之间,其中的一种方式就是将它们线性变换到[0, 1]范围之间。下面这个(线性)方程将z值变换到了0.0到1.0之间的深度值:

    \[ F_{depth} = \frac{z - near}{far - near}, 从近到远插值。 \]

    实际应用中不适用线性深度缓冲,而是使用 倒数,由于非线性方程与 1/z 成正比,在1.0和2.0之间的z值将会变换至1.0到0.5之间的深度值,这就是一个float提供给我们的一半精度了,这在z值很小的情况下提供了非常大的精度。在50.0和100.0之间的z值将会只占2%的float精度,这正是我们所需要的。这样的一个考虑了远近距离的方程是这样的:

    \[ F_{depth} = \frac{\frac{1}{z} - \frac{1}{near}}{\frac{1}{far} - \frac{1}{near}}, 从近到远插值。 \]

  3. 深度可视化 >内建gl_FragCoord向量的z值包含了那个特定片段的深度值 > 颜色大部分都是黑色,因为深度值的范围是0.1的近平面到100的远平面,它离我们还是非常远的。结果就是,我们相对靠近近平面,所以会得到更低的(更暗的)深度值。

        void main()
        {
            FragColor = vec4(vec3(gl_FragCoord.z), 1.0);
        }

    由于gl_FragCoord.z非线性的,所以转换为线性的,首先我们需要首先将深度值从[0, 1]范围重新变换到[-1, 1]范围的标准化设备坐标(裁剪空间)

        float z = depth * 2.0 - 1.0;

    接下来使用获取到的z值,应用逆变换来获取线性的深度值,这个方程是用投影矩阵推导得出的 cpp float linearDepth = (2.0 * near * far) / (far + near - z * (far - near));

  4. 深度冲突 > 一个很常见的视觉错误会在两个平面或者三角形非常紧密地平行排列在一起时会发生,深度缓冲没有足够的精度来决定两个形状哪个在前面。结果就是这两个形状不断地在切换前后顺序,这会导致很奇怪的花纹。这个现象叫做深度冲突(Z-fighting),因为它看起来像是这两个形状在争夺(Fight)谁该处于顶端。

    1. 防止深度冲突
      • 第一个也是最重要的技巧是永远不要把多个物体摆得太靠近,以至于它们的一些三角形会重叠。
      • 第二个技巧是尽可能将近平面设置远一些。在前面我们提到了精度在靠近近平面时是非常高的,所以如果我们将近平面远离观察者,我们将会对整个平截头体有着更大的精度。
      • 另外一个很好的技巧是牺牲一些性能,使用更高精度的深度缓冲。大部分深度缓冲的精度都是24位的,但现在大部分的显卡都支持32位的深度缓冲,这将会极大地提高精度。所以,牺牲掉一些性能,你就能获得更高精度的深度测试,减少深度冲突。

文章作者: Yonggang Long
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Yonggang Long !
 上一篇
2022-08-10 Yonggang Long
下一篇 
2022-08-10 Yonggang Long
  目录