Skip to main content

Chapter 42: Irradiance Caching

Irradiance Caching 解决的问题很具体:在以 diffuse indirect lighting 为主的场景中,间接光在大面积墙面、地面和天花板上变化相对平缓,逐像素或逐 shading point 重新追踪大量半球光线会产生重复计算。读完本章后,读者应能追踪一帧室内 GI 中的缓存查询、样本生成、插值复用、错误拒绝和调试观察路径,并能判断一个缓存结果是稳定复用、需要补样本,还是已经把错误传播到画面边界。

本章使用一个固定贯穿材料:一间室内房间,顶部有面光源,墙面和地面为 diffuse 材质,靠墙放置一张桌子,门洞附近存在几何遮挡。朴素路径追踪会在每个可见 shading point 上采样半球入射光;Irradiance Cache 会在少量表面位置计算高成本间接照度,把结果写入 cache record,再在邻近表面通过位置、法线、距离、可见性和误差度量决定复用范围。

Irradiance Caching 的收益来自一个明确前提:diffuse 表面的出射光主要由半球积分后的 irradiance 决定,irradiance 信号通常比镜面反射、高光和阴影边界更低频。这个前提在大面积粗糙墙面上成立,在门洞边界、桌脚接触阴影、薄墙两侧和动态物体附近会迅速失效。本章的所有管线设计都围绕这个前提展开。

历史上,Ward、Rubinstein、Clear 的 A Ray Tracing Solution for Diffuse Interreflection 将 ray tracing 与 diffuse interreflection 的缓存复用结合起来;Ward 的 RADIANCE lighting simulation and rendering system 也让这类光照模拟进入可工程化工具链。这里引用它们只支撑一个判断:Irradiance Caching 是带偏差的稀疏采样与插值方案,工程价值来自受控复用,而非每个点独立求出无偏估计。

42.1 Irradiance Caching 的基本理念

Irradiance 是落到某个表面点上的半球入射能量密度。对表面点 xx 和法线 nn,diffuse 间接照度可写成行内形式 E(x,n)=Ω+Li(x,ω)max(0,nω)dωE(x,n)=\int_{\Omega^+}L_i(x,\omega)\max(0,n\cdot\omega)d\omega。如果材质近似 Lambertian,间接出射 radiance 可由 Lo=ρE/πL_o=\rho E/\pi 得到,其中 ρ\rho 是 diffuse albedo。这个公式在管线中的位置很清楚:ray hit 提供 xxnn,半球采样提供 LiL_i,积分结果进入 shading,再和 direct lighting、材质项、tone mapping 一起形成最终像素。

在贯穿室内房间中,地面中央、墙面中部和天花板大片区域的间接光变化通常平缓。路径追踪如果在这些相邻点重复发射相似半球光线,会得到相近估计,同时消耗大量 traversal、material evaluation 和 visibility test 成本。Irradiance Cache 把这些重复计算改成两阶段:先在少量位置求高质量 irradiance record,再在邻近点查询可用 record 并插值。

一个 cache record 至少包含表面位置、法线、irradiance 值、估计可信半径、样本射线命中的几何距离统计、可选梯度、时间戳和调试标记。位置决定空间邻近,法线决定表面朝向一致性,距离统计决定记录能覆盖多远,梯度用于补偿 irradiance 随位置和方向的局部变化,时间戳用于动态场景更新。工程实现中还会加入 surface id、room id、primitive id 或 probe cell id,用于把插值限制在同一类几何区域内。

Irradiance Caching 的核心判断是“这个 shading point 能否被已有 record 解释”。它先查询空间结构,例如 kd-tree、hash grid、BVH leaf 附加表或 screen tile 列表,找到附近记录;再根据距离、法线夹角、可见性和误差阈值给每条记录打权重;最后在权重足够稳定时插值输出。权重不足时,当前点会发射新的半球采样,生成新 record,并把结果写回缓存。

下面的图只描述一帧中一个可见点的判断路径。它的重点是 cache hit 与 cache miss 之间的分叉,以及 record 如何回到后续像素。

