案例 22.7Z-Fighting 与深度偏移 Offset入门
目标:理解共面网格为什么闪烁,用 Offset 解决。
贴花网格与底面在同一个平面上时,两者在每个像素上的深度理论上相等。但它们的三角形划分不同(地面是细分网格,贴花是一个 Quad),光栅化器各自对顶点深度插值,浮点舍入误差的方向逐像素随机,ZTest LEqual 的结果就一会儿是贴花赢、一会儿是地面赢,形成斑驳的碎片,相机移动时还会闪烁——这就是 Z-Fighting。
ShaderLab 的 Offset Factor, Units 在光栅化阶段给深度加一个偏移:
其中 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]

Offset 不是万能的它只能修正“几乎共面”的情况,偏移量是深度缓冲单位而不是米,远处和近处的实际效果不同;偏移太大,贴花会穿透到前方的物体上(比如透过薄墙看到另一侧的贴花)。另一个常用的补充手段是生成网格时沿法线外推 1~2 毫米(下一案例的
pushOut)。还要注意:在本书的配图环境里,水平地面上的一块共面 Quad 并没有出现明显的 Z-Fighting——平台使用 32 位浮点 Reversed-Z 深度,精度很高;把地面换成倾斜的细分网格后问题才显现出来。所以“我这里没闪”不代表别的设备、别的角度也不会闪,共面贴花一律加 Offset。