22 KiB
NanoVDB 稀疏体积渲染正式集成计划(Unity 对齐版)
文档日期:2026-04-08
适用范围:当前 XCEngine 的 Resources / Shader / RHI / Rendering / Components / Editor 体系,以及未来要对齐 Unity 的 C#、Shader、SRP 工作流。
文档目标:把 MVS/VolumeRenderer 中已经验证过的 NanoVDB + GPU Buffer + Ray Marching 原型,整理成一套能正式进入引擎主线、且从一开始就严格兼容 Unity 风格 shader/C# 语义的落地方案。
1. 先给结论
这条主线的正式方向不是:
- 把体积数据退化成
Texture3D - 给当前引擎临时塞一套 volume 专用 shader 语法
- 只在某个后端里私下打通一条 demo 路径
这条主线的正式方向必须是:
- 保留
NanoVDB的稀疏体积本质 - 把 buffer 资源能力补齐到引擎正式架构里
- 让高层 authoring 语义严格对齐 Unity
- 让低层 RHI 分类只存在于引擎内部
- 让 volumetric 成为 renderer / future SRP 中的正式 pass,而不是特例后处理
因此,这份计划默认坚持一条红线:
StructuredBuffer / RawBuffer 可以作为引擎内部资源分类存在,但作者可见语义、未来 C# API 语义、shader 写法语义,都必须对齐 Unity 既有做法,而不是引擎自创。
2. 设计总原则
2.1 Unity 对齐原则
后续所有设计都必须同时满足下面这些条件:
- shader 文件继续朝 Unity 风格
.shader + HLSLPROGRAM组织收口。 - 作者侧 buffer 语义必须沿用 HLSL/Unity 现成写法,例如:
StructuredBuffer<T>ByteAddressBufferRWStructuredBuffer<T>RWByteAddressBuffer
- 不允许为了体积渲染单独发明新的高层 buffer 关键字、声明块或材质 schema。
Properties块只用于可序列化作者参数,不用于声明或承载 GPU buffer。- 未来 C# 层的 buffer 接口方向,必须对齐 Unity 的:
ComputeBufferGraphicsBufferMaterial.SetBufferShader.SetGlobalBufferCommandBuffer.SetGlobalBuffer
- renderer 侧对 buffer 的真正绑定,必须是运行时 per-pass / per-object / global resource binding,而不是材质资产直接持有底层 view。
StructuredBuffer / RawBuffer在引擎里首先是底层编译和绑定分类,不是高层 authoring 概念。
2.2 稀疏体积原则
NanoVDB必须按稀疏体积方案正式接入。- 不允许把正式路线退化成
Texture3D替代方案。 Texture3D可以作为引擎未来的独立能力预留,但不是这条主线的收口路径。
2.3 SRP 兼容原则
- volumetric 必须被设计成正式 scene pass。
- 它的输入和生命周期要能自然映射到未来 SRP 的 renderer feature / render pass event。
- 不允许把它设计成只能在 builtin pipeline 中硬编码存在的特例逻辑。
3. Unity 参考基线
参考/Unity NanoVDB 已经给出了一条对本引擎非常重要的参考方向:
- C# 侧使用
ComputeBuffer - 运行时通过
Material.SetBuffer("buf", gpuBuffer)绑定 - shader 侧通过
StructuredBuffer<uint>读取 ComputeBufferType.Default对应的正是通用 GPU buffer 资源路径,而不是材质属性系统
这说明后续正式集成时,高层语义应该对齐的是:
- Unity 的 buffer authoring / binding 模式
- Unity 的 shader 资源声明模式
- Unity/HDRP 中把体积渲染插入 render pass 的方式
而不是:
- 直接照抄
MVS的 D3D12 私有代码 - 用引擎内部
RawBuffer这个名词去污染高层 shader/C# 接口 - 为了先出图,临时开 volume 专用旁路
4. 本轮明确不做什么
为了防止架构被 demo 反向拖偏,本轮明确不做下面这些事:
- 不把
NanoVDB退化成正式Texture3D方案。 - 不把
MVS/VolumeRenderer的 D3D12 私有 helper、私有绑定逻辑直接搬进引擎主线。 - 不先做全局体积雾、froxel fog、clustered volumetrics。
- 不先做复杂编辑器 authoring 工具,例如体积笔刷和节点编辑器。
- 不先做 DXR / path tracing 体渲染。
- 不为体积渲染另开一套与 Unity 不一致的 shader/material/C# 表层语法。
5. 当前真正缺的不是“算法”,而是正式能力
当前原型已经证明了下面几件事:
NanoVDB数据可以读。NanoVDB数据可以上传到 GPU buffer。- shader 可以基于
pnanovdb做树遍历、HDDA 跳空和 ray marching。 - D3D12 原型能完成从数据到画面的闭环。
但引擎主线真正还缺下面这些正式能力:
.nvdb如何进入资产与 artifact 体系。- shader 如何用 Unity 风格语义正式声明 buffer 资源。
- 材质系统如何和运行时 buffer 绑定做严格职责分离。
- RHI 三后端如何正式支持 buffer SRV/UAV。
- renderer 如何把体积渲染作为正式 pass 插入 frame。
- 未来 C# 层如何以 Unity 风格接口绑定 buffer。
- editor 和测试如何验证整条链路不会回归。
所以本计划的本质不是“移植 demo”,而是“先补正式能力,再把 demo 算法放到正式能力之上”。
6. 正式架构目标
6.1 作者可见层:严格对齐 Unity 风格
这一层是未来引擎用户、shader 作者、C# 脚本作者看到的语义表面。
要求如下:
.shader文件语法继续按 Unity 风格推进。- buffer 在 shader 中的声明使用 HLSL 现成资源类型,不使用自定义资源关键字。
- 材质面板暴露的是作者参数,而不是底层 GPU buffer 本体。
- 如果未来提供脚本绑定接口,脚本看到的应该是
ComputeBuffer / GraphicsBuffer风格对象和SetBuffer风格 API。
这层明确禁止:
- 在
Properties中定义StructuredBuffer或RawBuffer - 在
.shader外层再发明BufferResources { ... }之类自定义块 - 让作者直接接触
RHIResourceView或后端 view descriptor
6.2 引擎中层:运行时绑定契约
这一层负责把“作者声明的 shader 资源”和“运行时真实绑定的 GPU 资源”接起来。
正式边界应该是:
Material只持有:- shader 引用
- keyword / render state
- 序列化参数
- 常规纹理/采样器类作者资源
VolumeField或其他 runtime producer 提供真实 GPU buffer 资源Renderer/CommandBuffer/ future script binding API 在渲染阶段把 buffer 绑定进 pass
也就是说:
NanoVDB主数据不是普通 material property- 它是运行时资源绑定
- 材质只负责“怎么渲染”
VolumeField负责“渲染的数据是什么”
6.3 引擎底层:RHI 资源分类
在 RHI 与编译反射层,可以存在正式的内部分类,例如:
StructuredBufferRawBuffer- 后续可扩展:
ByteAddressBuffer的内部映射RWStructuredBufferRWByteAddressBuffer
但要强调:
- 这些是内部分类,不是高层 authoring 语法。
- 高层写的是 Unity/HLSL 资源声明。
- 引擎负责把这些声明反射并落到底层分类。
6.4 资源层:正式 VolumeField 资产
新增正式资源抽象:
ResourceType::VolumeFieldResources::VolumeFieldVolumeFieldLoaderNanoVDBVolumeImporter
导入链路建议:
- 源资产:
Assets/.../*.nvdb - artifact:
Library/Artifacts/.../main.xcvol
main.xcvol 建议包含:
- schema version
- storage kind
- grid type / grid class
- world bbox
- voxel size
- active voxel statistics
- payload byte size
- 原始
NanoVDBblob
原则:
- 第一阶段不做有损转换。
- artifact 中保留原始稀疏表示。
- metadata 单独结构化,供 loader / renderer 快速读取。
6.5 渲染层:正式体积 pass
正式新增:
VolumeRendererComponentVisibleVolumeItemRenderSceneData.visibleVolumesBuiltinVolumetricPass
该 pass 的职责:
- 消费
VisibleVolumeItem - 绑定 camera / depth / lighting / shadow / volume buffer / material 参数
- 执行 ray marching
- 合成到 scene color
推荐阶段位置:
ShadowCasterDepth / OpaqueSkyboxVolumetricsTransparentPostProcessFinalColor
这保证它天然兼容未来 SRP 的 renderer feature / pass event 方向。
6.6 C# 层预留目标
即使当前 C# 模块还没完全落地,这条计划也必须先把接口方向冻结。
后续需要预留的高层语义包括:
ComputeBuffer/GraphicsBuffer风格资源对象Material.SetBufferShader.SetGlobalBufferCommandBuffer.SetGlobalBuffer
这一层的原则是:
- 先冻结语义,再决定托管层具体封装细节
- 不允许等到以后 C# 接入时再发现底层 buffer 路径与 Unity 语义冲突
7. 关键命名与职责冻结
为了避免后续反复推翻命名,这一轮先冻结:
- 正式资源抽象:
VolumeField - 存储种类:
VolumeStorageKind::NanoVDB - 导入器:
NanoVDBVolumeImporter - 运行时加载器:
VolumeFieldLoader - 组件:
VolumeRendererComponent - frame data:
VisibleVolumeItem - pass:
BuiltinVolumetricPass
命名原则:
- 引擎对外抽象是
VolumeField NanoVDB是第一种正式存储后端- 不把具体文件格式命名泄露到整个 renderer 表面
8. Shader 与 Buffer 的正式契约
这是整个计划最关键的部分。
8.1 shader 作者看到的语义
作者应该写的是:
StructuredBuffer<uint>这类 Unity/HLSL 现成声明- 未来必要时的
ByteAddressBuffer - 如果进入写路径,再扩到
RWStructuredBuffer<T>/RWByteAddressBuffer
作者不应该写的是:
RawBufferStructuredRawBuffer- 任何引擎自创的高层 buffer 类型关键字
8.2 材质系统的职责边界
材质系统只负责:
- 密度、吸收、散射、步长、相位函数参数
- tint / emission
- shader variant / keyword
- render state
材质系统不负责:
- 序列化
NanoVDBpayload - 序列化 GPU buffer handle
- 序列化底层 SRV/UAV view
8.3 运行时绑定职责
运行时绑定应该由以下层级之一完成:
RendererCommandBuffer- future script API
典型语义应接近:
material.SetBuffer("buf", volumeBuffer)commandBuffer.SetGlobalBuffer(name, buffer)
8.4 内部映射规则
建议的内部映射是:
StructuredBuffer<T>->ShaderResourceType::StructuredBufferByteAddressBuffer->ShaderResourceType::RawBufferRWStructuredBuffer<T>-> UAV + structured 分类RWByteAddressBuffer-> UAV + raw 分类
这里的重点不是名字,而是边界:
- 高层保持 Unity/HLSL 语义
- 中层做反射和 metadata
- 低层做真正 view 创建与 descriptor 绑定
8.5 一个硬性前置条件
如果当前 shader authoring 体系还不能正式表达 Unity 风格 buffer 声明,那么这个缺口的优先级高于体积渲染本身。
也就是说:
- 不允许为了先做 volumetric,单独给它走私有 shader 语法
- 必须先把正式 shader authoring / reflection / runtime binding 路径打通
9. RHI 正式能力目标
RHI 侧至少需要补齐:
RHIDevice::CreateShaderResourceView(RHIBuffer*, const ResourceViewDesc&)RHIDevice::CreateUnorderedAccessView(RHIBuffer*, const ResourceViewDesc&)ResourceViewDesc的 buffer 字段:- firstElement
- elementCount
- structureByteStride
- raw / structured / formatted 标记
RHIResourceView对 buffer SRV/UAV 的统一表达- descriptor set / bind group 更新路径对 buffer view 的正式支持
三后端预期映射:
- D3D12:
- buffer SRV
- buffer UAV
- Vulkan:
- storage buffer
- 需要时使用 texel buffer 路径
- OpenGL:
- SSBO 优先
- 明确 GLSL 转译和绑定规则
这里必须强调:
- 这些是底层实现能力
- 它们服务于高层 Unity 风格语义
- 它们不能反过来决定高层作者接口长什么样
10. 正式实施阶段
阶段 0:冻结 Unity 对齐边界
目标
在改代码前,先冻结高层语义、中层契约、低层分类三层边界。
任务
- 冻结
VolumeField/VolumeRendererComponent/VisibleVolumeItem/BuiltinVolumetricPass命名 - 冻结体积渲染在 scene pass 中的阶段位置
- 冻结高层 buffer 语义:
- 只接受 Unity/HLSL 写法
- 冻结材质边界:
- buffer 不进
Properties
- buffer 不进
- 冻结未来 C# 方向:
ComputeBuffer / GraphicsBuffer + SetBuffer语义
- 冻结内部映射:
StructuredBuffer<T>-> internal structuredByteAddressBuffer-> internal raw
验收标准
- 后续实现阶段不再反复摇摆高层语义
阶段 1:先补底层 buffer 资源视图能力
目标
补齐 renderer 目前缺失的“buffer 作为正式 shader 资源”的底层能力。
任务
- 完成
RHIBuffer的 SRV/UAV 创建接口 - 补齐
ResourceViewDesc的 buffer 视图字段 - 让 descriptor set 更新路径正式支持 buffer 类 view
- D3D12 / Vulkan / OpenGL 三后端同时按正式接口设计
- 明确 backend capability 与错误回报,不允许 silent wrong image
测试
tests/RHI/unit增加 buffer SRV/UAV 创建测试- descriptor set buffer binding 测试
- 三后端分别验证 buffer view 不再被误当成 texture view
验收标准
- 引擎能在不依赖 texture hack 的前提下,正式把 GPU buffer 绑定给 shader
阶段 2:打通 Unity 风格 shader buffer 语义
目标
让 shader authoring / parser / reflection / artifact / runtime binding 能正式表达 Unity/HLSL 风格 buffer 资源。
任务
- 扩展 shader 反射模型以支持 structured/raw buffer 资源
- 明确
StructuredBuffer<T>与ByteAddressBuffer的内部映射 - 扩展 runtime build、pass layout、descriptor metadata 对新资源类型的支持
- 明确 OpenGL 的 GLSL/SSBO 转译规则
- 严格禁止 volume 专属私有 shader 语法旁路
强约束
- 不新增高层
RawBuffer关键字 - 不新增
MaterialPropertyType::StructuredBuffer / RawBuffer - 如果
Properties中尝试声明 buffer,必须报错或拒绝导入
测试
tests/Resources/Shadertests/Resources/Materialtests/Rendering/unit中的 pass layout / metadata 测试- authoring 约束测试:
- Unity/HLSL 风格 buffer 声明可以被正确反射
- 非法把 buffer 塞进
Properties会被拒绝
验收标准
- shader 资源描述层正式支持 buffer 资源
- 高层 authoring 语义仍保持 Unity 风格,不被底层分类污染
阶段 3:冻结 future C# buffer 绑定契约
目标
在 C# 模块尚未完全落地前,先把与本主线相关的 buffer 绑定语义冻结。
任务
- 定义 native 侧可对应 future C# 的 buffer 绑定入口
- 约束 API 形状向 Unity 对齐:
Material.SetBufferShader.SetGlobalBufferCommandBuffer.SetGlobalBuffer
- 明确
VolumeField与 runtime buffer producer 的关系 - 保证未来托管层不会直接看到 RHI view 对象
测试
- native 侧 binding metadata 测试
- material/runtime/global 三类 buffer 绑定路径测试
验收标准
- 当前实现方向已经为 future C# 留好 Unity 风格接口落点
阶段 4:接入正式 VolumeField 资产链路
目标
让 .nvdb 成为项目里的正式资源,而不是 demo 私有文件。
任务
- 新增
ResourceType::VolumeField - 新增
Resources::VolumeField - 新增
VolumeFieldLoader - 新增
NanoVDBVolumeImporter AssetDatabase识别.nvdb- 生成
main.xcvol - 接入 reimport / dependency / cache / runtime load
测试
tests/Resources/Volumetests/Core/Asset.nvdb改动后的 reimport 测试
验收标准
.nvdb可以像 mesh / material / shader 一样进入正式资产体系
阶段 5:接入组件、提取与 frame data
目标
让场景系统知道“什么是可渲染的体积对象”。
任务
- 新增
VolumeRendererComponent - 组件持有:
VolumeFieldMaterial- enable/disable
- 局部参数 override
- bounds / transform
RenderSceneExtractor增加体积对象提取- 新增
VisibleVolumeItem RenderSceneData.visibleVolumes接入
测试
- 组件序列化 / 反序列化
VisibleVolumeItem提取测试- frustum / bounds culling 与稳定顺序测试
验收标准
- renderer 已经能从正式场景数据中拿到体积对象输入
阶段 6:BuiltinVolumetricPass 首次正式点亮
目标
在当前 renderer 正式链路中点亮第一版 NanoVDB 稀疏体积渲染。
任务
- 新增
BuiltinVolumetricPass - pass 输入包括:
- camera
- depth
- scene color
- light / shadow
- volume buffer
- material parameters
- D3D12 首次点亮
- 允许保留一条集成测试级别的 fullscreen debug path
- 正式运行时路径收口到 proxy box / unit cube volume renderer
强约束
- fullscreen path 只能作为 bring-up / integration debug 路径
- 正式场景运行时不能停留在 fullscreen 方案
- 不得保留
MVS风格直接文件加载或私有绑定路径
测试
tests/Rendering/integration/volume_nanovdb_minimal- GT 对比
- 中间调试图输出:
volume_mask_debugdepth_debug- 必要时的 shadow debug
验收标准
- 正式 renderer 已经能稳定渲染一个来自
.nvdb的体积对象
阶段 7:深度、阴影、合成规则收口
目标
把“能出图”收口成“行为正确”。
任务
- 深度遮挡规则确定
- scene color 合成规则确定
- 主方向光与阴影采样接入
- transform 与 bbox 变换一致
- 步长、阴影采样步数、质量参数稳定化
测试
volume_nanovdb_occlusionvolume_nanovdb_transform- 光照方向变化的回归测试
验收标准
- 画面行为能解释、能回归、能维护,不再只是 demo level
阶段 8:多后端 rollout
目标
把已在 D3D12 验证的正式能力推进到 Vulkan 与 OpenGL。
任务
- Vulkan storage buffer 路线点亮
- OpenGL SSBO 路线点亮
- 明确 capability 检测与 fallback / disable 行为
- 补齐 backend-specific shader compile / runtime binding 回归
测试
- 各后端统一 GT 对比
- backend-specific shader compile 测试
- buffer binding 回归测试
验收标准
- 不是只有 D3D12 demo 可用,而是正式进入多后端 renderer 能力集合
阶段 9:Editor 最小工作流接入
目标
让这项能力进入项目工作流,而不是只能靠测试程序看图。
任务
ProjectPanel识别.nvdbInspector显示VolumeFieldmetadataVolumeRendererComponentinspector 编辑- scene view / game view 中体积对象可见
- 资源缺失、编译失败、后端不支持时给出明确状态
验收标准
- 用户可以从资产、组件、场景三个层面完整使用这项能力
11. 测试体系规划
11.1 单元测试
需要新增或扩展:
tests/RHI/unit- buffer SRV/UAV 创建
- descriptor set buffer binding
tests/Resources/Shader- Unity/HLSL 风格 buffer 声明解析
- 非法
Propertiesbuffer 声明拒绝
tests/Resources/Material- 材质参数 schema 与 runtime buffer 绑定边界
tests/Resources/Volume.nvdb -> .xcvol- metadata roundtrip
- reimport
tests/Rendering/unitVisibleVolumeItem提取BuiltinVolumetricPass输入校验
11.2 集成测试
建议新增:
tests/Rendering/integration/volume_nanovdb_minimaltests/Rendering/integration/volume_nanovdb_occlusiontests/Rendering/integration/volume_nanovdb_transform
GT 规则:
- 使用单一
GT.ppm - 各后端输出都对同一张
GT.ppm做对比 - 必要时保留中间调试图,但不把调试图当正式输出
11.3 性能与稳定性验收
至少记录:
- 单体积对象 frame time
- 多体积对象 frame time
.nvdb首次导入时长- artifact 命中后的加载时长
12. 关键风险
12.1 最大风险不是算法,而是语义边界被做脏
如果一开始把 StructuredBuffer / RawBuffer 直接暴露成高层 authoring 概念,后面整个 shader/material/C# 体系都会被污染。
12.2 OpenGL 路线风险最高
原因:
StructuredBuffer到 GLSL/SSBO 的映射最容易出边角问题PNanoVDB这种大 include 在 GLSL 转译路径上更容易暴露兼容性问题
12.3 fullscreen debug path 容易反客为主
它只能用于 bring-up 和集成测试,不能变成正式场景架构。
12.4 如果当前 shader 体系还没正式支持 Unity 风格 buffer 语义,这会成为主阻塞项
这不是额外问题,而是本主线的前置依赖。
13. 本阶段收口标准
当下面条件同时满足时,可以认为“稀疏体积渲染第一阶段正式完成”:
.nvdb已成为正式项目资源,可导入、可 reimport、可 artifact 化。- 引擎 shader / runtime binding / RHI 已正式支持 Unity 风格 buffer 资源链路。
- 高层 authoring 没有引入任何体积专用自定义 buffer 语法。
VolumeRendererComponent能把VolumeField放进场景。BuiltinVolumetricPass已作为正式 scene pass 存在。- D3D12 至少已经在正式链路中点亮。
- 集成测试能稳定出图并做 GT 对比。
- future C# 的
SetBuffer风格接口已经有明确落点,不会和当前底层设计冲突。 - 代码中不再依赖
MVS/VolumeRenderer那套 D3D12 私有 demo 实现。
14. 一句话结论
这条主线的正确做法不是“先做出体积渲染再说”,而是“从第一天开始就按 Unity 风格的 shader/C# 语义设计 buffer 路径,把 NanoVDB 作为正式稀疏体积能力接入 renderer”,这样后面无论是 C# 层还是 SRP,都不会被今天的底层实现反噬。