Blog·Studio
文章系列日历归档关于搜索
Blog·Studio

一个记录思考、笔记与作品的技术博客。

Connect

© 2026 · Blog Studio

鄂ICP备19019526号

crafted with care

stay curious ✦

  1. 文章
  2. ›全局光照探针网格 2026:DDGI、屏幕空间与级联辐射度

Index

  • 一、问题的提出:为什么 GI 比阴影难一个数量级
  • 二、形式化:三种场的表示与它们各自的失真
  • 2.1 辐照度探针:把方向积分掉
  • 2.2 DDGI 的八面体辐照度图:方向维度的有限分辨率
  • 2.3 Radiance Cascades:把辐照度换成辐射度
  • 2.4 三种表示的失真对比
  • 三、探针网格:位置维度的离散化
  • 3.1 网格间距与能量分辨率
  • 3.2 引擎内的降级链编排
  • 四、DDGI 的更新循环:射线、矩与可见性
  • 4.1 从 backface 距离到可见性矩
  • 4.2 射线调度与更新摊销
  • 4.3 从探针到像素:混合权重
  • 4.4 官方实现的现状
  • 五、屏幕空间 GI:便宜的替代与它的天花板
  • 六、级联辐射度:换一种存储来换一种失真
  • 七、引擎集成:把三条路线变成一条产品线
  • 7.1 分层预算
  • 7.2 跨路径融合的仲裁
  • 7.3 引擎侧的验证:Lumen 作为一个完整样本
  • 7.4 Godot 作为对照:模块化 GI 的代价
  • 八、六个工程陷阱与它们的诊断信号
  • 九、给引擎开发者的落地清单
  • 十、讨论:三条路线会收敛成一条吗
  • 参考文献

全局光照探针网格 2026:DDGI、屏幕空间与级联辐射度

全局光照的本质是用有限预算近似一个六维辐射场。本文拆解 DDGI 的八面体辐照度图与切比雪夫可见性矩、Radiance Cascades 的分层辐射度表示以及屏幕空间 GI 的能力边界,逐层对比三者的失真来源、显存开销与动态延迟,并给出横跨 PC 与移动端产品线时如何编排降级链、仲裁多路径贡献并设计可操作的诊断流程。

2026年10月25日·约 42 分钟阅读·12,374 字·2 次阅读·博主
#游戏引擎与渲染
全局光照探针网格 2026:DDGI、屏幕空间与级联辐射度

Index

  • 一、问题的提出:为什么 GI 比阴影难一个数量级
  • 二、形式化:三种场的表示与它们各自的失真
  • 2.1 辐照度探针:把方向积分掉
  • 2.2 DDGI 的八面体辐照度图:方向维度的有限分辨率
  • 2.3 Radiance Cascades:把辐照度换成辐射度
  • 2.4 三种表示的失真对比
  • 三、探针网格:位置维度的离散化
  • 3.1 网格间距与能量分辨率
  • 3.2 引擎内的降级链编排
  • 四、DDGI 的更新循环:射线、矩与可见性
  • 4.1 从 backface 距离到可见性矩
  • 4.2 射线调度与更新摊销
  • 4.3 从探针到像素:混合权重
  • 4.4 官方实现的现状
  • 五、屏幕空间 GI:便宜的替代与它的天花板
  • 六、级联辐射度:换一种存储来换一种失真
  • 七、引擎集成:把三条路线变成一条产品线
  • 7.1 分层预算
  • 7.2 跨路径融合的仲裁
  • 7.3 引擎侧的验证:Lumen 作为一个完整样本
  • 7.4 Godot 作为对照:模块化 GI 的代价
  • 八、六个工程陷阱与它们的诊断信号
  • 九、给引擎开发者的落地清单
  • 十、讨论:三条路线会收敛成一条吗
  • 参考文献

全局光照探针网格 2026:DDGI、屏幕空间与级联辐射度

上一篇聊的是帧图与瞬态资源,这篇往回走一步,去看那些真正决定画面"空气感"的东西:光在场景里弹了一圈之后,是怎么被采样、被存下来、再被取出来的。

间接漫反射是渲染管线里最贵、也最容易被"糊过去"的一环。你可以在引擎里把一个角色的 indirect intensity 拉高,让它看起来不那么像贴纸;也可以打开一盏补光,让阴影角落里不至于死黑。但这两种做法都不是全局光照,只是让观众停止质疑。

真正的 GI 系统要回答的是一个几何问题:从一个着色点出发,进入上半球的那些方向上,究竟有多少光能打过来? 这个问题的答案随位置、随方向、随时间变化,还要在 16 毫秒内算完,还要在 10 瓦的移动 GPU 上算完,还要在没有任何光追硬件的设备上"表现得像是算过了"。

