案例 8.1用 C# 构造环境光:Color、Gradient 与 Skybox入门
目标:不经过 Lighting 窗口,直接用脚本构造三种来源的球谐环境光,对比它们对物体的影响。这也是在运行时自定义环境光(比如昼夜循环)的基本手段。
SphericalHarmonicsL2 是 Unity 提供的三阶球谐结构体,有两个方便的方法:AddAmbientLight(color) 加一个常数项,AddDirectionalLight(dir, color, intensity) 加一盏方向光。“Color”模式只需要前者;对于 Gradient 和任意的天空函数,我们在球面上均匀撒点,把每个方向当成一盏小方向光加进去——这就是蒙特卡洛投影:
/// 蒙特卡洛投影:在球面上均匀取 sampleCount 个方向,每个方向当成一盏小方向光加进 SH。 /// AddDirectionalLight 内部已经做了余弦卷积,但它的归一化是“在灯光方向上求值 = 2 × 强度” /// (实测:AddDirectionalLight(up, 白, 1) 在 up 方向求值得 2.0)。而三阶截断的余弦波瓣在峰值处 /// 理论值是 1/4 + 1/2 + 5/16 = 17/16,所以 Unity 的结果比“辐照度/π”大 2÷(17/16) = 32/17 倍。 /// 均匀环境 L 拆成 N 盏灯时,每盏取 4L/N 才能得到 L,再乘 17/32 抵消上面的倍数。 public static SphericalHarmonicsL2 ProjectToSH(Func<Vector3, Color> radiance, int sampleCount = 4096, int seed = 1) { var sh = new SphericalHarmonicsL2(); var rnd = new System.Random(seed); float weight = 4f / sampleCount * (17f / 32f); for (int i = 0; i < sampleCount; i++) { float z = (float)rnd.NextDouble() * 2f - 1f; float a = (float)rnd.NextDouble() * Mathf.PI * 2f; float r = Mathf.Sqrt(1f - z * z); var d = new Vector3(r * Mathf.Cos(a), z, r * Mathf.Sin(a)); sh.AddDirectionalLight(d, radiance(d), weight); } return sh; }
权重的推导值得一看。假设环境是均匀的、辐射率为 L,我们希望 SH 在任何方向上求值都得到 L(URP 约定下的“辐照度 / π”,下一节解释)。把它拆成 N 盏均匀分布的小方向光,每盏强度 w,对法线 n 的 Lambert 响应总和是 (max(0, cosθ) 在整个球面上的平均值是 1/4),令它等于 L 得 w = 4L/N。但实测 Unity 的 AddDirectionalLight(up, 白, 1) 在 up 方向上求值得到 2.0,而三阶截断的余弦波瓣在峰值处的理论值是 1/4 + 1/2 + 5/16 = 17/16——Unity 的结果整体大了 32/17 倍。所以代码里再乘 17/32 抵消。这类“约定上的倍数”在不同引擎、不同 API 之间经常不一样,写完投影代码后务必用一个均匀环境验证一次:本例中用白色均匀环境投影后,Evaluate 在各方向上都应得到 1 附近的值。
Gradient 的环境函数只是按方向的 y 分量在三种颜色之间插值(Unity 内部的具体插值曲线未公开,这里用一个形状相近的函数):
public static Func<Vector3, Color> Gradient(Color sky, Color equator, Color ground) => d => d.y >= 0 ? Color.Lerp(equator, sky, Mathf.Pow(d.y, 0.6f)) : Color.Lerp(equator, ground, Mathf.Pow(-d.y, 0.6f)); // 使用: RenderSettings.ambientProbe = EnvironmentTools.ProjectToSH( EnvironmentTools.Gradient(skyColor, equatorColor, groundColor));

注意中图球体下半部分的暖色——这是 Gradient 环境里 Ground 颜色的贡献,它模拟了“地面反弹光”。Color 模式下物体的立体感完全来自主光,背光面是一片均匀的灰,这就是为什么老游戏里的背光面总显得“假”。
本案例出自《URP 渲染实战》第 8 章「环境光、光照贴图与反射探针」。在章节中阅读上下文 →