GameTech/URP 渲染实战

案例 22.7Z-Fighting 与深度偏移 Offset入门

目标:理解共面网格为什么闪烁,用 Offset 解决。

贴花网格与底面在同一个平面上时,两者在每个像素上的深度理论上相等。但它们的三角形划分不同(地面是细分网格,贴花是一个 Quad),光栅化器各自对顶点深度插值,浮点舍入误差的方向逐像素随机,ZTest LEqual 的结果就一会儿是贴花赢、一会儿是地面赢,形成斑驳的碎片,相机移动时还会闪烁——这就是 Z-Fighting。

ShaderLab 的 Offset Factor, Units 在光栅化阶段给深度加一个偏移:

z′=z+Factor×m+Units×r

其中 m 是该多边形深度相对屏幕坐标的最大斜率(表面越斜,m 越大),r 是深度缓冲格式能分辨的最小深度差。负值表示“往相机方向拉”(Unity 会按平台的深度方向处理符号)。Factor 项处理斜着看的表面——斜面上相邻像素的深度变化大,舍入误差也大,需要更大的偏移;Units 项处理正对相机的表面。

Assets/Cookbook/Ch22/MeshDecal.shader(节选:state)
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
ZTest LEqual
// 深度偏移:depth += factor × 斜率 + units × 最小可分辨深度单位
// 负值 = 往相机方向拉一点,让与底面共面的贴花稳定地赢下深度测试
Offset [_OffsetFactor], [_OffsetUnits]
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
ZTest LEqual
// 深度偏移:depth += factor × 斜率 + units × 最小可分辨深度单位
// 负值 = 往相机方向拉一点,让与底面共面的贴花稳定地赢下深度测试
Offset [_OffsetFactor], [_OffsetUnits]
图 22-9一块倾斜的细分地面,贴花 Quad 作为它的子物体放在完全相同的平面上。左:Offset 0, 0,大片地面“穿透”贴花;右:Offset −1, −1,贴花稳定地显示在地面之上
Offset 不是万能的它只能修正“几乎共面”的情况,偏移量是深度缓冲单位而不是米,远处和近处的实际效果不同;偏移太大,贴花会穿透到前方的物体上(比如透过薄墙看到另一侧的贴花)。另一个常用的补充手段是生成网格时沿法线外推 1~2 毫米(下一案例的 pushOut)。还要注意:在本书的配图环境里,水平地面上的一块共面 Quad 并没有出现明显的 Z-Fighting——平台使用 32 位浮点 Reversed-Z 深度,精度很高;把地面换成倾斜的细分网格后问题才显现出来。所以“我这里没闪”不代表别的设备、别的角度也不会闪,共面贴花一律加 Offset。