LearnOpenGL-高级OpenGL-1.深度测试

本人刚学OpenGL不久且自学,文中定有代码、术语等错误,欢迎指正

我写的项目地址:https://github.com/liujianjie/LearnOpenGLProject

深度测试

  • 有点长的简介

    • 深度缓冲就像颜色缓冲(Color Buffer 储存所有的片段颜色:视觉输出)一样,在每个片段中储存了信息,并且(通常)和颜色缓冲有着一样的宽度和高度
    • 深度缓冲是由窗口系统自动创建的,它会以16、24或32位float的形式储存它的深度值。在大部分的系统中,深度缓冲的精度都是24位的。
    • 当深度测试(Depth Testing)被启用的时候,OpenGL会将一个片段的深度值与深度缓冲的内容进行对比。OpenGL会执行一个深度测试,如果这个测试通过了的话,深度缓冲将会更新为新的深度值。如果深度测试失败了,片段将会被丢弃
    • 深度缓冲是在片段着色器运行之后(以及模板测试(Stencil Testing)运行之后)在屏幕空间中运行的。
    • 屏幕空间坐标与通过OpenGL的glViewport所定义的视口密切相关,并且可以直接使用GLSL内建变量gl_FragCoord从片段着色器中直接访问。
    • gl_FragCoord的x和y分量代表了片段的屏幕空间坐标(其中(0, 0)位于左下角)。gl_FragCoord中也包含了一个z分量,它包含了片段真正的深度值
  • 提前深度测试

    • 是硬件属性

    • 只要我们清楚当前渲染的片段永远不会是可见的(它在其他物体之后),我们就能提前丢弃这个片段。

    • 提前深度测试允许深度测试在片段着色器之前运行。

    • 片段着色器通常开销都是很大的,所以我们应该尽可能避免运行它们来渲染像素片段,而提前深度测试可以丢弃片段减少片段着色器运行。

  • 当使用提前深度测试时

    • 片段着色器的一个限制是不能写入片段的深度值。

    • 如果一个片段着色器对它的深度值进行了写入,提前深度测试是不可能的。

  • 程序相关

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

      glEnable(GL_DEPTH_TEST);
      
    • 如果启用了深度缓冲,还应该在每个渲染迭代之前使用GL_DEPTH_BUFFER_BIT来清除深度缓冲,否则会仍在使用上一次渲染迭代中的写入的深度值:

      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      

      清除后,深度缓冲区内的深度值默认都是1

    • 需要对所有片段都执行深度测试并丢弃相应的片段,但希望更新深度缓冲,一个只读的(Read-only)深度缓冲,可以用以下函数

      glDepthMask(GL_FALSE);
      

深度测试函数

  • 设置测试函数代码

    glDepthFunc(GL_LESS);
    
  • 参数

    函数描述
    GL_ALWAYS永远通过深度测试
    GL_NEVER永远不通过深度测试
    GL_LESS在片段深度值小于缓冲的深度值时通过测试
    GL_EQUAL在片段深度值等于缓冲区的深度值时通过测试
    GL_LEQUAL在片段深度值小于等于缓冲区的深度值时通过测试
    GL_GREATER在片段深度值大于缓冲区的深度值时通过测试
    GL_NOTEQUAL在片段深度值不等于缓冲区的深度值时通过测试
    GL_GEQUAL在片段深度值大于等于缓冲区的深度值时通过测试
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_ALWAYS);
    

例子

  • GL_LESS的效果

    请添加图片描述

  • GL_ALWAYS的效果

    后绘制的会覆盖先绘制的片段,如下:后绘制的地板会覆盖先绘制的cube

深度值精度

  • 引入

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

    注意所有的方程都会将非常近的物体的深度值设置为接近0.0的值,而当物体非常接近远平面的时候,它的深度值会非常接近1.0。

    • 而实际中

      我们不需要对1000单位远的深度值和只有1单位远的充满细节的物体使用相同的精度

      意思是:

      近处物体的深度值要对比明确,正确的分清谁大谁小,应该值的范围设大一点。若值的范围小,当两物体相近的话,深度值相似,对比深度值可能出错,而远处的要求没那么高,可以粗略的对比就行。

  • 非线性变换

    OpenGl默认使用非线性

    由于非线性方程与 1/z 成正比,在1.0和2.0之间的z值将会变换至1.0到0.5之间的深度值,这就是一个float提供给我们的一半精度了,这在z值很小的情况下提供了非常大的精度。在50.0和100.0之间的z值将会只占**2%**的float精度

