一、深度测试:Depth Test
1.回顾深度测试的内容
- 深度测试位于渲染管线哪个位置
○ 深度测试位于逐片元操作中、模板测试后、透明度混合前
为什么做深度测试
● 深度测试可以解决:物体的可见遮挡性问题
○ 我们可以用一个例子说明
■ 图的解释:
● 首先先渲染紫色三角形,紫色三角形的深度值为5,当渲染它的时候,它与深度缓冲区中的∞做比较,因为默认的test比较条件为LEqual,所以5小于∞,并且写入了深度缓冲器
● 之后进行黄色三角形的渲染,和上一步同理,会进行深度对比并进行相关操作,渲染完成后的结果如右图下边所示
深度测试流程图
● 具体每一步都可以对应之前讲的笔记理解
深度测试的逻辑/伪代码
2.深度测试带来的问题
● 前边已经说过很多次了,就是性能浪费、OverDraw
● 简单地概括就是
○ 在深度测试前计算过的片元,有一些通过不了深度测试将会被直接抛弃(图中的红色片元),那么之前做得计算就都是无效计算了。
● 这个问题的解决方法就是:early-z,先进行取舍,然后进入测试。
二、提前深度测试:early-z
1.Early-Z的内容
● 是在传统管线中的光栅化阶段之后、片元着色器之前加的一步操作。
○ 图中的例子:
■ 片元1写入深度后,在渲染片元2、3的时候,会进行提前深度测试(z-cull),因为没有通过,所以这两个片元不会被计算
● 区分两次深度测试
■ 提前的深度测试叫作Z-Cull
■ 后续的深度测试为了确定正确的遮挡关系,叫作Z-Check
○ 也就是在计算之前就做一次深度测试,如果不通过就直接不计算了,这样就避免了无效的计算
● 补充:
○ Early-Z同样可以搭配使用模板测试
2.Early-Z失效的情况
● ①开启Alpha Test 或 clip/discard等手动丢弃片元操作
○ 通常Early-Z不仅会进行深度测试,还要进行深度写入
○ 例如以下情况:
■ 如果经过AlphaTest,前面渲染的片元被丢弃了(但写入了深度),那么后续的像素都将无法正常渲染。
● ②手动修改GPU插值得到的深度
○ 类似上述情况
● ③开启Alpha Blend
○ 开启了透明度混合不会开启深度写入,也就不符合Early-Z了
● ④关闭深度测试
○ 都关了还测试啥
3.高效利用Early-Z
- 不透明物体由远往近渲染,early-z将没有任何优化效果
● 在渲染前,将不透明物体从近往远渲染的话,Early-Z能发挥最大的性能优化
● 具体怎么排序?
○ ->可以让cpu将物体按照由近到远的顺序排好,再交付给gpu进行渲染
○ 问题:
■ 复杂的场景,cpu性能消耗很大
■ 严格按照由近到远的顺序渲染,将不能同时搭配批处理优化手段。
● 有没有其他方法? ->pre-z
三、使用 Z-Prepass(Pre-Z)
1.方式1:双pass
- 内容
● 使用两个pass
○ pass1:Z-prepass中仅仅写入深度,不计算输出任何颜色。目的只是为了深度值写入缓冲区
○ pass2:关闭深度写入,将深度比较函数改为相等,进行正常的透明度混合(AlphaBlend)
● 效果:
○ 每个物体都会渲染两个pass,且所有物体的z-prepass的结果就自动形成了一个最小深度值的缓冲区Z-buffer,无需cpu进行排序
● 代码:
问题1:动态批处理
○ 多pass shader无法进行动态批处理 —> Draw Call问题 - 问题2:Draw Call
○ 使用z-prepass shader 的物体,draw call会多一倍
2.方式2:提前分离的Prepass
用于解决DrawCall问题
- 内容
● 仍然使用两个pass
○ 将pass1的z-prepass单独分离出一个shader,并用这个shader将场景的不透明物体先渲染一遍
○ 原来shader中的pass,仍然关闭深度写入,深度比较函数仍然为相等,进行正常的透明度混合
-勘误
● //摘自评论区:
● URP的SRP batch做的合批是不会减少Draw Call的
○ 他的最大的优化在于合并set pass call,减少set pass call的开销
○ 因为CPU上的最大开销来自于准备工作(设置工作)
○ 而非DrawCall本身(这只是要放置GPU命令缓冲区的一些字节而已),draw call是不会减少的
3.Pre-Z也是透明渲染的一种解决方案
● 这样会存在一个问题:无法看到透明物体的背面
● 解决方法:透明物体的双面渲染
○ 核心思路:将渲染分为正面背面两部分
○ pass1:
■ 只渲染背面(cull front)
○ pass2
■ 只渲染正面(cull back)
○ 由于Unity会顺序执行Subshader中的各个Pass,所以我们可以保证背面总是在正面被渲染之前渲染,来得到正确的深度渲染关系
四、Z-prepass的其他问题
1.Z-prepass的性能消耗是否能被忽视
● 国外论坛一位名为lipsryme的老哥做了一项实验:
○ 可以看到,Z-prepass的消耗为2.0ms,而带来的优化只减少了0.3ms(2.7-2.4)
○ 后续讨论中,发现Z-prepass是需要根据项目的实际情况来决定是否采用的。
● 总结有以下建议
○ 当一个有非常多OverDraw的场景,且不能很好的将不透明物体从前往后进行排序时,可以考虑使用PreZ进行优化
○ 注意,PreZ会增加DrawCall,如果用错了可能是负优化
五、Early-Z 和 Z-prepass的实例应用
1.面片叠加的头发渲染
● 对于半透明的面片来说,需要从后往前进行排序渲染才能得到正确的透明度混合结果
2.排序后的头发渲染
●
● 分为3个pass
○ pass1
■ 处理不透明部分,开启Alpha test透明度测试,仅通过不透明的像素,
■ 关闭背面剔除
■ 开启深度写入
○ pass2
■ 剔除正面,渲染背面
○ pass3
■ 剔除背面,渲染正面
● 问题:会带来非常多OverDraw的问题
3.性能改善
● 使用Early-Z剔除
● 透明度测试开启时Early-Z无法使用的解决方案:
○ 使用一个简单的shader进行透明度测试形成 Z-Buffer,(就是我们上边说的提前分离的z-prepass)
4.改善的渲染方案
● pass1:准备Z-Buffer
○ 开启透明度测试
○ 关闭背面剔除
○ 开启深度写入,深度测试设置为less
○ 关闭颜色缓冲区写入
○ 用于一个简单的片元着色器来返回透明度值
● pass2、pass3、pass4参考之前排序后的头发渲染部分,同理
六、其他参考资料
● https://www.cnblogs.com/ghl_carmack/p/10166291.html —深入剖析GPU Early Z优化
七、总结Early-Z的限制
● 适用情况:
○ Early-Z适用于OverDraw很高的情况
● 会失效的情况:
○ ①开启Alpha Test 或 clip/discard等手动丢弃片元操作
○ ②手动修改GPU插值得到的深度
○ ③开启Alpha Blend
○ ④关闭深度测试
● 如何生效?
○ Early-Z属于硬件优化,如果硬件支持会自动开启