围绕这个问题,业界长出了三条截然不同的技术路线:把辐射度烘进探针网格的 DDGI、只在屏幕内追踪的 SSGI、以及把辐射场分层存储的 Radiance Cascades。本文做的事情,是把这三条路线放回它们各自的约束里——数学表示、更新机制、可见性判定——然后回答工程上更实际的那个问题:当你要在一条产品线上同时支持从高端 PC 到千元安卓机的全谱系硬件时,这三条路线该怎么编排成一条降级链。

一、问题的提出:为什么 GI 比阴影难一个数量级

阴影解决的是"光到不到得了",GI 解决的是"光经过多次弹射之后还剩多少"。两者的计算量差异不在于多几次弹射,而在于弹射把问题的维度从二维升到了六维。

直接光照的经典解法是 shadow map:把光源深度投影到一张纹理上,着色时比较深度。这是一个查表问题,代价是 O(texels)O(\text{texels})O(texels)。而辐照度是半球上的积分:

E(x,n)=∫ΩL(x,ω) max⁡(0,n⋅ω) dωE(x, \mathbf{n}) = \int_{\Omega} L(x, \omega)\, \max(0, \mathbf{n} \cdot \omega)\, d\omegaE(x,n)=∫Ω​L(x,ω)max(0,n⋅ω)dω

其中 L(x,ω)L(x,\omega)L(x,ω) 是从着色点 xxx 沿方向 ω\omegaω 收到的入射辐射度,Ω\OmegaΩ 是法线半球。注意积分变量是方向——对每一个方向,你还要知道那束光在传播途中被什么挡住了、又被什么颜色染过。光是弹射的,一次弹射就把"沿路径积分"这个问题引了进来,而路径长度是无界的。

工程上通常做两个截断:弹射次数截断(Godot 官方文档对间接漫反射的说法是,游戏引擎里通常限制在 1 到 2 次,烘焙 lightmap 时可以到 16 次)和方向分辨率截断。后者正是探针方法的立足点——既然方向上积分太贵,那就把方向维度积分掉,只留下位置维度。

于是整个 GI 工程问题被重述为:设计一个关于位置的分段常数场 E^(p)\hat{E}(p)E^(p),让它逼近真实的辐照度场,并且在运行时以可控的代价更新它。 所有主流方案——从最早的 irradiance volume 到今天的 DDGI 与 Radiance Cascades——都是这个句子的不同实例化。差别在于场的表示、更新时机、可见性处理三处。

顺便说清边界:本文讨论的是间接漫反射与间接镜面中的漫射部分。镜面反射(间接高光)的处理路径完全不同——SSR、Radiance Cache、DDGI 的 glossy 变体各有各的问题域,本文只在它们与漫反射共享数据结构的交叉处提及。

二、形式化:三种场的表示与它们各自的失真

一个 GI 方案好不好,取决于它用什么量来表示场。三种主流表示在数学上差异很大。

2.1 辐照度探针:把方向积分掉

最经典的做法是在空间中放置探针,每个探针存一个方向相关的辐照度函数。存法上,SH(球谐)基展开是最省事的选择:9 个系数就能把低阶辐照度压得很准。

E^(p,n)≈∑k=03∑m=−kkck,m(p) Yk,m(n)\hat{E}(p, \mathbf{n}) \approx \sum_{k=0}^{3} \sum_{m=-k}^{k} c_{k,m}(p)\, Y_{k,m}(\mathbf{n})E^(p,n)≈∑k=03​∑m=−kk​ck,m​(p)Yk,m​(n)

代价是方向维度的分辨率被死死限制在二阶多项式级别。你永远看不到探针之间的方向性细节——这正是 NVIDIA 自己在 RTXGI-DDGI 文档里承认的局限:"DDGI 组合计算得到的漫反射辐照度本质上属于低频信息,无法捕捉精细的高频细节。"(原文表述为 low frequency global illumination signal,高频辐射度量与几何细节无法复现)

2.2 DDGI 的八面体辐照度图:方向维度的有限分辨率

DDGI 的做法不同:它不压成 SH,而是给每个探针配一张二维辐照度图,用八面体映射(octahedral mapping)把方向球面参数化到正方形的两个三角形上。于是方向维度从"9 个数"变成"一张小纹理"。

// 八面体编码:单位方向 <-> 方形域 [0,1]^2
float2 dir_to_oct(float3 n) {
    n /= (abs(n.x) + abs(n.y) + abs(n.z));
    float2 uv = n.xy;
    if (n.z < 0.0) {
        uv = (1.0 - abs(uv.yx)) * float2(n.x >= 0.0 ? 1.0 : -1.0,
                                          n.y >= 0.0 ? 1.0 : -1.0);
    }
    return uv * 0.5 + 0.5;
}

二维纹理带来两个额外好处。第一,可以做多级 mip,每一级对应不同的模糊半径,于是采样时能按"探针离着色点多远"来选模糊程度——远处的探针取模糊层级,近处的取锐利层级。第二,插值不再是 SH 系数空间的插值,而是纹理空间的插值,这可以用硬件双线性过滤白送。

