Add model and GaussianSplat asset pipelines

This commit is contained in:
2026-04-10 20:55:48 +08:00
parent 8f5c342799
commit 503e6408ed
39 changed files with 5900 additions and 141 deletions

View File

@@ -0,0 +1,693 @@
# 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<InputSplatData>() == 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<GaussianSplat>("Assets/.../room.ply")`
2. `AssetDatabase::EnsureArtifact(...)`
3. `ResourceManager` 实际加载 `main.xcgsplat`
也必须支持:
1. `Load<GaussianSplat>("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<GaussianSplat>` 全链通过
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 23DGS 专用 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 3AssetDatabase / Library / ResourceManager 接入
目标:
1.`GaussianSplat` 完整接进项目资源工作流
任务:
1. `.ply -> GaussianSplatImporter`
2. `EnsureArtifact(..., ResourceType::GaussianSplat)`
3. `GaussianSplatLoader`
4. `Load<GaussianSplat>()`
验收标准:
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 提供唯一、稳定、无旁路的正式输入。

View File

@@ -0,0 +1,779 @@
# Unity 风格模型导入与 Model 资产架构重构计划
日期2026-04-10
## 1. 文档定位
这份计划覆盖 XCEngine 现阶段外部模型资源导入链路的结构性重构,目标不是“再给 `MeshLoader` 多加几个扩展名”,而是把模型资源正式提升为接近 Unity 的 `ModelImporter + ModelAsset` 工作流。
本计划默认遵循以下战略判断:
1. 当前工程已经具备基于 `Assimp` 的基础静态模型读取能力,`.obj/.fbx/.gltf/.glb/.dae/.stl` 都能进入导入链。
2. 当前主资源仍然是 `Mesh`,这导致导入时会把大量上层语义压扁到静态网格层。
3. 如果要走 Unity 风格,后续继续在 `Mesh` 主资产上堆功能会越来越别扭,必须先补一层真正的 `Model` 资产。
4. 骨骼动画不是本轮主目标,但本轮的数据结构和 artifact 设计必须为它预留正式扩展位。
本轮计划的本质是:
1. 把“source model import”和“runtime mesh load”拆开。
2. 把“外部模型文件的主资产类型”从 `Mesh` 提升到 `Model`
3.`.obj/.fbx/...` 的导入体验统一到一条 `ModelImporter` 主链上。
---
## 2. 当前状态判断
基于当前代码结构,可以确认已有能力与缺口如下。
### 2.1 已有能力
1. `MeshLoader` 已通过 `Assimp` 支持 `.fbx/.obj/.gltf/.glb/.dae/.stl/.xcmesh`
2. `AssetDatabase` 已将 `.obj/.fbx/.gltf/.glb/.dae/.stl` 识别为 `ModelImporter`
3. 编辑器 `MeshFilter` 的资源选择 UI 已允许 `.fbx/.obj/.gltf/.glb`
4. 工程已接入 `assimp-vc143-mt.dll`,说明构建链和基础运行依赖已经建立。
5. 当前 artifact 管线已经具备 `xcmesh/xcmat/xctex` 的写入与回读能力。
### 2.2 当前核心问题
1. `ModelImporter` 的主资源类型实际上仍然是 `Mesh`,并不是真正的 `Model`
2. 导入时递归遍历 Assimp node但会把 node transform 烘进顶点原始层级、局部变换、pivot 语义丢失。
3. `Mesh` 顶点结构当前仍是静态网格视角,只覆盖 `position/normal/tangent/bitangent/uv0`
4. `MeshImportSettings` 中虽然已经出现 `ImportSkinning / ImportAnimations / ImportCameras / ImportLights` 等标志,但没有形成对应的正式数据通路。
5. `.meta` 当前只写 importer 名称和版本,没有形成 Unity 风格的模型导入设置持久化。
6. 测试主要覆盖 `obj` 路径,针对真实 `fbx` fixture 的链路验证明显不足。
7. 拖模型进场景时,现有工作流更接近“给一个 `GameObject` 绑定单个 `Mesh`”,而不是实例化一棵模型层级。
### 2.3 当前阶段最重要的判断
当前问题不在于“FBX 能不能读进来”,而在于“读进来以后是被当成 `Mesh` 还是被当成 `Model`”。
如果继续让外部模型文件直接产出主 `Mesh`
1. `OBJ` 还能勉强成立。
2. `FBX` 会不断丢失层级、pivot、子节点、多部件组织等上层语义。
3. 后续无论做子资产展开、模型 prefab 化、骨骼动画还是稳定 reimport都会越来越难。
---
## 3. 目标架构决策
### 3.1 总体决策
长期目标不是:
1. `OBJ -> Mesh`
2. `FBX -> Model`
而是统一到:
1. 外部模型格式统一进入 `ModelImporter`
2. `ModelImporter` 的主产物统一是 `Model`
3. `Model` 内部再引用一个或多个 `Mesh/Material/Texture`
即:
1. `.obj -> 简化 Model`
2. `.fbx -> 完整 Model`
3. `.gltf/.glb/... -> 同样走 Model 主链`
这样做的好处是:
1. 资源系统主线统一。
2. 编辑器工作流统一。
3. 子资产稳定引用机制统一。
4. 骨骼、动画、blend shape、嵌入纹理等后续扩展都有正式落点。
### 3.2 `Mesh` 与 `Model` 的职责边界
`Mesh` 只回答一件事:
1. 这个表面怎么画。
它应该承载:
1. 顶点数据
2. 索引数据
3. section
4. bounds
5. 材质槽位数量与 section-material 映射
`Model` 回答另一件事:
1. 这个资源整体怎么组织。
它应该承载:
1. 节点层级
2. 节点局部 TRS
3. 节点名字与路径
4. 节点绑定的 mesh 引用
5. 每个 mesh 节点的材质槽绑定
6. 根节点信息
7. 导入元数据
8. 未来的 skeleton / animation / blend shape 扩展位
### 3.3 运行时链路的长期形态
长期上应当形成三层:
1. `ModelImporter`
负责 source file -> artifact graph
2. `ModelLoader`
负责读取 `xcmodel`
3. `MeshLoader`
负责读取 `xcmesh`
其中:
1. `MeshLoader` 不再承担完整 source scene import 的主责任。
2. `MeshLoader` 更适合退化成“运行时 mesh artifact 加载器 + builtin mesh loader”。
3. 所有 source model 的正式导入都应该通过 `ModelImporter` 的专用实现完成。
---
## 4. 本轮范围与明确不做的内容
### 4.1 本轮必须完成
1. `Model` 资源类型与 `xcmodel` artifact。
2. 统一的 `ModelImporter` 主链。
3. `.meta` 中的 Model Import Settings 持久化。
4. 保留层级的静态模型导入。
5. 场景中实例化模型层级的工作流。
6. 子资产与稳定 `LocalID` 规则。
7. 编辑器中的模型导入设置 Inspector。
8. `OBJ/FBX` 静态模型的正式测试闭环。
### 4.2 本轮暂不做
1. `SkinnedMeshRenderer` 正式渲染链。
2. 骨骼矩阵上传与 GPU skinning。
3. 动画 clip runtime 播放系统。
4. blend shape runtime。
5. 完整相机/灯光从 `FBX` 到 scene 的自动生成。
6. 模型子资产在 Project 面板中的完整可展开 UI。
### 4.3 本轮只预留不落地的内容
1. `Skeleton` 资源类型接口
2. `AnimationClip` 资源类型接口
3. `Model` 中 skeleton/animation 的 artifact 扩展位
4. skinning/blend shape 所需的稳定 source 标识规则
---
## 5. 目标数据结构设计
### 5.1 新增资源类型
建议在 `ResourceType` 中正式新增:
1. `Model`
同时保留已有:
1. `Mesh`
2. `Material`
3. `Texture`
4. `AnimationClip`
5. `Skeleton`
即使 `AnimationClip/Skeleton` 本轮不真正导出,也应在设计上与 `Model` 并列存在。
### 5.2 `Model` 资源结构建议
`Model` 至少包含以下结构:
1. `ModelNode`
- `name`
- `parentIndex`
- `firstChildIndex / childCount` 或 child index array
- `localPosition`
- `localRotation`
- `localScale`
- `meshBindingStart`
- `meshBindingCount`
2. `ModelMeshBinding`
- `meshLocalID`
- `materialBindingStart`
- `materialBindingCount`
3. `ModelMaterialBinding`
- `slotIndex`
- `materialLocalID`
4. `ModelImportMetadata`
- importer version
- source file signature
- import settings snapshot
### 5.3 `Mesh` 资源职责调整
`Mesh` 继续作为低层渲染资源保留,但职责要收紧:
1. 不再把 source model file 当成它的长期主输入。
2. 主要负责:
- `xcmesh`
- builtin mesh
3. source format 直接读取只作为过渡能力,后续应弱化。
### 5.4 `Model` artifact 文件布局
建议 artifact 目录布局统一为:
1. `main.xcmodel`
2. `mesh_0.xcmesh`
3. `mesh_1.xcmesh`
4. `material_0.xcmat`
5. `material_1.xcmat`
6. `texture_0.xctex`
7. `texture_1.xctex`
8. 未来可扩展:
- `skeleton_0.xcskel`
- `anim_0.xcanim`
### 5.5 `LocalID` 稳定规则
这是整个 Unity 风格方案里最关键的一部分。
不能简单使用“导入顺序下标”作为长期稳定标识。应当引入基于源语义的稳定 `LocalID` 生成策略:
1. Node基于节点路径生成
2. Mesh基于节点路径 + mesh name + source mesh index 生成
3. Material基于 material name + source material index 生成
4. Texture基于 source texture path 或 embedded texture key 生成
5. 后续骨骼/动画基于骨骼名、clip 名、source index 生成
目标是:
1. 同一个模型在普通 reimport 后,子资产 `LocalID` 不变。
2. 场景/prefab/材质槽引用不因为 artifact 目录变化而失效。
---
## 6. 导入链路重构方案
### 6.1 引入专用 source importer
建议新增专用模块,例如:
1. `AssimpModelImporter`
它的职责是:
1. 读取 `.obj/.fbx/.gltf/.glb/...`
2. 产出统一中间结构 `ImportedModel`
3. 再由 artifact writer 写出 `xcmodel/xcmesh/xcmat/xctex`
不建议继续把这部分主逻辑堆在 `MeshLoader` 内部。
### 6.2 导入中间结构
建议先建立 importer 内部中间结构:
1. `ImportedModel`
2. `ImportedNode`
3. `ImportedMesh`
4. `ImportedMaterial`
5. `ImportedTexture`
这样可以把:
1. Assimp scene 读取
2. 引擎内部资源表示
3. artifact 写出
三者解耦。
### 6.3 节点与变换处理原则
这是与当前实现最大的差异之一。
当前做法是把 `node` 变换乘到顶点上,再把所有几何收进同一个 `Mesh`。本轮要改成:
1. Mesh 顶点保持在 mesh local space
2. Node local transform 单独存进 `Model`
3. Scene 实例化时再把 node local transform 还原到 `GameObject`
只有这样才能保留:
1. 层级
2. pivot
3. 本地 TRS
4. 后续动画骨骼的基础结构
### 6.4 轴系与缩放的处理原则
建议明确以下规则:
1. 轴系转换属于导入标准化步骤,但不能以“摧毁原始层级”为代价。
2. 全局 import scale 应优先体现在 root 侧或 mesh 侧的单一标准化策略中,不要 mesh 与 node 两边重复施加。
3. 一旦规则确定,要在 `ModelImportSettings` 中固定,并进入 `metaHash`
本轮必须把“坐标转换在哪里做、缩放在哪里做”写成正式约束,而不是散落在 loader 里。
### 6.5 `OBJ` 的导入策略
`OBJ` 仍然统一走 `ModelImporter`,但导入结果通常是一个简化模型:
1. 一个 root node
2. 一个或少量 mesh node
3. 少量 material bindings
即:
1. `OBJ` 不是继续作为架构特例保留在 `Mesh` 主链外面
2. 它只是“语义很简单的 model source”
### 6.6 `FBX` 的导入策略
`FBX` 则要保留完整静态结构:
1. node hierarchy
2. local transform
3. mesh attachments
4. material slots
5. embedded/external textures
6. 后续可扩展 skeleton/animation metadata
即使本轮不做动画,也不应该再把 `FBX` 烘成单个主 `Mesh`
---
## 7. AssetDatabase 与 ArtifactDatabase 改造计划
### 7.1 主资产类型调整
当前 `ModelImporter` 的主资产仍是 `Mesh`。本轮要改成:
1. `ModelImporter` 主资产类型为 `Model`
2. `mainLocalID` 指向 `Model` 主资产
3. `Mesh/Material/Texture` 成为其子资产
### 7.2 `EnsureArtifact` 语义调整
`Assets/Models/robot.fbx` 执行 `EnsureArtifact(..., ResourceType::Model)` 时:
1. 返回主 `xcmodel`
2. 内部子资产可通过 `AssetRef(assetGuid, localID, resourceType)` 访问
若后续有需要,`MeshFilter` 等组件可直接引用某个 `Mesh` 子资产,而不是引用主 `Model`
### 7.3 依赖追踪调整
当前依赖追踪对 `obj -> mtl -> texture` 已有基础覆盖,但本轮要升级成通用模型依赖追踪:
1. source model file
2. 外部纹理
3. embedded texture 的派生 key
4. 导入设置 meta
5. future外部 animation source / skeleton source
目标是:
1. 改 texture 会触发模型 reimport
2. 改 importer settings 会触发模型 reimport
3. 子资产 artifact 会稳定重建
### 7.4 资源查询接口调整
建议新增或调整:
1. `TryGetAssetRef(path, ResourceType::Model, ...)`
2. `TryGetSubAssetRef(path, subAssetKey or localID, ResourceType::Mesh, ...)`
3. `TryGetPrimaryAssetPath(guid, ...)``Model` 继续保持主资产语义
同时要保证:
1.`Model` 与子 `Mesh` 的引用路径语义清晰
2. 编辑器与场景序列化知道自己引用的是主资产还是子资产
---
## 8. `.meta` 与导入设置计划
### 8.1 当前问题
当前 `.meta` 只有:
1. `guid`
2. `folderAsset`
3. `importer`
4. `importerVersion`
这不足以支撑 Unity 风格模型工作流。
### 8.2 新增 `ModelImportSettings`
建议正式引入或升级为:
1. `ModelImportSettings`
至少包括:
1. `globalScale`
2. `axisConversion`
3. `flipUVs`
4. `flipWindingOrder`
5. `generateNormals`
6. `generateTangents`
7. `importMaterials`
8. `extractEmbeddedTextures`
9. `preserveHierarchy`
10. `mergeStaticMeshes`
11. 预留:
- `importSkinning`
- `importAnimations`
- `importCameras`
- `importLights`
### 8.3 `.meta` 持久化规则
应当把上述设置正式写入 `.meta`,并参与 `metaHash`
目标是:
1. 切换 importer setting 后 artifact key 会变化
2. `Reimport` 有明确语义
3. Inspector 改完设置能稳定反映到导入结果
### 8.4 版本策略
建议:
1. `ModelImporter` 单独维护 importer version
2.`xcmodel` schema 或子资产布局变化时,显式提升版本号
3. 不再使用“整个 AssetDatabase 共用一个笼统版本号”来覆盖全部导入器变化
---
## 9. 编辑器工作流计划
### 9.1 Project 面板语义
`.obj/.fbx/.gltf/.glb/...` 在 Project 面板中应统一显示为:
1. `Model`
而不是继续让用户默认把这些资源理解成“单个 mesh 文件”。
### 9.2 Model Importer Inspector
选中模型资源时Inspector 需要出现真正的导入设置面板:
1. Import Settings
2. Apply
3. Revert
4. Reimport
首批显示的设置至少包括:
1. Scale
2. Axis Conversion
3. Preserve Hierarchy
4. Import Materials
5. Generate Normals
6. Generate Tangents
7. Flip UVs
### 9.3 场景拖拽行为
把模型资源拖进场景时,不应只创建一个空物体加一个 `MeshFilter`
推荐行为:
1. 读取 `Model`
2. 生成对应 `GameObject`
3. 按节点局部变换恢复 TRS
4. 对带 mesh 的节点挂 `MeshFilter + MeshRenderer`
5. 材质槽按 `Model` 内的绑定还原
这才接近 Unity 的“拖入模型得到层级实例”。
### 9.4 子资产访问
本轮不强制要求 Project 面板完整展开子资产树,但至少应保证:
1. `MeshFilter` 能引用模型产出的某个 `Mesh` 子资产
2. 场景序列化后引用稳定
3. 后续补 Project 子资产展开 UI 时不需要推倒重来
---
## 10. 场景实例化与运行时路径
### 10.1 不建议增加 `ModelComponent`
为了贴近 Unity建议不要把“整个模型实例”抽成一个新的 `ModelComponent` 挂在单个对象上。
更合理的做法是:
1. `Model` 是资源
2. “实例化模型”是一个 utility / service
3. 实例化结果是 `GameObject` 层级
### 10.2 建议新增实例化工具层
可以新增例如:
1. `ModelInstantiationUtility`
2. `InstantiateModelAsset(...)`
职责:
1. 根据 `Model` 创建场景层级
2. 为每个节点恢复局部 TRS
3. 绑定子 `Mesh`
4. 绑定默认材质或导入材质
### 10.3 与现有渲染链的兼容策略
现有渲染链以 `MeshFilter + MeshRenderer` 为中心,这一层本轮不应推翻。
本轮应当:
1. 保持 runtime 渲染主链稳定
2. 通过新的 `Model -> GameObject hierarchy` 实例化路径,把 `Model` 翻译回现有组件体系
这样风险最小,且后续加 `SkinnedMeshRenderer` 时也有自然落点。
---
## 11. 测试与验证计划
### 11.1 Fixture 规划
必须新增真实模型 fixture不再只停留在字符串层面的 `CanLoad("test.fbx")`
建议至少准备:
1. `single_mesh_static.obj`
2. `single_mesh_static.fbx`
3. `multi_node_static.fbx`
4. `multi_material_static.fbx`
5. `embedded_texture_static.fbx`
6. `external_texture_static.fbx`
### 11.2 Unit Test
建议新增或扩展:
1. `ModelLoader` 基础加载测试
2. `ModelImportSettings` 持久化测试
3. `ModelArtifact` 写入/回读测试
4. `LocalID` 稳定性测试
5. `AssetDatabase` 主资产/子资产引用测试
### 11.3 Integration Test
建议新增:
1. `FBX -> xcmodel -> scene instantiate` 集成测试
2. reimport 后 `AssetRef` 稳定性测试
3. 多节点模型实例化后层级与局部变换恢复测试
4. 多材质槽绑定测试
### 11.4 Editor Test
建议补:
1. Project 面板模型类型识别
2. Model Importer Inspector 设置编辑
3. Apply/Reimport 行为
4. 拖模型到场景生成层级
### 11.5 验收原则
本轮不能以“能读进一个 FBX 文件”作为完成标准。
必须同时满足:
1. artifact 正确
2. hierarchy 正确
3. reimport 稳定
4. editor 工作流成立
---
## 12. 分阶段执行计划
### Phase 1`Model` 资源与 artifact 基础设施落地
目标:
1. 新增 `ResourceType::Model`
2. 新增 `Model` 类与 `ModelLoader`
3. 新增 `xcmodel` artifact 格式
任务:
1. 设计 `ModelNode / ModelMeshBinding / ModelMaterialBinding`
2.`ArtifactFormats` 中定义 `ModelArtifactHeader`
3. 实现 `WriteModelArtifactFile / LoadModelArtifact`
4.`ResourceManager` 注册 `ModelLoader`
验收标准:
1. `xcmodel` 能写出与读回
2. `Model` 可被 `ResourceManager` 正式加载
### Phase 2source import 主链从 `Mesh` 迁移到 `Model`
目标:
1. 建立 `AssimpModelImporter`
2.`.obj/.fbx/...` 的正式导入主链切换到 `Model`
任务:
1. 建立 `ImportedModel` 中间结构
2. 从 Assimp scene 提取节点层级、局部 TRS、mesh、material、texture
3. 停止在导入主链中把 node transform 烘平到单个主 mesh
4. 输出 `main.xcmodel + sub assets`
验收标准:
1. `OBJ` 被导入成简化 `Model`
2. `FBX` 被导入成保留 hierarchy 的 `Model`
### Phase 3`AssetDatabase` 子资产与稳定 `LocalID`
目标:
1. 完成主资产/子资产语义
2. 建立稳定 `LocalID` 规则
任务:
1. 修改 `ModelImporter` 的主资源类型
2. 实现模型子资产 `AssetRef`
3.`EnsureArtifact``TryGetAssetRef`、序列化链路理解 `Model` 主资产与子 `Mesh`
4. 建立 reimport 稳定性测试
验收标准:
1. reimport 后子资产引用不漂移
2. 场景中的 mesh 引用可稳定恢复
### Phase 4`.meta` 与 Model Import Settings 正式化
目标:
1. 让模型导入参数进入正式工作流
任务:
1. 定义 `ModelImportSettings`
2. 把 settings 写入 `.meta`
3. 调整 `metaHash`
4. 保证 settings 变化触发 reimport
验收标准:
1. 改 scale/axis/material 等设置会稳定影响导入结果
2. artifact key 与导入设置一致变化
### Phase 5编辑器 Inspector 与场景实例化工作流
目标:
1. 建立 Unity 风格的模型资源使用体验
任务:
1. Project 面板统一把模型文件识别为 `Model`
2. 新增 Model Importer Inspector
3. 实现 `Apply/Reimport`
4. 实现拖模型到场景生成 `GameObject` 层级
验收标准:
1. 从 editor 可完整配置模型导入
2. 拖入场景后层级与局部变换正确
### Phase 6清理过渡路径与补齐文档测试
目标:
1. 收紧旧路径,避免双轨架构长期并存
任务:
1. 评估并逐步弱化“source file 直接由 `MeshLoader` 作为主入口”的旧路径
2. 清理命名、文档、注释、测试目录结构
3. 输出阶段总结
验收标准:
1. 主链路清晰,旧路径只保留必要兼容
2. 文档与测试同步完成
---
## 13. 关键风险与应对
### 13.1 最大风险:`LocalID` 不稳定
风险:
1. reimport 后子资产重排
2. 场景与 prefab 引用失效
应对:
1. 在 Phase 1 之前先写清 `LocalID` 规则
2. 在 Phase 3 前就建立稳定性测试
### 13.2 第二风险:轴系与缩放规则前后不一致
风险:
1. 同一模型在 direct load、artifact load、scene instantiate 三条路径下结果不一致
应对:
1. 明确标准化规则只允许一个真值来源
2. 写入 importer settings 并进入测试
### 13.3 第三风险:编辑器体验与底层数据不同步
风险:
1. Inspector 改了设置但 reimport 行为不稳定
2. Project 面板显示的是 `Model`,内部实际仍按 `Mesh`
应对:
1. 先完成 Resource/AssetDatabase 语义,再接 editor UI
2. 不允许 editor 先行伪装完成
### 13.4 第四风险:过渡期双轨逻辑长期共存
风险:
1. `MeshLoader` source import 与 `ModelImporter` source import 两套路径并行,长期维护成本失控
应对:
1. 在 Phase 6 明确收紧旧路径
2. 把 source import 主入口统一到 `ModelImporter`
---
## 14. 本轮完成标志
当以下条件同时成立时,本轮才算真正完成:
1. `.obj/.fbx/.gltf/.glb/...` 的主资产统一为 `Model`
2. `Model` artifact 已正式落地并进入 `ResourceManager`
3. `OBJ` 能导入成简化 `Model`
4. `FBX` 能导入成保留 hierarchy 的静态 `Model`
5. 模型导入设置已写入 `.meta` 并进入 reimport 逻辑
6. editor 中已具备 Model Importer Inspector
7. 拖模型到场景时会生成 `GameObject` 层级,而非单个烘平 mesh
8. 子资产 `LocalID` 在普通 reimport 下稳定
9. 测试已覆盖真实 `FBX` fixture 的关键路径
---
## 15. 一句话结论
这一轮不是“给 FBX 补支持”,而是把 XCEngine 的外部模型资源体系从“以 `Mesh` 为主资产的静态导入器”升级成“以 `Model` 为主资产、以子资产和稳定 reimport 为基础、可持续扩展到骨骼动画的 Unity 风格模型工作流”。