# 3DGS 专用 PLY 导入器与 GaussianSplat 资源缓存正式化计划 日期:2026-04-10 ## 1. 文档定位 这份计划只覆盖 3DGS 落地中的前两层基础设施: 1. `3DGS 专用 PLY importer` 2. `GaussianSplat 资源 / Artifact / ResourceManager / 缓存层` 这份计划明确不讨论以下内容: 1. 不实现最终的 3DGS 渲染 pass 2. 不实现 editor 里的 3DGS 编辑工具 3. 不实现 cutout、selection、导出、相机激活等 Unity 参考项目中的高级功能 4. 不提前把 3DGS 强行塞进现有 mesh / volume 路径 这份计划的目标不是“先把 `.ply` 读出来”,而是把 3DGS 资产链正式纳入引擎现有的资源系统,使它从一开始就是一条可缓存、可复用、可异步、可测试、可长期维护的正式路径。 --- ## 2. 当前参考与现状 当前参考工程是: 1. [mvs/3DGS-Unity](D:/Xuanchi/Main/XCEngine/mvs/3DGS-Unity) 当前测试样本是: 1. [room.ply](D:/Xuanchi/Main/XCEngine/mvs/3DGS-Unity/room.ply) 当前已确认的事实: 1. `3DGS-Unity` 并不是运行时直接渲染 `.ply`,而是先把 `.ply` 转成更接近 GPU 消费形态的运行时资产。 2. 它的导入工作流核心在 [GaussianSplatAssetCreator.cs](D:/Xuanchi/Main/XCEngine/mvs/3DGS-Unity/Editor/GaussianSplatAssetCreator.cs)。 3. 它的 `.ply` 读取器 [PLYFileReader.cs](D:/Xuanchi/Main/XCEngine/mvs/3DGS-Unity/Editor/Utils/PLYFileReader.cs) 本质上是一个偏工程化的快速路径,不是健壮的通用 PLY 解析器。 4. 它的运行时资产 [GaussianSplatAsset.cs](D:/Xuanchi/Main/XCEngine/mvs/3DGS-Unity/Runtime/GaussianSplatAsset.cs) 已经把数据拆成了 `pos / other / sh / color / chunk` 几类 GPU 资源。 5. 它的运行时渲染 [GaussianSplatRenderer.cs](D:/Xuanchi/Main/XCEngine/mvs/3DGS-Unity/Runtime/GaussianSplatRenderer.cs) 依赖 compute、StructuredBuffer、RawBuffer、procedural draw、fullscreen composite。 对当前引擎的现状判断: 1. 引擎已经具备 `StructuredBuffer / RawBuffer / RWStructuredBuffer / RWRawBuffer` 级别的 shader 资源识别能力。 2. 引擎已经具备 compute shader 与 `Dispatch` 的 RHI 基础能力。 3. 引擎已经具备 runtime material buffer binding 能力。 4. 引擎已经具备 `AssetDatabase -> Library/Artifacts -> ResourceManager` 的正式资源链。 5. 引擎当前还没有 `GaussianSplat` 这种正式资源类型。 6. 引擎当前还没有 3DGS 专用 importer、artifact schema、loader、GPU residency cache。 --- ## 3. 本轮最核心的架构决策 ### 3.1 不允许运行时直接消费 `.ply` 正式方案必须是: 1. `Assets/*.ply` 2. 经过 `GaussianSplatImporter` 3. 生成 `Library/Artifacts/.../main.xcgsplat` 4. 运行时只加载 `xcgsplat` 不允许的错误方案: 1. renderer 首次遇到 `.ply` 时再现场解析 2. component 里直接持有 `.ply` 文件句柄 3. 把 `.ply` 读取逻辑塞进 render pass 4. 为了尽快出图先做“临时直接加载 `.ply`”然后以后再回收 原因很明确: 1. `.ply` 是 source asset,不是 runtime-ready asset 2. 直接 runtime 解析会破坏 `AssetDatabase / ResourceManager / Library` 体系 3. 3DGS 数据量远大于普通 mesh,更不能把 source 解析和 GPU 上传压到 draw path 上 ### 3.2 不做通用 PLY 导入器,先做 3DGS 专用 PLY 导入器 本轮 importer 的职责不是支持一切 PLY 变体,而是支持当前 3DGS 工作流所需的那类 PLY。 正式边界是: 1. 支持 `binary_little_endian` 2. 只关心 `element vertex` 3. 通过属性名映射识别 3DGS 语义字段 4. 对不支持的属性布局给出明确错误 不做的事: 1. 不支持带面片索引的通用模型 PLY 2. 不支持 ASCII PLY 3. 不支持任意 list property 4. 不支持“能读但语义不清晰”的模糊推断 这不是退让,而是边界明确。当前目标是 3DGS 正式化,不是通用点云 SDK。 ### 3.3 运行时正式资源类型命名为 `GaussianSplat` 建议引入: 1. `ResourceType::GaussianSplat` 2. `Resources::GaussianSplat` 3. `GaussianSplatLoader` 4. `GaussianSplatImporter` 不建议把运行时资源叫: 1. `GaussianSplatAsset` 2. `GaussianAsset` 3. `PLYAsset` 原因: 1. 引擎当前 `ResourceType` 命名体系都是运行时资源名,不是 editor 资产名 2. `Asset` 更适合出现在导入流程和文档语义中,不适合塞进正式 runtime `ResourceType` ### 3.4 Artifact 采用单文件主 artifact,而不是 Unity 式多 TextAsset 拼装 建议正式主 artifact 为: 1. `main.xcgsplat` 不建议照抄 Unity MVS 的多文件拆分为: 1. `*_pos.bytes` 2. `*_other.bytes` 3. `*_sh.bytes` 4. `*_col.bytes` 5. `*_chk.bytes` Unity 那样做是受 Unity 资产模型约束。我们自己的引擎不需要跟着它的工程妥协走。 本轮更合理的正式方案是: 1. 单个 `xcgsplat` 文件包含 header、section table、payload 2. loader 一次读入 metadata,按 section 定位各 payload 3. 后续如果要做分段流式或 memory mapping,再在 schema 上扩展,不先把文件形态做碎 这样做的好处: 1. artifact 边界清晰 2. `ArtifactDB` 记录简单 3. 依赖跟踪简单 4. reimport 稳定 5. 不会出现多 sidecar 丢失或部分过期的问题 --- ## 4. 对参考 MVS 的正式吸收方式 `mvs/3DGS-Unity` 里真正值得吸收的是流程,不是实现细节原样照搬。 本轮吸收的内容: 1. 输入语义 2. 数据重排思路 3. 运行时数据拆分维度 4. `chunk` 概念 5. 颜色纹理化而不是纯 buffer 化 6. 未来 compute 排序与 view-data 预处理对资源格式的需求 本轮不直接照搬的内容: 1. Unity 的 `TextAsset` 资产组织方式 2. 依赖 `UnsafeUtility.SizeOf() == vertexStride` 的固定内存布局导入 3. editor 窗口与工具链 4. HDRP/URP feature 接入方式 5. 所有编辑态 GPU buffer 简单说: 1. 流程借鉴 2. 数据语义借鉴 3. 工程架构不照抄 Unity 的壳 --- ## 5. `room.ply` 的正式支持目标 当前基线样本 [room.ply](D:/Xuanchi/Main/XCEngine/mvs/3DGS-Unity/room.ply) 已确认包含如下字段: 1. `x y z` 2. `nx ny nz` 3. `f_dc_0..2` 4. `f_rest_0..44` 5. `opacity` 6. `scale_0..2` 7. `rot_0..3` 本轮 importer 至少必须把这类文件稳定导入。 本轮导入结果必须包含: 1. splat 数量 2. bounds 3. position 4. rotation 5. scale 6. opacity 7. color / dc0 8. SH 数据 9. 供后续 chunk 化、排序、view-data 预计算所需的稳定 layout 本轮不要求: 1. 用 room.ply 直接出图 2. 完成 chunk 压缩优化后的最终视觉验证 但必须做到: 1. room.ply 可以稳定导入成正式 artifact 2. runtime 可以正式加载该 artifact 3. 资源缓存与异步链路已经为后续渲染阶段准备好 --- ## 6. 目标资源模型设计 ### 6.1 `Resources::GaussianSplat` 建议 `GaussianSplat` 运行时资源至少包含: 1. `splatCount` 2. `boundsMin / boundsMax` 3. `dataFormatVersion` 4. `positionFormat` 5. `otherFormat` 6. `colorFormat` 7. `shFormat` 8. `chunkCount` 9. `cameraInfoCount` 10. 各 section 的只读数据视图 这里的 section 建议为: 1. `positions` 2. `other` 3. `color` 4. `sh` 5. `chunks` 6. `cameras` ### 6.2 `other` 的语义边界 建议 `other` 区域承担: 1. rotation 2. scale 3. opacity 4. 可选 SH 索引或 chunk 相关辅助字段 原因: 1. 这与参考 MVS 的消费模型更接近 2. compute 阶段天然会把位置和其它数据拆开消费 3. 将来做压缩时,位置和其它数据的量化策略也不同 ### 6.3 `color` 保持纹理友好布局 建议 `color` section 不是简单“每 splat 一行 float4”,而是直接按运行时纹理消费友好的布局存储。 原因: 1. 参考 MVS 最终就是把颜色上传成纹理 2. 对 3DGS 而言,颜色作为纹理读取是合理路径 3. 如果导入期就固化好 texel 布局,运行时不必再做一次昂贵重排 ### 6.4 `chunks` 作为正式字段预留 即使第一阶段先允许 `chunkCount == 0`,artifact schema 也要正式留出 chunk 区域。 因为: 1. chunk 数据不是可有可无的小优化,它影响后续压缩、解码和排序输入 2. 后面一旦渲染 pass 接上,就会很自然依赖 chunk 3. 现在先把 schema 打对,比后面再迁移 artifact 版本更划算 --- ## 7. Importer 设计 ### 7.1 引入 `GaussianSplatImporter` `AssetDatabase` 对 `.ply` 的识别不应直接复用 `ModelImporter`。 建议规则: 1. `.ply` 不作为通用模型格式挂进 `ModelImporter` 2. 本轮把 `.ply` 明确识别为 `GaussianSplatImporter` 3. 后续如果将来要支持“通用点云 PLY”,再单独扩展,不污染当前 3DGS 主线 ### 7.2 Header 解析不能依赖固定顺序 正式解析流程必须是: 1. 读取 header 2. 收集 `element vertex` 3. 收集每个 `property` 的名字、类型、偏移 4. 建立 3DGS 语义字段到 property 的映射 5. 校验必需字段是否完整 不允许的方案: 1. 直接假定 `InputSplatData` 与文件二进制布局完全一致 2. 直接假定 `f_rest_*` 顺序永远固定且不校验 3. 因为 room.ply 能过就默认所有训练器导出的 PLY 都一样 ### 7.3 importer 输出的是 cooked runtime layout,不是 source mirror 导入器的正式职责不是把 `.ply` 原样搬进 artifact,而是做以下转换: 1. 按语义解包 source data 2. 生成规范化内部 splat 记录 3. 计算 bounds 4. 可选做 Morton reorder 5. 可选做 chunk 构建 6. 输出运行时友好的 section layout 这一步就是 source -> cooked artifact,而不是 source -> source copy。 ### 7.4 关于压缩策略 本轮计划分两步: 1. 第一阶段先实现无损或近无损基础 cooked 布局 2. 第二阶段再把参考 MVS 的压缩格式体系正式移植进来 原因: 1. 先把 artifact、loader、cache 链路跑通 2. 再叠加压缩和 chunking 3. 避免 importer、artifact schema、runtime loader、未来 renderer 四件事同时出错 第一阶段允许: 1. `position = float32` 2. `other = float32 / uint32 packed` 3. `color = RGBA32F` 或 `RGBA16F` 4. `sh = float32` 5. `chunk = 0` 第二阶段再引入: 1. `Norm16 / Norm11 / Norm6` 2. `BC7 / Norm8x4` 3. `SH clustering` 4. `chunk` 正式压缩路径 --- ## 8. Artifact 设计 ### 8.1 主文件 主文件建议: 1. `main.xcgsplat` ### 8.2 文件内容建议 建议 `xcgsplat` 文件包含: 1. 文件头 2. schema version 3. source metadata snapshot 4. splat metadata 5. section table 6. payload blob section table 至少描述: 1. section type 2. byte offset 3. byte size 4. element count 5. element stride 6. format enum ### 8.3 version 策略 建议单独引入: 1. `kGaussianSplatArtifactSchemaVersion` 不要复用其它 importer 的 schema version。 版本提升触发条件: 1. section 布局改变 2. chunk 编码改变 3. 颜色纹理布局改变 4. SH 格式或 camera 区块布局改变 ### 8.4 `.meta` 设计 即使本轮先不做完整 Inspector,也应该为后续 importer settings 预留正式字段。 建议至少预留: 1. `reorderMorton` 2. `buildChunks` 3. `positionFormat` 4. `otherFormat` 5. `colorFormat` 6. `shFormat` 7. `importCameras` 第一阶段如果先不开放 UI,也要把默认设置结构体和 hash 纳入 artifact key 计算。 --- ## 9. Loader 与 ResourceManager 接入 ### 9.1 `GaussianSplatLoader` 需要新增: 1. `GaussianSplatLoader` 职责: 1. 读取 `xcgsplat` 2. 构建 `Resources::GaussianSplat` 3. 提供各 section 的稳定只读视图 ### 9.2 `ResourceManager` 正式接入 正式链路应支持: 1. `Load("Assets/.../room.ply")` 2. `AssetDatabase::EnsureArtifact(...)` 3. `ResourceManager` 实际加载 `main.xcgsplat` 也必须支持: 1. `Load("Library/Artifacts/.../main.xcgsplat")` ### 9.3 不能把 GPU 上传塞进 loader `GaussianSplatLoader` 只负责 CPU 运行时资源,不负责 GPU residency。 原因: 1. loader 属于资源层 2. GPU residency 属于渲染缓存层 3. 如果在 loader 里直接创 GPU 资源,会重复 volume 这条链已经暴露过的架构问题 --- ## 10. 缓存与预热设计 ### 10.1 资源缓存层必须提前设计 GPU residency 状态机 即使本轮还不接最终 render pass,也必须把状态机设计写进正式方案: 1. `Uninitialized` 2. `CpuReady` 3. `GpuUploading` 4. `GpuReady` 5. `Failed` 后续 `BuiltinGaussianSplatPass` 只能消费: 1. `GpuReady` 不允许 draw path 现场把 `CpuReady -> GpuReady` 做完。 ### 10.2 建议新增 `CachedGaussianSplat` 建议未来挂在 `RenderResourceCache` 或其正式拆分后的 GPU 资源缓存模块中。 它至少应持有: 1. `posBuffer` 2. `otherBuffer` 3. `shBuffer` 4. `colorTexture` 5. `chunkBuffer` 6. `sortKeyBuffer` 7. `sortDistanceBuffer` 8. `viewDataBuffer` 9. runtime-ready flag / state 本轮即使还不把所有 GPU 辅助 buffer 全建出来,也要把正式边界写清楚: 1. asset static payload buffer/texture 2. per-frame transient / reusable working buffer ### 10.3 首次可见前预热,而不是首次 draw 同步补做 这点必须作为硬约束写死: 1. `GaussianSplat` 被场景反序列化后,CPU artifact 加载完成就进入 GPU 预热队列 2. GPU 上传在后台或明确的准备阶段完成 3. 首次 draw 只允许跳过未 ready 对象,不允许同步创建大资源 原因: 1. 3DGS 资产通常很大 2. room.ply 这种样本数据量已经足够把 draw path 压垮 3. 当前 volume 这条链已经证明“首次绘制再上传”不是可接受正式方案 ### 10.4 warm cache 验收标准 本轮资源 / 缓存层至少要达到: 1. 第二次加载 room.ply 时不重新解析 source `.ply` 2. 直接命中 `Library/Artifacts` 3. `ResourceManager` 不会因为 cache hit 又走 source importer 4. 后续 GPU 预热可以稳定复用 artifact 输出 --- ## 11. 测试计划 ### 11.1 基线样本 统一使用: 1. [room.ply](D:/Xuanchi/Main/XCEngine/mvs/3DGS-Unity/room.ply) 它将承担: 1. importer 基线 2. artifact 基线 3. cache hit 基线 4. future renderer 接入基线 ### 11.2 Unit Tests 本轮至少要补齐以下单测: 1. `PLY header parser` 正确识别 `vertexCount / properties / offsets` 2. `GaussianSplatImporter` 能正确识别 room.ply 的必需字段 3. 缺字段时给出明确错误 4. 非法格式时给出明确错误 5. `xcgsplat` 写入 / 读取 roundtrip 正确 6. `GaussianSplatLoader` 能读取 artifact 并恢复 metadata 与 section view 7. `ResourceManager` 能从 `Assets/.../room.ply` 正式加载 `GaussianSplat` 8. `AssetDatabase` 对 `.ply` 的 `EnsureArtifact` 能稳定复用 ### 11.3 Integration Tests 本轮先做资源链集成测试,不做最终出图测试。 至少要有: 1. `room.ply -> EnsureArtifact -> Load` 全链通过 2. 二次加载命中 artifact,不触发 reimport 3. 修改 source writeTime 后能触发 reimport 4. 清库后能重建 artifact ### 11.4 为后续渲染阶段准备的 smoke test 虽然本轮不做 3DGS pass,但建议提前补一个 GPU 资源 smoke test: 1. 读取 `GaussianSplat` 2. 构建最小 GPU cache entry 3. 创建 `pos/other/sh/chunk` buffer 与 `color` texture 4. 验证状态进入 `GpuReady` 这样后续 renderer 接入时,不会把“资源问题”和“渲染问题”混成一团。 --- ## 12. 分阶段执行计划 ### Phase 1:资源类型与 artifact schema 落地 目标: 1. 正式引入 `ResourceType::GaussianSplat` 2. 正式引入 `Resources::GaussianSplat` 3. 正式定义 `xcgsplat` artifact schema 任务: 1. 扩展 `ResourceType` 2. 新增 `GaussianSplat` 运行时资源类 3. 设计 artifact header 与 section table 4. 新增 `WriteGaussianSplatArtifactFile / LoadGaussianSplatArtifact` 验收标准: 1. `xcgsplat` 可写可读 2. 资源元数据可稳定 roundtrip ### Phase 2:3DGS 专用 PLY importer 正式化 目标: 1. 把 `.ply` 纳入 `GaussianSplatImporter` 任务: 1. 新增 header parser 2. 新增 3DGS property mapping 3. 读取 room.ply 并转换成规范化内部 splat 数据 4. 输出基础 cooked artifact 验收标准: 1. room.ply 可稳定导入 2. 不依赖固定 struct stride == 文件 stride 3. 错误路径有清晰日志 ### Phase 3:AssetDatabase / Library / ResourceManager 接入 目标: 1. 把 `GaussianSplat` 完整接进项目资源工作流 任务: 1. `.ply -> GaussianSplatImporter` 2. `EnsureArtifact(..., ResourceType::GaussianSplat)` 3. `GaussianSplatLoader` 4. `Load()` 验收标准: 1. 可以通过 `Assets/.../room.ply` 正式加载 2. cache hit 时不重走 source parse ### Phase 4:资源缓存与 GPU residency 预热骨架 目标: 1. 正式建立 3DGS GPU 资源缓存的边界 任务: 1. 设计 `CachedGaussianSplat` 2. 建立 GPU residency 状态机 3. 实现最小 GPU 资源构建 smoke path 4. 明确禁止 draw path 首次同步上传 验收标准: 1. room.ply 对应的 `GaussianSplat` 可以被 GPU cache 预热成 ready 状态 2. 资源层与渲染层边界清晰 ### Phase 5:测试补齐与收口 目标: 1. 让这条链路可回归、可持续演进 任务: 1. 补全 importer / loader / cache hit / reimport 单测与集成测试 2. 输出阶段性说明 3. 为后续 renderer 接入保留唯一正式资源路径 验收标准: 1. room.ply 全链路测试稳定 2. 不存在“临时直接读 ply”的旁路 --- ## 13. 明确不允许出现的临时方案 以下方案本轮禁止出现: 1. 为了尽快出图,先在 render pass 里直接解析 `.ply` 2. 先做一个 `BinaryResource` 包 `.ply` 内容,后面再说 3. 先把 `.ply` 当 `Mesh` 导入 4. 把 3DGS 的 GPU buffer 直接挂在 `Material` 资源本体上作为持久化资产 5. 首次 draw 时同步创建 `pos / other / sh / color` GPU 资源 6. 把 `room.ply` 单独写死成特判 这些做法都会把本该正式化的主线重新拉回临时方案。 --- ## 14. 本轮完成标志 当以下条件同时成立时,这份计划才算完成: 1. `.ply` 已正式被 `GaussianSplatImporter` 接管 2. `GaussianSplat` 已成为正式 `ResourceType` 3. `room.ply` 能稳定导入成 `xcgsplat` 4. `ResourceManager` 能正式加载 `GaussianSplat` 5. 二次加载能稳定命中 artifact 6. GPU residency cache 骨架已经建立,不允许首次 draw 同步补做 7. 资源层与缓存层测试已经覆盖 room.ply 主路径 --- ## 15. 一句话结论 这条主线的第一步不是“做一个 ply 读取器”,而是把 3DGS 正式升级为引擎里的 `GaussianSplat` 资源体系: 由 `GaussianSplatImporter` 把 `.ply` 转成 `xcgsplat` cooked artifact,由 `GaussianSplatLoader` 与 `ResourceManager` 正式接管加载,再由独立的 GPU residency cache 提前完成资源预热,为后续 3DGS 渲染 pass 提供唯一、稳定、无旁路的正式输入。