URP 渲染实战 / 第 8 章 环境光、光照贴图与反射探针 / 案例 8.4
案例 8.49 个系数到底有多准进阶
目标:选一个高对比度的“摄影棚”环境(深灰背景 + 两块强烈的柔光箱 + 一条暖色轮廓灯),用暴力积分算出真实的辐照度,与 SH 近似并排比较。
“标准答案”通过对每个法线方向做余弦加权的半球积分得到:在半球上按余弦分布采样方向,概率密度 cosθ/π 正好与被积函数中的 cosθ/π 抵消,积分就变成了样本的简单平均。我们把结果存进一张小 Cubemap:
Assets/Cookbook/Ch08/EnvironmentTools.cs(节选:irradiance)
/// “标准答案”:对每个法线方向做余弦加权的半球积分,得到 URP 约定下的间接漫反射(E/π)。 /// 用来和 9 个系数的 SH 近似做对比。 public static Cubemap BakeIrradiance(Func<Vector3, Color> radiance, int size = 32, int samples = 1024) { var cube = new Cubemap(size, TextureFormat.RGBAHalf, false) { name = "Ch08Irradiance", hideFlags = HideFlags.DontSave }; var px = new Color[size * size]; for (int f = 0; f < 6; f++) { for (int y = 0; y < size; y++) for (int x = 0; x < size; x++) { Vector3 n = FaceDirection((CubemapFace)f, (x + 0.5f) / size * 2f - 1f, (y + 0.5f) / size * 2f - 1f); Vector3 up = Mathf.Abs(n.y) < 0.999f ? Vector3.up : Vector3.right; Vector3 tx = Vector3.Normalize(Vector3.Cross(up, n)), ty = Vector3.Cross(n, tx); Color sum = Color.black; int sq = Mathf.RoundToInt(Mathf.Sqrt(samples)); for (int i = 0; i < sq; i++) for (int j = 0; j < sq; j++) { // 分层的余弦加权采样:pdf = cosθ/π,正好与积分里的 cosθ/π 抵消,结果就是简单平均 float u1 = (i + 0.5f) / sq, u2 = (j + 0.5f) / sq; float r = Mathf.Sqrt(u1), phi = 2f * Mathf.PI * u2; Vector3 l = tx * (r * Mathf.Cos(phi)) + ty * (r * Mathf.Sin(phi)) + n * Mathf.Sqrt(1f - u1); sum += radiance(l); } px[y * size + x] = sum / (sq * sq); } cube.SetPixels(px, (CubemapFace)f); } cube.Apply(false, false); return cube; }

SampleSH 求值。中、右两图几乎一致。左图里锐利的柔光箱在辐照度里变成了大面积的柔和渐变,而三阶 SH 准确地复现了这种渐变。SH 近似在什么时候会出问题?当环境里有一个极强的小光源(比如把太阳也算进环境光),截断到三阶会在光源的反方向产生“振铃”(Ringing),表现为背光面出现不该有的暗环甚至负值——SampleSH 最后的 max(0, …) 就是为了兜底。实践中的做法是把太阳交给方向光,环境光里只保留天空的漫射部分。本书拍摄环境的默认天空在投影 SH 时就把太阳尖峰截掉了。
光照探针
环境光 SH 对整个场景只有一组,室内和室外的物体拿到的环境光一样。光照探针(Light Probe)解决的是“空间上的变化”:在场景里摆放一组探针点(Light Probe Group),烘焙时每个点记录该位置的入射光(包括直接光和反弹光),同样压缩成三阶 SH。运行时,Unity 把所有探针点做四面体剖分,按动态物体的位置找到所在的四面体,用重心坐标插值四个探针的 SH,作为这个物体的 unity_SHAr 等变量。
- 插值是逐物体的(按包围盒中心或 Anchor Override 指定的点),不是逐像素的。一个很大的动态物体跨越明暗交界时,整个物体只能拿到一组 SH。Built-in 管线用 Light Probe Proxy Volume(LPPV)解决这个问题,但 URP 14 不支持 LPPV——GlobalIllumination.hlsl 的
SampleSH里写得很直白:“LPPV is not supported”。Unity 6 的 URP 引入了 Adaptive Probe Volumes,实现逐像素的探针采样,不在本书版本范围内。 - 探针要放在光照发生变化的地方:明暗交界、颜色强烈的墙边、楼梯上下;空旷且光照均匀的区域不需要密集摆放。
- 对 Shader 来说,有没有探针完全透明:都是读
unity_SH*,代码不需要任何改动。
本案例出自《URP 渲染实战》第 8 章「环境光、光照贴图与反射探针」。在章节中阅读上下文 →