GPU Gems 3 · Chapter 9. Interactive Cinematic Relighting with Global Illumination

把 cinematic relighting 的昂贵几何关系预计算成 direct-to-indirect transfer,再用 wavelet 压缩和 GPU sparse multiply 让灯光设计保持交互。

学习目标

  • 能解释 view samples、gather samples 与 deep frame buffer 如何为 cinematic relighting 提供可重用输入
  • 能修改 Relighting Lab 的灯光、gather budget、wavelet coefficients、bounce 与 material,观察 direct、indirect 和稀疏计算趋势
  • 能回答:为什么 transfer matrix 必须离线预计算、如何拆成 final gather 与 multibounce、运行时 GPU 只保留哪些操作

先把“重新打光”从整条渲染链里拆出来

电影灯光设计常常需要在镜头、物体和材质已经确定之后,反复尝试光源位置、颜色和能量。如果每次改灯都重新做可见性、间接反弹和材质求值,设计师等的是一条完整的离线渲染链,而不是一次交互反馈。

本章的关键转换是:把不随灯光改变的几何关系提前算好,运行时只重做直接光,再把它通过一份压缩的 direct-to-indirect transfer 传播到当前相机看到的点。它不是把全局光照变得“免费”,而是把昂贵的场景关系搬到离线阶段,让灯光迭代拥有可预测的 GPU 工作量。

只改灯光,不重建场景:把昂贵关系提前缓存view samples相机看见的像素点position · normal · materialVd + Vi → final pixelsgather samples全场可能反射光的表面点direct illumination gd可见区域之外也要采样transfer Tprecomputed offlineVi = T · gd;运行时只更新 direct light 与稀疏矩阵乘法

1. cinematic relighting 的边界:只改灯光

这里的“固定”不是说场景永远不能动,而是先选定一个适合反复布光的工作区间:相机和物体不变,材质参数也不变,灯光设计师只试光源方向、强度和颜色。可见点的属性可以被记录,场景中可能贡献间接光的点也可以被整理成另一组输入。

这一边界也说明了它不适合什么:如果相机大幅移动、几何发生形变,或者材质变化改变了光的传播关系,旧 transfer 的契约就可能失效。发布一个 relighting cache 时,必须把它的适用场景和重新烘焙条件一起记录下来。

2. view samples 与 gather samples:两种不同的空间责任

view samples 来自当前视点真正看见的表面。一个 deep frame buffer 可以为每个可见像素保存 position、normal、diffuse 和 specular 等属性;因此运行时可以先根据新的灯光求出这一像素的直接贡献,而无需再次从场景几何中查询这些属性。

gather samples 负责回答“间接光可能从哪里来”。它们需要覆盖场景中可反射或散射光的表面,而不是只覆盖屏幕上当前可见的区域;否则藏在墙后、侧面或另一片可见性区域的贡献会在 transfer 中根本没有列。为了让相近位置和相近法线的样本便于处理,可以把样本展平到 power-of-two 的方形纹理中,并保持相似样本相邻。

两朵云:一个负责“看见”,一个负责“可能反弹”camera-visible clouddeep frame bufferposition · normal · BRDFwhole-scene gather cloudflatten to power-of-two texture位置 + 法线相近的点相邻gather samples 不是屏幕上的可见点;它们承载“间接光从哪里来”的候选空间

3. direct-to-indirect transfer 的线性契约

设 view samples 的直接和间接光分别为 VdVi,gather samples 的直接照明为 gd。最终像素可以写成 Vf = Vd + Vi,而间接部分先由 Vi = T · gd 得到。这里的矩阵不是每次改灯时临时从几何体生成的,它是把“哪些 gather 点能影响哪些 view 点、影响多大”这份昂贵关系封装起来的离线产物。

直接把所有 view × gather 的系数存成 dense T 会迅速失去实用性:行列规模随两种样本数相乘,精度、带宽和乘法工作都同时膨胀。更有用的分解是 Vi = F · (M + I) · gd:F 表示 final gather,从 gather 空间传到 view 空间;M 表示 gather 空间中的 multibounce;I 保留零次额外反弹。F 更接近最终图像,可以使用更高精度,M 则可以用较低精度换取容量。

不直接存一张“不可乘”的巨矩阵naive Tview × gather · densesplit TF final gatherview ← gatherM multibouncegather ↔ gatherruntimeF · (M + I)gather directindirect on viewF 保持高精度,M 可以低精度;误差最接近最终图像的地方最值得保留预算
gd = computeDirectIllumination(gatherSamples, light)
gIndirect = sparseMultiply(M, gd) + gd
viewIndirect = sparseMultiply(F, gIndirect)
finalPixel = viewDirect + viewIndirect

这段伪代码的重点是依赖关系而不是某个 API:CPU 或离线工具负责求出 F、M 的压缩表示,GPU 在布光时计算新的 gd,再按稀疏结构做两次向量乘法。相机或材质超出缓存假设时,不能靠把矩阵乘得更快来修复错误的输入契约。

4. Haar wavelet:把矩阵行变成可裁剪的结构

transfer matrix 的一行可以看作 gather domain 上的一张图:平滑的漫反射传播会集中在低频,阴影边界和高光则出现在局部高频。二维 Haar wavelet 在每一级把相邻值变成 average、horizontal、vertical、diagonal 四种子带,再对更小的 average 子块继续分解。变换后的许多系数接近零,就有机会裁剪成稀疏结构。

裁剪不能只看系数的绝对值。一个幅度不大的低频系数可能覆盖很大的 wavelet support area,累积后的像素误差反而比一个尖锐但只覆盖小区域的高频系数更显著。可以把系数幅度乘上它的支持区域,按对最终图像的影响排序,再为不同级别设定误差预算。