这条路径把算法收益和风险放在同一个位置。cache hit 减少半球采样成本,画面会更稳定,噪声会下降;cache miss 保持局部准确性,代价是增加射线追踪。错误的 cache hit 会把门洞另一侧、薄墙背面或桌脚附近的 irradiance 插值到当前点,形成漏光、灰雾和边界漂移。因此,Irradiance Caching 的工程质量取决于“哪些记录有资格被复用”,也取决于 miss 后生成的新记录是否能覆盖后续点。

这个理念与 lightmap、probe grid、path tracing 的关系需要放在同一组维度比较。lightmap 把间接光烘焙到 UV 纹理,适合静态几何和可离线处理资产;probe grid 把光照存到体素或探针位置,适合实时场景中的空间复用;path tracing 在每个 shading point 上做随机估计,理论上可收敛到高质量结果。Irradiance Cache 介于这些方案之间:它围绕实际可见表面生成稀疏记录,倾向服务 diffuse 间接光,并通过插值把高成本积分摊到邻近区域。

42.2 样本选择与插值策略

样本选择首先回答“哪里需要新 record”。在室内房间中,地面中央可以使用较稀疏的记录,因为附近半球可见几何变化小;桌脚、墙角、门洞边缘需要更密的记录,因为一点点位置变化就会改变可见半球。一个实用的 placement 策略通常从 shading point 出发:先查附近记录,若没有足够权重,或者误差超过阈值,就在当前点创建 record。这样生成的缓存会顺着相机可见区域自适应分布。

record 的可信范围不宜使用固定半径。半球采样时,每条 sample ray 会碰到某个表面或逃逸到环境;这些命中距离反映了局部几何尺度。开阔墙面上的命中距离较大,record 可覆盖更远;墙角、桌脚、门洞附近的命中距离较小,record 半径应收缩。许多实现会使用最近距离、调和平均距离或分位数距离来估计局部尺度,目标是让缓存密度跟随几何变化,而非跟随屏幕分辨率机械变化。

插值权重应同时处理 sample position、normal variation 和 distance weight。位置距离衡量当前点与 record 是否位于同一局部区域;法线差异衡量两个点的半球方向是否一致;距离权重衡量 record 的可信半径是否覆盖当前点。一个常见形态是把空间距离除以 record 半径,再加入法线角度项,得到综合误差;误差越小,权重越高。具体公式属于实现参数,正文只固定工程含义:位置负责空间邻近,法线负责朝向一致,半径负责局部几何尺度。

visibility 是插值策略中最容易被弱化的约束。两个点在欧氏距离上很近,仍可能被薄墙、桌面或门框分隔。贯穿房间里,墙的一侧和另一侧在坐标上接近,但它们看到的入射光完全不同;门洞两侧也可能共享邻近位置,却受不同房间亮度影响。工程实现可使用 depth discontinuity、surface id、primitive id、portal/room id、normal cone、ray-based visibility check 或 cache cell 分区来控制跨边界复用。

gradient 解决的是“邻近点能否通过局部变化预测”。Irradiance gradient 把 irradiance 随位置移动和法线旋转的变化记录下来,查询点在复用 record 时可根据相对位移和法线变化做一阶修正。它的价值体现在大面积曲面、缓变墙面和相机移动时的稳定性;它的风险也清楚:当遮挡关系突变时,一阶近似会继续平滑错误。因此 gradient 应服务低频变化补偿,几何边界仍由 rejection 规则负责。

把这些条件放到一个可复用判断顺序中,可以形成稳定的 cache lookup:先按空间结构取候选,再剔除跨 surface 或跨 room 的记录,再检查法线夹角和半径覆盖,接着计算误差权重,然后根据权重总量和最大误差决定插值或补样本。这个顺序把昂贵检查放在后面,把结构性错误挡在前面。若先做插值再看画面伪影,调试会变成猜测;若先约束候选来源,很多漏光会在查询阶段被阻断。

插值输出也需要保留可观察证据。每个像素除了最终颜色,还可以输出 cache record id、record count、最大权重、误差值、miss 标记和 rejected reason。调试 overlay 可以用不同颜色显示“使用一个 record”“使用多个 record”“生成新 record”“因法线拒绝”“因可见性拒绝”。这些通道能把画面伪影映射回具体规则,而非停留在“GI 看起来脏”这种难定位描述。

