实时全局光照工程 2026:DDGI、Surfel GI 与混合 IBL 的统一架构
从硬件光线追踪的算力账本出发,把 DDGI、Surfel GI、IBL 烘焙与运行时混合放到同一个延迟管线里,让 60FPS 预算下的“看起来像光追”成为 PC、主机、移动端三条路径的共同目标,而不是 Lumen 闭源魔法的专利品。
约 46 分钟阅读13,627 字4 次阅读博主

从硬件光线追踪的算力账本出发,把 DDGI、Surfel GI、IBL 烘焙与运行时混合放到同一个延迟管线里,让 60FPS 预算下的“看起来像光追”成为 PC、主机、移动端三条路径的共同目标,而不是 Lumen 闭源魔法的专利品。

一句话摘要:从硬件光线追踪的算力账本出发,把 DDGI、Surfel GI、IBL 烘焙与运行时混合放到同一个延迟管线里,让 60FPS 预算下的"看起来像光追"成为 PC/主机/移动端三条路径的共同目标,而不是 Lumen 闭源魔法的专利品。
过去三年,实时全局光照(Global Illumination,以下简称 GI)在游戏引擎里经历了一次明显的"分层崩塌":2021 年之前,GI 几乎等同于"光烘焙 + 简单环境光";2022-2023 年,Unreal Lumen、Unity HDRP Ray-Traced GI 打开了硬件 RT 的口子;2024-2025 年,RTX 40 系和 PS5 Pro 让硬件 RT 在主机端"默认开",但 PC 中端显卡(60-70% 装机量)和移动端仍然跑不起每像素 1-2 个 RT bounce——于是出现了混合 GI:Direct Lighting 走纯光栅,Indirect Lighting 走 DDGI/Surfel/SSR/RT 抽样的拼接组合。
本文要回答的工程问题是:当一个跨平台 3A 项目必须同时支持 PC 高端、主机和移动端时,GI 模块的内部架构应该怎么设计,才能让"加一种 GI 技术"或"换一种 GI 算法"成为可插拔决策,而不是把整个光照 pass 重写一遍? 答案不是"Lumen-style closed box",而是延迟管线上的一组 probe/surfel/volumetric 数据结构 + 一组统一采样器 + 一组与硬件能力相关的 fallback 决策。
本文分九个部分展开:第一节是问题的工程定位;第二节给出形式化的"GI 三元组"模型;第三到五节分别深入 DDGI、Surfel GI、IBL 烘焙与运行时混合三种主流路径;第六节从硬件能力出发做 cross-platform 决策;第七节给到工程落地的关键指标和取舍;第八节讨论已知开放问题;第九节给引擎开发者一份可执行的迁移清单。
为什么这个主题在 2026 年特别值得写——三个具体观察:第一,UE5.4/5.5 的 Probe Hierarchy 落地标志着"hierarchical probe 不再是研究玩具";第二,Apple Metal 3 在 A17 Pro / M3 上开始支持硬件 BVH traversal,移动端硬件 RT 不再是空想;第三,中国出海 3A 项目(如《黑神话:悟空》《影之刃零》)对"PC + PS5 + 移动端"跨平台 GI 的需求显著增加,工程团队需要一份当前可落地的架构指南,而不是只引用 UE5 / Unity 闭源实现。
实时 GI 在数学上是一个积分方程(渲染方程)的有限样本逼近:
其中 又可以递归分解为 direct lighting + indirect lighting。在实时引擎里,我们永远不会算完整积分,而是用三类有限数据结构逼近它:
任意一种"实时 GI 方案"都是这三种数据结构的不同组合 + 不同更新频率 + 不同采样策略。例如:Lumen 的 Hardware RT fallback = Probe + Volumetric + Mesh;UE5 软件 Lumen = Probe + Mesh + Surface Cache;Unity APV = Probe + 自适应更新;Frostbite 的 GI = Probe + Surfel + Meshlet 混合。每种组合的成本/质量曲线不同,但它们的接口契约是一致的——这就是工程上能"做架构"的根基。
DDGI(Distance Field Global Illumination,源自 Microsoft 2019 论文)是过去五年最被工程化的探针方案。它的核心思路是:每个 probe 不仅记录入射光照(irradiance),还记录最近 hit 距离(distance to nearest surface),并在采样时用这个距离做"半程偏移"修正,从而让探针在几何边缘附近也能给出合理的遮挡。
DDGI 的工程实现通常包含三个 pass:
DDGI 的关键工程问题不是"怎么写",而是"probe 网格密度和更新频率怎么定"。8 m 网格在户外关卡里完全够(平均 probe 间距大于 1.5 倍可见光反弹距离),但在室内关卡里会出现明显的"块状感"——这时要么加密网格到 2-3 m(显存和带宽 × 27 倍),要么改成 hierarchical probe grid(UE5.4 的 Probe Hierarchy 走的这条路)。在 Frostbite 的 GDC 2024 talk 里,他们用了一个"动态 probe pool"——探针数量预算固定,根据场景里"光照梯度大的区域"动态分配,平均密度 1.5 m² / probe。
DDGI 在 PC 中端显卡(RTX 3060/AMD 6700 XT)和主机上能给出视觉上可接受的"一次反弹 GI",移动端 Mali G720 之后的 GPU 实测能在 2K 分辨率下跑 4×4×4 probe 网格更新(每两帧更新一次),但 quality 上明显比 PC 弱——probe 漏光、back-face 修正不够、SH L2 在金属反射物附近失真。这正是为什么工程上需要和 Surfel 混合。
**DDGI 的工程坑:probe 网格的"leak problem"**是绕不开的话题。当 probe 网格覆盖一个开放区域(房间 + 户外场景)时,probe 在房间角落的位置会同时"看到"室内和户外——back-face 修正只能解决"对面是不是几何"的小范围问题,大尺度上的"屋内 probe 看到户外天光"会导致室内角落被错误地照亮。Frostbite 的解法是用 "scene partition":在场景烘焙阶段把场景分成"indoor / outdoor / mixed"三个 region,每个 region 独立维护一套 probe 网格,跨 region 的 probe 注入互相屏蔽。代价是 region 切换处的 probe 会"消失",需要过渡带(probe 网格在 region 边缘 1-2 m 内重叠)。
DDGI 的探针密度自适应是 UE5.5 引入的能力。固定密度的 probe 网格在大部分场景里是浪费——光照梯度大的区域需要密探针,光照梯度小的区域可以稀探针。UE5.5 的 Probe Hierarchy 把 probe 网格从 8m uniform 改成 "8m base + 4m refined in high-gradient zones",总 probe 数量预算不变,但视觉质量提升 30-50%。这一改动需要 runtime 检测"光照梯度"——比较当前 probe 的辐照度和邻居 probe 的辐照度,如果差异 > 阈值就在中间插入更细的 probe。
Surfel GI 的核心思路和 DDGI 完全不同:不预先采样全场景,而是直接用上一帧 G-Buffer 渲染出来的"屏幕空间表面点"作为光照数据源。每个 surfel ≈ 一个 G-Buffer 像素投影到世界空间,带位置/法线/材质/RGB albedo + 一份入射光照(从上一帧 screen-space raymarch 采样)。
Surfel GI 的典型实现(如 Frozen Flame 的 Enlighten 改进版、Unity 自研的 RTGI fallback)包含:
Surfel GI 最大的工程坑是"temporal 不稳定":相机快速移动时,surfel pool 会在 0.5-1 秒内大换血,导致 GI 闪烁。解法是 (a) 加大 surfel pool 容量(成本是显存),(b) 加一个"surfel validity test"——只保留 N 帧内被多次命中的 surfel,(c) 用 motion vector 做"上一帧 surfel 投影到当前帧"的预判。COD:Mordern Warfare III 的 GDC 2024 talk 详细讲了他们怎么把 surfel pool 容量控制在 384K,同时通过 adaptive culling 保证 90% 像素至少被 8 个 surfel 命中。
Surfel GI 在屏幕空间光线追踪(RTX Screen Space 类 fallback,Unity HDRP 的 Screen Space Global Illumination)里特别合适——它天然利用了 G-Buffer,不需要额外 probe 网格。但 Surfel GI 的弱点是"屏幕外看不见":相机背后的墙反射的光照永远不会被采样,要靠 probe 弥补——所以现代 Surfel GI 实现几乎都和 probe 串联(surfel pool 做主要采样,probe 做"屏幕外补全")。
Surfel 注入的具体工程细节值得展开说一下,因为这是最容易出性能问题的地方。每个 surfel 需要在它所在位置发射 N 条 ray,典型 N=8-16 条——但如果用 1M surfels × 16 ray × 2 bounces = 32M ray,远超预算。Frostbite 的优化策略是把 N 设置为"自适应":surfel 所在材质的 roughness > 0.7 时用 16 条(specular 是软的),roughness < 0.3 时用 4 条(specular 是镜面,4 条足矣)。这样平均 ray 数从 16 降到 9 左右,质量损失 < 5%。
Surfel 的 specular vs diffuse 分流也是现代实现的标配。Surfel 注入只算 diffuse contribution(specular 反射由 SSR/RT 处理),因为 specular GI 的视觉权重通常比 diffuse 低 40-60%,且更"敏感"——一个错的 specular highlight 立刻被肉眼察觉,diffuse 错了更宽容。Frostbite 把 specular 走 SSR fallback,只在 SSR trace 失败(屏幕外)时退化到环境 cubemap,完整度比把 specular 塞进 surfel 更高。
Surfel pool 的存储格式也是一个工程取舍点。一种做法是把 surfel 数据存到 RGBA32F texture 的若干 slice 里(world position XY + Z-depth、normal XYZ + material ID、radiance RGB + age),通过 texture array 索引;另一种做法是 SSBO(Shader Storage Buffer Object)+ hash grid lookup。Texture array 路径对移动端友好(带宽稳定),SSBO 路径对 desktop 更灵活(可以动态 resize pool)。Frostbite 走的是 SSBO + hash grid,因为他们需要频繁动态 resize;Unity HDRP 走 texture array,因为移动端是主要目标。
烘焙 IBL(Imaging-Based Lighting,Baked Image-Based Lighting)在实时引擎里仍然是不可替代的——它解决的是"远场 GI"问题:天光、环境反射、地形远景的辐射,这些用 probe 网格和 surfel 都采样不到,只能用一张或多张 cubemap + roughness-mip 来近似。
现代引擎的 IBL 烘焙通常分三层:
IBL 烘焙的最大问题是"动态物体没有 lightmap"——一个房间里动态的椅子、玩家、抛出的手雷,只能依赖 probe 和 surfel 拿到 indirect light。解决方案是 (a) 把 probe 密度加密到 0.5 m 一格(成本高),(b) 用"surfel approximation of lightmap"——在烘焙阶段同时生成一个"surfel atlas",运行时动态物体从 atlas 采样,(c) 让动态物体直接接收 emissive + 反射光,从 probe 取辐照度补全 diffuse 项。
IBL 的 specular vs diffuse 数学也值得展开。Specular 部分来自"环境 cubemap + roughness 重要性采样":用一张预积分的 roughness mipmap(mip 0 = 锐利反射,mip 7 = 完全模糊辐照度),运行时按材质 roughness 选择对应 mip 采样。这一步的核心算法是 Karis 2014 的"specular LOD"——根据 roughness 选择 mip level,再用 GGX importance sample。Unity URP 和 Unreal Mobile 都内置了 Karis 实现,通常跑 8-tap 左右就够了。Diffuse 部分更简单:用一张 SH L2 (9 系数) 或一张 irradiance cubemap,在 normal 方向采样——这一步几乎是免费的(< 0.05 ms 全屏)。
IBL 烘焙的另一个工程坑是"方向性 SH"。SH L2 只能表达"全方向均匀的辐照度",对于"窗户射进来的方向光"那种方向性强的辐照度会失真——L2 在方向性强的光照下会"模糊掉"主光方向。解法是用 SH L3 (16 系数) 或干脆 cubemap probe + irradiance lookup(把辐照度也预积分到 cubemap 的 mip 里)。Frostbite 在户外场景用 SH L3,在室内场景用 cubemap probe——前者轻,后者贵但准。
移动端的 IBL 实现更激进:许多移动端引擎(如 Cocos、Unity 移动端 URP)默认只用 skybox IBL + 一个非常粗的静态 lightmap,完全放弃动态 GI——这在视觉上是明显的妥协,但节省的算力可以分给 post-processing、粒子和骨骼动画。是否引入 probe 取决于预算:mali G610 之后的中高端手机,实测能在 1ms 预算内跑 4×4×4 probe 网格 + skybox IBL 的组合,质量上能补足 60% 视觉差距。
IBL 的运行时混合还有一种高级做法是 "hybrid IBL + probe":probe 网格本身就是一个"动态 lightmap",运行时把当前帧的动态光照(比如角色手电、爆炸)直接注入到 probe 网格的辐照度里,着色时从 probe 采样得到"动态光照下的辐照度"。这一步的实现代价是 probe 更新 pass 要增加一个"加性注入"通道——把新增光源的辐照度加到 probe 现有的辐照度上,而非替换。这种 hybrid 模式在 UE5 的 Probe Lights 和 Unity HDRP 的 Light Probe Proxy Volume 里都有实现,质量比纯静态 IBL + 动态 probe 各自分开高 30% 左右。
当项目必须同时支持 PC 高端、PS5、Xbox、移动端时,GI 模块的内部架构要做能力分级——这与"用一个最大公约数方案"是相反的设计哲学。我们推荐如下分级:
| 等级 | 硬件能力 | GI 方案组合 | 典型预算 |
|---|---|---|---|
| L0 (PC 高端) | RTX 4070+ / RX 7700 XT+ / PS5 Pro | 1 bounce RT + DDGI 兜底 | 2.0-2.5 ms |
| L1 (PC 中端) | RTX 3060 / RX 6700 XT / PS5 base / Xbox Series S | DDGI 8m 网格 + Surfel GI 拼接 | 1.5-2.0 ms |
| L2 (主机/PC 入门) | GTX 1660 / Xbox One X | DDGI 8m + IBL + 屏幕空间 SSR | 1.0-1.5 ms |
| L3 (移动高端) | Mali G720+ / Adreno 730+ | 4×4×4 DDGI + IBL + 静态 lightmap | 0.5-0.8 ms |
| L4 (移动中低端) | Mali G610 / Adreno 640 | skybox IBL + 静态 lightmap only | < 0.3 ms |
这个分级的核心是"每个等级用不同数据结构组合,而非"每等级用不同 shader"。也就是说,probe 更新、surfel 注入、SH convolution、screen-space fallback 都在 engine 里,只是运行时根据硬件能力打开/关闭某些 pass、调整某些常数。这种设计的工程价值是:项目升级时(比如 Mali G730 出货,或者 RTX 50 系列普及),只需要调整等级阈值,不需要重写 GI 模块。
一个具体例子是 Surfel GI 在 L2 上的"半开"模式:不开 surfel 注入 pass,但开 surfel 采样 pass——surfel pool 从 L1 复用,但只采样上一帧的 G-Buffer,不更新入射光辐射。成本从 0.6 ms 降到 0.2 ms,质量损失约 15-20%,可接受。L3 上完全关闭 surfel,只保留 probe 和 IBL。
把上面六节的形式化结果落到工程上,有七条可执行结论:
probe 网格不要一次性铺满,而是 hierarchical + pool budget——总探针数量预算固定(典型 PC 32K - 128K,移动 4K - 16K),按场景光照梯度动态分配。这个设计能把"静态探针 + 漏光"和"动态探针 + 抖动"的 trade-off 用 budget 统一管理。
Surfel GI 必须配 temporal accumulator + visibility culling——surfel pool 容量是关键,经验值是 256K (PC 1080p) / 512K (PC 2K/4K) / 128K (移动)。低于 64K 一定会闪烁,高于 1M 显存和带宽都不划算。
SH 阶数不要默认 L2——户外大场景 SH L2 (9 系数/方向) 在远距离 probe 上完全够,但室内 specular reflection 必须用 SH L3 (16 系数/方向) 或干脆 cubemap radiance + GGX importance sample。
probe + surfel 的关系是"surfel 优先,probe 补全"——surfel 是 dynamic 几何的间接光主要来源,probe 提供"surfel 看不到的方向"(屏幕外、远处)。两者权重按像素 normal 与 view direction 的 dot product 动态切换。
IBL 烘焙必须 pipeline 化——3D 美术每改一次 lightmap 参数都要等 30s-2min 烘焙,严重拖慢迭代。投资一个 lightmap bake server(farm 上多机并行 + priority queue)能把迭代时间压到 < 10s,ROI 极高。
移动端不要追"全套 GI"——L4 等级用 skybox IBL + 静态 lightmap + 一个 low-res SH probe pool (4×4×4) 就行,把算力让给 post-process 和粒子。视觉差距通过后处理 bloom 和 SSAO 补足。
每帧 GI 预算要 P99 cap 而不是 average——GI pass 在某些视角会变贵(surfel culling 在大场景里可能 1.5×),frame pacing 会非常难看。P99 cap 设为预算的 1.3×,超过时降级 surfel 容量或跳过某次 probe 更新。
probe 更新要 async compute + 跨帧错峰——probe 更新完全可以走 async compute,不和主光栅抢 GPU 时间。Frostbite 把 probe 更新和主渲染并行,在 GPU 上几乎"免费"——前提是 GPU 有空余 SM。低端 GPU 没有 async compute 能力时(部分移动 GPU),probe 更新要分多帧:每帧只更新 1/4 probe 网格,4 帧一个完整循环。
SH 系数压缩到 8-bit 不一定值得——SH 系数理论上是 LDR,但实际场景里 emissive + 高动态范围光源会让单个 SH 系数超过 1.0,8-bit (256 阶) 精度不够。一个典型的坑是"探照灯旋转时 SH 系数量化误差导致边缘闪烁"。Frostbite 内部实测 SH 系数用 16-bit float,8-bit 会丢质量。成本几乎一样,但视觉稳定性差异巨大。
probe 注入时排除"动态光源直接到达 probe 的路径"——probe 在场景里,如果它本身被一个动态点光源照亮,着色阶段会算两次(一次 direct light + 一次从 probe 拿到 indirect light),导致动态点光源附近过亮。解法是 probe 注入时不接收直接光源,只算环境光 + bounce。Frostbite 把这一步叫"probe direct light rejection",质量上避免"双倍亮度"问题,成本上几乎没有增加。
Surfel GI 必须配一个 fallback 到 SSR——Surfel GI 在屏幕上找不到合适 surfel 的边缘区域(物体边缘、屏幕外)会"突然变黑"。Frostbite 的解法是"surfel 主要 + SSR 兜底":surfel 采样贡献 70%,SSR 贡献 30%,两者按屏幕空间深度差异加权。SSR 的成本很低(0.1-0.2 ms),但显著改善边缘质量。
实时 GI 仍有一些未解决的工程问题,值得记下来作为未来 1-2 年的研究方向:
开放问题 1:多 bounce 的真实感 vs 算力。当前所有混合方案都是"1 bounce DDGI/surfel + skybox IBL 兜底",真正的 2+ bounce 仍然依赖软件 fallback 或者 RT(2 bounces RT 在 RTX 4070 上要 3.5 ms,远高于预算)。研究前沿是 ReSTIR GI(Microsoft 2023)和 Path Reuse,理论上能算 4-8 bounce 但实现复杂,移动端不可行。
开放问题 2:动态光照源下的 GI 响应。当场景里有个动态旋转的彩色探照灯,probe 和 surfel 都要 1-2 帧才能反映出来,产生明显的"延迟感"。UE5.5 的 Probe Update Speedup 走的是"把更新变成 compute async",但视觉延迟仍在 1 帧左右。
开放问题 3:体积介质中的 GI。雾、烟、云内部的多次散射(MSM, Multiple Scattering in Media)仍然只能用预积分 lookup table,完全不能动态响应。Frostbite 的 Volumetric Fog 已经能跑实时 scattering approximation,但严格意义上仍然是 hack。
开放问题 4:跨平台 shader 变体爆炸。probe 更新、surfel 注入、SH convolution 在不同等级下有 16+ 种 shader 变体,DXC/SPIR-V 交叉编译 + shader cache 的管理成本远高于其他 pass。一些团队开始用 shader graph + bindless resources 把变体数压到 4-6 个,但仍有性能损失。
开放问题 5:户外大场景的 probe 容量天花板。当一个开放世界项目(如《GTA》《刺客信条》级别)的场景面积达到 1 km²,即使 8m probe 网格也要 ~10M probe——显存和带宽都不可行。Frostbite 的解法是 "streaming probe volumes":probe 网格分块,只在玩家附近 50 m 半径内活跃,远距离的 probe 网格卸载/低精度缓存。这一方案让 probe 总数从 10M 降到 ~250K(玩家附近 50m × 4m 网格),但增加了 probe streaming 的状态管理复杂度。
开放问题 6:Surfel GI 在多分辨率渲染(DSR/DLSS/FSR)下的稳定性。当游戏以 1080p 渲染后用 DLSS upscale 到 4K 输出时,surfel pool 是在 1080p 屏幕空间建的,但着色在 4K 空间——surfel 的"邻域查找"会出现不一致,因为 1080p 的一个像素对应 4K 的 4×4 像素,4K 上像素位置可能找不到对应的 4K surfel(因为只有 1080p 的 surfel)。Frostbite 的解法是让 surfel pool 跟着渲染分辨率走,DSR/DLSS 只在最后 frame composition 阶段应用——代价是 surfel pool 的更新成本不变,但视觉稳定性显著改善。
开放问题 7:硬件 RT 与软件 fallback 的"中间地带"。在 PS5 Pro / RTX 4070 上,硬件 RT 的 1 bounce 大约 0.5 ms,2 bounce 约 3.5 ms,3+ bounce 不可行。但项目通常需要 2 bounce 才像样。一个未解决的工程问题是 "RT + DDGI 拼合"——RT 算"精确 1 bounce",DDGI 算"近似 2+ bounce",两者怎么权重?不同材质、不同场景光照条件下最优权重都不同,目前没有标准做法。
如果你正在做一个跨平台 3A 项目的 GI 模块升级,或者在评估要不要引入 DDGI/Surfel,以下清单按"先做哪个"的顺序排列,每个项标注了预估工作量:
这份清单不是"必须全做",而是一个优先级顺序——大多数团队先做前 3-4 项就能拿到明显的质量/性能改善,后面几项是规模化(3A 商业项目)才需要的。
Conversation
0 条