chore(contracts): 同步契约到 0.4.0/0.3.0(GLB/VRM 主力格式、模型审核与骨骼分析)
This commit is contained in:
+56
-19
@@ -1,32 +1,69 @@
|
||||
# contracts/model-formats.md — 模型与动作格式契约 v0.3.0
|
||||
# contracts/model-formats.md — 模型与动作格式契约 v0.4.0
|
||||
|
||||
## 1. 角色模型(models)
|
||||
|
||||
| 格式 | 扩展名 | 支持级别 | 说明 |
|
||||
**系统以 GLB / VRM 为主力格式**(两个客户端都原生加载),其余格式一律在服务端转换。
|
||||
|
||||
| 格式 | 扩展名 | 支持级别 | 服务端行为 |
|
||||
|---|---|---|---|
|
||||
| VRM | `.vrm` | **首选(P0)** | glTF 2.0 二进制 + VRM 扩展;Windows 端 godot-vrm 直接加载 |
|
||||
| glTF Binary | `.glb` | P1 | 需含骨骼(skin);无骨骼则仅作静态展示 |
|
||||
| glTF | `.gltf` (+ `.bin`, 贴图) | P2 | 多文件,管理端需打包为 zip 上传 |
|
||||
| FBX | `.fbx` | P2 | 需 Assimp 转换,管理端仅归档,不保证重定向质量 |
|
||||
| **MMD 压缩包** | `.zip` | P1 | 内含 `.pmx`(+ `textures/`)。服务端自动解压并转换,见 §1.1 |
|
||||
| glTF Binary | `.glb` | **主力(P0)** | **直接导入**,不重编码 |
|
||||
| VRM | `.vrm` | **主力(P0)** | **直接导入**;= GLB + VRM 扩展(humanoid / 表情 / springBone / meta) |
|
||||
| glTF | `.gltf` (+ `.bin`, 贴图) | P1 | 直接导入(分离文件在 zip 内可用;单文件上传会缺 .bin) |
|
||||
| MMD 压缩包 | `.zip` | P1 | **自动搜索包内可用模型**:优先直接导入 `.vrm`/`.glb`/`.gltf`,否则转换 `.pmx`/`.fbx`,见 §1.1 |
|
||||
| MMD 模型 | `.pmx` | P2(输入) | 服务端转换(优先 GLB、其次 VRM);单文件上传无贴图 |
|
||||
| FBX | `.fbx` | P2(输入) | 服务端转换(优先 GLB、其次 VRM) |
|
||||
|
||||
### 1.1 模型压缩包转换(`.zip` → FBX / GLB)
|
||||
### 1.1 上传与转换策略
|
||||
|
||||
上传 `.zip` 时走"解压 → 选主模型 → 转换 → 入库",而不是把压缩包当模型文件存下来。
|
||||
**`.glb` / `.vrm` / `.gltf` 直接导入**(不重编码,零损失);**`.pmx` / `.fbx` 由服务端转换**:
|
||||
**优先 GLB,其次 VRM**。
|
||||
|
||||
#### 单文件上传
|
||||
|
||||
| 输入 | 行为 |
|
||||
|---|---|
|
||||
| `.glb` / `.vrm` | 直接导入;`.glb` 若含 `VRMC_vrm`/`VRM` 扩展会被识别为 VRM |
|
||||
| `.gltf` | 直接导入(外部 `.bin` / 贴图不在同一文件里,会缺资源) |
|
||||
| `.pmx` / `.fbx` | 转换:`ASSIMP_BIN` 导出 → 回读校验(见下) |
|
||||
|
||||
#### 压缩包上传(自动搜索)
|
||||
|
||||
不再"只找 `.pmx`",而是**按格式优先级自动搜索包内可用模型**:
|
||||
|
||||
```
|
||||
.vrm → .glb → .gltf (直接导入,优先)
|
||||
.pmx → .fbx (需要转换)
|
||||
```
|
||||
|
||||
同一优先级内:`MODEL_CONVERT_PMX` 指定的文件名优先,其次目录最浅,最后文件最大。
|
||||
压缩包原件以 `model-archive` 资产留存并写入 `models.source_asset_id`。
|
||||
|
||||
#### 转换与校验(PMX / FBX)
|
||||
|
||||
| 步骤 | 规则 |
|
||||
|---|---|
|
||||
| 选主模型 | 优先 `MODEL_CONVERT_PMX` 指定的文件名(默认 `Hatsune Miku.pmx`,大小写不敏感);否则取目录最浅、其次文件最大者。**只认 `.pmx`**(MMD 的 PMX 模型;PMD/PMX 之外的格式暂不支持) |
|
||||
| 转换 | `MODEL_CONVERT_FORMAT`(默认 `fbx`)指定首选格式,调 `ASSIMP_BIN` 导出 |
|
||||
| 校验 | **导出后必须回读校验**(`assimp info`)。Assimp 的 FBX 导出器对带蒙皮的大骨架模型会"写出损坏文件"(实测 714 骨骼的 PMX:导出退出码 11,产物回读报 `FBX-DOM no Objects dictionary found`),只信退出码会把坏文件入库 |
|
||||
| 回退 | FBX 失败(导出失败或回读失败)→ 自动改用 **GLB**,并把两次尝试写进 `models.conversion_note` |
|
||||
| 贴图 | GLB 产物里 Assimp 会把贴图写成外部 URI(还带 `\` 反斜杠),服务端把压缩包内的贴图**内嵌**进 GLB 的二进制块,保证单文件可加载 |
|
||||
| 溯源 | 压缩包原件以 `model-archive` 资产留存,写入 `models.source_asset_id` |
|
||||
| 默认模型 | 转换成功且 `autoDefault` 不为 false:`is_primary=1` + `status=published`,并把身份写进源码 `web-server/src/config/default-model.ts`,见 `data-models.md` §4.1 |
|
||||
| 确定性 | **转换产物不是字节级确定的**:同一份 zip 连续转换三次得到三个不同的 sha256(大小相同,BIN chunk 有十几字节浮动)。因此默认模型种子以**压缩包 sha256** 为匹配键,产物 sha256 只作记录 |
|
||||
| 失败 | 压缩包里没有 `.pmx`、Assimp 不可用、两种格式都失败 → **HTTP 422**,不产生模型记录 |
|
||||
| 1. GLB 直出 | `assimp export → .glb`,成功后把 Assimp 写成外部 URI 的贴图**内嵌**进 BIN chunk |
|
||||
| 2. glTF 打包 | 上一步失败时 `assimp export → .gltf`,由 `gltf-packer.ts` 把 `.gltf` + `.bin` + 贴图自行合成 GLB |
|
||||
| 3. VRM 封装 | `MODEL_CONVERT_FORMAT=vrm` 时,在 GLB 之上补 `VRMC_vrm` 扩展(humanoid 骨骼来自骨骼名映射,见 `model-inspector.ts` 的候选表,与 Godot 客户端一致);**humanoid 不全则回退交付 GLB**,缺失骨骼写进 `conversion_note` |
|
||||
| 回读校验 | 每一步产物都要 `assimp info` 回读。Assimp 会"写出损坏文件"(实测 FBX 导出退出码 11、产物回读报 `FBX-DOM no Objects dictionary found`),只看退出码会把坏文件入库 |
|
||||
| 说明 | 每次尝试都写进 `models.conversion_note`(例如 `Hatsune Miku.pmx → GLB,已内嵌 2 张贴图`) |
|
||||
| 失败 | 包内没有可用模型、Assimp 不可用、两条路径都失败 → **HTTP 422**,不产生模型记录 |
|
||||
|
||||
安全:解压前逐条校验路径,拒绝绝对路径与 `..`(zip-slip);拒绝加密条目;只支持 STORE / DEFLATE。
|
||||
#### 入库后(自动)
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|---|
|
||||
| 状态 | **一律 `pending`(待审核)**:模型必须审核通过后才可能被客户端消费,见 `database.md` 状态机 |
|
||||
| 分析 | `model-inspector.ts` 计算面数 / 顶点数 / 贴图 / **骨骼树** / humanoid 覆盖 / 可驱动性,写入 `models.stats`,供审核界面展示 |
|
||||
| 缩略图 | `model-thumbnail.ts` 纯 TS 光栅化生成 PNG,写入 `models.thumbnail_asset_id`(失败不影响入库) |
|
||||
| 默认模型 | 只在**审核通过并勾选"同时设为默认"**(或 `POST /api/models/:id/primary`)时设置,并把身份写进源码 `src/config/default-model.ts`,见 `data-models.md` §4.1 |
|
||||
|
||||
#### 其它约定
|
||||
|
||||
| 项 | 规则 |
|
||||
|---|---|
|
||||
| 确定性 | **转换产物不是字节级确定的**:同一份 zip 连续转换 3 次得到 3 个不同 sha256(大小相同,BIN chunk 有十几字节浮动)。因此默认模型种子以**上传压缩包的 sha256** 为匹配键,产物 sha256 只作记录 |
|
||||
| 安全 | 解压前逐条校验路径,拒绝绝对路径与 `..`(zip-slip);拒绝加密条目;只支持 STORE / DEFLATE |
|
||||
|
||||
**VRM 优先**的选取顺序见 `data-models.md` §4.1。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user