代价是显存。八面体图按探针数乘以纹理分辨率线性膨胀,而探针数本身按场景体积的三次方增长。这也是为什么 RTXGI-DDGI 文档把 "Probe data storage can become memory intensive in large environments" 列为明确限制。

2.3 Radiance Cascades:把辐照度换成辐射度

Radiance Cascades(RC)走的第三条路更有意思。它不存辐照度,存的是辐射度——方向与位置都保留,只把空间维度分层。

它的核心观察是:一个位置的辐照度是它周围所有辐射度的角积分,因此天然低频;而直接存储"以某个点为中心、某个方向上的辐射度"保留了高频。RC 用级联解决空间问题:第 0 层的探针间距为 ddd,第 kkk 层间距为 d⋅2kd \cdot 2^kd⋅2k,每层探针存储的辐射度覆盖自己格子加上一层邻居的范围。

Rk(x,ω)≈14π∫cell(x,k)L(y,ω) dyR_k(x, \omega) \approx \frac{1}{4\pi} \int_{\text{cell}(x, k)} L(y, \omega)\, dyRk​(x,ω)≈4π1​∫cell(x,k)​L(y,ω)dy

低层负责高频细节(每个格子单独存一份辐射度),高层负责低频(把范围扩大以覆盖更多能量)。层与层之间通过插值与邻域混合衔接。RC 的一个工程优势常被忽略:它没有时域累积,因此天然无噪声,不需要去噪器。DDGI 和大部分路径追踪类 GI 都要面对噪声问题(所以常配 SVGF 之类的时域滤波),RC 直接绕开了。

RC 的代价是层数与探针数的组合开销,以及更复杂的层间混合逻辑。截至 2026 年 10 月,RC 的公开参考实现与生产部署案例主要来自 Path of Exile 2 团队的 arXiv 论文(arXiv:2408.14425),以及社区的交互式复现教程,主流引擎中尚无默认集成——本文引用其作为技术对照组,不假定它已可直接商用。

2.4 三种表示的失真对比

维度SH 辐照度探针DDGI 八面体图Radiance Cascades
方向分辨率极低(9 系数)中(可调纹理分辨率 + mip)高(辐射度保留方向细节)
空间分辨率与探针间距同阶与探针间距同阶分层,高层低频低层高频
噪声无(烘焙或直接积分)有(射线估计)无(无时域累积)
显存最低中高(纹理随探针数线性增长)中高(层数 × 探针数)
动态响应需重烘焙或重积分逐帧增量更新,有最小延迟更新延迟取决于层数

这张表也是全文的骨架:三条路线不是在同一个维度上竞争,而是在"方向分辨率 vs 噪声 vs 显存 vs 动态延迟"四个维度上各占一个不同的位置。任何一条降级链,本质上都是在这些维度之间做工程妥协。

三、探针网格:位置维度的离散化

有了表示,还剩下位置怎么撒。

DDGI 使用的是相机相对(camera-relative)的规则网格:探针锚定在相机位置上的一组固定偏移上,随相机整体平移。当相机移动超过某个阈值时,网格整体重新锚定并触发一次全量更新;在这个阈值内,探针的采样数据随相机移动而平移,但每个探针仍然在它自己的位置上做射线追踪。

这个设计有一个容易被忽略的性质:同一个探针会先后服务多个世界位置。 探针数据存的是"在这个位置能收到多少光",而相机移动改变了哪些世界位置被照亮。做法上常见两种:一种是让探针跟着相机跑并持续更新(DDGI 的做法),另一种是让探针钉在世界空间不动、只更新可见区域(静态烘焙探针的思路)。

DDGI 文档明确提到了它的一个根本性约束:"辐照度在探针中时域累积,因此当光照发生变化时,存在不可避免的最小延迟。" 换句话说,你在探针里看到的间接光,永远是若干帧之前的世界的间接光。这不是实现缺陷,而是时域累积本身的性质。

3.1 网格间距与能量分辨率

设探针间距为 sss,场景中平均遮挡体厚度为 ddd。当 s≫ds \gg ds≫d 时,相邻探针之间的可见性差异无法被分辨,会出现典型的漏光(light leak);当 s≪ds \ll ds≪d 时,探针数量按 1/s31/s^31/s3 爆炸。

实践中常见的经验值是让 sss 大致等于场景中主要遮挡结构的典型尺寸。这不是能靠数学算出来的量,通常要靠观察漏光位置来调——这也解释了为什么 GI 的调试总是从"看漏光"开始而不是从"看性能"开始。

3.2 引擎内的降级链编排

把三条路线放到一条产品线上,编排问题就变成了这样:

图表加载中…

这张图有一个关键性质:降级链是叠加而非替换。 高精度路径通常只覆盖一部分空间(硬件 RT 有 BVH 覆盖不到的区域,屏幕空间追踪天然看不到屏幕外的物体),低精度路径负责把剩余的洞补上。所以引擎里真正要实现的不是"选一个 GI 算法",而是"多个 GI 结果的融合与仲裁"。