深度缓冲的可视化

非线性效果

  • 代码

    #version 330 core
    out vec4 FragColor;
    void main(){ 
    	FragColor = vec4(vec3(gl_FragCoord.z), 1.0);
    }
    

    在片段着色器用gl_FragCoord向量的z值包含了那个特定片段的深度值

  • 效果

    似乎都是白的,即:深度值接近1

    靠近物体才可以看到有黑的,即:深度值接近0

将非线性转为线性效果

  • 如何将非线性转为线性

    • 将深度值变换为NDC:标准化设备坐标(裁剪空间)

      float z = depth * 2.0 - 1.0;
      
    • 用获取到的z值,应用逆变换来获取线性的深度值:

      float linearDepth = (2.0 * near * far) / (far + near - z * (far - near));
      
  • 代码

    #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;// 从屏幕空间回到NDC空间
    	return (2.0 * near * far) / (far + near - z * (far - near));
    }
    
    void main(){ 
    	float depth = LinearizeDepth(gl_FragCoord.z) / far;
    	FragColor = vec4(vec3(depth), 1.0);
    }
    

    由于线性化的深度值处于near与far之间,它的大部分值都会大于1.0并显示为完全的白色。通过在main函数中将线性深度值除以far,我们近似地将线性深度值转化到[0, 1]的范围之间

  • 效果

    颜色大部分都是黑色,因为深度值的范围是0.1的平面到100的平面(根据投影矩阵设置),它离我们还是非常远的。结果就是,我们相对靠近近平面,所以会得到更低的(更暗的)深度值。

深度冲突

介绍

  • 什么时候出现

    在两个平面或者三角形非常紧密地平行排列在一起时会发生,深度缓冲没有足够的精度来决定两个形状哪个在前面

  • 造成后果

    这两个形状不断地在切换前后顺序,这会导致很奇怪的颜色

  • 例子

    箱子被放置在地板的同一高度上,这也就意味着箱子的底面和地板是共面的(Coplanar)。这两个面的深度值都是一样的,所以深度测试没有办法决定应该显示哪一个。

防止深度冲突

  • 永远不要把多个物体摆得太靠近,以至于它们的一些三角形会重叠

    • 具体做法

      通过在两个物体之间设置一个用户无法注意到的偏移值

    • 例子

      在箱子和地板的例子中,我们可以将箱子沿着正y轴稍微移动一点,箱子位置的这点微小改变将不太可能被注意到,但它能够完全减少深度冲突的发生。

    • 缺点

      然而,这需要对每个物体都手动调整,并且需要进行彻底的测试来保证场景中没有物体会产生深度冲突。

  • 尽可能将近平面设置远一些

    • 具体做法

      在前面我们提到了精度在靠近平面时是非常高的,所以如果我们将平面远离观察者,我们将会对整个平截头体有着更大的精度(由上讨论的非线性函数图像)。

      glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
      

      将0.1f放大

    • 缺点

      然而,将近平面设置太远将会导致近处的物体被裁剪掉,所以这通常需要实验和微调来决定最适合场景的平面距离。

  • 使用更高精度的深度缓冲

    • 详细说明

      大部分深度缓冲的精度都是24位的,但现在大部分的显卡都支持32位的深度缓冲,这将会极大地提高精度。

    • 代价

      牺牲了一些性能,获得更高精度的深度测试,减少深度冲突。

这三个技术是最普遍也是很容易实现的抗深度冲突技术了。还有一些更复杂的技术,但它们依然不能完全消除深度冲突。

深度冲突是一个常见的问题,但如果组合使用了上面列举出来的技术,可能不会再需要处理深度冲突了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘建杰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值