深度测试详解

深度测试是一种用来判断在场景中的物体之间的前后遮挡关系,以此决定哪些片段能够显示在屏幕上的技术。

深度测试的实现一般是用一个跟viewport拥有相同宽度高度的z-buffer来存储当前最浅深度,在片段着色器运行之后,根据是否通过深度测试来决定是否将片段颜色信息写入。如果通过将该片段颜色值写入colorbuffer,深度值用来更新z-buffer。

精度问题:

在opengl gl_FragCoord中查到的值实际上是NDC空间的值映射到0-1.0之间。并且这个0-1的值并不是线性的,这是因为经过projection矩阵 + 透视除法将点从view空间转换到裁剪空间到NDC(标准设备坐标系)在这个映射过程中,z-buffer存入的值是非线性的,在靠近近平面的部分精度很高,但稍远一点就精度很低,导致z-fighting。这是透视投影导致的,但是这个深度值也可以进行还原。(或者说为什么不把标准设备坐标里的w传进来?)

防止深度冲突的方法:

1.远平面近平面尽可能距离别太远,这样远处的会深度冲突

2.尽可能将远平面设置远一点

3.把两个物体留一点空隙

4.更高精度深度缓冲32位

回答什么是深度测试:

  1. 首先回答为什么要做深度测试(物体遮挡关系)

  1. 回答深度测试的实现方式(z-buffer)

  1. 深度测试在opengl里面的问题,比如精度问题(原因以及体现在可视化里的状况及避免方法)

为什么要让深度测试在着色之后?

个人理解,如果放在着色之前,那么他连alphatest都做不了,因为假设透明物体在更近的位置,那么首先进行深度测试,通过之后更新深度缓存,开始着色,中间发现要进行丢弃,但是深度缓存已经更新了,比之前的物体更近的就无法通过深度测试了。当然你也可以说那我从远到近依次渲染不就好了,问题是那你都从远到近渲染了,要深度缓存干什么?

如果是early z 延迟渲染等方法,先对所有物体做一遍深度测试,那这就只能渲染出最外面的一层壳,透明物体是做不了的。除非也先渲染透明物体再渲染不透明物体。

以下部分因为是从笔记里誊抄出来的,已经忘记是从哪里摘抄的了,如果涉及侵权请联系我删除。

透明物体渲染

概述

一般来说,我们使用Alpha Test(透明度测试)或者Alpha Blend(透明度混合)来实现透明效果,其中Alpha Blend(之后简称)Blend可以实现真正的半透明效果。

无论哪种实现方式,我们都需要关闭透明物体的深度写入(禁止ZWrite)。

为什么要关闭透明物体的深度写入?(见下面)

设想一种情况,假设半透明物体A在不透明物体B的前面。

如果开启了深度写入,那么由于A在B的前面,ZBuffer中存储的肯定是A的Z值,那么B就无法通过ZTest,导致A的颜色会覆盖掉B的颜色。这样最终呈现的效果就是半透明物体A挡住了不透明物体B,这显然不是我们需要的结果。

所以,我们必须关闭透明物体的深度写入,不过这样做实际上破坏了深度缓冲的工作机制,这带来了很大的副作用,为了让深度缓冲机制正常工作,我们就必须严格的控制物体的渲染顺序。

半透明物体和不透明物体的渲染顺序

假设半透明物体A和不透明物体B,A仍然在B的前面。

  • 先渲染半透明物体A再渲染不透明物体B :

(1) 渲染A,ZBuffer为空,通过ZTest,由于关闭了透明物体的ZWrite,所以A的Z值不会写入ZBuffer

(2) 渲染B,实际上此时ZBuffer还是初始值,B通过ZTest之后直接写入ZWrite,导致B的颜色覆盖掉A。

  • 先渲染不透明物体B再渲染透明物体A

(1) 渲染B,ZBuffer为空,通过ZTest,B的Z值写入ZBuffer。

(2) 渲染A,由于A是在B的前面,所以A会通过ZTest(但不写入ZWrite),之后根据设置好的Blend公式做出混合操作,实现半透明效果。