42.3 构建 Irradiance Cache 管线

构建管线要先确定 Irradiance Cache 位于 frame 的哪个阶段。对离线渲染器,它通常嵌在 surface shading 中:camera ray 命中表面后,integrator 先查询缓存,命中则插值,未命中则发射半球间接光线。对实时或混合渲染器,它可以作为 screen-space shading、ray traced GI pass 或 probe update pass 的一部分,并把结果写到历史缓存、空间缓存或渲染目标。无论具体 API 是 Vulkan、Metal、Direct3D 还是 CPU renderer,最小数据路径都是 hit point → cache lookup → irradiance value → diffuse indirect output。

在贯穿房间的实现中,最小 record 结构可以按字段理解,而不需要绑定某一种语言。positionWS 存世界空间位置,normalWS 存世界空间法线,irradianceRGB 存三通道间接照度,validRadius 存可信半径,gradientPosgradientRot 存可选一阶变化,surfaceKey 存几何或房间边界,lastUsedFrame 存生命周期。若在 GPU 上实现,这些字段会进入 structured buffer 或 storage buffer;若在 CPU 渲染器中实现,它们会进入空间索引节点。

sample placement 的入口应放在 miss 分支。当前点没有足够可用记录时,渲染器用半球采样估计 irradiance。采样方向应按 cosine-weighted hemisphere 或与材质相容的分布生成;每条间接光线返回入射 radiance,乘以余弦项并累积。对于 pure diffuse cache,本章只缓存 irradiance,不缓存 glossy radiance;glossy、mirror、透明折射和高频阴影仍应走对应的采样、追踪或独立缓存方案。

cache lookup 的空间结构决定查询成本。kd-tree 适合离线、增量插入频率较低且查询精度较高的场景;hash grid 适合 GPU 或实时系统,因为它可以把世界划成 cell,并行查询邻近 cell;BVH 附加记录适合与几何加速结构共享空间层级;screen tile list 适合只服务当前视角的短生命周期缓存。选择结构时先看更新频率,再看并行写入冲突,最后看查询半径是否大幅变化。

interpolation 需要明确合成规则。最简单的合成是对通过验证的 record 做归一化加权平均。带 gradient 的合成会先把每条 record 的 irradiance 修正到当前点,再参与平均。工程上还要记录最大单条权重和参与条数:一个超高权重 record 可能意味着插值接近单点复用,多个低误差 record 意味着区域稳定;大量低权重 record 被迫合成,往往说明缓存太稀或阈值过宽。

invalid sample rejection 是管线安全阀。它应覆盖至少五类拒绝:法线夹角过大、距离超过可信半径、surfaceKey 不一致、可见性被遮挡、误差指标超过阈值。动态场景还要加入 age 或 version 检查,例如灯光强度变化、门打开、桌子移动后,相关 record 的版本失效。失效策略可以粗粒度清空,也可以只清除受影响空间块;粗粒度方案简单稳定,局部失效方案更省成本但需要可靠依赖记录。

debug overlay 应从第一版管线就存在。没有 overlay 的 Irradiance Cache 很难维护,因为最终颜色把材质、直接光、间接光、tone mapping 和后处理混在一起。一个最小 overlay 至少显示四张图:irradiance value、record density、interpolation error、rejection reason。室内房间中,如果门洞边缘发亮,overlay 应能指出亮度来自哪几个 record、这些 record 的 surfaceKey 是什么、法线差多少、半径覆盖多大。

下面是一段简化的伪代码,用于说明查询与补样本的关键路径。它是 API 无关的关键路径示意,只展示 record 验证、插值和 miss 生成的顺序。

