Star Conflict 资源提取 — 项目总结 =============================== 解包: scunpack.exe (846 PAK) + tpak_extract.py (844 .pak) 模型: Noesis 26插件 (.msh000-987, 99.5%) + msh_to_obj_v3.py (OBJ+UV) 纹理: tex_targem_py.py (纯Python, PHP逻辑迁移) + batch_tex_all.py (批量) 纹理支持现状 (2026-06-18): ✅ _d, _nm — DXT1/3/5, 全覆盖 ✅ _s (新旧版) — DXT1/5/RGBA, format 0x07→DXT1 ✅ _s1 — DXT1/5/RGBA, format 0x07→DXT1, 非方形支持 ✅ mapskit/decorative — DXT1/3/5/RGBA, 全覆盖 ✅ fonts — R8/L8/ARGB, 70/70 (Noesis v2/v3, 纯Python不支持) ✅ ui/particles/reaper — 全量 ✅ levels — 基本覆盖 (irradiance cubemap缺TFD) PBR 贴图通道分析 (2026-06-30 — 着色器源码验证): ⚠️ v2.3 重大修正:通过 object.fx 着色器源码验证,废弃此前 ORM 假设 ✅ _d — RGB=漫反射 (sRGB) ✅ _nm — DXT5nm: R=AO(FetchBumpOccl.z), G=NormalY, A=NormalX (Non-Color) ✅ _sc / _msk_sc — RGB=高光颜色, A=Gloss(部分模式) (sRGB) ✅ _msk — R=Height(Parallax), G=AO, B=Glossiness (Non-Color) object.fx L422-428: masks = tex2D(AmbOcclSampler).rgb glossFactor = masks.b; texOcclusion = masks.g ✅ _pdo — R=余弦加权AO, G=PDO方向Y, Z=方向, W=强度 (Non-Color, UV2) object.fx L629: globalOcclusion = pdoTex.x ✅ _glow — RGB=自发光 (sRGB) ✅ _s1 — RGB=染色颜色, A=混合权重 (sRGB) 游戏使用 Specular-Gloss 工作流 (Blinn-Phong): specPower = exp2( 9 * glossFactor + 2 ) (object.fx L683) _msk 双用途 (已确认): 默认模式: R=未使用, G=AO, B=Gloss PARALLAX: R=高度偏移 BL2_DETAIL: RGB=细节反射率 SHIP_DECAL: G=AO _nm AO vs _msk AO 优先级: BL2_DETAIL或PD_OCCL时, _nm的R通道AO覆盖_msk的G通道AO (object.fx L550) 批量转换结果: 11,671个TFH → 11,623个DDS成功 (99.6%), 11.2GB 输出目录: scunpack/tex_universe_check/ (保持源层级) 核心工具: tex_targem_py.py — 主力纹理转换 (PHP TargemImage逻辑) batch_tex_all.py — 批量转换, 多进程, 保持目录结构 tex_StarConflict_tfh_tfd_v3.py — Noesis插件 (PHP mip表 + fallback) tex_StarConflict_tfh_tfd_v4_php.py — Noesis插件 (纯PHP对比验证) 已知限制: ⚠️ Noesis RGBA渲染: B/R通道互换 (DDS导出正常) ⚠️ RGBA DDS兼容性: 部分查看器不支持无压缩RGBA (用Honeyview/GIMP/PS) ⚠️ 极小尺寸纹理: 部分mip级仅几个像素, 属正常现象 ⚠️ irradiance cubemap: 缺TFD文件 ✅ VBytes=40 flag=0x13: UV偏移已修正 24→20 (2026-07-11, loader/reptile_01) ✅ VBytes=40 flag=0x10: UV偏移已修正 24→16 (2026-07-11, fed_mercenary_man, skinned character, sentinel@24) ✅ VBytes=32 flag=0x0F: UV偏移已修正 20→16 (2026-07-11, fed_mercenary_tool) ℹ️ jerichonec (flag=0x11 vb=28): UV正常, MDF贴图引用正确; 贴图位于 tex_universe_check\models\characters\jericho\*.dds ⚠️ VBytes=28模型: 无UV2空间, lightmap使用UV1采样 (PD_OCCL Type=0) ✅ fed_plates01_msk 通道: 已通过着色器源码确认为 G=AO, B=Glossiness ⚠️ Blender Principled BSDF 与 Blinn-Phong 不完全匹配: Gloss→Roughness 转换为近似 ── 透明材质着色器分析 (2026-07-02 — animated_mock.fx 源码验证) ── ✅ 透明遮罩材质 (SC_animated_mock) - 专门用于半透明空气墙/护盾/传送门/屏障效果 - UV 动画完全由顶点着色器驱动,**无需网格旋转或变换矩阵** - VS_INPUT: VertexTransformInput + Tex0(UV1) + Tex1(UV2/lightmap) - PS_OUTPUT: baseUv=Tex0, cmUv=Tex1 (Tex1 作为 ColormapSampler 的采样坐标) ✅ UV 动画模式 (animated_mock.fx vertex shader): - 平移偏移: o.uv0.xy += UserParam2_Float4.xy (常量正向偏移) - 垂直滚动: o.uv0.y += animTime * UserParam2_Float4.z (随时间滚动) - 全方向: o.uv0 += animTime * UserParam4_Float4.xyzw (UV 四方向平移) - 旋转变换: sincos(animTime * UserParam2_Float4.w * PI*2) 环绕 (0.5,0.5) - 离散帧: UserParam1_Float4.xz=周期, yw=帧数; 公式简化: frame = floor(frac(animTime / period) * frames) uv += sign * frame / frames - 时间源: CodeParam0_Float4[0].w (游戏代码驱动) 或 GlobalTime.y (回退) ✅ ColormapSampler (如 gate_mask02.dds) 在像素着色器中的双用途: float4 cm = tex2D(ColormapSampler, cmUv); albedo.rgb *= cm.rgb; ← RGB = 颜色调制 (与漫反射相乘着色) albedo.a = cm.a; ← A = 透明度混合 (最终 Alpha 值) - ⚠️ 这是 Alpha Blending (透明混合), 不是 Alpha Test (裁剪) - 无 CUBE 定义时走 else: albedo.a = cm.a (gate_mask02 走此路径) - 有 CUBE 时: albedo = lerp(cube, albedo, cm.a); albedo.a = 1.0 ✅ gate_mask02 在 pvp_omega map.mdf 中的材质参数 (animated_mock block #14/#17): DiffuseSampler "models\shields\gate_scroll" ← 基础纹理(卷轴发光图案) ColormapSampler "models\shields\gate_mask02" ← 遮罩纹理(RGB=色调, A=透明度) UserParam0_Float4 ( 1 1 0 0 ) ← 亮度=1.0, 光泽度=1.0 UserParam2_Float4 ( 0 0 0.5 0 ) ← 偏移=(0,0), 滚动速度=0.5, 旋转速度=0 UserParam3_Float4 ( 0 0 0 1 ) ← Fresnel 参数 UserParam1_Float4 ( 1e+006 1 1e+006 1 ) ← 离散帧动画=禁用(周期极大) - ⚠️ MDF 默认值中旋转速度=0, 游戏运行时可经由 CodeParam0 覆盖 - 该材质在 mdl-geo 中无变换节点, 所有动画效果由着色器参数驱动 ✅ 工程结论: map_504 (msh504) 网格无旋转矩阵, UV 动画完全由 animated_mock.fx 的 顶点着色器通过 UserParam/CodeParam 运行时参数驱动。gate_mask02 为混合遮罩材质: RGB 控制颜色调制, Alpha 控制半透明混合。 版本历史: v1: rawtex_py.py (简单TFH启发式) + tex_v1 (简单位流) v2: tex_v2 (NoeBitStream + guess_size fallback + font) v3: tex_v3 (PHP mip表精确计算 + v2 fallback, 当前主力) v4: tex_v4_php (纯PHP逻辑迁移, 对比验证用) ── 模型转换工具版本历史 ── v1.0 (初始): 面法线翻转,模型在 Blender/Maya 方向异常。 Noesis 用 RPGOPT_TRIWINDBACKWARD=1 内部修正,其他工具未处理。 v1.1 (2026-06-22, 试错): 三角形索引反转 + X 镜像 错误假设左右镜像导致法线问题。索引改为 [i0,i2,i1],X 轴取反。 结果: 法线正常,但模型仍左右颠倒,且 Maya 仍需 Y180 旋转。 v1.2 (2026-06-23, 正确): Z 轴取反 — 前向轴修复 根因: MSH 前向轴是 -Z,Maya/FBX 前向是 +Z。 Z 取反 (x,y,z)→(x,y,-z) 一次性解决法线翻转+前向问题。 修改: msh2fbx.c, Noesis inc_starconflict_msh.py, blender_plugin 基础版/Pro版, msh_to_obj_v3.py v2.3 (2026-06-30): PBR 通道修正 — 着色器源码验证 **重大修正**: 通过 object.fx 第422-428行着色器源码验证,废弃此前错误的ORM假设 _msk 通道: G=AO (非Roughness), B=Glossiness→Roughness (非Metallic) _pdo 通道: R=余弦AO (object.fx L629) _nm R通道: AO提取 (FetchBumpOccl.z, object.fx L493) 确认游戏使用 Specular-Gloss 工作流 (Blinn-Phong) 修改: material_builder.py, shader_presets.py, README.md, README_PRO.md v2.2 (2026-06-29): UV2 Lightmap + PBR _msk 通道完整解析 UV2: msh_parser.py 新增 VBytes=32 的 UV2 解析 (offset=28, uint16_unorm) dreadnought_control_cab_imp 等 bigship/map 模型现在正确导入 lightmap UV 通道 _msk: material_builder.py AmbOcclSampler 从单通道(R)改为三通道(R/G/B)提取 R→AO/Cavity, G→Roughness, B→Metallic (完整 PBR 还原) ⚠️ 以上通道映射已在 v2.3 中通过着色器源码纠正 shader_presets.py: 更新 object_norm/dyn_object_norm 采样器列表 README/README_PRO: 新增 PBR 贴图通道分析与 UV2 文档 v2.4 (2026-07-02): Blender 着色器节点还原修复 — alpha/自发光/lightmap/空气墙混合 通过遍历所有 .fx 着色器源码验证材质属性映射,修复三项 Blender 节点网络问题: ① Alpha 连接 (material_builder.py) - sky (大气层): ceres_aura.dds 的 alpha 通道为纯白色, 改用颜色亮度→BSDF Alpha (ShaderNodeRGBToBW: 亮部不透明, 暗部透明, 营造大气辉光渐变) - skybackground (建筑透贴片): fed_station_01.dds alpha有实际轮廓, 直连BSDF Alpha - animated_mock 等透明材质: ColormapSampler.Alpha → BSDF Alpha (albedo.a = cm.a) - sky材质 Alpha*Color 乘法保留 (模拟 color.a * MAX_RGBS_RANGE) ② 自发光连接 (material_builder.py) - sky/skybackground/planets 无 _glow 贴图时: DiffuseSampler → Base Color + Emission Color - 材质预览模式下不连接到自发光会显示灰白色 ③ Lightmap 混合系数 (material_builder.py) - 原为简单乘法 (强度=1.0) 导致颜色过暗 - 改为: BaseColor 与 BaseColor*Lightmap 的 Mix 混合, 默认系数 0.3 - 通过 ShaderNodeMix 实现用户可调系数 ④ 空气墙材质 gate 混合 (material_builder.py) - map_481 等空气墙: ColormapSampler RGB × DiffuseSampler → Base Color - animated_mock.fx: albedo.rgb *= cm.rgb (gate_scroll × gate_mask02 调色) - ColormapSampler Alpha → BSDF Alpha (透明度) - VB=28 flag=0x000E 无第二UV通道, Tex0/Tex1 共享同一套UV shader_presets.py: 新增 sky/skybackground/planets 到 TRANSPARENT_MATERIAL_TYPES 修改文件: material_builder.py, shader_presets.py, PROJECT_NOTES.txt, README_PRO.md ── Shader Type 别名字典与材质合并 (2026-07-03) ── 背景: 组件模型 (models/objects/) 使用 dyn_object_norm 等 dyn_ 前缀 shader, 关卡地图 (mapskit/maps/) 使用 object_norm 等无前缀 shader。 两者在 material_builder 中走完全相同的节点创建路径、sampler 集功能等价, 但因 shader_type 不同导致指纹不同, 生成两份完全相同的 Blender 材质。 例: bigship_empire_01 的 bs_communications_01 材质 vs allidium_yard 的 同一贴图组 → SC_dyn_object_norm_xxxx vs SC_object_norm_yyyy。 发现: 两套指纹算法不一致 material_library.py:366 _compute_fingerprint() — 缺 UserParam2_Float4 material_registry.py:255 _compute_fingerprint() — 含 UserParam2_Float4 不一致导致: 库 JSON 生成 mat_id ≠ 运行时生成 mat_id → 跨 session 复用失败。 * 非 FX UV2 导致。FX UV2 是 UV 层命名 ("FX" vs "lightmap"), 无关联。 方案: Shader Type 别名字典 shader_presets.py 新增 SHADER_TYPE_ALIASES: dyn_object_norm → object_norm dyn_object_norm_blend → object_norm_blend dyn_object → object dyn_animated_mock → animated_mock 两个 _compute_fingerprint 同步应用别名: - fingerprint key 使用 aliased shader_type - mat_id 命名也使用 aliased shader_type Lightmap 差异保护: 关卡模型 (pvp_omega) 的 MDF 含 LightmapSampler → 贴图基底名集合多出 lightmap basename (如 station_03) → 指纹自然不同 → 不会与无 lightmap 的 组件模型材质错误合并。这是正确的差异化行为。 验证: ✅ fed_big_station_08 (组件): VBytes=28 Flag=0x11 → 无 UV2 → 无 lightmap ✅ pvp_omega/map (关卡): VBytes=32 Flag=0x14 → 有 UV2 → 有 LightmapSampler ✅ FX UV2 命名 ("FX" 用于 animated_mock ColormapSampler) 不受影响 ✅ dyn_glass/dyn_fresnel 等不在别名表中, 不受影响 修改文件: shader_presets.py, material_registry.py, material_library.py README_PRO.md, PROJECT_NOTES.txt ── 关卡一键组装 + 批量自动放置 (2026-07-04) ── 新增文件: scene_xml_parser.py — scene.xml 解析器 level_assembler.py — 模型路径解析 + 关卡组装核心 新增功能: ① File → Import → Star Conflict Level (scene.xml) — 一键关卡组装 ② 批量导入 Auto-Place — 自动从 scene.xml 查找模型世界位置 scene.xml 解析能力: - 提取 实体列表 - 解析 Pos (世界坐标) + Rot (四元数) 变换 - 解析 子场景链(递归加载) - Model 路径 → unpack_root 中查找 .mdl-msh* 文件 - 策略A: 作为目录 → *.mdl-msh* - 策略B: /.mdl-msh* - 策略C: unpack_root 全量递归查找 坐标系转换: Hammer Y-up → Blender Z-up - 位置: (hx, hy, hz) → (hx, hz, hy) - 四元数: (qx, qy, qz, qw) → (qw, qx, qz, qy) - 默认 up_axis='Z_UP_TO_Y_UP' (与批量导入一致) 关卡静态场景导入: mapskit/maps///*.mdl-msh* → 全局匹配 (命名模式: map.mdl-msh*/.mdl-msh*/.mdl-msh*) Def 实体回退: 当 scene.xml 无 Model= 引用时, 尝试 Def 名称 → 模型路径映射: Def="PvE_Geostation_Diafragma" → mapskit/maps///pve_geostation_diafragma ⚠ 大部分 Def 实体 (Character_*, Debris_*, VitalPoint_*) 模型嵌入引擎, 解包数据中不存在对应 .mdl-msh 文件, 无法自动导入. dyn_glass 材质: allidium_glass_01/02 使用 dyn_glass shader, MDF 仅含参数无采样器: UserParam0_Float4: 玻璃着色 RGB UserParam0_Float[0]: Alpha 不透明度 (默认 0.5) UserParam1_Float4: Fresnel 参数 material_builder.py: BSDF 设置后早期返回 (Transmission=1.0, Alpha Blend) 新增 _parse_float4() 辅助函数 Collection 命名: 场景导入使用 NameResolver (与批量导入一致), 基于文件路径生成层级 Collection → 基于 unpack_root 相对路径自动组织 进度提示: operators.py → SC_PRO_OT_import_scene_xml.execute() 使用 wm.progress_begin/update/end + status_text_set 菜单注册: Blender 4.2 中 ImportHelper 自动注册 import_scene.* 操作符 scene_xml 需手动 TOPBAR_MT_file_import.append (其他两个是自动的) 修改文件: scene_xml_parser.py (新), level_assembler.py (新), operators.py, __init__.py, material_builder.py, shader_presets.py, README_PRO.md, PROJECT_NOTES.txt