Godot 的做法提供了一个反面参照。Godot 4.7 官方文档列出的 GI 技术包括 ReflectionProbe、LightmapGI、VoxelGI、SSIL 与 SDFGI,每种都有独立的可用性与限制;文档同时指出 VoxelGI、SSIL 与 SDFGI 在 Mobile 与 Compatibility 渲染器中不可用。文档对烘焙方案的判断也很直接:"如果你的目标是最大化视觉质量,烘焙方案通常看起来更好,而且漏光更少。" 这句话点破了核心权衡——实时方案换来的是灵活性,代价是漏光与噪点。

而在 Godot 的社区里,DDGI 仍是一个开放提案。godot-proposals 的讨论 #15115(2026 年 7 月 3 日由 thXor05 发起)提议把 DDGI 作为引擎的额外 GI 技术加入,理由是它"不需要预烘焙"且能解决光泄漏与阴影泄漏;维护者 Calinou 在回复中把它引向 godot#119869 "Upgrade SDFGI to HDDAGI",并提到曾有人在贡献者频道里尝试把屏幕空间 pass 集成进 HDDAGI 以改善质量、降低漏光,代价是明显的性能开销。截至 2026 年 10 月 25 日实测,godotengine/godot 主仓库最近的推送为 2026-10-23,MIT 许可,星标数约 11.9 万;讨论 #15115 在 2026 年 10 月 24 日仍有新回复,社区里也有人指向 godot#122999 这个分支做局部 GI 的尝试。

四、DDGI 的更新循环:射线、矩与可见性

DDGI 的核心是一套"用统计量近似可见性"的机制。理解它,就理解了 DDGI 相对普通辐照度探针的全部增量价值。

4.1 从 backface 距离到可见性矩

对每个探针,DDGI 向场景投出一组射线。每条射线返回三个信息:沿射线返回的辐射度贡献、命中点背后的 backface 距离(backface hit distance),以及用于加权的一组矩。

朴素做法是"射线打中就是可见",这会导致探针从被遮挡的位置收不到光,产生硬阴影式的暗斑。DDGI 的做法是记录 backface 距离的一阶矩与二阶矩:

μ=E[dback],σ2=E[dback2]−μ2\mu = \mathbb{E}[d_{\text{back}}], \qquad \sigma^2 = \mathbb{E}[d_{\text{back}}^2] - \mu^2μ=E[dback​],σ2=E[dback2​]−μ2

这两个矩构成一个单侧切比雪夫(Chebyshev)不等式的输入。对任意 t>0t > 0t>0:

P(dback≤t)≤σ2σ2+(t−μ)2P(d_{\text{back}} \le t) \le \frac{\sigma^2}{\sigma^2 + (t - \mu)^2}P(dback​≤t)≤σ2+(t−μ)2σ2​

在着色点做可见性测试时,我们知道着色点到探针的距离 ttt,也希望知道"探针到着色点这条连线被遮挡的概率"。取 ttt 为连线距离,若 μ<t\mu < tμ<t 说明探针那一侧的平均可见面比当前点更近,也就是当前点很可能被遮挡,把该方向的辐照度权重压低:

// 单侧切比雪夫可见性权重
float visibility_weight(float mean, float variance, float dist) {
    if (dist <= mean) return 1.0f;           // 比平均可见面还近,基本畅通
    float d2 = (dist - mean) * (dist - mean);
    return variance / (variance + d2);       // 越远越可能被遮挡
}

这就是"统计遮挡测试"的实际形态:不做几何求交,用矩来近似。代价是它只能给出统计意义上的可见性,无法区分"被一面墙挡住"和"被一块布挡住",这正是漏光与暗斑同时可能出现的根源。

4.2 射线调度与更新摊销

一帧内对所有探针投全部射线是不可能 budgets。DDGI 的做法是把更新摊销到多帧:每帧只更新一部分探针,探针的采样则通过时域抖动复用。

一个直观的实现是让每帧更新一个 8x8 的探针块,未更新的探针沿用上一次的辐照度与矩。探针数量多、更新周期长时,单帧成本就压到了可接受区间;但更新周期越长,"光照变化的最小延迟"就越长——这与前面提到的时域累积性质是同一枚硬币的两面。

射线在探针周围的球面上按黄金角(golden angle)分布生成,位置做逐帧抖动以在时间上均匀覆盖球面。命中点返回的辐射度按余弦加权累积进探针的辐照度图,这是蒙特卡洛估计半球积分的标准做法。

4.3 从探针到像素:混合权重

最终采样时,一个像素的辐照度由周围若干探针插值得到。权重通常由两部分相乘:空间距离权重(近的探针权重大)与方向权重(探针的法线与像素法线接近时权重大)。

