Godot 4 同时提供两种动态 GI:SDFGI(默认推荐)和 VoxelGI(4.0 时代的方案,文档里"新工程不建议使用")。既然都是 cone tracing,为什么还要两套?这篇拆 RendererVoxelGI 的实现,回答"它为什么被边缘化"——答案不在算法本身,在三个结构性差异。
实现:体素化 + mip 链 + cone tracing
VoxelGI 的烘焙(bake())把节点子树的网格光栅化到 64³ 的 3D 纹理(voxelization.glsl,三轴各渲一遍取保守体素化),每 voxel 存 RGBA16F 的 radiance + alpha(占据标志)。然后生成 mip 链——第 $i$ 级 mip 的 voxel 对应 $2^i$ 倍原始体素的平均 radiance,这条链就是 cone tracing 的"多分辨率 LOD"。
运行时 voxel_gi.glsl 对每个 shading point 做 cone tracing 求漫反射间接光:从表面沿法线半球铺若干个圆锥,沿轴步进时按当前距离选 mip 层级 $i = \lfloor \log_2(t \cdot \tan\theta / s) \rfloor$($s$ 为体素尺寸),采样 mip $i$ 的一个 voxel 即近似"圆锥在该处的总遮挡"。
这一步数学上优雅:mip 选择天然匹配圆锥的立体角,近处采样细 mip、远处采样粗 mip,单 texture fetch 就完成"圆锥内的积分近似",不需要逐体素循环。
三个结构性劣势
第一,烘焙粒度是手动的,且极慢。 VoxelGI 的烘焙范围以节点为根、按 bounds 参数划一个固定盒子,超出部分没有 GI。烘焙过程是光栅化 + compute 的多次全量遍历(sdfgi_preprocess 的 SDF 体素化是每帧自动的,VoxelGI 要手动按 Bake),64³ 密度下大场景要么精度不够要么分很多节点。SDFGI 把这套东西自动化到"开开关就行"。
第二,内存成本与覆盖范围成正比。 64³ RGBA16F = 每级 2MB,8 级 mip 链约 2.7MB——一个 VoxelGI 节点就要这么多,十个节点 27MB 显存,而且每个节点覆盖的体积是互斥的。SDFGI 的 4 级 cascade 是共享的 128³ SDF + 探针图集,相机走到哪用到哪,总量固定。开放世界场景里这个差别是"能不能用"级别的。
第三,specular GI 的尴尬位置。 VoxelGI 宣传点是支持镜面反射 GI(cone 沿反射方向 trace),但 64³ 的体素化对镜面路径分辨率太低,效果必须配合 roughness 才有意义;而 4.x 的 FSR2/TAA 普及后,屏幕空间反射(SSR)配 SDFGI 漫反射成了更实用的组合。VoxelGI 最后的阵地——动态物体的间接光——也被 SDFGI 的 probe 机制覆盖。
那为什么还留着它
兼容性是一方面(3.x 工程迁移的 GI 语义最接近),更重要的是它仍然是一个合法的离线烘焙 GI:小场景、封闭室内、不需要 cascade 滑动的场合,VoxelGI 的 cone tracing 质量(每 voxel 存的是真实 radiance,不是 SDF 的近似占据)比 SDFGI 的 probe 插值更细腻。文档里的"不建议新工程使用"准确含义是:SDFGI 在维护投入和通用性上都赢了,但 VoxelGI 没有被判死刑,只是不再演进。
一图流
选型结论
封闭室内小场景、追求离线烘焙质量、且不需要运行时更新——VoxelGI 仍可用,它的 cone tracing 比 SDFGI 的 probe 插值锐。其余场景一律 SDFGI:自动烘焙、固定内存、动态更新,三个维度全胜。需要注意的是两者可以共存(不同区域各管各的),混用时注意间接光会叠加,倾向关掉一处的 ambient 避免过曝。
本文基于 Godot 4.x 源码:scene/3d/voxel_gi.h/.cpp(VoxelGI/VoxelGIData)、servers/rendering/renderer_rd/environment/ 与 shaders/voxelization.glsl、voxel_gi.glsl(体素化与 cone tracing)、servers/rendering/renderer_rd/storage_rd/(体素纹理存储)。