解释Normal Offset与Slope Scale偏差差异

解读

国内Unity项目面试中,阴影“飘”“粉刺”是高频痛点。面试官问“Normal Offset与Slope Scale偏差差异”,表面考两个阴影偏移参数,实则验证候选人是否真正踩过阴影漏与摩尔纹的坑,能否在真机低端芯片上快速调参。答不到“斜面精度 vs 法向抗漏”这一层,会被直接判定为只背过文档。

知识点

  1. 阴影映射流程:Unity先渲染深度图(ShadowMap),再采样比较。深度图分辨率有限,一个纹素可能覆盖多个世界单位,导致表面与阴影自相交。
  2. 自相交的两种形态:
    • Slope误差:多边形越斜,ShadowMap纹素在切线方向拉伸,出现“阶梯粉刺”。
    • Depth偏移误差:深度值离散化后,平面本身被当成遮挡体,产生“阴影飘起”。
  3. Unity内置的两种偏移策略:
    • Normal Offset(法向偏移):在阴影采样阶段,把采样点沿法线外推一段距离,避开自身表面。距离=“Normal Bias”×阴影纹素世界尺寸。
    • Slope Scale Depth Bias(斜率深度偏移):在渲染ShadowMap阶段,把深度值往远裁剪面再写深一点,深度增量=“Slope Depth Bias”×tan(θ),θ为光线与法线夹角。
  4. 关键差异:
    • 生效阶段:Normal Offset在采样时修正坐标;Slope Scale在生成深度时修正深度。
    • 输入变量:Normal Offset只认法线方向;Slope Scale同时认法线与光线夹角。
    • 副作用:Normal Offset会让阴影位置整体外扩,角色脚边可能出现“悬空影”;Slope Scale过大则阴影断裂,斜面细节丢失。
  5. 国内真机经验:
    • 高通Adreno 5xx/6xx对浮点深度精度敏感,Slope Scale设0.5~1.0即可;
    • ** Mali-G71/G76** 需要把Normal Bias降到0.2以下,否则角色阴影会“漏光”;
    • WebGL 无硬件PCF,需把两者都下调30%,再补软阴影高斯模糊,否则低端安卓浏览器直接穿帮。

答案

“Normal Offset与Slope Scale偏差的核心差异是修正阶段与输入变量不同
Normal Offset在阴影采样阶段把坐标沿法线外推,用来解决深度离散导致的自遮挡,代价是阴影会整体外移,角色与地面之间可能出现悬空。
Slope Scale Depth Bias在生成ShadowMap时把深度值按‘光线与法线夹角’的tan值再写深一点,专门抑制斜面粉刺,但过大会让阴影断裂。
实际调参时,我习惯先固定Slope Scale为0.81.2把斜面摩尔纹压掉,再用Normal Offset 0.20.4处理剩余漏影,最后在低端 Mali 芯片上把两项同步下调20%,配合4 Tap PCF,真机帧率可保持在30 fps以上。”

拓展思考

  1. URP/HDRP统一管线已把Bias拆成“Depth Bias”与“Normal Bias”,但移动游戏仍大量用Built-in,面试官可能追问“如何在后处理Pass里自己实现Normal Offset”。答案:在ShadowCoord里沿世界法线偏移float3 shadowPos = worldPos + worldNormal * _NormalBias * shadowMapTexelSizeWorld;,再转回光源空间。
  2. **级联阴影(CSM)**中,不同级联的纹素世界尺寸差异10倍,需把Bias做成级联索引的数组,级联0用0.1,级联3用0.8,否则远端阴影会“飞”。
  3. 国内MMO国战场景同屏200人,ShadowMap 2K也扛不住,可放弃Slope Scale,改用屏幕空间接触阴影(Screen Space Contact Shadow)+Normal Offset,只让主角与Boss投射实时阴影,其余用Projector贴花,节省30% GPU时间。