float3 sample_probe_grid(ProbeGrid grid, float3 worldPos, float3 normal) {
    float3 acc = 0.0f; float wsum = 0.0f;
    for (int i = 0; i < kProbeCount; ++i) {
        float3 d = grid.probePos[i] - worldPos;
        float  dist = length(d);
        float  spatial = saturate(1.0f - dist / grid.probeSpacing);
        float  dirW = saturate(dot(normal, -d / max(dist, 1e-4)) * 0.5f + 0.5f);
        float  w = spatial * spatial * dirW;
        acc += grid.irradiance[i] * w;
        wsum += w;
    }
    return acc / max(wsum, 1e-4f);
}

距离权重用二次方衰减是有讲究的:平方让影响范围收缩得更快,避免远处探针把近处的阴影"抹平",这正是漏光的第二个来源。

4.4 官方实现的现状

截至 2026 年 10 月 25 日实测,NVIDIA GameWorks 的两个公开仓库状态如下:NVIDIAGameWorks/RTXGI-DDGI 星标约 875,最后推送为 2024-02-05;NVIDIAGameWorks/RTXGI 星标约 368,最后推送为 2026-10-13,其仓库描述明确提到 "RTXGI v2.0 Update including Neural Radiance Cache and Spatial Hash Radiance Cache"。

这个组合透露出一个信息:NVIDIA 把重心从 DDGI 单一算法,转移到了更通用的一组 radiance cache 结构上。DDGI 仓库的文档还提到一个对工程部署很关键的性质——"探针可以被预先计算并加载到不支持 GPU 光追的平台上,从而为所有平台提供统一的光照路径"。这条性质让 DDGI 有了一个别的实时 GI 方案不具备的定位:它可以是唯一的运行时 GI 路径,而烘焙只是它的离线预计算形态,而不是与之并列的第二套系统。

五、屏幕空间 GI:便宜的替代与它的天花板

SSGI(Screen Space Indirect Lighting)走的是完全不同的思路:不追踪场景几何,只在屏幕空间里追踪。

典型流程是:给定当前像素的法线与深度,沿半球方向在屏幕空间步进;每一步用前一帧的深度缓冲与颜色缓冲判断是否被遮挡,命中则取其辐射度作为间接光贡献。由于所有数据都来自上一帧的屏幕纹理,实现成本极低,且天然受益于 TAA/TSR 的时域累积。

它的问题也是结构性的,有四条:

  1. 屏幕外无解。 视野外的几何完全不存在于深度缓冲中,任何朝向屏幕外的射线都只能返回"未命中"。这意味着 SSGI 天然只能提供画面中心附近的间接光,边缘和角落直接丢失。
  2. 历史帧依赖导致鬼影与拖影。 相机移动时,上一帧的颜色与当前帧的几何不对应。
  3. 低分辨率与粗糙几何不可靠。 步进采样命中的是深度缓冲上的离散像素,厚度小于步长的几何会被穿透。
  4. 能量不守恒倾向。 屏幕空间追踪天然会漏掉从屏幕外绕回来的光,导致画面整体偏暗。

Godot 的 SSIL 在这一点上做得很直白:官方文档说明它的渲染"默认在半分辨率下执行(然后线性放大)以保证合理的性能水平",并且明确标注在 Mobile 与 Compatibility 渲染器中不可用。一个半分辨率、无去噪、屏幕空间的间接光通道,在定位上只能是"有总比没有好"的补充,而不是主力。

工程结论很明确:SSGI 是降级链的末端,不是起点。 它最适合的位置是当硬件光追不可用、DDGI 需要光追支持而软件光追又太贵时,作为一个零硬件门槛的兜底。

六、级联辐射度:换一种存储来换一种失真

Radiance Cascades 值得单独一节,因为它的思路转变足够彻底。

传统的探针方法存的是辐照度——方向维已经被积分掉,代价是高频信息永久丢失。RC 存的是辐射度:一个 probe 不只记"我总共收到多少光",而是记"从我周围某个空间单元的某个方向上来的是多少光"。

实现上,每个 probe 存一个小型的三维辐射度体(常见做法是 4x4x4 的 RGB 体素),每个体素对应探针周围空间的一个格子。层级之间通过上采样衔接:第 k 层的探针覆盖的范围是第 k-1 层的两倍宽。

这带来三个直接后果:

  • 高频保留。 近处 probe 的体素足够小,能表达墙角的明暗渐变、接触阴影的软化。
  • 无噪声。 因为没有时域累积,也就没有蒙特卡洛估计的方差。RC 的论文标题里 "noisy" 的缺席不是偶然——它把噪声问题整个绕开了。
  • 无最小延迟。 光照变化立刻反映,与帧率解耦。

代价集中在两点:内存(层数乘以每层探针数)和层间混合的复杂度。而且它并非万能——RC 同样需要解决"屏幕空间之外"的可见性,只是处理方式不同(用高层的粗粒度辐射度场来近似远场)。

