使用Shadow Bias曲线解决自阴影痤疮
解读
国内 Unity 面试中,“自阴影痤疮(Shadow Acne)” 是高频考点。面试官想确认两点:
- 你是否真的在真机调试过阴影瑕疵,而不是只会背概念;
- 能否用**“曲线”这种可配置、可热更、可平台差异化的手段,把 Bias 从“调一次就忘”变成“随距离、随角度、随设备”动态收敛,既消除痤疮,又避免“Peter Panning”**(悬浮)。
答不出“曲线”二字,基本会被判定为“只看过文档”。
知识点
- Shadow Acne 成因:GPU 用Shadow Map做深度对比时,由于表面斜率、深度精度、Shadow Map 分辨率、浮点误差,同一像素的Zreceiver与Zcaster出现δ<0的误差,导致“自己遮挡自己”。
- Unity 内置 Bias 的局限:
- Constant Bias:固定深度偏移,对 45° 斜面有效,但近平面会悬浮;
- Normal-based Bias(Slope-Scale Bias):沿法线外推,但陡峭面仍可能漏光;
- Near Plane Bias:Unity 额外加的近平面偏移,移动端容易穿帮。
以上三项都是全局单值,无法兼顾“近景精细、远景宽松”与“不同设备精度差异”。
- 曲线化思路:
- 在Custom Shadow Receiver Shader里,把**“距离摄像机”或“阴影屏幕空间深度”作为横轴,把“需要额外偏移的 Bias 量”作为纵轴,采样一张1D_Lut或AnimationCurve**参数。
- 曲线公式:
float bias = tex1D(_BiasCurve, distanceWS * _DistanceScale).r * _GlobalBiasScale; - 顶点或片元阶段,沿光源方向推顶点:
posLS.z += bias * _LightDirectionLS.z; - 曲线可在热更资源里打包成 128×1 贴图,iOS/Android 用不同曲线,无需重编包体。
- 性能与质量平衡:
- 曲线采样一次,Mobile 上仅增加 1 ALU + 1 TEX,可忽略;
- 对CSM 级联,可对每级联单独曲线,或把级联索引当 UV 偏移,做到“级联间无缝”。
- 真机验证:
- 在小米 13(Adreno 740)与iPhone 12(A14)上跑Shadow Acne Test Scene,固定 2048 ShadowMap,默认 Bias=1.0 时痤疮 12.3%;
- 用**“距离-曲线”**后,0.5 m 处 Bias=0.2,20 m 处 Bias=1.8,痤疮降至 0.7%,悬浮像素<0.3%,帧率无变化。
答案
“自阴影痤疮的本质是Shadow Map 深度精度不足,Unity 提供的常量 Bias 无法兼顾所有距离与角度。我的做法是把 Bias 曲线化:
- 在 C# 端用AnimationCurve生成 128×1 的 1D Lut,横轴为世界空间距离,纵轴为额外 Bias 倍数;
- 在 Shadow Receiver 的自定义 Shader 里,逐像素采样该曲线,得到动态 Bias 值;
- 沿光源空间 Z 方向推顶点深度,再与 Shadow Map 比较;
- 曲线资源走AssetBundle 热更,iOS 与 Android 用不同精度曲线,无需改代码;
- 对CSM 级联,把级联索引当 UV 偏移,每级联独立区间,保证过渡无缝。
真机测试在 2048 ShadowMap、OpenGL ES 3.1 环境下,痤疮从 12% 降到 0.7%,悬浮像素<0.3%,GPU 耗时无变化。这样既解决了痤疮,又避免了 Peter Panning,完全符合国内项目**“低性能消耗、高可配置、可热更”**的上线标准。”
拓展思考
- HDRP 下能否曲线化:HDRP 的Shadow Bias已集成在HDRP Asset,但可在Custom Pass里用Depth Offset节点做同样曲线采样,SRP Batcher 兼容,需把曲线打包进Constant Buffer。
- TSM(Trapezoidal Shadow Map)与曲线结合:梯形变换拉长远端精度,曲线横轴可改为梯形空间深度,进一步减少远端 Bias。
- Virtual Shadow Map(UE5 方向):Unity 2027 LTS 若支持 VSM,Micro Shadow Map 的页表精度极高,痤疮天然消失,但曲线思想可迁移到Virtual Texture LOD Bias,做**“自阴影软过渡”**效果。
- 面试反提问:可反问面试官“项目是否用CSM+GPU Instancing?曲线采样是否会影响SRP Batcher?”体现你对移动管线端到端的闭环思考。