结论是,如果半透明物体和不透明物体共存,那么首先开启ZWrire,渲染不透明物体。再关闭ZWrite,渲染半透明物体。

半透明物体和半透明物体的渲染顺序

假设半透明物体A和半透明物体B, A在B的前面。

  • 先渲染A再渲染B :

(1) 渲染A,ZBuffer为空,通过ZTest,将A写入Framebuffer(颜色缓冲不是ZBuffer)

(2) 渲染B,ZBuffer仍然为空,通过ZTest,将B和A颜色混合。如果使用SrcAlpha和OneMinusSrcAlpha这样的公式,会让B的颜色重于A,造成了B在A前面的视觉效果。

  • 先渲染B再渲染A :

(1) 渲染B,ZBuffer为空,通过ZTest,将B写入Framebuffer(颜色缓冲不是ZBuffer)

(2) 渲染A,ZBuffer仍然为空,通过ZTest,将B和A颜色混合。如果使用SrcAlpha和OneMinusSrcAlpha这样的公式,会让A的颜色重于B,A在B前面的视觉效果,正是我们要的效果。

结论是,多个透明物体,要按照由远到近的顺序渲染。

PS :Blend公式

DstColor(new)=SrcAlpha∗SrcColor+(1−SrcAlpha)∗DstColor(old)

其中DstColor是颜色缓冲中的颜色值,SrcColor是(纹理采样+光照计算)后的的颜色,SrcAlpha是纹理采样的alpha通道。

开启ZWrite的透明效果

关闭ZWrite并依靠排序的方法来搞定渲染顺序,有时候也会出错(因为某个model很可能是多个不在一个平面上的不规则模型),这里有一种开启ZWrite的渲染方法。

核心思想就是渲染两次:

第一遍渲染开启ZWrite但是不把颜色写入到Framebuffer中,得到一个正确结果的ZBuffer

第二遍渲染就像正常的Blend过程,关闭ZWrite直接混合,由于深度信息已经正确,所以无需排序。

这样的方法是比较消耗性能的,之前的Blend效果就比较可以,一般不使用这种方式。

PS : 这种方法仅适用于渲染半透明物体,对不透明物体不需要这么做。

双向透明渲染

核心思想也是渲染两次:

第一次渲染,CullFront,渲染背面,剔除前面,当然这个过程是关闭ZWrite的,就像常规的Blend操作。

第二次渲染,CullBack,渲染前面,剔除背面,当然这个过程是关闭ZWrite的,就像常规的Blend操作。

这两次渲染隐含了半透明物体共存时,渲染顺序必须是由远到近的顺序渲染。

为啥要关闭z write?

最近复习透明物体的渲染方式,让我又浮现出了曾经的疑惑:为什么透明物体渲染要关闭深度写入?于是查了很多资料,却也没有把这个问题说的特别明白,而且很多情况下分别对比深度开和关的渲染结果,二者也没有任何显示上的差异,最后借用《Unity Shader入门精要》里面的一个例子摆弄了一下,终于把这个问题想明白了。

大部分的渲染引擎对透明物体的处理方式过程为:首先渲染所有的不透明物体,其次对所有的透明物体由远到近后排序后开启混合进行渲染(在此过程中关闭深度写入)。

我一开始的想法是这样的,既然不透明物体都经过由远到近排序了,那么即使深度写入是开启的,所有透明物渲染时也是可以通过深度测试的,实际上,对于简单场景模型来说深度写入关与否确实是没有区别。

而问题在渲染单个复杂模型的情况下就暴露出来了,一般情况下我们无法对单个模型进行像素级别的渲染排序,(到光栅化这一步,画bounding box, 光栅化,着色,并不是排好序的,比如这里左下角先渲染)以下面的这个透明环状物体为例,最左侧部分的网格最先渲染,在开启深度写入的情形下,后面部分的网格就无法通过深度测试,结果被GPU剔除了。再对比一下第二张图,关闭深度写入的情形,所有的片元都会参与到混合渲染的过程中,虽然渲染结果并非完全正确,但是对比之下也是相对正确。(ps:想要获得理想的透明效果不是一件简单的事情,暂时不在本文讨论范围之内)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值