RC 的公开材料截至 2026 年 10 月主要来自 Path of Exile 2 团队的 arXiv:2408.14425,以及社区基于该论文的交互式复现(如 jason.today/rc 的实现指南)。社区文章中把它描述为"一种高效、无噪声、面向 2D 与 3D 全局光照的新方法"。本文把它当作技术对照组:它提示了一个重要方向——不是改进辐照度探针,而是干脆不存辐照度。

七、引擎集成:把三条路线变成一条产品线

现在到了工程上最实际的部分:一条产品线要覆盖高端 PC、主机与移动端,GI 层应该怎么搭。

7.1 分层预算

一个可用的预算框架是按"每帧 GPU 毫秒"和"显存 MB"两个维度给每一层分配额度,然后让降级链在预算内自动选路。

层触发条件典型代价显存
硬件 RT 路径设备支持 DXR/Vulkan RT/Metal RT依赖场景复杂度,波动极大BVH + 命中点着色
DDGI(硬件 RT 供射线)支持光追但不追求路径追踪中等,可摊销到多帧探针网格 + 辐照度图 + 矩
DDGI(软件 RT 供射线)无光追但有 compute较高,需距离场等表示同上 + 距离场
SSGI兜底低,但受半分辨率拖累低
烘焙辐照度探针离线流程存在运行时几乎为零探针数据(可流式加载)

引擎要提供的是运行时探测 + 动态选路的能力。Godot 社区在讨论 #15115 时提出的设想正是这个形态:运行时检测显卡是否具备 RT 核心,据此在 DDGI 与 HDDAGI 之间切换;拥有对应硬件的玩家用 DDGI,其余玩家用替代方案。

7.2 跨路径融合的仲裁

当多个路径同时生效且结果重叠时,需要明确的仲裁规则,否则会出现亮度叠加或接缝。

一个务实的规则集是:

  • 硬件 RT 命中优先。 命中点着色的结果在物理上最可信,其他路径在同一像素上应当让位。
  • 探针贡献按"屏幕空间未覆盖"补入。 SSGI 只填补屏幕空间追踪失败的像素,两者用遮罩相乘而非相加。
  • 烘焙贡献作为基底。 静态光照始终在底层,实时路径在其上叠加"动态差异",而不是从零重建整个辐照度场。

这三条规则背后的判断是:不同路径的误差是不相关的,因此加权融合的收益通常高于择一使用。这也是很多自研引擎在实践中发现的经验——哪怕各路径单独看都有明显瑕疵,叠在一起反而比任何单一路径都稳定。

7.3 引擎侧的验证:Lumen 作为一个完整样本

如果要研究一个把 GI 做进生产管线的完整样本,Unreal Engine 5.8 的 Lumen 文档是目前公开信息最全的之一(Epic 的 EpicGames/UnrealEngine 仓库并未公开,GitHub API 拉取返回 404,因此以下全部依据官方文档)。

文档里几个值得单独拎出来的设计决策:

Surface Cache 与卡片化参数化。 Lumen 为场景表面生成自动参数化(Surface Cache),用于在射线命中点快速查光照。每个 mesh 的捕获位置称为 Cards,默认放 12 张,可在 Static Mesh 编辑器的 Build Settings 里用 Max Lumen Mesh Cards 调整。没有 Surface Cache 覆盖的区域在视图模式里显示为粉色,这些区域"不会弹射光,在反射中显示为黑色"。骨骼网格因为动态特性没有离线生成的卡片,默认在其预蒙皮包围盒周围放置 6 张,导致覆盖与质量相当有限。

软件光追的距离场策略。 默认对每条光线的前两米追踪单个 mesh 的距离场以保证精度,其余部分走合并后的 Global Distance Field 以加速。项目设置里的 Software Ray Tracing Mode 提供 Detail Tracing 与 Global Tracing 两种。

屏幕追踪优先的求值顺序。 文档明确:Lumen 先做 Screen Traces,找不到命中或光线穿过表面之后,再退回到更可靠的方法。文档也诚实指出屏幕追踪的代价——它"极大限制了美术调控的能力",因为像 Indirect Lighting Scale 这类调参只作用于间接光,而屏幕追踪无法正确支持它。

最终聚集包含天空光。 文档指出天空光被作为 Lumen 最终聚集过程的一部分来求解,包含天空遮蔽,这让室内比室外暗得多,效果更自然。

性能基线。 文档给出的 Epic 质量档位目标是:在次世代主机上以 1080p 内部分辨率跑 GI 与反射约 8 毫秒,并依赖 Temporal Super Resolution 输出接近原生 4K 的画质。

与烘焙的互斥。 文档说明启用 Lumen 后,预先计算的静态光照贡献会被禁用、所有 lightmap 被隐藏。这是一条重要的架构分界线:Lumen 不是"烘焙的补充",而是"替代烘焙"。这与 DDGI 文档里"探针可预计算以统一所有平台光照路径"的思路形成了两种不同的整合哲学。

7.4 Godot 作为对照:模块化 GI 的代价

