按 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
28 KiB
渲染一致性差距清单 —— 距 ingame-shinsoo.png 还差什么
更新:2026-08-29 · 配套
SHINSOO-WORLD-RENDERING.md(实施规格)、BACKLOG.md。本文回答一个问题:现在
test/golden/world-a1-{persp,r2}.png到「与游戏内截图目视等价」之间, 还有哪些工作。不是 SHINSOO 的重复 —— SHINSOO 是「怎么把世界渲染出来」,本文是「怎么把观感做到 参考图那样」。当前已落地 W0–W6 的可运行纵切,以及 W7/W8 的部分首版;必要.mse、真实 UI、 完整流式性能路径和移动端验证仍未完成。本文同时列出正确性缺口与保真化缺口。
0. 「一致」的定义与现状
目标:在同一全局坐标 + 同一 yaw/pitch/FOV + 同一时间/天气下,Godot 输出与原 D3D 客户端截图, 经 ROI 直方图 + 边缘结构(SSIM)+ 人工签核判为「同一场景、同一观感」。不要求逐像素(渲染器不同)。
现状(world-a1-persp.png / world-a1-r2.png):
- ✅ 地貌 / 道路网 / 建筑布局 / 水域形状 = 认得出是神兽国 A1
- ✅ 第三人称角色(带发型)在地形上跑、HUD 布局在位、
.msenv驱动的天空/光/雾 - ❌ 观感不等价:树仍是 proxy(虽已用真实 bark/composite atlas)、无
.mse特效、水面已用真 30 帧序列但深度系数未标定、HUD 已用真实九宫格贴图但布局未按受控帧对齐、光照/色调未对受控帧校准、机位随手放、角色材质 sphere-map 高光已由装备层驱动(equip_model.gd:283-288用item_proto specular/100)但只覆盖身体/护甲一个部位、power 未按受控帧标定
粗估:把下面全部做到「目视等价」,单人 ≈ 3–6 个月(不含 Phase 2 移动端 bring-up;
需在地形/水面正确性修复和 .spt 转换可行性验证后重估)。.spt 当前缺少可分发的跨平台 reader;
.mse 是 ASCII 文本且原客户端带 parser,不属于不可读资产,但完整 EffectLib 运行时移植仍是大工程。
0.1 ingame-shinsoo.png 受控参考契约
本文的具体目标帧不是泛指「某张神兽国截图」。实施前必须创建
test/maps/ingame-shinsoo.json,把下列状态冻结;无法从截图可靠反推的值一律写成
待采集,不能凭观感猜一个值后继续调材质。
| 域 | 必须冻结的字段 |
|---|---|
| 输出 | 1920×1080、Godot 版本、renderer、MSAA/TAA、色彩空间、随机种子 |
| 地图 | map id、全局坐标、加载半径、可见区块 |
| 相机 | yaw、pitch、roll、与角色距离/高度、FOV、near/far |
| 角色 | race、sex、body/hair、武器/装备、动作名、动画时间/帧 |
| NPC | race、装备、全局坐标、动作帧、名字/标签状态 |
| 环境 | .msenv、时间/天气、太阳方向、雾、曝光、后处理参数 |
| UI | locale=es、背包打开、圆形小地图、聊天/状态栏/快捷栏以及顶部三行消息内容 |
门禁:上表未落盘前,只允许修格式/管线正确性问题,不接受「为匹配这张图」进行的相机、光照、
色调或材质调参。参考图本身应进入仓库为 test/reference/ingame-shinsoo.png,避免依赖个人目录路径。
0.2 单帧范围与完成定义
对这张目标图,阻塞交付的可见项按顺序是:固定机位 → 地形全部 splat/UV → 建筑与材质 →
非占位树 → 玩家/NPC/装备/阴影 → 光照与色调 → 真实 HUD。水体、完整 .mse、移动端流式和远离
机位的地图内容,只有在目标帧或其验收 ROI 内可见时才阻塞该单帧;被排除项必须在 preset 中记录
原因和对应 ROI,不能把「当前看不到」写成「已经支持」。
单帧完成同时要求:
- 世界层能辨认为同一位置、同一构图,主要建筑、道路、树冠和角色轮廓对齐;
- UI 层在 1920×1080 下使用真实贴图/字体,窗口边界、锚点、层级与参考图一致;
- 通过 §9 的世界 ROI、UI 几何门禁与人工签核;不同渲染器导致的逐像素差异不作为失败条件。
0.3 精确资产清单
另建 test/maps/ingame-shinsoo-assets.json,逐个登记目标帧里实际可见的资源,而不是只写资源类别:
| 可见对象 | 清单必须记录 |
|---|---|
| 地形 | map/区块、height.raw、tile.raw、TextureSet 图层及 shadow/minimap |
| 建筑/道具 | property CRC、buildingfile、实例变换、GR2、每个材质贴图 |
| 树 | property CRC、treefile、实例变换、最终使用的 mesh/LOD/impostor 与树皮/叶片贴图 |
| 玩家/NPC | MSM/MSA/GR2、body/hair、武器装备、动作和实例位置 |
| UI | 每个窗口的脚本/贴图/locale 图、字体、图标和九宫格来源 |
| 环境 | .msenv、天空/云/flare 贴图以及所有颜色、雾、曝光输入 |
每条资源保存原虚拟路径、AssetResolver 最终命中的磁盘路径、pack 优先级和内容 hash。这样 assets
目录内容变化后,golden 偏差能够定位到具体资源,而不是重新肉眼猜测。
1. 特殊资产与专项管线
| # | 项 | 为什么卡 | 实施路径 | 规模 |
|---|---|---|---|---|
| 1.1 | .spt(SpeedTree 树) |
几何解析在闭源 CSpeedTreeRT::LoadTree();仓库静态库实测为带符号的 COFF x86-64,不能链进 macOS/移动端且分发需确认授权。formats/spt 已能嗅探 bark/composite atlas,真实资产共 118 个 |
(a) 先用随库 SpeedTreeRT.h + .lib 验证经授权的 Windows x64 离线 exporter,调用 LoadTree/Compute/GetGeometry 转 .glb;(b) 当前运行时 proxy 使用真实 bark/composite DDS;(c) exporter 不可用时继续用授权清晰的替代树 |
L(exporter spike 约 1 周;成功后转换与校形 2–3 周) |
| 1.2 | .mse(特效 / 粒子) 🟡 首版打通(见 CLIENT-ROADMAP.md P5) |
资产是 ASCII Group/List 文本;EffectLib/EffectData.cpp、ParticleSystemData.cpp、EffectMesh.cpp、SimpleLightData.cpp 已包含 parser 和运行时真值 |
✅ project/fx/:mse.gd 文本树解析(真文件 6 particle)· effect_player.gd Particle→GPUParticles3D(发射形状/曲线/颜色渐变/billboard/加法混合)· effect_registry.gd 名字→.mse+缓存 · wire.h GC_SPECIAL/SPECIFIC_EFFECT→M2Client.effect_cue。⬜ .dds 粒子纹理、.mde mesh 解码、SimpleLight(本资产集未用)、绑骨骼挂点、时间轴位置动画 |
L–XL(parser 端口 + 运行时/材质/挂点,独立子项目) |
| 1.3 | lens flare | .msenv 有 LensFlare 块(已解析),但需屏幕空间实现 |
太阳屏幕投影 + flare billboard;遮挡先用相机到太阳方向的物理射线,或在后处理里读深度。若使用低层 occlusion query,必须基于锁定的 Godot 4.7 API 实证,不使用不存在的 get_1d_probe |
S–M |
1.4 树木方案门禁
A1 已确认 368 棵 / 14 树种,全资产树库共 118 个 .spt;现有 R2 proxy 已按 treefile 分组为
MultiMeshInstance3D,从 .spt 嗅探真实树皮/composite atlas,并生成确定性枝干与多组交叉叶簇。
它仍没有真实 SpeedTree 几何、leaf table、LOD/impostor。批量开发前先从最终机位按屏幕投影面积选 3 个代表树种,
分别验证以下来源:
- 具备授权且能实际读取这些旧
.spt的 SDK/离线 converter; - 保留原 treefile → 替代 mesh 映射的人工/半自动重建;
- 改良程序化树,仅作为 converter 和替代 mesh 都不可行时的降级路径。
每条路径都必须产出同一份 TreeDescriptor(树干/叶片材质、LOD0/1、远景 impostor、包围盒、风参数),
并以树冠轮廓、树干比例、叶片 alpha、环境着色、阴影、LOD 跳变、批次数和授权可分发性签核。
未通过单树 spike 前不批量处理 118 个 .spt,运行时也不得直接依赖 Windows x64 专有 .lib。
实例语义修正(已落地):旧 Metin2World 会给每棵占位树额外生成随机 yaw 和 scale jitter;原客户端
CArea::TObjectInstance::SetTree → CSpeedTreeForest::CreateInstance 在这条地图放置路径上只传
position、property CRC 和 treefile,实例本身只调用 SetPosition,LoadTree 也没有从这里收到
TreeSize/TreeVariance。当前实现已移除这些随机变换;若替代树为避免重复确实需要
随机旋转,应作为明确的 fallback/增强开关,而不能污染基线。
2. 角色 / 模型保真
| # | 项 | 现状 | 目标 | 工作 | 规模 | BACKLOG |
|---|---|---|---|---|---|---|
| 2.1 | 武器 mesh | 🟡 首版已做 + 2026-08-31 复核:Metin2Model.weapon_gr2 / weapon_bone(默认 equip_right_hand,warrior bone[10])。加载武器 gr2 → build_mesh(rigid)→ 子 MeshInstance3D;每帧 update_weapon_pose(world_pose[bone])。隔离探针实测:warrior_novice/warrior_cheongrin × 8 个武器文件,weapon MI 全局原点 == equip_right_hand 骨骼全局原点,Δ = 0.00–0.06 m;world_demo 目视剑握在手里。之前记忆里「武器偏 ~10 m」是 cpu_skin >= / MapCoord 修复前的旧状态,已失效。新增 grip pre-transform(weapon_pre = mul4x3(weaponInvWorld[0], weaponLocal[0]),对齐客户端 weaponComposite[0] = ModelInstanceUpdate.cpp:171),update_weapon_pose 里 mul4x3(weapon_pre, world_pose[hand]);标准武器多校准 ~4–6 cm 握点。个别非标准文件(02010/03010/03150)骨骼带米级偏移 → 30 cm 夹断退回原始手部位姿(不回退)。mul4x3 提到 gr2_bridge.h 与 anim 共用 |
右手单持跟手 1:1;03150 类网格在文件里就离骨骼原点 ~3 m(资产缺陷,非变换问题)—— 需 per-weapon 偏移数据或修 .gr2。⬜ 左手/双持/盾、_lod 变体 |
M | C5 / D2 | |
| 2.2 | GPU 蒙皮路径的发型 | ✅ 已做:_build_gpu_mesh() —— enable_gpu_skin 带发型时,把发型 mesh 以 bind pose 并入 GPU mesh,每个 ARRAY_BONES 值经 hair_bone_remap(发型骨 idx → base 骨 idx)重映射。无需改 shader / 第二骨索引区(warrior 74/74 全匹配)—— SRC_SKIN + 共享 bones_tex 直接形变。_apply_materials 给追加的发型 surface 上「skinned + alpha-test + hair 贴图」材质。实测 5 base + 1 hair surface,红发(TargetSkin)贴头、镂空干净、跟动画 |
M(完成) | A1 | ||
| 2.3 | 换装 / 时装 / 翅膀 | 无 | 多 MeshInstance3D 绑同一骨架 + 部位遮挡 / 隐藏规则 |
.msm + 物品系统的上层 |
L | D1 / D4 |
| 2.4 | 贴图换色 SourceSkin→TargetSkin |
✅ 首版已做:Metin2Model.hair_skin —— 设了就用该 dds 当发型 albedo(代替 gr2 sibling 的 SourceSkin),对齐客户端 SetMaterialImagePointer(part, SourceSkin, load(TargetSkin))(ActorInstanceData.cpp:152/225)。get_hair_options() 现返回解析成绝对路径的 source_skin / target_skin。formats.msm_hair CTest 钉了 HairData 解析(同 gr2+SourceSkin、不同 TargetSkin)。demo 用红色变体 |
⬜ 非发型部位(护甲/时装)的 skin 表;warrior_cheongrin 头盔挡住发型,换色需换模型目视 |
S(首版完成) | D3 | |
| 2.5 | 固定功能材质状态覆盖 | 单 stage MODULATE(TEX,DIFFUSE) 近似;sphere-map 分支已按源码端口(见 §2.7)。blend / two-sided 改读 gr2::MaterialInfo:metin2_model::decide_blend() 优先 alpha_blend(dump_materials 端口 Material.cpp:233 = Name Blend* + MapCount>1),decide_two_sided() 读 two_sided(+ hair/cloak 名兜底),m2_material 加 cull_disabled shader 变体。仅 hair 镂空 / effect 加法这两类 GR2 没编码的仍按表面名 |
复现最终机位实际触发的 alpha-test ref、多贴图组合 | 逐分支盘点 EterGrnLib/Material.cpp;GR2 不承载任意 D3D stage 状态 |
M | B2/B4 |
| 2.6 | 顶点色 | ✅ 排查完毕:不适用。全资产 .gr2(PC / monster2 / effect)零 DiffuseColor/Color0 顶点成员(grep 整个 assets 目录 = 0)。Metin2 的「逐顶点颜色」实际是:① 地形雾 —— MapOutdoorRenderSTP 运行时动态 VB 里 dwDiffuse/dwSpecular 存雾色/雾系数(非资产,已由 Godot Environment 雾覆盖);② .mse 特效 colorfactor / 粒子 TimeEventColor(属 §1.2 .mse 端口)。libgr2 无需改,build_mesh 无需补 ARRAY_COLOR |
— | B7 | ||
| 2.7 | 镜面 / 球面反射(护甲金属光) | 🟡 管线已端口:SRC_MIX / SRC_SKIN 加 spec_map/spec_power/spec_enable。按 EterGrnLib/Material.cpp:305 __ApplySpecularRenderState 复现:仅不透明面(客户端 alpha-blend 时回退纯 diffuse)、相机空间 reflect(VERTEX,NORMAL).xy 查 sphere map、MODULATEALPHA_ADDCOLOR = EMISSION += sphere.rgb * tex.a * power(tex.a = 护甲高光遮罩,power = D3DRS_TEXTUREFACTOR.a)。sphere map = 全局 ETC/ymir work/special/spheremap.jpg(客户端 index 0),Metin2Model::_load_sphere_map() 沿模型目录上溯解析。Metin2Model.specular_power 属性。默认 0 = 完全休眠,与之前逐字节一致 |
端口 __ApplySpecularRenderState(✅);接装备层用 item_proto bSpecular/100 驱动(✅ 身体/护甲:project/ui/equip_model.gd:283-288 _apply_body();⬜ 武器 / 发型 / 时装等其余部位未接);受控帧标定 power(⬜,§0 门禁) |
M | B9 | |
| 2.8 | 角色专属光(DirectionalLight.Character) |
只用 Background 光 |
角色颜色与原客户端 Character light 接近 | 给 SRC_MIX/SRC_SKIN 加 character-light uniform,或用 light cull_mask 分层 |
S–M | E2 |
| 2.9 | 动画混合 / crossfade | ✅ 首版已做:Metin2AnimPlayer.blend_time(默认 0.15s)。切 anim_path 时保留旧 clip 冻结在当前时间,每帧对旧/新 sample_pose 的 world_pose 逐骨 slerp(rot)+lerp(pos/scale)(smoothstep 权重),再 skin = invWorld · world_blended 重建;武器挂点跟混合后的手。shear 骨在 ≤blend_time 过渡内退化为无 shear(文档许可)。目视 wait→run 连续无 T-pose 闪 |
⬜ 受控帧核对过渡时长 | M(首版完成) | C2 | |
| 2.10 | LOD | ✅ 首版已做:Metin2Model reload 时自动加载 <base>_lod_01/02/03.gr2(同 75 骨骼、抽面 mesh),_process 按相机距离切换渲染 mesh(lod_distances 属性,默认 [18,42,90]m)。切换时重建 cpu_mesh + 重解材质 + 重蒙皮;骨架共享故蒙皮不受影响。烟测:d=12→L1 d=21→L2 d=30→L3(wait.msa 播放中不崩),4 级渲染均干净。⬜ 淡入淡出过渡、GPU 蒙皮路径的 LOD |
M(首版完成) | C9 | ||
| 2.11 | ypr_basis 符号终核 |
✅ 发现并修复真实 bug:物体旋转 yaw#pitch#roll 之前未做 Z-up→Y-up 轴共轭就塞进 Godot Basis,导致 66% 的 A1 物体(roll≠0 = 朝向)绕 Godot Z 翻滚而非绕 Y 转向。新增 m2_coord::object_basis_godot() = ypr_basis() 共轭 rotate(-90°,X);metin2_world 放置改用它。已核对 ypr_basis 本身忠实移植客户端 D3DXMatrixRotationYawPitchRoll(Area.cpp:520/619 + GrpObjectInstance.cpp:165;单值旋转=roll=朝向,Area.cpp:820)。formats.map_formats 加断言(identity / roll=90→绕 Godot+Y / 正交 det=+1)。目视:A1 建筑竖直、门楼与桥沿路对齐 |
⬜ 对原客户端截图的逐建筑朝向复核(§0 受控帧) | S(首版完成) | — |
3. 地形 / splat 保真
| # | 项 | 现状 | 目标 | 工作 | 规模 |
|---|---|---|---|---|---|
| 3.1 | 活动图层上限 | ✅ 首版已修:shader 支持 16 层(4 张 RGBA8 权重贴图),min(8) → min(16);A1 单区块最多 12 层(000004/003004)不再静默丢 |
每区块全部活动图层按原顺序混合 | ⬜ 按 patch 分批省带宽;真实 A1 层数断言 | M(P1,首版完成) |
| 3.2 | 每图层平铺密度 | ✅ 首版已修:layer_uv = (8·UScale, -8·VScale, UOffset, -VOffset),来自 TextureSet.cpp:185 + TexCoordBase = 1/(PATCH_XSIZE·CELLSCALE) = 1/3200。⬜ 对 minimap.dds / 受控参考帧的视觉验收 |
精确移植原客户端 UV 变换 | — | S–M(P1,首版完成) |
| 3.3 | shadowmap.dds 调制 |
🟡 首版已分层:树 MultiMeshInstance3D + shadowflag≠1 的建筑/地牢块 → SHADOW_CASTING_SETTING_OFF。shadowflag=1 建筑 + 玩家/NPC → 实时投影。terrain shader 仍 col *= shadowmap.rgb。+ 角色接触阴影:project/ui/char_shadow.gd(CharShadow.attach)—— player_view/mob_view 建完模型后强制所有 MeshInstance3D cast_shadow=ON(防被 world 的 OFF 波及)+ 脚下一张径向渐变 Decal 投影到地形/物体(对齐 GameLib/MapOutdoorCharacterShadow.cpp 的结果,不复刻 RT)。太阳接近正午实时投影很短时也有接触暗影。eterngrn_polish_test.gd 覆盖 |
⬜ baked/realtime 精确不叠加的 mask、Decal 参数受控帧标定 | M(首版完成) | |
| 3.4 | 近景清晰度 | ✅ 首版已修:删掉程序化 macro_detail;Texture2DArray 尺寸取「用到的图层里最大源边长(≤1024)」,只放大不硬降 512² 地表贴图 |
严格 parity 基线不叠加现代化增强;确认需要后再加可开关 detail map | — | S–M(首版完成) |
| 3.5 | 地形 LOD / patch 裁剪 | 🟡 首版已做:Metin2World.terrain_patches(默认 4)—— 每区块地形拆成 N×N 个 MeshInstance3D patch(16 个,各 32×32 quad),共享 splat 材质。逐 patch 视锥剔除(Godot 自动,主要增益)+ visibility_range_end(默认 3500m 硬剔)远景整片剔除。顶点/法线/UV 从整块 TerrainMesh 拷贝 → 无裂缝无接缝(顶视 + 斜视验证)。terrain_patches=1 = 旧单 mesh |
⬜ 逐 patch 几何 LOD(抽稀索引集 + skirt 防裂);碰撞体也按 patch | M(首版完成) | |
| 3.6 | 草(instanced grass) | 无;当前参考截图不能明确证明存在独立草片几何 | 只有原客户端源码、属性或受控近景帧确认后,才在对应区域撒 MultiMesh 草片 + 风摆 |
先取证,避免把现代化增强混入 parity 基线 | 可选 M |
| 3.7 | 相邻区块共享边断言 | ✅ 已加 formats.map_formats seam 断言:000000 col-128 高度 == 001000 col-0(边界样本)+ 生成顶点位置一致,实测无接缝 |
— | — | S(完成) |
4. 水面
| # | 项 | 现状 | 目标 | 工作 | 规模 |
|---|---|---|---|---|---|
| 4.1 | 原客户端水纹序列 | ✅ 首版已做:special/water/01..30.dds → Texture2DArray,shader frame = (TIME·1000/70)%30(MapOutdoorWater.cpp:43 实测 70ms/帧),UV 平铺 = 1/8m(m_fWaterTexCoordBase = 1/(CELLSCALE·4))。⬜ wrap/filter 细调、原客户端叠加 blend 状态 |
— | S–M(P1,首版完成) | |
| 4.2 | 水深透明与高度动画 | ✅ 首版已做:逐顶点 COLOR.a 从 waterHeight - terrain_height_at() 算(60cm 深→近不透明),vertex shader 轻微高度浮动(近似 MapOutdoorWater 0..-15cm 的 0.6Hz 摆动)。⬜ 深度衰减系数按受控参考帧标定(现 A1 浅水偏透明) |
— | M(P1,首版完成) | |
| 4.3 | 反射 | 无真实反射 | 若受控参考帧证明必要,使用镜像相机 + SubViewport 做平面反射,或以 SSR 近似 |
ReflectionProbe 是 cubemap,不作为平面反射实现 |
可选 M |
| 4.4 | 折射 | 无 | Godot 4 sampler hint_screen_texture + SCREEN_UV 扰动,结合 depth sampler 衰减 |
属于增强项;原客户端水面 parity 完成后再做 | 可选 S–M |
| 4.5 | 岸线泡沫 / 法线贴图 | 无 | 仅在参考帧明确需要时增加;不宣称原客户端使用 normal map | 与严格 parity 材质做开关和 golden 对照 | 可选 S |
| 4.6 | 网格合并 | 同水层/区块已经是一个 mesh surface,内部按行生成 span quad | 行列矩形合并只减少顶点/索引;要减少 draw call 需跨区块/水层合批或统一 surface | 分别记录 vertex/index 与 draw-call 指标,避免把几何优化写成 draw-call 优化 | S |
5. 环境 / 光照 / 色调
| # | 项 | 现状 | 目标 | 规模 |
|---|---|---|---|---|
| 5.1 | 云层 | .msenv 有 cloud texture/scale/height/speed(已解析),未渲染 |
天空 shader 加滚动云层 + List CloudColor 着色 |
M |
| 5.2 | 光照对受控参考帧校准 | .msenv → Godot 近似,肉眼调 |
在原 D3D 客户端到已知机位截图 → Godot 复现 → ROI 亮度/饱和/主色迭代到匹配(SHINSOO §11.4) | M(含反复采集) |
| 5.3 | 阴影质量 | 4-split,cascade 500m,bias 调过 | 逐场景标定 cascade split / normal bias / peter-panning;角色脚下清晰接触阴影 | S–M |
| 5.4 | 昼夜 / 天气 | 无 | 若参考图之外还要,.msenv 多套 + 插值 |
M(可选) |
| 5.5 | 色调校准 / LUT | adjustment 饱和/对比 |
先用同机位、同曝光的多组 ROI/校准帧拟合曝光、曲线、饱和度;必要时再固化为 3D LUT。单张场景截图不足以唯一反推出 LUT | S–M |
6. HUD / UI
目标截图的 UI 不是「有相似窗口即可」。建立 test/maps/ingame-shinsoo-ui.json,为圆形小地图、右侧已打开
背包、底部聊天/HP/MP/快捷栏以及顶部 objective/action/绿色提示逐项保存:1920×1080 像素 rect、anchor、
pivot、z-index、九宫格边距、贴图/字体路径、字号、描边、颜色、文本和 visible 状态。布局比对直接读取
该文件,禁止在测试里散落第二套魔法数字。
| # | 项 | 现状 | 目标 | 规模 |
|---|---|---|---|---|
| 6.1 | UI 美术 | 🟡 机制已做:hud.gd 用真实 ETC/ymir work/ui/pattern/ 贴图 —— board/thinboard 九宫格(4 角 + 4 边 + *_base 合成 atlas → NinePatchRect,margin=32/16),HP/MP 用 gauge_slot_{left,center,right} 框 + gauge_red/gauge_blue 填充,快捷栏 8 格 thinboard,小地图套 board 框。贴图按资产根 + 各 pack ymir work/ui/pattern/ 解析(.tga 走 Image.load)。⬜ 精确布局(rect/anchor/字体/描边)待 §0 ingame-shinsoo-ui.json;locale/<lang>/ui/ 本地化图;windows.dds atlas 子图 |
M(机制完成) | |
| 6.2 | 小地图 | minimap.dds 拼图 + 玩家点 |
先保持地图 north-up、按玩家 heading 旋转箭头;若原客户端配置/受控帧证明整图旋转,再实现地图旋转。补缩放、NPC/怪/出口图标和交互 | M |
| 6.3 | 状态栏 / 快捷栏 / 背包 | 静态占位 | 真实布局 + 数值绑定 + 拖拽 + tooltip(不接服务端物品逻辑) | M–L |
| 6.4 | 头顶文字 / 血条 | 无 | Label3D 名字 + 屏幕空间血条跟随 |
S–M |
| 6.5 | 聊天框 / 系统消息 | 无 | RichTextLabel + 频道 tab(参考图里通常有) |
S |
| 6.6 | 1920×1080 布局 | 1152 测试分辨率 | 目标分辨率像素对齐 + 缩放策略 | S |
7. 后处理
| # | 项 | 现状 | 目标 | 规模 |
|---|---|---|---|---|
| 7.1 | Bloom / glow | 🟡 已收敛(intensity 0.35→0.12,hdr 阈值 1.1→1.5,bloom→0;曾让角色发糊)。参考帧未落盘前保守 | 按参考图高光溢出量标定 | S |
| 7.2 | SSAO | 🟡 已收敛(intensity 1.4→0.7)。参考帧未落盘前保守 | 标定强度 / 半径 | S |
| 7.3 | 抗锯齿 | MSAA 4× | + TAA 或 FXAA(移动端权衡) | S |
| 7.4 | 景深 | 无 | 参考图若有远景虚化则加 | S(可选) |
| 7.5 | 暗角 / 色差 | 无 | 参考图若有则轻加,禁过度 | S |
8. 相机 / 构图
| # | 项 | 现状 | 目标 | 规模 |
|---|---|---|---|---|
| 8.1 | 机位匹配 | 🟡 渲染分辨率已提到 1920×1080(project.godot);机位仍是脚本里随手放(fov 60) |
精确复现参考图高度/距离/pitch/FOV,存 test/maps/ 确定性 preset |
S |
| 8.2 | 相机遮挡淡出 | ✅ 首版已做:Metin2World 给每个静态物体加层 2 盒碰撞(mesh AABB → Y-up 米,非缩放 shape,occ_mesh meta 指回 MeshInstance3D;A1 601 个)。game_camera.gd::_fade_occluders 每帧 cam→player 射线(ray exclude 逐个穿,最多 4 层)→ 命中建筑 GeometryInstance3D.transparency = 0.72,不挡时恢复 0。⬜ 树(MultiMesh 无逐实例透明)、渐变淡入淡出 |
M(首版完成) | |
| 8.3 | 相机碰撞 | ✅ 首版已做:game_camera.gd::_desired_pos head→want 射线打层 2,命中建筑就把相机拉到撞点前 0.3m(叠加已有地形采样防穿)。静态物体碰撞体作为 8.2 的副产品一并落地(player_controller 仍用 attr.atr 网格,可后续接物理)。⬜ SpringArm3D 平滑、.mdatr 精确碰撞体 |
M(首版完成) |
9. 验证方法论(做「一致」的前提)
| # | 项 | 说明 | 规模 |
|---|---|---|---|
| 9.1 | 受控参考帧采集 | 在原 D3D 客户端脚本化到同一「已知全局坐标 + yaw/pitch + FOV」连续采两帧:关 HUD 的世界帧与开 HUD 的合成帧;参数存进 test/maps/。原客户端不能脚本化则「同地标 + 人工对齐 + 只比 ROI」 |
M |
| 9.2 | ROI 直方图 + SSIM 门禁 | test/compare.py 扩:世界帧按天空 / 道路 / 草地 / 建筑 / 树 / 角色分区,比均亮 / 饱和 / 主色 / 边缘结构;合成帧另做 UI rect 与文本基线比对 |
M |
| 9.3 | golden 锁定项 | Godot 版本 / renderer / 窗口 / 相机 / 时间 / 动画帧 / 随机种子 / 曝光 / 天气 / 资源优先级 全锁 | S |
| 9.4 | world-a1-r1/r2.png 进 CI |
formats/tests 已进 CTest;golden 视觉门禁待接 |
S |
| 9.5 | 目标帧验收配置 | 在 ingame-shinsoo.json 为天空/草地/道路/建筑/树/角色分别保存 ROI、metric、threshold;UI 窗口 rect/锚点初始容差为 1 px |
S |
| 9.6 | 阈值冻结规则 | 同一机器连续渲染 5 次测噪声底;世界 ROI 阈值必须高于噪声并经一次人工签核后冻结。不得在回归失败时临时放宽阈值 | S |
10. 平台(SHINSOO 的前置,不是观感项但影响「能不能发」)
- Android(一加 13 / Vulkan)+ iOS(iPhone 16 / Metal,静态链接)bring-up:一次都没跑过。见
BACKLOGF1–F7。 - 所有新 shader(
SRC_TERRAIN/SRC_WATER/SRC_LEAF/SRC_SKIN)在 Mobile renderer 下未验。 - 移动端性能预算(流式 +
MTGODOT_GPUSKIN人群)未测真机。
11. 建议推进顺序
第 1 梯队(性价比最高,1–1.5 月)
- 0.1–0.3 目标帧/资产/UI 契约 + 8.1 机位 preset + 9.1/9.5 受控参考与验收配置 —— 没有基准就没法谈「一致」
- 3.1 全活动 splat 图层 + 3.2 原始 UV 公式 —— 先修复会丢内容/尺度错误的地表正确性问题
- 1.4 最终机位可见树种清单 + 单树 spike;先用替代 mesh 达到轮廓,再并行验证授权 converter
- 5.2 光照校准 + 5.5 色调拟合 —— 全画面观感一次性拉近
- ✅ 2.1 武器(右手单持)+ ✅ 2.11 ypr 终核(修了漏轴转换 bug)+ ✅ 2.9 crossfade + ✅ 2.4 换色 —— 角色补齐(首版)
- 🟡 6.1 HUD 真美术 —— 九宫格机制已上,布局待 §0;✅ 3.3 阴影分层首版
第 2 梯队(保真化,1.5–2 月)
7. 1.1 .spt reader/converter 通过授权与技术验证后批量离线转 Mesh,并接入统一 TreeDescriptor
8. 4.1–4.2 原客户端 30 帧水纹 + 深度 alpha;4.3–4.5 只按受控参考帧选择性增强
9. 5.1 云层 + 1.3 lens flare
10. ✅ 2.7 sphere-map 高光(管线已端口 + 装备层已驱动身体部位;逐部位与 power 标定仍缺);⬜ 2.5 其余固定功能材质分支 / 2.8 角色专属光
11. 3.6 instanced grass(仅在取证确认后)
第 3 梯队(大工程 / 可并行 / 部分可选)
12. 1.2 .mse parser/EffectLib 语义到 Godot 的端口 —— 独立子项目,L–XL
13. 2.3 换装系统、6.2–6.5 完整 HUD
14. 10. 移动端 bring-up
贯穿:每个梯队做完过一次 9.2 的 ROI 门禁 + 人工签核,用数据说话,别靠感觉。