IrradianceResult shadeDiffuseIndirect(const ShadingPoint& point) {
CandidateList candidates = cache.query(point.positionWS, maxLookupRadius);
WeightedIrradiance sum;

for (const CacheRecord& record : candidates) {
Validation validation = validateRecord(point, record);
if (!validation.accepted) {
debug.markRejected(point.pixel, validation.reason);
continue;
}

Vec3 predicted = applyGradient(record, point.positionWS, point.normalWS);
sum.add(predicted, validation.weight);
}

if (sum.hasEnoughWeight()) {
debug.markCacheHit(point.pixel, sum.recordCount(), sum.maxWeight());
return IrradianceResult{sum.normalized(), false};
}

CacheRecord created = traceAndCreateRecord(point);
cache.insert(created);
debug.markCacheMiss(point.pixel, created.validRadius);
return IrradianceResult{created.irradianceRGB, true};
}

这段代码的关键点是 validateRecord 位于插值之前,applyGradient 位于验证之后,traceAndCreateRecord 只在权重不足时执行。若把 gradient 修正放到验证之前,错误 record 可能被数学修正掩盖;若把补样本放到每个低权重点上,缓存会快速膨胀;若缺少 rejected reason,调试只能从最终画面反推,成本会很高。

GPU 实现还要处理并行写入。多个像素可能同时 miss,并在相近位置创建重复 record。常见做法是使用 screen tile 预聚合、atomic append buffer、按 cell 限制 record 数量、两阶段 compaction 或延迟插入。第一阶段收集 miss 请求,第二阶段按空间 cell 合并相近请求,第三阶段发射高成本采样并写入缓存。这样可以降低重复半球采样,也能控制每帧新增记录数量。

42.4 优化与误差控制技巧

Irradiance Caching 的优化目标是在同等画面误差下减少半球采样、ray traversal 和内存访问,record 数量只是实现这个目标的中间变量。过少的 record 会导致漏光、过度平滑和边界漂移;过多的 record 会接近逐点采样,缓存收益下降。一个稳定调参顺序是先固定画质阈值,再观察 record density 和 miss ratio,接着压缩查询与存储成本,最后调整动态失效策略。

漏光通常来自跨几何边界插值。室内房间中,薄墙两侧、门洞边缘、桌面下方和墙角都容易出现这种错误。处理顺序应先检查候选记录是否来自同一 surfaceKey 或 room id,再检查法线夹角,然后检查深度或可见性。若这些结构性约束缺失,单纯缩小半径会让漏光减少,但也会制造更多 miss,最终把性能收益消耗掉。

过度平滑来自权重阈值过宽或 gradient 过度外推。它的画面表现是接触阴影变浅、墙角能量堆积被抹平、门洞亮暗交界变成灰色过渡。处理顺序是提高边界区域的 cache density,降低跨法线插值权重,限制 gradient 修正幅度,并在高误差区域强制生成新 record。若场景使用 artist-authored lightmap 或 baked AO,还要确认缓存结果没有把这些高频项重复平滑。

几何边界需要独立处理。边界的困难点在于 irradiance 信号和几何可见性同时突变:门框一侧能看到亮房间,另一侧看到暗走廊;桌脚附近一小段位移就会让半球样本命中桌腿。工程实现可在 record 中存储局部几何尺度、最近遮挡方向、dominant occluder id 或 normal cone,用于识别“空间近但可见半球变化大”的情况。边界区域的记录半径应自然收缩。

动态物体会破坏 record 的时间有效性。桌子移动后,原先桌脚附近的缓存会继续携带旧遮挡;灯光强度改变后,全部依赖该灯的 irradiance 都需要更新;门开合会改变房间之间的可见性。实用做法是把缓存分成静态间接、动态修正和短生命周期历史三层:静态层服务墙体和固定家具,动态层覆盖移动物体影响区域,历史层为 temporal stability 提供上帧参考。每层都需要独立 version 或 age 标记。

cache density 应受画面误差驱动。常见指标包括 miss ratio、平均参与 record 数、最大插值误差、单位面积 record 数、边界区域 record 数、每帧新增 record 数和内存占用。室内 GI 中,门洞和角落密度高属于正常结果;开阔墙面密度高说明半径估计过保守;薄墙附近密度低说明边界保护不足。密度图比最终颜色更适合调参,因为它直接显示缓存如何覆盖表面。