Godot 4.7 的文档给出了一个模块化的 GI 谱系,每种技术的视觉代价被逐项列出:VoxelGI 因体素化特性,在墙与地板薄于一个体素时会出现漏光,官方建议所有实体表面至少厚于一个体素,且斜面上可能出现条纹状伪影,可通过调整 bias 属性或旋转 VoxelGI 节点改善;SDFGI 会有漏光与可见的级联切换(visible cascade shifts),其 GI 细节层次随相机到表面的距离变化。

这些描述很有价值——它们把"GI 看起来不对"从玄学变成了可操作的检查项。漏光先查遮挡体厚度,条纹先查空间表示的各向异性,色阶断层先查精度。 这条诊断顺序比任何单一算法的实现细节都更实用。

八、六个工程陷阱与它们的诊断信号

综合前面的分析,落地一条 GI 降级链时反复出现的坑大致是这六个。

陷阱一:探针间距与遮挡体厚度失配。 症状是隔墙漏光。诊断方法是把探针间距临时减半,看漏光是否消失——若消失,就是间距问题;若不消失,是遮挡体太薄或距离场表示有洞。

陷阱二:方向维度被过度压缩。 症状是墙角、物体边缘的间接光完全没有方向性,一片死平。SH 探针是典型受害者,八面体图可以缓解但不能根除。

陷阱三:把统计可见性当成几何可见性。 切比雪夫权重是概率估计,"遮挡 60%"不等于"遮挡 60% 的区域"。表现是间接阴影边界出现半透明的灰雾,而不是清晰的软阴影。

陷阱四:时域累积带来的延迟被误判为 bug。 探针里的光是几帧前的世界的光。表现是快速移动的光源(比如玩家手里的火把)照亮墙面有肉眼可见的滞后。解法是缩短更新周期或提高受影响探针的更新优先级,而不是去调混合权重。

陷阱五:多路径叠加导致能量超发。 硬件 RT 与探针覆盖同一像素时若简单相加,会出现比任何单一路径都亮的区域,且亮度随相机移动跳变。必须用遮罩做互斥。

陷阱六:把 GPU 侧成本当成唯一成本。 探针网格的 CPU 侧维护(网格锚定、可见性剔除、更新列表维护)在大型场景里可能比 GPU 更新还贵。一个纯粹的"GI 团队只管 GPU"的分工方式在这里会失效。

关于工具链,值得单独提一句 GPU 验证:Khronos 官方的 KhronosGroup/Vulkan-ValidationLayers(实测约 1047 星,Apache-2.0,2026-10-24 有推送)与 KhronosGroup/SPIR-V-Cross(约 2536 星)这类工具,能帮你确认着色器在跨平台编译后语义一致;而 microsoft/DirectXShaderCompiler(约 3674 星,2026-10-23 推送)是 DXC 本身,在着色器变体与跨平台编译上的排查经常用到。GI 路径之所以难调,很大程度上是因为它同时跨了数学、GPU 与内容三个层,工具链的断裂会让人误判问题所在。

九、给引擎开发者的落地清单

如果要在一个自研或基于现有引擎的项目里搭一条可维护的 GI 降级链,可以按这个顺序推进。

第一步,先定测量口径。 在写任何 GI 代码之前,先能回答:"当前这一帧的间接光贡献是多少?"没有这个测量能力,后面所有的优化都是盲改。

第二步,把烘焙路径做成一等公民。 烘焙方案在静态内容上的画质与漏光表现都优于实时方案,且运行时成本接近零。RTXGI-DDGI 文档里"探针可预计算并加载到所有平台"的说法,值得作为架构目标而不是技巧。

第三步,明确每条路径的不变量。 硬件 RT 路径应该追求物理正确,探针路径应该追求覆盖与低延迟,屏幕空间路径应该追求零硬件门槛。给每条路径写清楚它保证什么、不保证什么,美术与程序才知道什么时候该用哪一个。

第四步,建立融合的仲裁规则,并把它们写进文档。 遮罩互斥、优先级、基底叠加——这些规则如果只存在于某个人的脑子里,每次重构都会重新踩一遍。

第五步,做诊断工具,而不是调参。 Godot 文档里那些具体到"墙至少厚于一个体素""调整 bias 属性或旋转 VoxelGI 节点"的建议,才是 GI 调试应有的颗粒度。引擎应该提供可视化探针网格、可视化可见性权重、可视化各路径贡献分量的开关。

第六步,把降级做成产品决策而非技术兜底。 一个只有 8% 玩家用得上的高端路径,和一个覆盖 100% 玩家的低端路径,是两种完全不同的美术指导方向。降级链的每一级都应当对应一个明确的用户群与画质预期。

十、讨论:三条路线会收敛成一条吗

回到开头的问题。DDGI 走的是"用统计量补足可见性"的路线,RC 走的是"换一种辐射度表示"的路线,屏幕空间走的是"干脆只求近似"的路线。它们看起来分岔,但底层都在回答同一个问题:在有限预算下,如何近似一个六维的辐射场。

有几个可能的收敛方向值得留意。