wavelet 把“有结构的图像行”变成少量系数matrix row as imagesharp shadows / glossy details仍然有局部高频结构2D HaaravgHVDcoarse → fine每级缩小一半sparse按 support保留重要项不能只保留绝对值最大的系数:大 support 的低幅系数可能覆盖更多像素

5. multibounce、final gather 与 sparse matrix packing

wavelet 裁剪之后,非零项往往不是均匀散落的单个数字,而是一些局部矩形块。把这些 block 按列聚集并打包进 texture atlas,可以让 GPU 用纹理采样路径读取系数,同时减少频繁切换纹理状态的成本;代价是必须保存每个 block 的坐标、尺寸和它对应的 view 或 gather 索引。

multibounce 的预算还要和 final gather 分开看。F 直接决定当前 view 点收到的结果,对误差更敏感;M 只在 gather 空间继续传播,多次反弹可以用较低精度和更激进的稀疏化控制容量。把二者都按最高精度保存,容量会失控;把二者都按最低精度处理,则会把最终画面最敏感的误差放大。

把稀疏矩阵当作很多张小图来打包sparse columns非零系数成簇texture atlas少量 texture state changesatlas 让 GPU 以图像采样路径读取稀疏块,但需要保存每个 block 的坐标与边界

6. GPU relighting engine:运行时留下什么

运行时的第一步仍是 direct illumination:根据 light 参数和 deep frame buffer 中的 position、normal、material,配合 shadow map 或其他可见性信息求出 Vdgd。为了降低直接光的锯齿,可以对 direct pass 做 2×2 supersampling;间接光通常按像素计算,再用 position 和 normal 的 closest-neighbor 规则上采样,避免把墙的一侧泄漏到另一侧。

因此 GPU 侧的核心操作可以收敛成三类:在纹理矩形上运行 direct shader;对方形纹理做 wavelet transform;按 atlas 中的非零 block 做 sparse matrix multiplication。CPU 并没有消失,它负责离线构建 transfer、选择裁剪阈值、打包 atlas 和保存索引;但灯光设计师改变参数时,不必再次执行这些重活。

运行时只留下三种 GPU 操作direct illuminationlight shader + shadow mapwavelet transformsquare texture passessparse multiplyatlas blocks × vectorsview full = view direct + view indirect2×2 direct supersample · indirect upsample · closest neighbor 避免跨边缘 leakingCPU 负责离线预计算;GPU 负责灯光改变后的即时更新

三步验收:从样本域到可交互灯光

分步1 / 3

第一步:分清 view domain 与 gather domain

用 deep frame buffer 记录当前相机可见的 view samples,再在整个参与光传输的场景中布置 gather samples。后者应按位置和法线相近性组织成方形纹理,不能从屏幕可见范围偷换采样域。

两朵云:一个负责“看见”,一个负责“可能反弹”camera-visible clouddeep frame bufferposition · normal · BRDFwhole-scene gather cloudflatten to power-of-two texture位置 + 法线相近的点相邻gather samples 不是屏幕上的可见点;它们承载“间接光从哪里来”的候选空间
只改灯光,不重建场景:把昂贵关系提前缓存view samples相机看见的像素点position · normal · materialVd + Vi → final pixelsgather samples全场可能反射光的表面点direct illumination gd可见区域之外也要采样transfer Tprecomputed offlineVi = T · gd;运行时只更新 direct light 与稀疏矩阵乘法

Cinematic Relighting Lab

把灯光设计参数与离线缓存分开调节,观察 direct、indirect、稀疏乘法工作和边缘 leaking 风险的趋势。

diffuse · 2 bounce · gather 256view samples ← indirect contribution读数direct74indirect60sparse work128edge risk21%light energy 64 · coefficients 64趋势示意:真实画面还取决于 visibility、BRDF 和场景尺度

diffuse 的间接光更平滑,适合先验证 transfer 与 sample coverage 的契约。

本章小结

  • cinematic relighting 适合固定相机、几何与材质、反复调整灯光的工作区间;它的收益来自复用场景关系,而不是取消全局光照成本。
  • view samples 来自当前可见像素,gather samples 覆盖全场可能贡献间接光的点;二者不能互换。
  • transfer matrix 把 gather direct 映射到 view indirect,拆成 final gather F、multibounce M 与 I 后才有可管理的精度和容量预算。
  • Haar wavelet 把传播行的低频与局部细节分开;裁剪时必须考虑 support area,之后再用 sparse matrix 和 atlas 降低 GPU 工作。
  • 运行时主要保留 direct illumination、wavelet transform 与 sparse multiplication;2×2 direct supersampling 和基于位置/法线的间接上采样帮助控制画质。

练习

问题 1|样本域。 一个室内场景里,当前相机只能看见墙的正面,但侧墙和天花板会把新灯光反弹到画面。为什么只用当前屏幕的 view samples 作为 gather samples 会失败?

问题 2|修改 Demo 代码。 给 Relighting Lab 增加一个 keepSupport 选项:系数裁剪不只比较绝对值,还把 wavelet support area 乘入重要性。你会怎样解释它对低频和高频系数的不同影响?

问题 3|方案选型。 一个固定镜头只允许设计师调整灯光;另一个工具允许用户任意移动相机并改变材质。哪一个更适合本章的 transfer cache?第二个工具需要补什么策略?

名词解释

本章出现的专业名词,用大白话再讲一遍。

view samples
gather samples
transfer matrix
Haar wavelet
sparse matrix

讨论

评论区加载中…