光照贴图与 GI:UV2、烘焙 lightmap、light probe
实时光照只算了「直接光」,而现实里房间的主要亮度来自墙壁反弹的间接光。间接光实时算不动,Unity 的答案是烘焙:静态物体烘进 lightmap,动态物体烘进 light probe。shader 侧要做的就是采样这两样东西——采错或忘采,动态物体发黑、烘焙场景对不上,都是这里的账。
烘焙在离线阶段存了什么
- lightmap:一张(或几张)纹理,存每个 texel 的间接光 + 烘焙直接光的辐照度。静态网格的 UV2 通道(
TEXCOORD1)映射到这张图。 - light probe:场景里撒的探针点,存每个点周围的低频球谐(SH)系数——动态物体按位置插值取用。
- 烘焙质量参数(间接光反弹次数、贴图分辨率)属引擎配置,URP/Built-in 差异见 /notes/unity/engineering-1/,本篇只讲 shader 侧。
UV2:烘焙的接线柱
lightmap 要求每个静态物体的 UV2 不重叠(Unwrap 自动参数化生成)。两条硬规则:
- 改模型/重排 UV2 后必须重烘,否则光「贴错位置」。
- 运行时换 mesh、动态合批改 UV 的操作会破坏 lightmap 对应关系。
shader 里把 UV2 变换到 lightmap 图集(unity_LightmapST 是这张物体在图集里的缩放偏移):
// 顶点程序(Built-in)
o.lightmapUV = v.unityTexCoord * unity_LightmapST.xy + unity_LightmapST.zw;
unity_LightmapST 由引擎自动填,多 lightmap 切片时按物体分片,shader 不用管切片逻辑。
采样 lightmap:注意编码
lightmap 存的是 HDR 值,纹理里按平台编码(RGBM、双通道 dual、或 R11G11B10)。永远用引擎的解码函数,不要直接 tex2D 当颜色用:
// 片元程序(Built-in)
half4 lm = DecodeLightmap(tex2D(unity_Lightmap, i.lightmapUV));
o += albedo * lm;
// 带方向 lightmap(DIR)时,方向要和法线结合:
o += albedo * DecodeDirectionalLightmap(tex2D(unity_Lightmap),
tex2D(unity_LightmapInd), i.normalWS);
URP 里对应 SAMPLE_LIGHTMAP + SampleLightmap,接口不同、原理相同(见 /notes/unity/engineering-1/)。直接采原始编码值的典型症状:烘焙场景整体偏品红或亮度差几倍。
light probe:动态物体的间接光
动态物体没有 UV2、不在 lightmap 里,间接光来自 SH 探针。shader 侧一行:
// Built-in:按世界法线取 SH 环境光
o += albedo * SampleSH(i.normalWS); // UnityCG.cginc
// URP:
o += albedo * SampleSH9(SHADERGRAPH_NORMAL_WS);
引擎按物体位置在探针四面体里插值系数,shader 只管按法线取方向项。
动态物体发黑的排查顺序:
- 场景有没有烘焙过探针(Lighting 窗口 → Lightprobes 组)?物体在探针包围的凸包外时取最近探针,探针组太小会「走进暗区突然发黑」。
- shader 有没有采样 SH/lightmap?自定义 shader 忘了
SampleSH,动态物体就没有环境光。 - Mixed 灯模式下烘焙直接光 + 实时直接光会不会叠加双份?(Baked/IndirectOnly/Subtractive/Realtime 四种模式语义不同,配置在引擎侧。)
烘焙直接光的「双份」问题
Mixed 灯下,静态物体的直接光可能烘进 lightmap,实时又算一遍——Unity 用 shadowmask/距离切换处理,但自定义 shader 手写光照时容易漏掉 multi_compile 的 lightmap 变体导致表现不一致。原则:手写光照尽量走引擎的 LightingXXX 函数族,它们已处理这些组合;坚持全手写就要把 LIGHTMAP_ON、DIRLIGHTMAP_COMBINED、SHADOWS_SHADOWMASK 这些 keyword 的分支全部写全(keyword 机制见 /notes/unity/engineering-3/)。
与实时光照的分工
| 内容 | 承担者 |
|---|---|
| 静态物体间接光 | lightmap |
| 动态物体间接光 | light probe(SH) |
| 直接光 + 实时阴影 | /notes/unity/lighting-1/、/notes/unity/lighting-5/ |
| 反射(镜面间接光) | reflection probe / 屏幕空间反射(超出本区域) |
烘焙换的是「间接光零运行时成本」,代价是静态 + 烘焙时长 + 内存——移动端主力方案,取舍见 /notes/unity/performance-1/。
自检三问
- 动态角色走进烘焙房间后全身没有环境光,前两个排查点是什么?
- lightmap 为什么必须解码?直接采会出现什么症状?
- UV2 重叠的物体烘焙后出什么问题?