其一,硬件光追的普及会重排优先级,但不改变数据结构问题。 光追让"射线追踪"变便宜了,但"把结果存到哪里、怎么插值"的问题一个都没少。NVIDIA 从 DDGI 转向包含 Neural Radiance Cache 与 Spatial Hash Radiance Cache 的 RTXGI v2.0,看起来正是这个方向——用可学习的结构替换手工设计的探针网格。

其二,学习型 radiance cache 正在成为第三条路线的补充而非替代。 它的思路是用神经网络直接预测辐照度场,跳过显式的射线积分与探针存储。这是本轮 GPU 光追浪潮里最有希望压缩成本的方向之一,但截至 2026 年 10 月,其在生产项目中的大规模可复现质量数据仍然缺乏公开验证。

其三,降级链本身可能会被更少的技术吃掉。 如果一台设备能以可接受代价跑硬件 RT 命中点着色,那么"没有烘焙、没有探针、没有屏幕空间"的全动态路径是最简洁的架构。Lumen 走向"替代烘焙"而非"补充烘焙",正是这种趋势的具体体现。

但对绝大多数同时面对 PC 与移动端的团队来说,"多条路径 + 明确仲裁 + 逐级降质"仍然是唯一现实的选择。差别只在于,你是把这套东西设计出来的,还是每次逐帧手搓。


参考文献

  1. Zander Majercik, Jean-Philippe Guertin, Derek Nowrouzezahrai, Morgan McGuire. Dynamic Diffuse Global Illumination with Ray-Traced Irradiance Fields. Journal of Computer Graphics Techniques (JCGT), vol. 8, no. 2, 1-30, 2019.
  2. Zander Majercik. Scaling Probe-Based Real-Time Dynamic Global Illumination for Production. Journal of Computer Graphics Techniques (JCGT), vol. 10, no. 2, 1-29, 2021. 预印本见 arXiv:2009.10796.
  3. Alexander Sannikov et al. Radiance Cascades: A Novel High-Resolution Formalization of Global Illumination. arXiv:2408.14425, 2024.
  4. NVIDIA GameWorks. RTXGI-DDGI: Algorithms. GitHub 仓库文档, 最后推送 2024-02-05.
  5. NVIDIA GameWorks. RTXGI v2.0. GitHub 仓库, 含 Neural Radiance Cache 与 Spatial Hash Radiance Cache, 最后推送 2026-10-13.
  6. Epic Games. Lumen Technical Details in Unreal Engine. Unreal Engine 5.8 官方文档.
  7. Epic Games. Lumen Global Illumination and Reflections in Unreal Engine. Unreal Engine 5.8 官方文档.
  8. Epic Games. Temporal Super Resolution in Unreal Engine. Unreal Engine 5.8 官方文档.
  9. Godot Engine. Introduction to global illumination. Godot Engine 4.7 官方文档.
  10. Godot Engine. Add DDGI as an additional GI-technique. godotengine/godot-proposals 讨论 #15115, 2026-07-03 发起.
  11. Khronos Group. Vulkan 1.4.366 Specification. 规范版本 1.4.366, 2026-10-23.
  12. LunarG. Vulkan SDK 1.4.363.0 Release Notes. 2026-09-29.
  13. AMD. AMD FidelityFX Super Resolution 3 (FSR 3). GPUOpen 开发者文档.
  14. Intel. Intel XeSS SDK. GitHub 仓库, 最后推送 2026-09-09.
  15. NVIDIA. NVIDIA DLSS SDK. GitHub 仓库, 最后推送 2026-10-16.
  16. Google. Filament. GitHub 仓库, Apache-2.0, 最后推送 2026-10-24.
  17. Khronos Group. Vulkan-ValidationLayers 与 SPIRV-Cross. GitHub 仓库, 均于 2026-10 有推送.
  18. Jeroen van der Zijp. Jolt Physics. GitHub 仓库, MIT 许可, 2026-10-24 推送.
  19. Embree. Embree Ray Tracing Kernels. GitHub 仓库, Apache-2.0, 2026-10-23 推送.
  20. Majercik et al. Efficient Light Probes for Real-Time Global Illumination. ACM SIGGRAPH 2022, DOI 10.1145/3550454.3555452.

一句话摘要:全局光照的本质是用有限预算近似一个六维辐射场,DDGI 用统计可见性补足探针、Radiance Cascades 用分层辐射度保住高频、屏幕空间 GI 兜底无硬件场景,而把它们编排成一条可维护的降级链,才是横跨 PC 与移动端的引擎真正要解的题。

←返回文章列表

Related

可能也会喜欢

  • 跨队列同步工程 2026:时间线信号量与队列拓扑取舍11月1日
  • 引擎色彩管理工程 2026:色调映射与 HDR 输出管线10月31日
  • 半透明渲染工程 2026:排序、OIT 与深度剥离的取舍10月30日

Conversation

0 条

留下你的想法

加载评论中…

New comment