深度测试:Depth Test
渲染管线位置
深度测试位于逐片元操作中、模板测试后、透明度混合前
深度测试解决什么问题
解决物体可见遮挡性的问题✊
深度测试流程
深度测试伪代码
1 | // 遍历每个三角形 |
深度测试带来的问题
- OverDraw问题
- 片元进行了着色计算,却因为深度测试不通过被舍弃,白计算了
- 可以在CPU端对物体排序,保证离相机近的物体在前面。但物体一多排序也会造成性能损耗,需要Trade-off
- 由于光栅化阶段已经知道深度了,所以一个简单的构想是在片元着色器前进行一个Early-Z(额外的深度测试)
提前深度测试:Early-Z
什么是Early-Z
-
从渲染管线看,是在传统管线中的光栅化阶段之后、片元着色器之前加的一步操作
-
案例:片元1写入深度后,在渲染片元2、3的时候,会进行提前深度测试(z-cull),因为没有通过,所以这两个片元不会被计算
-
区分两次深度测试
- 提前的深度测试叫作Z-Cull
- 后续的深度测试为了确定正确的遮挡关系,叫作Z-Check
- 提前深度测试可以避免无效着色计算
-
Early-Z同样可以搭配使用模板测试
Early-Z失效
Early-Z并不能完全解决OverDraw问题,下面情况下会导致失效
-
开启Alpha Test 或 clip/discard等手动丢弃片元操作
- 通常Early-Z不仅会进行深度测试,还要进行深度写入
- 如果经过AlphaTest,前面渲染的片元被丢弃了(但写入了深度),那么后续的像素都将无法正常渲染
-
手动修改GPU插值得到的深度
-
开启Alpha Blend(透明度混合需要关闭深度写入)
-
关闭深度测试
高效利用Early-Z
-
如果不透明物体由远往近渲染,Early-Z将没有任何优化效果
-
在渲染前,将不透明物体从近往远渲染的话,Early-Z能发挥最大的性能优化
- 可以让cpu将物体按照由近到远的顺序排好,再交付给gpu进行渲染
- 复杂的场景,cpu性能消耗很大
- 严格按照由近到远的顺序渲染,将不能同时搭配批处理优化手段
-
有没有其他方法---->Pre-Z
Z-Prepass(Pre-Z)
理解
深度测试就是为了确认有哪些片元能显示在屏幕上,但它不管多少片元参与计算,如果能提前知道谁该显示,那就能避免那些看不见的片元做无用功了。
而Pre-Z就是来做这项工作的,当然它也会需要一定的计算量。
做法1:双PASS
- 流程
- pass1:Z-Prepass中仅仅写入深度,不计算输出任何颜色。目的只是为了深度值写入缓冲区
- pass2:关闭深度写入,将深度比较函数改为相等,进行正常的着色计算
- 效果
- 每个物体都会渲染两个pass,且所有物体的Z-Prepass的结果就自动形成了一个最小深度值的缓冲区Z-buffer,无需cpu进行排序
- 问题
- 多pass shader无法进行动态批处理,无法有效减少Draw Call
- 使用Z-Prepass shader 的物体,Draw Call会多一倍
做法2:提前分离的Prepass
- 流程
- 将pass1的Z-Prepass单独分离出一个shader,并用这个shader将场景的需要Prepass的物体先渲染一遍
- 原来shader中的pass,仍然关闭深度写入,深度比较函数仍然为相等,进行正常的着色计算
- 参考:雨松大佬文章
- 这样做法还有个好处是能利用上URP的SRP Batch,不会打断合批
- SRP Batch最大的优化在于合并set pass call,减少set pass call的开销,而并非能减少DrawCall
- 合批相关知识可看这篇:https://zhuanlan.zhihu.com/p/432223843
Pre-Z也是透明渲染的一种解决方案
-
单Pass由于关闭深度写入,同一个模型的遮挡关系就不能保证了
-
但使用Pre-Z会导致:无法看到透明物体的背面
-
解决方法:透明物体的双面渲染
- 核心思路:将渲染分为正面背面两部分
- pass1:只渲染背面(cull front)
- pass2:只渲染正面(cull back)
- 由于Unity会顺序执行Subshader中的各个Pass,所以我们可以保证背面总是在正面被渲染之前渲染,来得到正确的深度渲染关系
Z-Prepass的其他问题
Z-prepass的性能消耗是否能被忽视
- Z-Prepass的耗时比优化出来的耗时更多
- 需要根据项目实际情况使用
- 当一个有非常多OverDraw的场景,且不能很好的将不透明物体从前往后进行排序时,可以考虑使用PreZ进行优化
- 注意,PreZ会增加DrawCall,如果用错了可能是负优化
URP中的Pre-PASS
- 2021版本URP新出了Depth Priming选项,可以自动实现pre-pass,需要着色器有DepthOnly
- 一些限制与不建议使用的场景:
- 只能在Forward前向渲染路径中使用,且需要着色器有DepthOnly
- 场景不复杂,Overdraw不是造成GPU效率的瓶颈时
- 手机上Depth Priming与MSAA同时开启带来的开销比Overdraw带来的开销还要大时
- DrawCall或其他图形api调用的开销比Overdraw带来的开销还要大时
Early-Z 和 Z-prepass的实例应用
面片叠加的头发渲染
对于半透明的面片来说,需要从后往前进行排序渲染才能得到正确的透明度混合结果
排序后的头发渲染
-
分为3个pass
- pass1
- 处理不透明部分,开启Alpha test透明度测试,仅通过不透明的像素
- 关闭背面剔除
- 开启深度写入
- pass2
- 剔除正面,渲染背面
- pass3
- 剔除背面,渲染正面
- pass1
-
问题:会带来非常多OverDraw的问题
性能改善
- 使用Early-Z剔除
- 透明度测试开启时Early-Z无法使用的解决方案:
- 使用一个简单的shader进行透明度测试形成 Z-Buffer
改善的渲染方案
-
pass1:准备Z-Buffer
- 开启透明度测试
- 关闭背面剔除
- 开启深度写入,深度测试设置为less
- 关闭颜色缓冲区写入
- 用于一个简单的片元着色器来返回透明度值
-
pass2、pass3、pass4如图所示
参考资料
- https://blog.csdn.net/puppet_master/article/details/53900568
- https://zhuanlan.zhihu.com/p/28557283
- https://blog.csdn.net/ddgf01/article/details/118725903
- https://blog.csdn.net/puppet_master/article/details/73478905?spm=1001.2014.3001.5501
作业
做一下Pre-Z的效果测试
先偏个题,实现下之前没做的X-Ray效果
由于使用了两个Pass,SRP-Batch失败了,可能有更适合URP的方案,后面再研究下。
然后是很常见的功能:
- 当主角走到建筑物背后时,建筑物需要透明化。
半透明效果很适合Pre-Z, 因为如果是复杂模型的话由于关闭了深度写入,模型就很容易出现自我穿模效果。引入Pre-Z可以有效解决这个问题。
为了能正常SRP Batch,使用RendererFeature来写入半透明物体的深度(详见雨松大佬的文章),实际要渲染的物体关闭深度写入,深度测试为Equal。就是前面说的提前分离的Prepass做法。
可以看到确实是一个Batch画出了这两棵树
总结Early-Z的限制
-
适用情况:OverDraw很高的情况
-
会失效的情况:
- 开启Alpha Test 或 clip/discard等手动丢弃片元操作
- 手动修改GPU插值得到的深度
- 开启Alpha Blend
- 关闭深度测试
-
Early-Z属于硬件优化,如果硬件支持会自动开启