URP 渲染实战 / 第 8 章 环境光、光照贴图与反射探针 / 案例 8.3
案例 8.3每一阶 SH 贡献了什么进阶
目标:用一个色彩强烈的环境(上方暖白、左侧红、右侧蓝、下方偏绿),分别只用 L0、L0+L1、完整三阶来渲染,看清每一阶的作用。
Assets/Cookbook/Ch08/AmbientSH.shader(节选:bands)
// unity_SHAr/Ag/Ab:xyz = L1 三个系数,w = L0 系数“减去”L2 里 Y20 的常数部分; // unity_SHBr/Bg/Bb:L2 中 xy、yz、zz、zx 四项;unity_SHC:L2 中 x²-y² 一项(rgb 三通道)。 // 这些系数已经乘上了“余弦卷积”因子并除以 π,所以直接算出来的就是 URP 约定下的间接漫反射。 half3 EvaluateSHBands(half3 N, int bands) { // L2 在整个球面上积分为 0,但 zz 项的平均值是 1/3。Unity 把 Y20 的常数部分挪进了 w, // 想要“纯 L0”,就把 zz 项的平均值 SHB.z/3 加回去。 half3 L0 = half3(unity_SHAr.w + unity_SHBr.z / 3.0, unity_SHAg.w + unity_SHBg.z / 3.0, unity_SHAb.w + unity_SHBb.z / 3.0); if (bands == 0) return L0; half3 L1 = half3(dot(unity_SHAr.xyz, N), dot(unity_SHAg.xyz, N), dot(unity_SHAb.xyz, N)); if (bands == 1) return max(0, L0 + L1); // 完整三阶:与 URP 的 SampleSH(N) 完全相同(SHEvalLinearL0L1 + SHEvalLinearL2) return SampleSH(N); }
// unity_SHAr/Ag/Ab:xyz = L1 三个系数,w = L0 系数“减去”L2 里 Y20 的常数部分; // unity_SHBr/Bg/Bb:L2 中 xy、yz、zz、zx 四项;unity_SHC:L2 中 x²-y² 一项(rgb 三通道)。 // 这些系数已经乘上了“余弦卷积”因子并除以 π,所以直接算出来的就是 URP 约定下的间接漫反射。 vec3 EvaluateSHBands(vec3 N, int bands) { // L2 在整个球面上积分为 0,但 zz 项的平均值是 1/3。Unity 把 Y20 的常数部分挪进了 w, // 想要“纯 L0”,就把 zz 项的平均值 SHB.z/3 加回去。 vec3 L0 = vec3(unity_SHAr.w + unity_SHBr.z / 3.0, unity_SHAg.w + unity_SHBg.z / 3.0, unity_SHAb.w + unity_SHBb.z / 3.0); if (bands == 0) return L0; vec3 L1 = vec3(dot(unity_SHAr.xyz, N), dot(unity_SHAg.xyz, N), dot(unity_SHAb.xyz, N)); if (bands == 1) return max(vec3(0.0), L0 + L1); // 完整三阶:与 URP 的 SampleSH(N) 完全相同(SHEvalLinearL0L1 + SHEvalLinearL2) return SampleSH(N); }
“只要 L0”并不是简单地取 unity_SHAr.w:Unity 把 Y20 = c(3z² − 1) 中的常数项 −c 挪进了 w,所以 w 里混着一部分 L2。L2 整体在球面上积分为 0,而 zz 项在球面上的平均值是 1/3,所以把 SHBr.z / 3 加回去,就恢复出真正的 L0。这个小推导能帮你在需要“平均环境亮度”时取到正确的值(例如给粒子、头发卡片一个不随方向变化的环境光)。

从 L0 到 L1 是质变,从 L1 到 L2 是改进。在极度受限的情况下(例如非常低端的设备、大量的植被实例),只用 L0+L1 也是可以接受的近似;URP 的混合模式也正是基于这样的判断——L1 必须在像素里算,L2 可以降到顶点。
本案例出自《URP 渲染实战》第 8 章「环境光、光照贴图与反射探针」。在章节中阅读上下文 →