解释Normal Offset与Slope Scale偏差差异
解读
国内Unity项目面试中,阴影“飘”“粉刺”是高频痛点。面试官问“Normal Offset与Slope Scale偏差差异”,表面考两个阴影偏移参数,实则验证候选人是否真正踩过阴影漏与摩尔纹的坑,能否在真机低端芯片上快速调参。答不到“斜面精度 vs 法向抗漏”这一层,会被直接判定为只背过文档。
知识点
- 阴影映射流程:Unity先渲染深度图(ShadowMap),再采样比较。深度图分辨率有限,一个纹素可能覆盖多个世界单位,导致表面与阴影自相交。
- 自相交的两种形态:
- Slope误差:多边形越斜,ShadowMap纹素在切线方向拉伸,出现“阶梯粉刺”。
- Depth偏移误差:深度值离散化后,平面本身被当成遮挡体,产生“阴影飘起”。
- Unity内置的两种偏移策略:
- Normal Offset(法向偏移):在阴影采样阶段,把采样点沿法线外推一段距离,避开自身表面。距离=“Normal Bias”×阴影纹素世界尺寸。
- Slope Scale Depth Bias(斜率深度偏移):在渲染ShadowMap阶段,把深度值往远裁剪面再写深一点,深度增量=“Slope Depth Bias”×tan(θ),θ为光线与法线夹角。
- 关键差异:
- 生效阶段:Normal Offset在采样时修正坐标;Slope Scale在生成深度时修正深度。
- 输入变量:Normal Offset只认法线方向;Slope Scale同时认法线与光线夹角。
- 副作用:Normal Offset会让阴影位置整体外扩,角色脚边可能出现“悬空影”;Slope Scale过大则阴影断裂,斜面细节丢失。
- 国内真机经验:
- 高通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以上。”
拓展思考
- URP/HDRP统一管线已把Bias拆成“Depth Bias”与“Normal Bias”,但移动游戏仍大量用Built-in,面试官可能追问“如何在后处理Pass里自己实现Normal Offset”。答案:在ShadowCoord里沿世界法线偏移
float3 shadowPos = worldPos + worldNormal * _NormalBias * shadowMapTexelSizeWorld;,再转回光源空间。 - **级联阴影(CSM)**中,不同级联的纹素世界尺寸差异10倍,需把Bias做成级联索引的数组,级联0用0.1,级联3用0.8,否则远端阴影会“飞”。
- 国内MMO国战场景同屏200人,ShadowMap 2K也扛不住,可放弃Slope Scale,改用屏幕空间接触阴影(Screen Space Contact Shadow)+Normal Offset,只让主角与Boss投射实时阴影,其余用Projector贴花,节省30% GPU时间。