01 Voxel GI:原理与管线
01 Voxel GI:原理与管线
本文是系列的入口:先说明全局光照要解决什么问题,再说明为什么用有限体素场景表示承载间接光,最后把一帧中的数据阶段和 Render Graph 的调度关系串起来。这里保留总体认知,体素写入、光照采样和滤波的实现细节分别由后续文章展开。
一、全局光照
渲染方程可以写成出射辐射亮度:
其中 是表面自发光,积分项表示来自半球各方向的入射光经过材质反射后的结果。
1.1 直接光与间接光
直接光沿一条可见路径从光源到达表面;间接光至少经过一次表面反射。墙面接收方向光后照亮地面,地面再把光反射到相机可见的物体上,这就是实时 GI 要近似的传播过程。
在当前实现中,体素阶段保存表面属性与自发光,光照阶段将方向光可见性与表面属性组合成直接辐射;可选的间接阶段再从场景表示中查询反弹辐射。两类结果保持清晰的阶段语义:
这里 是方向光阴影得到的可见性, 是 Albedo。全局光照(Global Illumination,GI)关心的正是遮挡、反射、发光和多次反弹共同形成的整体光能分布。
二、实时 GI 方案
不同实时 GI 方案的核心差异在于“把场景光照存在哪里,以及怎样查询它”。下面的比较用于定位 Voxel GI,并不表示项目同时实现了所有方案。
| 方案 | 场景表示 | 动态性 | 典型优点 | 典型代价 |
|---|---|---|---|---|
| 烘焙光照 / Lightmap | 纹理 | 静态或有限更新 | 运行时查询便宜、质量稳定 | 烘焙时间与存储较高,动态物体处理受限 |
| Light Probe / 体积探针 | 探针与插值 | 中等 | 对动态物体友好、集成简单 | 空间细节和遮挡表达有限 |
| 屏幕空间 GI | 屏幕深度/颜色 | 高 | 不需要完整场景表示 | 屏幕外信息缺失,边界处容易漏光 |
| 硬件光线追踪 | 加速结构 | 高 | 路径表达直接、遮挡准确 | 硬件、采样噪声和预算要求高 |
| Voxel GI / VCT | 三维体素纹理 | 取决于场景更新 | 可查询屏幕外场景,适合 Compute 并行 | 体素数量随三个维度增长,需要平衡范围与细节 |
Voxel GI 选择有限范围的规则三维网格保存 Albedo、Normal、Opacity、Emissive 与 Radiance。Screen Trace 则从相机深度和法线重建世界位置,再在体素空间中查询它;它是屏幕端的查询组织方式,不替代场景体素化。
三、体素场景表示
体素把连续的场景几何转换为规则三维网格。规则网格的查询坐标直接,适合通过 Compute Shader 并行构建,也适合使用三维纹理在空间中进行邻域采样。体素数据是可复用的场景缓存,不是最终画面的替代品:相机可见范围之外的几何仍可以参与屏幕端的间接光查询。
3.1 范围与统一采样单位
体素网格使用世界空间轴对齐立方体。Volume 的影响区域只决定相机何时启用 GI,独立的 Voxelization Bounds 才决定三维网格覆盖的世界范围。对原始 Bounds 取最长轴,可以得到三个方向统一的采样单位:
这套约定避免长方体在三个轴上使用不同的体素尺寸,也让体素步进、边界求交和空间查询共享同一个坐标契约。体素网格、体素中心和 3D UV 的具体定义将在空间契约章节中明确。
3.2 Voxel Cone Tracing 的位置
普通 Ray Tracing 沿一条细射线前进;Cone Tracing 把射线扩展成随距离变宽的锥体,用有限的空间采样近似半球积分。锥体越远截面越大,查询就越适合使用更粗的空间层级。这个思想连接了“体素作为场景缓存”和“从表面查询间接光”两端,但具体采样方向、步进与层级选择不在本文展开。
四、Voxel GI 管线
一帧的主路径是:相机选择有效 Volume 和 Bounds,准备每相机上下文;随后按需建立阴影、体素 GBuffer 与辐射结果;屏幕阶段从深度/法线重建查询位置,经过可选滤波后合成回相机颜色。
4.1 场景与辐射阶段
VoxelGIVolume 负责提供生效设置与独立 Bounds;每个 Base Game Camera 都有自己的上下文,用来持有可跨帧复用的体素纹理、阴影深度和屏幕历史。阴影阶段为方向光提供可见性,Blocker 可以只参与 GI 阻挡而不贡献表面颜色。体素阶段把场景表面属性写入 3D GBuffer,随后直接光照阶段生成体素辐射;需要时再计算间接辐射。
4.2 屏幕与合成阶段
Screen Trace 读取 URP 相机 Depth/Normals,从深度重建世界位置并转换到体素空间。天空像素和体素范围外的位置提前结束,命中体素后得到屏幕端间接光。Temporal 通过 Motion Vector 重投影历史,Bilateral 按深度与法线抑制跨表面混合,最终将间接光加回场景颜色。
五、Render Graph 调度
插件从 ScriptableRendererFeature 进入 URP,VoxelGIRenderPass.RecordRenderGraph 是一帧的总调度入口。Feature 负责资源与 Pass 生命周期,并限制只对支持的 Base Game Camera 入队;它不会替换普通物体的 URP/Lit 渲染,而是在合适的相机阶段追加体素、屏幕查询、滤波和合成。
5.1 一帧快照与阶段依赖
调度开始时,从当前相机和 Volume 栈得到不可变的设置快照,再组合 Volume、Bounds、方向光、坐标矩阵、Renderer 注册表快照与 Camera Context。各阶段使用同一份快照,避免在执行过程中重新读取并得到互相矛盾的参数。
Render Graph 的重点不是简单地按函数顺序调用,而是为每个 Pass 声明资源的读写访问。下面的总览强调阶段依赖与分支:体素数据没有重建时可以直接从上下文中的有效结果继续进入光照或 Screen Trace;屏幕历史只有在条件满足时才加入图。
5.2 持久与瞬态资源
Camera Context 跨帧持有体素 GBuffer、带空间层级的 Radiance、方向光 Shadow Depth,以及按屏幕尺寸管理的 History A/B。这些资源是后续帧可以继续读取的外部资源,进入 Render Graph 时需要通过导入形成图中的 Handle。
原子累积、屏幕追踪输出、滤波输出、Scene Color Copy 和 Composite 输出只服务于当前帧。它们由 Render Graph 创建为 transient,并通过 UseTexture、UseBuffer 的读写访问表达先后关系;最后一个消费者完成后,底层内存即可回收或复用。体素化执行函数需要动态遍历外部 Renderer,但图内的 Buffer 与 3D 输出仍然显式声明,从而保留总体依赖。