内存预算需要从 record 生命周期设计开始。每条 record 存 RGB irradiance、位置、法线、半径、梯度和调试字段,单条记录并不大,但高分辨率、多视角和动态场景会让数量增长。可用策略包括 cell 内数量上限、LRU 淘汰、相近 record 合并、半精度存储、梯度可选开关、按 view relevance 降级和分层缓存。压缩字段前要先确认瓶颈类型:若帧耗时主要来自 trace miss,压缩内存收益有限;若 GPU memory bandwidth 或 cache lookup 成本突出,字段压缩和布局整理会更有效。

优化阶段也要区分 CPU、GPU 和 API 状态。CPU 离线渲染器的主要成本可能是半球射线数量和空间索引查询;GPU 实时管线的主要成本可能是 append buffer 原子写、随机内存访问、ray query divergence、barrier 和 history resolve;显式 API 中还会出现 resource state transition 与同步等待。调试时应把每项成本归到对应层级,把具体耗时归因到真实瓶颈。

现代实时 GI 方案常把稀疏缓存、探针、reservoir sampling、radiance cache 或神经近似组合使用。例如 GI-1.0 讨论了实时全局光照中的两级 radiance caching 思路,Real-time Neural Radiance Caching for Path Tracing 则展示了用神经网络承载 radiance cache 的方向。这些资料说明了一个趋势:实时系统仍在追求“用少量昂贵估计服务大量 shading point”,但每种缓存的信号类型、更新策略和误差边界不同。本章讨论的 Irradiance Cache 仍应限定在 diffuse irradiance 这个对象上理解。

42.5 室内 GI 渲染性能提升实践

把 Irradiance Cache 放进室内 GI 实践时,应先从分项证据开始,再回到最终帧率。正确观察顺序是:固定相机、固定随机种子、关闭 tone mapping 之外的非必要后处理,输出 direct lighting、uncached indirect、cached indirect、record density、miss ratio 和 rejection reason。这样可以确认缓存改变的是间接光路径,材质、曝光和降噪器没有掩盖判断对象。

第一组观察看渲染时间。把一帧拆成 cache lookup、cache miss tracing、record insertion、interpolation resolve 和 debug output。若开启缓存后 miss tracing 大幅下降,但 lookup 成本快速上升,说明空间结构或候选数量需要优化;若 lookup 很轻但 miss ratio 高,说明阈值过严或半径估计过小;若 miss ratio 低但画面漏光,说明 rejection 规则过宽。性能数字必须和错误图一起看,单独看耗时会把错误复用误判成优化成功。

第二组观察看间接光稳定性。室内墙面上的 cached indirect 应在相机轻微移动时保持平滑,并维持低随机闪烁;门洞、墙角和桌脚附近可以出现更高 record density,但亮暗边界应保持可解释。若相机移动后大片墙面亮度跳变,通常是 record 创建顺序、随机种子、temporal history 或插值权重不稳定;若局部边界随视角漂移,通常是 screen-space cache 过度依赖当前可见样本。

第三组观察看边界伪影。门洞漏光、薄墙透光、接触阴影变灰、墙角亮斑和桌底漂白是最典型的失败症状。排查顺序应从 overlay 开始:先看错误像素用了哪些 record,再看这些 record 的 surfaceKey、法线和半径,然后看半球采样命中距离,最后复查动态失效。若错误 record 来自同一 surfaceKey,但可见性不同,需要增加 visibility 或 occluder 约束;若错误 record 来自不同 surfaceKey,候选过滤顺序有问题。

第四组观察看内存与并发。GPU 管线中,每帧新增 record 数量应有上限,append buffer 溢出要有可见标记,cell 内记录过多要触发合并或淘汰。若某个门洞区域在相机移动时持续追加记录,说明动态边界或阈值在抖动;若同一开阔墙面上产生大量重复记录,说明多个线程同时 miss 后缺少合并阶段。并发控制属于算法正确性的一部分,因为重复记录会改变权重分布,进而改变颜色。

