实时 GI 的教科书答案是预烘焙 lightmap,代价是动态物体吃不到间接光。Godot 4 给的动态方案是 SDFGI:把场景体素化成有向距离场(SDF),再用 cone tracing 在 SDF 上积分间接光,探针结果缓存进图集复用多帧。它不追求物理精确(那是 path tracing 的活),追求"每帧花得起的成本下,静态场景间接光大致对"。这篇拆 servers/rendering/renderer_rd 里 SDFGI 的实现和背后的数学。
两个分离的数据结构
SDFGI 的核心拆解是"几何可见性"和"光能分布"分开存:
- SDF cascade:世界按立方体盒子切成多级嵌套的
cascade_size³(4.x 固定 128³)体素,每帧对相机周围的 cascade 用 compute shader 做 SDF 体素化(sdfgi_preprocess.glsl,从场景深度/网格光栅化出最近表面距离)。SDF 值 $s(\mathbf p)$ 是位置 $\mathbf p$ 到最近表面的有向距离,体内为负。 - probe 间接口图集:每个 cascade 在体素空间里按固定间隔(8 体素)摆一个探针,每个探针把周围间接光烘进一张 octahedral 图集(方向 → 纹理坐标的等积映射)。
sdfgi_integrate.glsl负责把"这一帧新到的直接光 + 上一帧相邻 probe 的光"混合进探针。
为什么要分开?因为两者的更新频率不同:SDF 只在相机移动、几何变化时重烘;而光能是逐帧渐进传播的(间接光从光源"扩散"到远处要好几帧,靠多帧累积近似多次 bounce)。混在一起就得每帧全量重算,分开后探针更新只是一张图集的局部刷新。
cone tracing:在 SDF 上积分的数学
cone tracing 可以想成"把一条射线换成一个越来越粗的圆锥去扫场景"。对方向 $\mathbf d$ 的圆锥,沿轴步进时不是固定步长,而是用当前点的 SDF 值自适应推进:
$$ \mathbf p_{i+1} = \mathbf p_i + \max\big(s(\mathbf p_i),\ \epsilon\big) \cdot \mathbf d $$
圆锥在距离 $t$ 处的半径 $r = t \cdot \tan\theta$,而 SDF 保证 $s(\mathbf p) \geq$ 到最近表面的距离,所以 $s(\mathbf p) \cdot \mathbf d$ 的步长天然"不会跨过任何表面"——表面逼近时步长自动缩到 0。这就是 SDF 相对普通距离场的全部意义:** marching 的步长场是免费的**。
每个采样点的贡献按占据概率加权。设圆锥覆盖到该点时未遮挡的概率为 $T_i$(透射率),命中表面则 $T$ 衰减为 0:
$$ T_{i+1} = T_i \cdot (1 - \alpha_i), \qquad \alpha_i = \text{clamp}\left(1 - \frac{s(\mathbf p_i)}{r_i},\ 0,\ 1\right) $$
物理直觉:SDF 值离圆锥中心越近(相对圆锥半径),说明圆锥壁越可能撞到东西,透明度掉得越快。最终的间接光就是 $\sum_i T_i \cdot L_i \cdot \alpha_i$,$L_i$ 是该处探针方向的光照。半球用 4~6 个圆锥铺满(代码里按质量档取)。
cascade 滑动:无限世界靠盒子漂移
单个体素盒子装不下开放世界。SDFGI 的做法是 cascade 的盒子跟着相机量化滑动——相机越过体素边界时整个 cascade 平移一格,体素内容整体搬移(sdfgi_preprocess 里对重叠区做 copy,新露出的条带重烘)。级联结构(4 级,每级盒子大一倍、probe 间隔翻倍)让近处精度高、远处靠上一级覆盖。这个设计的代价是:相机连续移动时每帧都在搬数据,快速转视角时探针有短暂的不连续(表现为间接光"糊一下"),静止后几帧内收敛。
一图流
实战推论
- SDFGI 的"动态"有三档成本:静态场景几乎免费(探针不更新),相机移动中等(cascade 搬移),几何或光源变动最贵(dirty 区域探针重烘,帧时间会跳一下)。
- 它给的是低频间接光,细节靠 SSAO 和屏幕空间项补。把 SDFGI 当高精度方案用会失望——这是设计上"接受近似换实时性"的取舍。
- 半分辨率 tracing 是默认:质量档调低时 cone 数和图集分辨率一起降,画面表现为间接光变糊而非消失,这个行为和烘焙 lightmap 的"精度"完全不同维度。
本文基于 Godot 4.x 源码:servers/rendering/renderer_rd/sdfgi.cpp/h(RendererSDFGI)、servers/rendering/renderer_rd/shaders/sdfgi_preprocess.glsl、sdfgi_direct_light.glsl、sdfgi_integrate.glsl、sdfgi_ambient.glsl。