12 KiB
3DGS 渲染集成测试与 Renderer 正式接入计划
日期:2026-04-10
1. 文档定位
这份计划承接已经完成的 GaussianSplat 资源 / Artifact / ResourceManager / GPU cache 正式化工作,正式进入下一阶段:
- 在
tests/Rendering/integration中把 3DGS 正式渲染出来 - 为引擎 renderer 增加一条正式的 3DGS 场景渲染路径
这份计划的首要目标不是 editor 接入,也不是 3DGS 编辑工具,而是:
- 让
room.ply -> GaussianSplat -> Scene -> Renderer -> GT 比对成为一条正式、可回归、可维护的链路
这份计划明确不做的事:
- 不做 editor 里的 3DGS Inspector / gizmo / 交互编辑
- 不做 cutout、selection、导出、编辑态 GPU buffer
- 不在这一轮提前做 streaming / LOD / chunk 压缩升级
- 不把 3DGS 临时塞进
MeshRenderer或VolumeRenderer路径里凑合出图
2. 当前基线与已具备能力
当前已完成的正式基础:
.ply -> GaussianSplatImporter -> main.xcgsplat -> GaussianSplatLoader -> ResourceManager已打通RenderResourceCache已具备GaussianSplat静态 GPU 驻留能力room.ply已可稳定导入、复用 artifact、并通过测试验证
当前 renderer / RHI 已具备的关键能力:
StructuredBuffer / RawBuffer / RWStructuredBuffer / RWRawBuffershader 资源描述能力已具备- compute shader 与
Dispatch已具备 Draw(vertexCount, instanceCount, ...)已具备,可以走 instanced quad 而不需要先补新的 RHI 接口- renderer 已有成熟的 integration test 模式:
- 一个场景一个 target
- 真正创建
Scene + SceneRenderer + RenderSurface - 输出 PPM 并和
GT.ppm比对
- 当前已有可借鉴的参考:
3. 当前还缺什么
虽然资源链已经完整,但 renderer 侧现在仍然没有正式的 3DGS 路径:
- 没有
GaussianSplatRendererComponent - 没有
VisibleGaussianSplatItem RenderSceneData没有visibleGaussianSplatsRenderSceneExtractor / RenderSceneUtility不会提取 3DGS 场景对象BuiltinForwardPipeline没有 3DGS 专用 pass- 还没有用于 3DGS 的 transient GPU working buffers:
- sort distance / key
- view-data
- offscreen accumulation / compose target
tests/Rendering/integration还没有 3DGS 场景测试
所以这一步不是“写个测试 main.cpp 就行”,而是要正式把 3DGS 纳入 renderer 架构。
4. 本轮核心架构决策
4.1 必须引入专用场景组件 GaussianSplatRendererComponent
不允许:
- 让
MeshRendererComponent直接持有GaussianSplat - 复用
VolumeRendererComponent - 在测试里绕过组件层,直接把
GaussianSplat塞进 pass
正式方案应为:
- 新增
GaussianSplatRendererComponent - 它持有:
ResourceHandle<GaussianSplat>Material*或正式Material句柄- 运行时参数,例如缩放、透明度倍率、SH 阶数、排序策略等
原因:
- 这条路径是专用渲染 primitive,不是 mesh,也不是 volume
- 场景提取、渲染排序、后续 editor 支持,都需要一个正式组件边界
- 这和参考项目中单组件
GaussianSplatRenderer的职责边界是一致的
4.2 3DGS 必须走专用 VisibleGaussianSplatItem
应新增:
Rendering/FrameData/VisibleGaussianSplatItem.hRenderSceneData.visibleGaussianSplatsAppendVisibleGaussianSplatsForGameObject(...)
原因:
- 3DGS 与 mesh、volume 的排序依据和 GPU 资源都不同
- 把它塞到
visibleItems或visibleVolumes里只会制造更多特判
4.3 3DGS 必须作为 BuiltinForwardPipeline 中的一条正式 pass
不允许:
- 直接把 3DGS 逻辑硬塞进
DrawVisibleItems - 把 3DGS 伪装成普通 forward lit / unlit surface
- 在测试代码里直接手写 command list 绕过 renderer
正式方案应为:
- 新增
BuiltinGaussianSplatPass - 它在
BuiltinForwardPipeline中作为一条显式阶段执行 - 先按当前 pipeline 约束放在:
OpaqueSkyboxGaussianSplatVolumetricTransparent
这一步的理由是:
- 参考 Unity MVS 的落点本质上属于
BeforeForwardAlpha - 3DGS 需要依赖 opaque depth,但又不应混入普通 mesh transparent 队列
- 当前 pipeline 还没有更通用的 feature graph,因此先做一条正式内建 pass 是合理路径
4.4 静态 GPU 资产与每帧 working set 必须分层
已经完成的静态层:
RenderResourceCache::CachedGaussianSplat- positions
- other
- color
- sh
- chunks
下一步必须新增的是每帧 / 每相机 working set,而不是污染静态 cache:
- sort distance buffer
- sort key / order buffer
- view-data buffer
- splat accumulation render target
结论:
RenderResourceCache继续负责静态 asset residencyBuiltinGaussianSplatPass或其专属 surface cache 负责 transient working resources
4.5 第一张图必须先在 rendering integration test 里落地
这一轮的第一个正式验收点不是 editor 里看到 3DGS,而是:
- 新增
tests/Rendering/integration/gaussian_splat_scene - 使用真实
room.ply - 跑通过至少 D3D12 / OpenGL / Vulkan 的 GT 比对
原因:
- 这能把资源链问题、scene 提取问题、shader 问题、pass 问题一次性锁进正式回归
- editor 接入如果先做,调试成本会更高,问题边界也更模糊
5. 渲染技术路线
5.1 第一轮渲染方案
当前最合适的正式方案是:
- 使用 instanced quad 绘制 splat
- 顶点着色器通过
SV_InstanceID / gl_InstanceID从StructuredBuffer读取 per-splat 数据 - compute 负责:
- 计算相机空间深度或排序 key
- 生成 draw 顺序
- 预计算 view-data
- graphics pass 负责:
- 深度测试
- splat 展开
- 累积到专用 render target
- fullscreen compose pass 负责把结果混合回主场景颜色
这条路线的优点:
- 与参考
GaussianSplatRenderer.cs的总体思路一致 - 与现有 RHI 能力匹配,不需要新发明 RHI 接口
- 可以自然落到当前 built-in pipeline 中
5.2 关于排序
第一轮正式实现就应该有排序,不建议无排序硬上 GT。
原因:
- 3DGS 本质上依赖近似正确的后向前顺序
- 如果先做无排序,integration test 的 GT 价值会很差
- 后面再补排序,很可能把第一版 shader / pass 全打碎
因此建议:
- 第一张 integration 图就包含 compute 排序链
- 如果排序系统需要简化,也只能简化为“全局每帧排序”,不能退化到“不排序”
5.3 关于 SH 与颜色
当前 cooked-v1 中已有:
color = SH0ToColor(dc0) + opacitysh = f_rest_0..44
正式阶段建议:
- 第一版 pass 即支持当前 artifact 语义
- 不为了追求和 Unity 一模一样的颜色纹理缓存形态,先回头推翻当前 cooked-v1
- 如果实际实现中发现
color buffer明显不适合后续长期演进,再做正式 schema 升级,而不是临时特判
也就是说:
- 当前目标是先让
room.ply通过正式 renderer 路径稳定出图 - 不是在出第一张图之前就重做第二版 artifact 格式
6. Renderer 需要做的正式改动
Phase 1:场景对象与提取层正式接入
目标:
- 让 Scene 能正式携带 3DGS 渲染对象
任务:
- 新增
GaussianSplatRendererComponent - 新增
VisibleGaussianSplatItem - 扩展
RenderSceneData - 扩展
RenderSceneUtility / RenderSceneExtractor - 补对应 unit tests,覆盖:
- 提取
- culling mask
- render queue
- 排序稳定性
验收标准:
RenderSceneExtractor能输出visibleGaussianSplats- 不污染 mesh / volume 路径
Phase 2:3DGS pass 的 GPU working set 正式化
目标:
- 让 3DGS 渲染时所需的每帧资源有正式边界
任务:
- 为
BuiltinGaussianSplatPass设计 transient resource 集合 - 明确静态 cache 与 transient working set 的所有权
- 实现:
- sort distances / keys
- order buffer
- view-data buffer
- accumulation target
- 补 cache / pass smoke tests
验收标准:
- pass 在没有 editor 的情况下独立准备好运行所需 GPU 资源
- 不把每帧资源塞回
RenderResourceCache
Phase 3:BuiltinGaussianSplatPass 正式落地
目标:
- 在 renderer 中画出第一张 3DGS 正式图
任务:
- 新增内建 shader / material 资产:
- sort compute
- view-data compute
- splat draw
- compose
- 新增
BuiltinGaussianSplatPass - 接入
BuiltinForwardPipeline - 定义与主深度、主颜色的交互规则
验收标准:
- pass 不绕过
SceneRenderer - pass 不绕过
RenderSceneData BuiltinForwardPipeline的阶段顺序清晰且可测试
Phase 4:rendering integration test 正式落地
目标:
- 把 3DGS 第一张图纳入 GT 回归
任务:
- 新增
tests/Rendering/integration/gaussian_splat_scene - 使用真实样本:
- 复制运行所需资源到测试输出目录
- 建立 GT 基线图
- 至少覆盖:
- D3D12
- OpenGL
- Vulkan
建议场景内容:
- 相机
- 黑或深灰背景
- 单个
GaussianSplatRendererComponent - 不引入外部灯光依赖作为第一张图前提
原因:
- 3DGS 的基础显示不应依赖当前主光 / 多光源系统是否正确
- 第一张图应尽量减少无关变量
Phase 5:测试收口与回归闭环
任务:
- 跑通:
gaussian_splat_testsrendering_unit_testsrendering_integration_gaussian_splat_scene- 相关既有 rendering integration tests
- 修复跨后端 shader / 资源绑定差异
- 输出阶段总结
验收标准:
- 3DGS 进入正式 rendering regression 集
- 不存在“只有 editor 里能看,测试里没有”的非正式路径
7. 需要提前注意的风险
7.1 当前 command list 没有 DrawProcedural
这不是 blocker。
当前可行正式方案是:
- 使用固定 6 顶点 quad
Draw(6, splatCount, 0, 0)- 顶点阶段通过 instance id 读 buffer
也就是说,不需要先为了 3DGS 强行扩展新的 RHI draw API。
7.2 3DGS 与 volume 都属于“专用透明路径”
这意味着当前 built-in pipeline 中将出现:
VolumetricPassGaussianSplatPass
两者在这一轮可以并存,但必须保证:
- 顺序明确
- 资源边界明确
- 不相互复用错误的数据结构
如果过程中发现当前 BuiltinForwardPipeline 已经开始过载,就应该正式抽出更清晰的内建 feature 组织层,而不是继续堆 if 分支。
7.3 integration test 先用真实 room.ply
这意味着:
- 样本量不小
- shader / sort / view-data 任何一步出错,画面都会直接坏掉
但这恰恰是好事,因为:
- 第一张图就能真实暴露架构问题
- 不会被“用一个简化假样本先糊过去”的临时路线误导
8. 本轮完成标志
当以下条件同时成立,这份计划才算完成:
GaussianSplatRendererComponent已存在且被SceneRenderer正式消费RenderSceneData.visibleGaussianSplats已成为正式帧数据BuiltinGaussianSplatPass已正式接入BuiltinForwardPipelineroom.ply能在tests/Rendering/integration/gaussian_splat_scene中稳定渲染- 至少 D3D12 / OpenGL / Vulkan 的 GT 回归通过
- 现有
gaussian_splat_tests与相关 rendering tests 不被破坏 - 不存在任何 render pass 直接读取 source
.ply的旁路
9. 一句话结论
3DGS 的下一步不是直接去 editor 里“先看见再说”,而是先在 renderer 正式增加一条 GaussianSplatRendererComponent -> VisibleGaussianSplatItem -> BuiltinGaussianSplatPass -> rendering integration GT 的闭环路径;
只有这条闭环成立,后面的 editor 显示、选择、材质面板和 SRP 演进才有可靠基础。