实践中可以使用三档质量配置。低档使用较少半球 rays、较宽阈值、无 gradient 或简化 gradient,适合交互预览;中档使用正常 rays、法线和可见性拒绝、基础 gradient,适合编辑器视口或实时预览;高档使用更严格误差、更多边界检查、更密 record 和更长历史验证,适合离线预览或静态镜头。三档配置应共享同一套 debug overlay,这样调参差异能直接映射到缓存行为。

室内 GI 的最终判断应超出“速度提升”。更可复用的验收说法是:在固定输入下,cached indirect 与高采样参考相比,开阔 diffuse 表面误差集中在可接受范围内;门洞、墙角、接触区域没有跨边界漏光;相机小幅移动时 record density 和 miss ratio 稳定;每帧新增记录、缓存内存和 lookup 成本都在预算内。这个验收把视觉结果、管线阶段、GPU 资源和调试证据连到一起。

本章主线可以压缩成一个工程判断:Irradiance Cache 把 diffuse indirect lighting 从逐点高成本积分改成稀疏记录、局部插值和错误拒绝。它的成功条件是每个 record 都能说明自己覆盖哪些表面、因什么条件被复用、在什么边界失效,以及这些判断如何通过 overlay 和分项计时被复查。

最小自检任务

给定一个室内房间场景:顶部面光源照亮白色墙面,房间右侧有门洞,门洞外是较暗走廊,房间中央靠墙有一张桌子。你正在为 diffuse indirect lighting 加入 Irradiance Cache。当前画面中,门洞边缘出现发亮漏光,桌脚接触阴影也变浅。请设计一套排查顺序,说明先查看哪些 cache 证据,再根据证据调整哪些样本选择、插值或失效规则。

答案要点

先输出 cached indirect、record density、record id、miss ratio 和 rejection reason overlay,把门洞边缘和桌脚区域的错误像素定位到具体 cache record。接着检查这些 record 的 surfaceKey、位置、法线、valid radius 和参与权重,确认它们是否来自门洞另一侧、薄墙背面或桌面上方。如果错误来自跨区域候选,应先加强 surfaceKey、room id 或 visibility 过滤;如果错误来自同一区域但半径过大,应收缩局部几何尺度估计,并在门洞、墙角、桌脚附近提高 cache density。若参与记录通过了结构过滤但仍产生灰化,应降低法线差异权重、限制 gradient 外推,并在高误差像素触发新 record。最后检查动态或历史缓存版本,确认桌子移动、灯光变化或门状态变化后相关 record 已更新或失效。

本章知识点总结

  • 核心对象:Irradiance Cache 缓存的是 diffuse 间接照度记录,服务大面积低频间接光复用。
  • 积分位置:表面 hit point 提供位置和法线,半球采样估计 irradiance,结果进入 diffuse indirect shading。
  • 复用前提:开阔 diffuse 表面的 irradiance 变化较平缓,门洞、墙角、桌脚和薄墙区域会破坏这个前提。
  • 记录字段:cache record 应包含位置、法线、irradiance、可信半径、几何尺度、可选梯度和生命周期信息。
  • 查询顺序:稳定查询应先取候选,再做结构过滤、法线检查、半径检查、误差权重计算和插值决策。
  • 样本密度:record 半径应跟随局部几何尺度变化,开阔墙面可稀疏,边界和遮挡区域应更密。
  • 插值约束:distance weight、normal variation、visibility 和 error metric 共同决定 record 是否能解释当前点。
  • 梯度作用:irradiance gradient 可补偿局部平滑变化,但几何可见性突变仍需 rejection 规则处理。
  • 管线分支:cache hit 走插值路径,cache miss 触发半球采样、record 创建和缓存插入。
  • 错误来源:漏光多来自跨几何边界复用,过度平滑多来自阈值过宽或 gradient 外推过强。
  • 动态失效:移动物体、灯光变化和门状态变化应通过 version、age 或局部 invalidation 更新相关记录。
  • 性能判断:优化应同时观察 miss tracing、lookup 成本、record insertion、内存占用和画面误差。
  • 调试证据:record density、rejection reason、参与记录数量和最大权重能把伪影映射回具体规则。
  • 验收标准:合格实现应在开阔表面稳定复用,在几何边界拒绝错误插值,并把成本控制在预算内。