首页 > 技术文章 > 深度测试

GarrettWale 2019-08-13 08:17 原文

一: 深度测试

  1. 深度缓冲(Depth Buffer)来防止被阻挡的面渲染到其它面的前面。

  2. 深度缓冲就像颜色缓冲(Color Buffer)(储存所有的片段颜色:视觉输出)一样,在每个片段中储存了信息,并且(通常)和颜色缓冲有着一样的宽度和高度。深度缓冲是由窗口系统自动创建的,它会以16、24或32位float的形式储存它的深度值。在大部分的系统中,深度缓冲的精度都是24位的。

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

  4. 深度缓冲是在片段着色器运行之后(以及模板测试(Stencil Testing)运行之后)在屏幕空间中运行的。屏幕空间坐标与通过OpenGL的glViewport所定义的视口密切相关,并且可以直接使用GLSL内建变量gl_FragCoord从片段着色器中直接访问。gl_FragCoord的x和y分量代表了片段的屏幕空间坐标(其中(0, 0)位于左下角)。

  5. gl_FragCoord中也包含了一个z分量,它包含了片段真正的深度值。z值就是需要与深度缓冲内容所对比的那个值。

  6. 深度测试默认是禁用的,所以如果要启用深度测试的话,我们需要用GL_DEPTH_TEST选项来启用它。

  7. 当它启用的时候,如果一个片段通过了深度测试的话,OpenGL会在深度缓冲中储存该片段的z值;如果没有通过深度缓冲,则会丢弃该片段。如果你启用了深度缓冲,你还应该在每个渲染迭代之前使用GL_DEPTH_BUFFER_BIT来清除深度缓冲,否则你会仍在使用上一次渲染迭代中的写入的深度值。

  8. 在某些情况下你会需要对所有片段都执行深度测试并丢弃相应的片段,但不希望更新深度缓冲。基本上来说,你在使用一个只读的(Read-only)深度缓冲。OpenGL允许我们禁用深度缓冲的写入,只需要设置它的深度掩码(Depth Mask)设置为GL_FALSE就可以了。

  9. 深度测试函数:OpenGL允许我们修改深度测试中使用的比较运算符。这允许我们来控制OpenGL什么时候该通过或丢弃一个片段,什么时候去更新深度缓冲。我们可以调用glDepthFunc函数来设置比较运算符
    9.1 默认情况下使用的深度函数是GL_LESS,它将会丢弃深度值大于等于当前深度缓冲值的所有片段。
    9.2 深度值精度:深度缓冲包含了一个介于0.0和1.0之间的深度值,它将会与观察者视角所看见的场景中所有物体的z值进行比较。观察空间的z值可能是投影*截头体的**面(Near)和远*面(Far)之间的任何值。我们需要一种方式来将这些观察空间的z值变换到[0, 1]范围之间,其中的一种方式就是将它们线性变换到[0, 1]范围之间。

    • 要想有正确的投影性质,需要使用一个非线性的深度方程,它是与 1/z 成正比的。它做的就是在z值很小的时候提供非常高的精度,而在z值很远的时候提供更少的精度。花时间想想这个:我们真的需要对1000单位远的深度值和只有1单位远的充满细节的物体使用相同的精度吗?线性方程并不会考虑这一点。
    • 重要的是要记住深度缓冲中的值在屏幕空间中不是线性的(在透视矩阵应用之前在观察空间中是线性的)。深度缓冲中0.5的值并不代表着物体的z值是位于*截头体的中间了,这个顶点的z值实际上非常接***面!
    • 深度值很大一部分是由很小的z值所决定的,这给了*处的物体很大的深度精度。这个(从观察者的视角)变换z值的方程是嵌入在投影矩阵中的,所以当我们想将一个顶点坐标从观察空间至裁剪空间的时候这个非线性方程就被应用了。
      非线性

    9.3 如果我们想要可视化深度缓冲的话,非线性方程的效果很快就会变得很清楚。
    9.4 屏幕空间中的深度值是非线性的,即它在z值很小的时候有很高的精度,而z值很大的时候有较低的精度。

  10. 深度缓冲的可视化
    10.1 我们知道片段着色器中,内建gl_FragCoord向量的z值包含了那个特定片段的深度值。如果我们将这个深度值输出为颜色,我们可以显示场景中所有片段的深度值。我们可以根据片段的深度值返回一个颜色向量来完成这一工作。
    10.2 片段的深度值会随着距离迅速增加,所以几乎所有的顶点的深度值都是接*于1.0的。如果我们小心地靠*物体,你可能会最终注意到颜色会渐渐变暗,显示它们的z值在逐渐变小
    10.3 深度值的非线性性质:*处的物体比起远处的物体对深度值有着更大的影响。只需要移动几厘米就能让颜色从暗完全变白。
    10.4 我们也可以让片段非线性的深度值变换为线性的。要实现这个,我们需要仅仅反转深度值的投影变换。这也就意味着我们需要首先将深度值从[0, 1]范围重新变换到[-1, 1]范围的标准化设备坐标(裁剪空间)。接下来我们需要像投影矩阵那样反转这个非线性方程(方程2),并将这个反转的方程应用到最终的深度值上。最终的结果就是一个线性的深度值了。
    10.5 将屏幕空间中非线性的深度值变换至线性深度值的完整片段着色器如下:

    #version 330 core
    out vec4 FragColor;
    float near = 0.1; 
    float far  = 100.0; 
    float LinearizeDepth(float depth) 
    {
    	  float z = depth * 2.0 - 1.0; // back to NDC 
    	  return (2.0 * near * far) / (far + near - z * (far - near));    
    }
    void main()
    {             
    	  float depth = LinearizeDepth(gl_FragCoord.z) / far; // 为了演示除以 far
    	  FragColor = vec4(vec3(depth), 1.0);
    } 
    
  • 由于线性化的深度值处于near与far之间,它的大部分值都会大于1.0并显示为完全的白色。通过在main函数中将线性深度值除以far,我们*似地将线性深度值转化到[0, 1]的范围之间。这样子我们就能逐渐看到一个片段越接*投影*截头体的远*面,它就会变得越亮,更适用于展示目的。

  • 颜色大部分都是黑色,因为深度值的范围是0.1的**面到100的远*面,它离我们还是非常远的。结果就是,我们相对靠***面,所以会得到更低的(更暗的)深度值。

    1. 深度冲突
      11.1 一个很常见的视觉错误会在两个*面或者三角形非常紧密地*行排列在一起时会发生,深度缓冲没有足够的精度来决定两个形状哪个在前面。结果就是这两个形状不断地在切换前后顺序,这会导致很奇怪的花纹。这个现象叫做深度冲突(Z-fighting)。
      11.2 箱子被放置在地板的同一高度上,这也就意味着箱子的底面和地板是共面的(Coplanar)。这两个面的深度值都是一样的,所以深度测试没有办法决定应该显示哪一个。
      11.3 深度冲突是深度缓冲的一个常见问题,当物体在远处时效果会更明显(因为深度缓冲在z值比较大的时候有着更小的精度)。深度冲突不能够被完全避免,但一般会有一些技巧有助于在你的场景中减轻或者完全避免深度冲突、
      11.4 防止深度冲突:不要把多个物体摆得太靠*,以至于它们的一些三角形会重叠;尽可能将**面设置远一些;使用更高精度的深度缓冲。

推荐阅读