学习教程来自:【技术美术百人计划】图形 3.5 Early-z和Z-prepass

Early-z和Z-prepass

1.深度测试

Depth Test,用来解决物体可见遮挡性

顶点着色器->曲面细分->几何着色器->光栅化->片元着色器->透明度测试->模板测试->深度测试->混合

渲染流程详见:
技美知识学习1100:渲染管线

没有通过测试的片元会被丢弃,导致这些片元的计算量被浪费

2.提前深度测试

Early-Z,用来解决过多不必要的片元计算问题(1中的缺陷)
顶点着色器->曲面细分->几何着色器->光栅化->提前深度测试->片元着色器->透明度测试->模板测试->深度测试->混合
没有通过Early-Z的片元会被丢弃,不进入片元着色器进行计算
在提前阶段的位置也可以添加模板测试

2.1.Early-Z失效的情况

  1. 开启Alpha Test或clip/discard等手动丢弃片元操作(但是深度信息还在)
  2. 手动修改GPU插值得到的深度(原因同上)
  3. 开启Alpha Blend(深度写入关闭ZWrite Off)
  4. 关闭深度测试Depth Test

2.2.Early-Z使用条件

当渲染顺序为如图所示时,每次提前深度测试时都抛弃了上一次计算得到的片元,保留了当前的计算结果。这样的渲染顺序不会有优化结果
反之,顺序颠倒后则优化效果最大

渲染顺序从右向左,依次被较大物体遮挡

CPU:将待渲染物体按照距离摄像机的远近排序后,再交给GPU处理,可以达到Early-Z的优化目的,但是频繁的排序操作会消耗CPU的性能。并且严格按照由近到远的顺序渲染,将不能搭配合批的优化方法 ## 3.Z-Prepass 用来解决上一方法中的缺陷 ### 3.1.双Pass法 1. Pass1(Z-Prepass):仅仅写入深度、不输出颜色(保留了最近的深度信息) 2. Pass2:关闭深度写入,深度比较条件为相等(和上一步的深度信息对比,相等则为最近的物体) 以上两步完成了深度的排序,减少了CPU对排序计算的消耗,但增加了DrawCall的数量 ### 3.2.提前分离的PrePass 1. Z-Prepass单独作为一个Shader渲染整个场景的不透明物体,写入深度 2. 同样作为单独的Shader,计算相同 参考教程[雨松MOMO:Unity3D研究院之URP下PrePassZ(一百一十九)](https://www.xuanyusong.com/archives/4759) 以上,Z_Prepass也是透明渲染的一种解决方案,解决了排序问题但也导致剔除了透明物体的背面(解决思路:渲染正面时剔除背面,渲染背面时剔除正面,再将结果合并) ## 4.Z-Prepass的其他问题 ### 消耗问题 以下数据来自:lipsryme的实验,50k个带有完整贴图的不透明cube |条件|性能| |:----:|:----:| |关闭Z-Prepass + 几何变换光栅Pass|0 + 2.7 ms| |开启Z-Prepass + 几何变化光栅Pass|2.0+ 2.4 ms| 后续:当一个复杂场景中存在大量OverDraw但又无法用排序来有效解决时,可以尝试用Z-Prepass来优化 ### 5.Early-Z和Z-Prepass的实例应用 ### 面片叠加的头发渲染 3个Pass: 1. 不透明部分:对不透明物体开启深度测试,关闭背面剔除,开启深度写入,深度测试中保留深度值较小的片元 2. 透明-背面部分:对透明物体开启深度测试,剔除正面多边形,关闭深度写入,深度测试中保留深度值较小的片元 3. 透明-正面部分:对透明物体开启深度测试,剔除背面多边形,开启深度写入,深度测试中保留深度值较小的片元 以上过程会带来OverDraw问题,解决思路: 使用一个Shader将透明度测试的结果写入Z buffer(即Z-Prepass方法) 解决后的流程,4个Pass: 1. 对不透明物体开启深度测试,关闭背面剔除,使用Z-Prepass 2. 不透明部分:关闭背面剔除,关闭深度写入,深度测试中保留深度值相等的片元(同上1) 3. 透明-背面部分:剔除正面多边形,关闭深度写入,深度测试中保留深度值较小的片元(同上2) 4. 透明-正面部分:剔除背面多边形,开启深度写入,深度测试中保留深度值较小的片元(同上3) # 作业 ## preZ效果测试 硬件环境:8700k 1080 ### 测试场景1:
10k个球体,由近到远,依次变大,逐个嵌套

左:单Pass,关闭深度写入 中:单Pass,开启深度测试 右:双Pass PreZ + 关闭深度写入、Ztest Equal
左:EarlyZ失效 中:EarlyZ生效 右:EarlyZ生效

中:尽管待渲染物体的位置变化是规律的,但Unity渲染时还是对其进行了某种排序,使得Early-Z起到了优化作用

结论:
在渲染不透明物体时,尽管prePass提前绘制了深度信息,但也造成了的成倍的Batches数量增加,性能下降。其次,Unity本身没有按照由近到远的顺序渲染物体,而是做了最大限度优化GPU的排序(见下面参考),这使得当在如上图的过程中,序号为9332的物体绘制完成后,编号在这之前的物体都不会通过EarlyZ的深度测试,不会造成大量的OverDraw,也就避免了片元着色器的浪费。
因此手动增加一个Pass来渲染深度,从结果上看没有必要。

来自Unity文档OpaqueSortMode的参考:
Opaque objects are sorted by various criteria (sorting layers, shader queues, materials, distance, lightmaps etc.) to maximize both the CPU efficiency (reduce number of state changes and improve draw call batching), and to maximize GPU efficiency (many GPUs prefer rough front-to-back rendering order for faster rejection of invisible surfaces).

测试场景2:双Pass仿X光透视效果实现

灵感来源及参考资料:

Unity3D-Shader-实现X光效果
一口气解决RenderQueue、Ztest、Zwrite、AlphaTest、AlphaBlend和Stencil
Unity Shader-渲染队列,ZTest,ZWrite,Early-Z
模型来自互联网

实现效果

效果图1

效果图2

效果图3

实现原理

第一个Pass关闭深度写入,使用Greater渲染透视的效果,第二个Pass正常渲染Lambert效果
预先配置: Tags {“Queue”=”Transparent” “RenderType”=”TransParent” “IgnoreProjector”=”True”}

第一个Pass:透视效果

  1. 配置:ZWrite off ZTest Greater Blend SrcAlpha OneMinusSrcAlpha
  2. 顶点着色器:片元颜色 = (1-viewDir · normalDir) Color Intensity
  3. 片元着色器:返回顶点中计算的颜色

第二个Pass:Lambert

  1. 配置:ZWrite On ZTest LEqual Blend Off
  2. 计算Lambert漫反射效果
效果图

总结earlyZ的限制

  1. 很多简单物体存在场景中时,如上测试场景1,单独一个Pass计算深度信息消耗很大。
  2. 有效的排序、合理的渲染顺序可以很好的发挥EarlyZ的效果(见OpaqueSortMode)。
  3. 反之,渲染顺序的不合理会频繁的让待渲染的物体通过EarlyZ、进行片元计算,造成OverDraw。