VCTRenderer 动态体素化实战:flag volume 如何实现场景每帧实时更新
VCTRenderer 动态体素化实战:flag volume 如何实现场景每帧实时更新
【免费下载链接】VCTRendererA real time global illumination solution that achieves glossy surfaces, diffuse reflection, specular reflection, ambient occlusion, indirect shadows, soft shadows, emissive materials and 2-bounce GI. Published here http://ieeexplore.ieee.org/abstract/document/7833375/项目地址: https://gitcode.com/gh_mirrors/vc/VCTRenderer
核心机制速览
VCTRenderer 是一套基于体素锥追踪(Voxel Cone Tracing)的实时全局光照(实时GI)方案,能够呈现光泽表面、漫反射、镜面反射、环境光遮蔽、间接阴影与软阴影等效果。要让这些效果真正"实时",场景中的动态体素化(每帧更新运动物体的体素数据)是关键难点——而 VCTRenderer 的解法,就是一套精巧的flag volume(静态标记体积)机制。
为什么动态物体是体素化的大麻烦
体素化(Voxelization)的本质,是把场景几何离散成三维网格并写入 3D 纹理。静态场景可以只做一次,但一旦物体开始移动,旧的体素数据就会"残留"在原地,产生错误的间接光照和阴影。
最朴素的想法是"每帧全部重新体素化",但代价高昂。VCTRenderer 的高明之处在于只更新变化的部分:先为静态几何一次性建立体素,再为动态几何单独做增量更新。而这一切的枢纽,就是staticFlag这张小小的 R8 3D 纹理——flag volume。
flag volume 是什么?一张"体素身份证"
flag volume 是一张与体素网格同分辨率的 3D 纹理,每个体素记录一个字节(R8):
- 值为
1:该体素被静态几何占据; - 值为
0:该体素当前由动态物体占据,或为空。
它在VoxelizerRenderer::SetupVoxelVolumes()中被创建,并采用Nearest过滤、ClampToEdge环绕,专为读写标记而设计。核心渲染器源码位于engine/assets/code/renderers/voxelizer_renderer.cpp。
两步体素化:静态与动态各司其职
VCTRenderer 将场景几何按Node::GeometryState分为Static与Dynamic两类(见engine/scene/node.h),体素化因此拆成两个 pass:
第一步:静态场景体素化 + 写 flag
VoxelizeStaticScene()执行时,体素化片元着色器收到flagStaticVoxels = 1的 uniform。在voxelization.frag中,每写入一个体素就同步执行:
if (flagStaticVoxels == 1) { imageStore(staticVoxelFlag, position, vec4(1.0)); }这一趟不仅把静态几何的 albedo、法线、自发光写入体素纹理,还在 flag volume 上"盖章",标记哪些体素属于静态。
第二步:动态场景体素化 + 读 flag
当物体移动时,VoxelizeDynamicScene()以flagStaticVoxels = 0运行,只通过DrawListState(Node::Dynamic)绘制动态节点。此时片元着色器先查 flag:
if (flagStaticVoxels == 0) { bool isStatic = imageLoad(staticVoxelFlag, position).r > 0.0f; if (isStatic) opacity = 0.0f; // 静态体素,取消写入 }动态几何在写入时自动避开静态体素,避免覆盖已标记的数据。
动态体素的"清理-重写"循环
动态物体移动后,旧位置的体素必须清除。这一步由计算着色器clear_dynamic.comp完成,其逻辑极其简洁:
- 体素为空(alpha 接近 0)→ 跳过;
- 体素被 flag 标记为静态 → 跳过;
- 其余(即"动态体素")→ 清空 albedo、normal、emissive 三张纹理。
这就是ClearDynamicVoxels()所做的工作。清除之后,再以当前变换重新体素化动态节点,实现"旧数据擦除、新数据写入"的完整闭环。相关着色器源码位于engine/assets/shaders/clear_dynamic.comp与engine/assets/shaders/voxelization.frag。
更新频率:按需触发还是每 N 帧刷新
光有机制还不够,更新策略同样讲究。VCTRenderer 通过framestep提供两种模式(由VoxelizerRenderer::SetUpdateFrequency()控制):
- 按需更新(
framestep = -1):遍历Transform::TransformChangedMap(),只有检测到节点变换发生变化时才重新体素化动态场景;若只是光源变化,则只更新 radiance,成本更低。 - 定时刷新(
framestep >= 1):每隔 N 帧强制执行一次动态体素化,适合持续运动的场景。
在运行时,你可以在 Voxelization 面板中通过 "Update When Needed" 与 "Update Frequency" 两个选项实时切换,界面代码位于engine/assets/code/interfaces/voxelization_options.cpp。
这套设计带来了什么
flag volume 机制的价值可以归结为三点:
- 性能友好:静态体素只算一次,每帧只处理动态部分的增量,而不是全量重算;
- 数据一致性:动态体素在写入时自动与静态数据隔离,保证 GI 结果稳定;
- 策略灵活:按需/定时两种更新模式,让开发者在画质与性能之间自由取舍。
动手实践建议
想上手体验这套动态体素化流程,可以从这些模块入手:
- 渲染器主体:
engine/assets/code/renderers/voxelizer_renderer.cpp - 体素化片元着色器:
engine/assets/shaders/voxelization.frag - 动态体素清理计算着色器:
engine/assets/shaders/clear_dynamic.comp - 节点静态/动态状态定义:
engine/scene/node.h - 体素化参数面板:
engine/assets/code/interfaces/voxelization_options.cpp
理解了 flag volume 的读写时机,你就抓住了 VCTRenderer 动态体素化实时更新的核心脉络——这也是许多体素 GI 引擎通用的经典思路。
【免费下载链接】VCTRendererA real time global illumination solution that achieves glossy surfaces, diffuse reflection, specular reflection, ambient occlusion, indirect shadows, soft shadows, emissive materials and 2-bounce GI. Published here http://ieeexplore.ieee.org/abstract/document/7833375/项目地址: https://gitcode.com/gh_mirrors/vc/VCTRenderer
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
