Files
mtgodot-poc/docs/GODOT-POC-PLAN.md
T
shenleiandClaude Opus 5 fb4d2d222b docs: 文档审计——修正失效链接与过期结论
按 docs/ 目录逐篇核对,只改与事实/路径不符的部分,不动尚未验证的计划条目:

- `docs/steps/**` 在早前整理时已迁到 `docs/reference/steps/**`,全仓的旧路径
  引用(PLAN.md / README.md / M0-gr2-reader.md 及 libgr2 的头注释)一并改正。
- CLIENT-GAP.md / CLIENT-GAP-FIX.md / BACKLOG.md / MIDREVIEW.md:把已经落地的
  条目从「待办」改为已完成,删掉与代码现状矛盾的描述。
- ANDROID-TESTING.md / CLIENT-PORT.md / CLIENT-ROADMAP.md / GODOT-POC-PLAN.md /
  SHINSOO-WORLD-RENDERING.md / PARITY-GAP.md:同上,另补当前实际的构建/测试入口。
- THIRD-PARTY.md:补齐实际在用的第三方来源与许可说明。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJugvEJwz3FK4hw9ti3SRb
2026-09-08 17:15:17 +09:00

332 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Godot 渲染 Demo — 开发计划
> 内部研究方案,不对外公开。
>
> **2026-08-29 更新**:并行的 bgfx 自研引擎方案(`xrender-poc` / [`reference/PLAN.md`](./reference/PLAN.md)
> 经中期评审([`MIDREVIEW.md`](./MIDREVIEW.md)**停止开发**,本方案成为唯一在研路线。
> `libgr2` / `formats` / `oracle` / `tools` 已从 xrender-poc **vendored 进本仓库**
> `../libgr2`、`../formats`、`../oracle`、`../tools`),不再是 submodule / sibling 引用。
> 下文中出现的 `xrender-poc/xxx` 路径按 [`reference/README.md`](./reference/README.md) 的映射表读。
> bgfx demo 截图作为交叉核对基线留在 `../test/bgfx-reference/`。
| | |
|---|---|
| 状态 | **已被工作范围超越(2026-09-08 复核)**。Phase 1macOS)完成,见 [`MIDREVIEW.md`](./MIDREVIEW.md)Phase 2 的 iOS / Android 交叉编译与 APK 导出也已完成(`BACKLOG.md` F1/F2、[`PLATFORMS.md`](./PLATFORMS.md)),故文中「Phase 2 本轮不做」不再成立。§01「明确排除」清单已大面积失效,见该节的复核注。当前工作以 [`BACKLOG.md`](./BACKLOG.md)、[`CLIENT-GAP.md`](./CLIENT-GAP.md)、[`RENDERING-REPAIR-PLAN.md`](./RENDERING-REPAIR-PLAN.md) 为准 |
| 技术栈 | Godot **4.7.1** stable · GDExtension`godot-cpp` 精确提交 `101ae38`4.7 API)· C++20 |
| 目标平台 | **Phase 1macOS**(当前)→ Phase 2Android(一加 13 / Vulkan)· iOSiPhone 16 / Metal)。自用,三台现代设备,不涉及 GLES3 / Compatibility |
| 判定周期 | Phase 1 ≈ 3.5–5 周(单人)→ **中期评审**Phase 2 = 三设备各一次 bring-up + 性能留档(自用,无对外 go/no-go) |
| 复用 | `libgr2`gr2 v6 读取器)· `formats/`textscript · msa · msm)· `oracle/` golden · bgfx demo 截图 |
| 自研 | `Metin2Model` GDExtension 节点(gr2 → `Skeleton3D` + `ArrayMesh` + 材质) |
---
## 00 · 阶段划分
| 阶段 | 平台 | 里程碑 | 回答什么 | 产出 |
|---|---|---|---|---|
| **Phase 1** | macOS | M0' · M1 · M2 · **M2.5** | ① gr2 → Godot 场景对象能否正确映射;② 动画运行时(采样 / retarget / 事件)能否移植;③ **Metin2 观感能否在 Godot 里还原** | 中期评审报告:桥接层工作量实测 + 观感对比 + 剩余风险 |
| **Phase 2** | Android(一加 13)· iOSiPhone 16 | M3(·M4 可选) | ④ 三端渲染一致;⑤ 两台移动设备性能 / 生命周期 | 三设备 bring-up 报告 + 性能留档 |
**关键澄清**Godot 在 macOS 上渲染一个蒙皮 gr2 **几乎不可能失败**,Phase 1 的价值是把**平台无关**的 GDExtension 工程(占工程量大头)做完并确认视觉保真度可达。bgfx 自研引擎方案已于 2026-08-29 停止开发,Godot 是唯一在研路线;本项目自用、不对外发布,**没有"押哪条路线"的产品决策**。Phase 2 只剩"三台目标设备(Mac / 一加 13 / iPhone 16)上能不能跑 + 性能够不够自己用"。
**进入 Phase 2 的前置**:M2.5 门禁通过、中期评审确认桥接层与观感可接受即可推进。三台目标设备都是现代硬件,全程不涉及 GLES3 / Compatibility 渲染器,不做中低端 / 老机器。
---
## 01 · 目标与判定标准
### Phase 1 要证明的三件事(macOS
1. **gr2 → Godot 场景对象**`libgr2` 读出的骨架 / 蒙皮网格 / 权重能否正确映射到 `Skeleton3D` + `ArrayMesh` + `Skin`,覆盖多种骨架(不止 warrior)。
2. **动画正确** — 每帧从 `libgr2::sample_pose` 驱动 `Skeleton3D`,结果与 bgfx demo / Granny oracle 逐帧一致;跨文件 retarget、motion event 成立。
3. **观感可还原** — 多 texture-stage 材质、alpha test、顶点色、additive 混合、fog、方向光阴影在 Godot 里做出来,与原客户端目视接近。
### Phase 2 要证明的两件事(移动端,本轮不做)
4. **三端一致** — 同一 GDExtension + Godot 工程在 iPhone 16Metal+ 一加 13Vulkan)渲染与 Mac 一致。
5. **性能 / 生命周期够自用** — 两台设备单角色 / 多角色帧率可接受(旗舰基线,非发布门槛),后台 / 恢复稳,Godot 节点开销可控。
### 明确排除(两个阶段都不做)
> **⚠️ 本清单已失效(2026-09-08 复核)。** 下列 9 项中 8 项后来都做了 —— POC 判定通过后项目
> 继续做成了完整客户端,范围扩张是有意的(`SHINSOO-WORLD-RENDERING.md` 的「定位」一节最早
> 记录了 UI 这一条)。逐项现状:
>
> | 原「不做」项 | 现状 | 落点 |
> |---|---|---|
> | SpeedTree 植被 | 🟡 已做 proxy | `formats/spt.cpp``BACKLOG` E10 |
> | 地形(Metin2 户外地图) | ✅ 已做 | `Metin2World` + `formats/terrain_mesh.cpp``BACKLOG` E8/E11/E12 |
> | UI / Python 脚本层 | ✅ 已做 | `project/ui/*`uiscript 装载器 + 多个 1:1 窗口移植) |
> | eterpack | ✅ 已做 | `mtpack` + `packtool``BACKLOG` G1 |
> | GPU 蒙皮优化 | ✅ 已做 | `MTGODOT_GPUSKIN=1``m2_material` `SRC_SKIN` |
> | LOD 策略 | ✅ 已做 | `lod_distances` + crossfade`BACKLOG` C9 |
> | 多角色 AI | ⬜ 仍不做 | —— |
> | 网络 | ✅ 已做 | `mtnet`m2dev + 40250 classic 双后端) |
> | 玩法 | ✅ 大量已做 | `CLIENT-GAP.md` / `CLIENT-ROADMAP.md` P0P10 |
SpeedTree 植被、地形(Metin2 户外地图)、UI / Python 脚本层、eterpackPhase 1 用散文件)、GPU 蒙皮优化、LOD 策略、多角色 AI、网络、玩法。
> 地形与 `.mse` 特效在 Phase 1 也不做,但 **M2.5 会做通用 `ShaderMaterial` + 混合模式的地基**,为后续正式移植时的地形 / 特效渲染铺路。
### 判定口径
- **M2.5 门禁通过 + 中期评审"可接受"** = 推进 Phase 2。中期评审已判定桥接层工作量与观感均可接受([`MIDREVIEW.md`](./MIDREVIEW.md));bgfx route 已停,无「转回」分支。
- **Phase 2 若撞上方案性死路**(自写蒙皮着色器在 iPhone 16 / 一加 13 上跑不出、Godot 节点开销在自用规模下就卡)——记录清楚、就地想办法(回落 CPU LBS / `RenderingServer` 直调 / uniform 传骨骼),不再有另一条引擎路线兜底。
- **Phase 2 末交付**:三设备 bring-up + 性能留档报告(自用参考,不做对外选型结论)。
### 与 bgfx route 的关系(历史对照,bgfx route 已于 2026-08-29 停止开发)
| | bgfx route`PLAN.md` | Godot route(本文件) |
|---|---|---|
| 渲染器 | 自研(bgfx 薄封装 + `.sc` 着色器) | Godot 内置渲染器 |
| 平台壳 / 输入 / 音频 / 打包 | 自己拼(SDL3 / 薄壳 + 各库) | Godot 全包 + 编辑器 |
| gr2 解析 | `libgr2` | `libgr2`(同一份) |
| 动画运行时 | 自研(复用 EterGrnLib 逻辑) | 映射到 `Skeleton3D` / `AnimationMixer` |
| 阴影 / 环境 / sky | 自研 | Godot 内置(M2.5 直接用) |
| 长期 | 自己养一个引擎 | 跟随 Godot 版本(GDExtension ABI 绑 minor |
**`libgr2` 在两条路里都是核心,完整复用。**
---
## 02 · 架构总览
```
metin2-client/ # 建仓时叫 mtgodot-poc,后更名
extension/ GDExtensionC++godot-cpp 4.7
src/
register_types.cpp 注册 Metin2Model / Metin2AnimPlayer
metin2_model.{h,cpp} load_gr2 → Skeleton3D + ArrayMesh + Skin + material
metin2_anim.{h,cpp} 每帧 libgr2::sample_pose → Skeleton3D bone pose;跨文件 retargetmotion event
gr2_bridge.{h,cpp} libgr2 POD 视图 → Godot 数组打包;basis + 单位换算(集中一处)
dds_loader.{h,cpp} DDS → Godot Image(散文件路径用;res:// 里的 .dds 交 Godot 原生导入)
m2_material.{h,cpp} texture-stage 描述 → ShaderMaterialM2.5
(libgr2 / formats 在仓库根,顶层 CMake add_subdirectory 进来)
SConstruct / CMakeLists godot-cpp 构建(Phase 1 只出 macOS arm64
project/ Godot 4.7.1 demo 工程
main.tscn orbit 相机 + DirectionalLight3D(+shadow) + WorldEnvironment(sky) + Metin2Model
main.gd 加载资产、切模型 / 动画、截图、性能 HUD
assets/ warrior + 另 2 种骨架的 gr2/dds
.gdignore ← 让 Godot 不去 import .gr2(见下)
export_presets.cfg macOSPhase 1
test/
golden/ godot-macos-*.png
compare.py 与 test/bgfx-reference/ 参考截图 + oracle 对拍
docs/
GODOT-POC-PLAN.md 本文件
steps/ 按需拆
```
### 分层(Phase 1
| 层 | 内容 |
|---|---|
| 平台 / 窗口 / 输入 / 打包 | **Godot**macOS |
| 渲染器 | **Godot 内置** Forward+macOS / Metal |
| 资源运行时 | **`Metin2Model` GDExtension**gr2 → `Skeleton3D` / `ArrayMesh` / `Skin` / `ShaderMaterial` |
| 动画 | 每帧 `libgr2::sample_pose``Skeleton3D`**B 方案**);A/B 决策**推迟到 Phase 2**(Mac 上信号不足),M2 只做一次 50 角色压力采样留档 |
| 解析 | **`libgr2`(复用)** · `formats/`msa/msm,复用) · DDS |
| 数学 / 坐标 | Godot `Transform3D` / `Basis`gr2 art-tool basis → Godot 右手 Y-up + 单位缩放,在 `gr2_bridge` 一次性处理,约定见 §03 |
### 两个实操约定(Phase 1 T1 前定死,写进 `gr2_bridge` 注释)
- **`.gr2` 不进 import 流程**`project/assets/``.gdignore``.gr2``Metin2Model` 在运行时用绝对路径 / `user://` 直接读;`.dds` 可留在 `res://` 交 Godot 原生导入,或也走运行时 `dds_loader`(二选一,T1.3 决定并记录)。
- **单位缩放**:定 `M2_TO_GODOT = 0.01`Metin2 约 100 unit ≈ 1 Godot m),相机 near/far、`DirectionalLight3D` 阴影范围按此设。若与资产实测不符,在 T1.1 调整并记录。
---
## 03 · 技术选型
| 项 | 选择 | 理由 |
|---|---|---|
| 引擎版本 | **Godot 4.7.1 stable**(本机已装),锁死不随手升 | GDExtension ABI 绑 minor;升级作独立任务 |
| 扩展机制 | `godot-cpp` **`101ae38034304346a46ea9ea84ae156d3e860496`**GDExtension(非模块编译) | gitlink 精确锁定,目标 API 4.7;升级作独立任务 |
| 构建工具 | `godot-cpp`**CMake 路径**(本机已有 cmake;不装 SCons)或 `brew install scons` 二选一 | Phase 1 只出 macOS arm64,摩擦最小 |
| gr2 解析 | `xrender-poc/libgr2` 静态库嵌入 extension | 与 bgfx route 同一份,已过 9166 fuzz + oracle |
| 动画方案 | **B(每帧写骨骼)**A(烘 `Animation`**决策推迟到 Phase 2** | B 直接复用 `libgr2` 采样、最保真;A 的动机(多角色 CPU)在 Mac 上压不出来 |
| 骨骼权重 | `ArrayMesh` 4 权重优先;>4 影响则 `ARRAY_FLAG_USE_8_BONE_WEIGHTS` | M1 T1.2 早验,三种骨架都查 |
| 贴图 | 见 §02 实操约定(`.dds` 原生导入 vs 运行时 `dds_loader`T1.3 定) | 两条都留 |
| 材质 | **自定义 `ShaderMaterial`**`light()` 自定义着色贴 Metin2 观感);M1 先 `StandardMaterial3D` 上屏,M2.5 换 | 多 stage 只做近似,列差距清单 |
| 渲染器 | Forward+ / Mobile renderer | 三台目标设备(Mac / 一加 13 / iPhone 16)都是现代硬件,全程不涉及 Compatibility(GLES3) |
| 阴影 / sky | Godot 内置 `DirectionalLight3D` shadow + `WorldEnvironment` procedural skyM2.5 接入 | 成本近零,Mac 观感必要 |
| 资产来源 | 从 `assets/`**warrior + 另 2 种骨架** 的 gr2/dds 到 `project/assets/`,散文件 | 不接 eterpackPhase 2 / M4 |
| 真值源 | `xrender-poc/oracle` golden + bgfx demo 同机位 / pose / t 截图 | 复用现成对拍基建 |
| 仓库 | 新建 `mtgodot-poc/``libgr2` / `formats` submodule 指向 `xrender-poc` | 隔离,不污染 bgfx route |
---
## 04 · 里程碑
### M0' · 脚手架(macOS12 天)
**目标**GDExtension 骨架 + `libgr2` 集成 + Godot 工程能跑,仅 macOS。
| 任务 | 完成判据 |
|---|---|
| T0.1 `godot-cpp` 4.7 GDExtension 骨架,注册空节点 `Metin2Model`(继承 `Node3D` | Godot 4.7.1 编辑器里能添加该节点、无报错 |
| T0.2 构建集成 `libgr2` 静态库(CMake 或 SCons),仅 macOS arm64 | `libgr2` 符号进 extension`.dylib` 加载成功 |
| T0.3 Godot 工程骨架:`main.tscn`orbit 相机 + `DirectionalLight3D` + `WorldEnvironment` + 空 `Metin2Model`),`main.gd`(orbit 控制 + 截图键) | 编辑器运行出带 sky 的场景 + 一个 cube,鼠标可轨道 |
| T0.4 `project/assets/.gdignore``export_presets.cfg` 加 macOS preset | 导出一次 macOS `.app`,空场景能跑 |
**门禁 M0'**macOS 上跑起「Godot 场景 + sky + cube + orbit 相机」,导出的 `.app` 也能跑。
---
### M1 · 静态渲染(11.5 周)
**目标**warrior + 另 2 种骨架的 bind pose + 贴图在 Godot 里正确显示,多部件组装。
对标 [`reference/steps/M1-static-render.md`](./reference/steps/M1-static-render.md) T2T5。
| 任务 | 完成判据 |
|---|---|
| T1.1 `gr2_bridge``libgr2::Skeleton``Skeleton3D`bone 名 / parent / rest `Transform3D`);basis + 单位缩放在此处理,约定写进注释 | 骨架层级树与 `gr2dump --members` 一致;三种骨架的骨骼数都正确 |
| T1.2 `libgr2::Mesh``ArrayMesh``ARRAY_VERTEX/NORMAL/TANGENT/TEX_UV/BONES/WEIGHTS`+ `Skin`bind 矩阵);处理 >4 权重 | 白模轮廓 = Blender 里同模型;无破面;三种骨架都过 |
| T1.3 贴图路径决策(`.dds` 原生导入 vs 运行时 `dds_loader`),实现选定方案;warrior 贴图(实测 512×512 DXT3 5mip | UV 无错位 / 无镜像;决策写进 `extension/README.md`**该文件未产出**,2026-09-08 复核;决策实际记录在 `MIDREVIEW.md` §3「贴图」与 `extension/src/dxt.cpp` 注释里) |
| T1.4 材质:`StandardMaterial3D`albedo 贴图 + unshaded 先看轮廓),可切白模 | 贴图目视正确 |
| T1.5 多部件(body / weapon / hair)组装,武器挂 `BoneAttachment3D` | 部件数正确、武器跟手骨 |
| T1.6 `test/compare.py`:与 bgfx demo 同机位截图 | 几何一致(允许色彩空间 / 光照差异) |
**门禁 M1**warrior + 2 种骨架 bind pose 下与 bgfx demo 同机位截图**几何像素级接近**;顶点数、部件数、骨骼数与 oracle dump 一致。
---
### M2 · 骨骼动画(1.52 周)
**目标**:从 GDExtension 每帧驱动 `Skeleton3D` 播动画,含跨文件 retarget 与 motion event。
对标 [`reference/steps/M2-anim-skinning.md`](./reference/steps/M2-anim-skinning.md)。
| 任务 | 完成判据 |
|---|---|
| T2.1 `Metin2AnimPlayer``_process``libgr2::sample_pose(t)` → 写 `Skeleton3D` 每骨 local `Transform3D` | 单个动画连续播放、不抖 |
| T2.2 跨文件 retarget:骨架 gr2 + 独立动画 gr2 / `.msa` 按骨名对齐(必要时 `RetargetModifier3D` / 骨名映射表) | warrior 骨架播 `dance_1`(独立文件)姿势正确 |
| T2.3 motion event`.msa`/`.msm` 里的帧事件(放音 / 特效锚点 / 震屏)解析并派发(Phase 1 只需回调打点,不接实际特效) | 事件在正确帧触发,日志可见 |
| T2.4 `main.gd`:idle + 一个动作,按键 / HUD 按钮切换 | 切换无爆姿势 |
| T2.5 逐帧对拍:同 `t``Skeleton3D` 世界骨骼矩阵 vs bgfx demo / oracle | 差值在坐标系 / 单位换算误差内(沿用 M2 的 ~6.5e-5 量级口径);全程无 NaN |
| T2.6 全量采样自检:181 个 warrior 动画逐个 load + sample | 0 崩溃、0 NaN |
| T2.7 留档:50 角色同播动画的帧时间 / `_process` 耗时采样(**不作门禁**,给 Phase 2 A/B 决策用) | 数据写进 `test/godot-macos-stress.json` |
**门禁 M2**:181 动画全部加载采样无 NaN;选定动作与 bgfx demo 同 `t` 视觉一致、数值在换算误差内;motion event 触发帧正确。
---
### M2.5 · 材质与观感保真(11.5 周)★ Phase 1 的核心门禁
**目标**:把 Metin2 的固定管线材质在 `ShaderMaterial` 里近似出来,让 Mac 版渲染"看起来做好了"。
| 任务 | 完成判据 |
|---|---|
| T2.5.1 `m2_material`Metin2 材质描述(texture-stage op / arg / alpha op)→ 自定义 `ShaderMaterial`Godot shading language`light()` 自定义) | 单 stage `MODULATE(TEXTURE, DIFFUSE)` = `tex * vertex_color` 与 bgfx demo `texture_stage.sh` 输出一致 |
| T2.5.2 多 stage 近似:`MODULATE2X` / `ADD` / `BLENDDIFFUSEALPHA` 等常见组合 | warrior 全套贴图正确显示,与原客户端目视接近 |
| T2.5.3 alpha test`D3DCMP_*`+ alpha blend + additive(装备发光) | 半透明 / 发光部件正确,排序无明显错误 |
| T2.5.4 顶点色 + fog(线性 / exp2 | 与 bgfx demo 对拍一致 |
| T2.5.5 接 Godot `DirectionalLight3D` 阴影 + `WorldEnvironment`procedural sky + ambient | 角色有投影、受环境光;整体画面完整 |
| T2.5.6 差距清单:列出无法 1:1 还原的项(排序 corner case、特定 blend op、mip / 各向异性差异) | 写进中期评审报告 |
**门禁 M2.5**:warrior 完整着装 + 动画,在 Godot 里与原客户端 / bgfx demo **目视接近**(结构化对比 + 直方图,不做像素比);差距清单里没有"方案性死路"项。
---
### M3 ·(Phase 2,本轮不做)Android + iOS bring-up + 性能
一加 13Vulkan+ iPhone 16Metal)真机跑起来、三端一致、性能留档、生命周期。iOS 需把 GDExtension 按 arm64 **静态库**编出来(链进 app 二进制)。不涉及 Compatibility 渲染器。详见后续 `steps/M3-mobile.md`(届时再写)。
### M4 ·(Phase 2 可选拉伸)
接真实 eterpack、试水一个 `.mse` 特效、多 texture-stage 精确对拍。
---
## 05 · 工作量(Phase 1macOS
| 里程碑 | 单人 | 2 人并行 |
|---|---|---|
| M0' | 12 天 | 1 天 |
| M1 | 11.5 周 | 1 周(网格 / 骨架分工) |
| M2 | 1.52 周 | 11.5 周(动画 / 对拍分工) |
| M2.5 | 11.5 周 | 1 周(shader / 材质映射分工) |
| **Phase 1 合计** | **≈ 3.55 周** | **≈ 2.53.5 周** |
前置依赖:`xrender-poc``libgr2`(已完成)、`oracle` goldenM2 对拍要)、bgfx demoM1/M2/M2.5 截图对拍要,已可跑)、Godot 4.7.1 export templates(需在编辑器内下载)。
---
## 06 · 风险登记册
### Phase 1macOS
| 风险 | 触发信号 | 缓解 / 退路 |
|---|---|---|
| `godot-cpp` + `libgr2` 构建集成摩擦 | M0' 链接 / 加载失败 | 只出 macOS arm64,摩擦面最小;CMake 路径优先,SCons 备选 |
| `.gr2` 被 Godot import 流程扫到报错 | 打开工程时一堆 import 错误 | `project/assets/.gdignore``.gr2` 走运行时绝对路径 / `user://`T1 前定死 |
| gr2 basis / 单位换算到 Godot(左手 → 右手 Y-up + 缩放) | M1 模型躺 / 镜像 / 骨骼反向 / 大小离谱 | `gr2_bridge` 一次性处理,定 `M2_TO_GODOT`,用朝向明确资产手校,写注释 |
| `Skeleton3D` 骨骼数上限 / 8 权重路径行为 | M1 T1.2 蒙皮爆开 / 部位错位 | 三种骨架都查 `gr2dump`>4 权重走 8-bone flag |
| **多 stage 固定管线材质无法在 `ShaderMaterial` 近似到可接受** | M2.5 T2.5.2/3 关键装备观感明显错 | **这是 Phase 1 主要方案性风险**;差距清单区分"可延期"vs"死路"。中期评审已判定为可延期(观感打磨,见 `MIDREVIEW.md`);bgfx route 已停,无「转回」分支 |
| 半透明 / additive 排序 corner case | M2.5 T2.5.3 发光部件穿插 | 记入差距清单;Godot 的 `render_priority` / `depth_draw` 调,不追求 100% |
| motion event 语义(`.msa`/`.msm` 帧事件类型) | M2 T2.3 事件类型没覆盖全 | Phase 1 只需打点回调,实际特效 Phase 2;未覆盖类型列清单 |
### Phase 2(移动端,本轮不激活,仅登记)
| 风险 | 缓解 / 退路 |
|---|---|
| 每帧 CPU LBS 全量重建 mesh 的成本(多角色) | `MTGODOT_GPUSKIN=1` 自写蒙皮着色器(已实现);多线程构建 + eterpack 共享资产 |
| 自写蒙皮着色器(`texelFetch` + RGBAF + `BONE_INDICES`)在 iPhone 16 Metal / 一加 13 Vulkan 上的行为 | Phase 2 各跑一次;出问题回落 CPU LBS 或改 uniform 数组传骨骼。三台都是现代设备,无 GLES3 约束 |
| Godot 节点开销(大场景规模) | `MultiMesh` / `RenderingServer` 直调兜底 |
| GDExtension ABI 绑 Godot minor | 锁 4.7.1,升级作独立任务 |
| iOSGDExtension 须静态链接进 app 二进制(`libgr2` 出 iOS arm64 `.a`),签名 / 模板脾气大 | 自用免费 provisioning 够用;预留 23 天 bring-up |
---
## 07 · 验证方法论
- **真值源**`xrender-poc/oracle` 的 Granny golden 数据 + `xrender-poc` bgfx demo(同机位 / pose / `t` 截图与数值 dump,Metal 后端在同一台 Mac 上跑)。
- **每个门禁**存 `test/golden/godot-macos-<case>.png` + 骨骼矩阵 / 顶点 dump,跑 `test/compare.py` 与 bgfx route 对拍。
- **资产集**`xrender-poc/test/assets.list` 里的 warrior 相关文件 + 另选 2 种骨架(记入本仓库 `test/assets.list`)。
- **对拍容差**:几何 / 骨骼矩阵按坐标系 + 单位换算误差内严格比;颜色 / 光照因渲染器不同**不做像素比**,只做结构化目视 + 直方图。
---
## 08 · 交付物
### Phase 1(本轮)
| 产物 | 位置 |
|---|---|
| `mtgodot-poc/` 仓库:GDExtension 源 + Godot 4.7.1 demo 工程 | 新仓库 |
| macOS 导出配置 | `project/export_presets.cfg` |
| 与 bgfx demo 的几何 / 动画对拍报告 | ~~`test/godot-snapshot.json`~~ **未产出**2026-09-08 复核)。实际落地的是 `test/compare.py` + `test/compare-report.json`(进程 / 截图 / 结构硬门禁),与 bgfx 的比对停留在 `test/bgfx-reference/` 的人工交叉核对 |
| 材质差距清单 | 中期评审报告内 |
| 50 角色压力采样(供 Phase 2 A/B 决策) | `test/godot-macos-stress.json` |
| **中期评审报告** | 桥接层工作量实测 + 观感对比截图 + 剩余风险 + "是否进入 Phase 2"建议 |
### Phase 2(后续)
| 产物 |
|---|
| iOS `.ipa`iPhone 16+ Android APK(一加 13)+ 两台真机性能 / 生命周期报告 |
| 三设备 bring-up 报告:三端渲染一致性截图 + 各设备帧率留档(自用参考,不做对外选型结论) |
---
## 选型变更记录
- 2026-08-29 · **设备范围定死(自用)**:目标 = MacmacOS / Metal+ 一加 13Android arm64 / Vulkan+ iPhone 16iOS arm64 / Metal),各一台。取消对外 go/no-go 门禁(bgfx route 已停、项目自用);Phase 2 收敛为「三设备各一次 bring-up + 性能留档」。**全程不涉及 GLES3 / Compatibility 渲染器**,不做中低端 / 老机器。SHINSOO 的 W0→W1+ 因此无外部门禁。
- 2026-08-29 · 范围收敛为 **Phase 1 = macOS only**;新增 **M2.5 材质与观感保真** 作为 Phase 1 核心门禁;动画 A/B 决策推迟到 Phase 2Godot 版本钉死 4.7.1 / godot-cpp 4.7。
- 2026-08-29 · **M1 T1.3**:贴图走 **运行时 `dds_loader`**`src/dxt.cpp`,端口自 xrender-poc)。理由:Godot 4 的 `Image.load()` 不支持 `.dds`,且资产从 `res://` 外的绝对路径加载(`.gr2``.gdignore` 同理)。DXT1/3/5 + BGRA8level 0 + `generate_mipmaps()`
- 2026-08-29 · **动画方案**B 方案(每帧 `gr2::sample_pose` 驱动)。**两条蒙皮路径**,都用 libgr2 的 `skin` 矩阵完整仿射(shear 保留):默认 **CPU LBS**`metin2_anim.cpp``Metin2Model::cpu_skin`,每帧重建 `ArrayMesh`);`MTGODOT_GPUSKIN=1`**自写蒙皮顶点着色器**`m2_material` `SRC_SKIN`,逐骨 4×3 矩阵存 RGBAF 纹理,`Metin2Model::gpu_skin` 每帧更新,绕过 `Skeleton3D`)。原因:Godot 内置 GPU 蒙皮 / `set_bone_pose` 会正交化骨骼矩阵、**丢掉 Granny 烘在 rig 骨(`bone_front_*`/`Bone_shoulder_*`warrior 7 / sura 16)上的 shear**`tools/anim_probe` 定位,非 libgr2 —— `world[]` 对拍 Granny ≤4.1e-5)。旧 `Skeleton3D::set_bone_pose` 路径已删。A 方案(烘 `Animation`)走 `Skeleton3D` 骨骼、同样丢 shear,不用。
- 2026-08-29 · **坐标/单位**`gr2_to_godot()` = gr2 行主序 Mat4 的 4×4 转置 → Godot `Transform3D`Z-up cm → Y-up m`rotate(-90°,X)` + `scale(0.01)`)放在 `Metin2Model` 节点自身 transform`make_conv`)。warrior/warrior_lord/assassin/shaman_lord 实测 `flip_z=false``flip_winding=false``unit_scale=0.01` 正立、朝 +Z、无镜像。
### M2.5 T2.5.6 — 材质无法 1:1 还原 / 待办项清单
> **不再单独维护** —— 全部并入 [`BACKLOG.md`](./BACKLOG.md) B 段(`B1``B11`)。下表为历史快照。
| 项 | 现状 | 影响 | 出路 |
|---|---|---|---|
| **libgr2 POD API 不暴露材质数据** —— 只有 `material_count`/`texture_count`,无材质名、无贴图文件名、无 texture-stage op、无 `D3DBLEND_*`/alpha-test 标志 | 贴图靠**文件名约定 + 目录大小写不敏感扫描**(`_resolve_texture`);blend 靠**表面名关键字启发式**(`guess_blend`hair→alpha-test、cape/skirt→alpha、effect/glow→add,其余 opaque | warrior 5/5、assassin 17/17、shaman 1/1 表面命中贴图,但**多材质槽 → 具体贴图/混合的精确映射做不到**;换皮/多贴图变体、精确 alpha-test 阈值、MODULATE2X/ADDSIGNED 等 stage op 无法复现 | **需要给 libgr2 加材质 API**(新文件 `gr2_material.cpp`,走类型树取 `Materials[].Name` + `Maps[].Texture.FromFileName` + shader/blend hint)。这也正是 xrender-poc M1 T5「多 stage 材质对拍待 oracle」的同一缺口,两条 route 共用修法。 |
| **顶点色** | `gr2::Vertex` POD 无 color 字段;shader 已留 `COLOR` 通道,默认白 | Metin2 PC 模型基本不用顶点色,影响小;特效/地形会用 | libgr2 若暴露则 `build_mesh``ARRAY_COLOR` |
| ~~`TriGroup.material_index` 未接线~~ **已做(2026-08-29** | `gr2_bridge::build_parts()` 把一个 gr2 mesh 按 `tri_groups` 拆成 N 个 `RenderPart` → N 个 Godot surface,贴图取 `Mesh::material_textures[matidx]`mesh-local,非全局 `Materials[]`)。GPU 和 CPU 蒙皮路径都走 `RenderPart`。 | — |
| **ShaderMaterial 静态 `Shader` 退出泄漏** | `shader_for()` 用 function-static `Ref<Shader>`Godot 退出时报 "1 shader never freed" | 仅退出时一条 warning,无功能影响 | 改成每材质自持 Shader,或注册 cleanup |
| **alpha 排序 corner case** | 半透明/additive 用 `render_priority` 粗排,未做逐三角/OIT | 多层半透明部件可能穿插 | 记录,不追求 100%Phase 2 M2.5 打磨) |
| **法线变换** | CPU 与 GPU`SRC_SKIN` 着色器)两条路径都用混合矩阵的**上 3×3、不逆转置** —— 与 Granny runtime 一致(见 `docs/reference/steps/M2-anim-skinning.md`)。非均匀缩放骨骼下这是 Granny 自己的近似,我们与之一致。 | 已一致 | — |