Nosvemos/darkorbit-3d-model-tool

GitHub: Nosvemos/darkorbit-3d-model-tool

将 DarkOrbit 游戏的 AWD 网格与 ATF 纹理自动化转换为现代 3D 格式并支持精灵序列与粒子特效渲染的格式转换工具。

Stars: 2 | Forks: 0

# DarkOrbit 3D 模型工具 将 DarkOrbit 游戏资源 —— Away3D `.awd` 网格和 Adobe `.atf` 纹理 —— 转换为现代 3D 格式(`.glb` / `.gltf` / `.obj`),并渲染旋转台精灵序列,全部通过命令行完全自动化。 它用一个单一且可重复的 pipeline 取代了手动的、基于 GUI 的工作流(ATF2PNG → Prefab3D → Blender),同时**保留辅助场景节点**(`engine_*`、`laserpoint_*`、`light_position`)作为后续渲染和动画的参考点。 ## 功能 - **ATF → PNG** — 纯 Python 解码器,支持资源集中的所有 ATF 格式:通过 LZMA 索引 + JPEG-XR 端点的 DXT1 (2) 和 DXT5 (4)、原始 RGB/RGBA (0/1) 以及原始 DXT5 (5)。 - **AWD → 网格** — 纯 Python AWD2 解析器:几何体、带有名称和变换的场景图实例、材质以及顶点(姿态)动画片段。 - **glb / gltf / obj 导出** — 内置于 Blender headless,通过漫反射 / 法线 / 镜面反射 / 发光通道连接 PBR 材质。每个顶点动画片段都作为单独的命名 glTF 动画(morph targets)导出。 - **保留参考点** — `engine_*` / `laserpoint_*` / `light_position` 节点作为空对象导出,并父级化到主体上。 - **旋转台精灵渲染器** — 无头模式,可重现的光照,任意帧数,并带有每帧的参考点屏幕坐标。 - **批处理** — 使用单个命令转换或渲染整个资源集。 ## 要求 | 依赖 | 版本 | 使用者 | |------------|---------|---------| | Python | 3.10+ | pipeline、解码器 | | Pillow | ≥ 10 | PNG I/O、精灵裁剪 | | NumPy | ≥ 1.26 | DXT1 解码 | | imagecodecs| ≥ 2024.1| JPEG-XR 解码 | | Blender | 5.x | 场景构建、glTF/obj 导出、渲染 | ``` pip install -r requirements.txt # runtime deps pip install -e . # optional: installs the `do3d` command ``` 如果 Blender 不在默认的 Steam 路径下,请通过 `BLENDER` 环境变量设置其可执行文件(参见 [`src/config.py`](src/config.py))。 ## 使用方法 所有操作都可以通过一个命令完成 —— `do3d`(在 `pip install -e .` 之后)或等效的 `python -m src`: ``` do3d convert [--all] [--fx] [--gltf] [--obj] [--no-blender] do3d render [--all] [--fx] [--mode auto|ship|item] [render options] do3d fx [--all] [--frames N] [--resolution PX] [--margin F] do3d extract-awp do3d list [meshes|fx|effects|textures|all] do3d info [--fx] do3d ui [--host H] [--port P] [--no-browser] ``` ``` do3d list meshes # what can I convert? do3d info sibelon # objects, reference points, clips, textures do3d convert sibelon --gltf --obj do3d render sibelon --frames 32 do3d fx explosion0 ``` 各个独立模块(`python -m src.pipeline`、`python -m src.render`、`python -m src.fx_render`)仍然可用,并接受与匹配子命令相同的选项。以下详细的选项表适用于这两种形式。 ### 转换 — `do3d convert` / `python -m src.pipeline` ``` python -m src.pipeline sibelon # one mesh -> out/sibelon/model/sibelon.glb python -m src.pipeline sibelon --gltf --obj # also emit gltf and obj python -m src.pipeline --all # every mesh in meshes/ ``` | 参数 | 描述 | |----------|-------------| | `mesh` | 不带扩展名的网格名称(例如 `sibelon`)。使用 `--all` 时请省略。 | | `--all` | 转换 `meshes/` 中的每个 `.awd`(或使用 `--fx` 时转换 `fx/` 中的)。 | | `--fx` | 从 `fx/` 读取 `fx_*.awd` + 纹理;输出到 `out/fx//` 下。 | | `--gltf` | 同时导出 `.gltf`(单独文件)到 `model/gltf/`。 | | `--obj` | 同时导出 `.obj`(+ `.mtl`)到 `model/obj/`。 | | `--no-blender` | 仅解码纹理并生成场景 JSON;跳过 Blender。 | 纹理查找优先使用可用的最高分辨率(`__512/256/128.atf`),如果找不到,则回退到绑定为基色的单个 `.atf`(由没有通道命名的 `fx/` 网格使用)。 ### 渲染 — `do3d render` / `python -m src.render` ``` python -m src.render sibelon # 72-frame turntable, 256 px python -m src.render sibelon --frames 32 # any count -> full 360° turntable python -m src.render lf4 --mode item # plain item/ore render, no points python -m src.render sibelon --resolution 1024 --samples 128 --persp python -m src.render sibelon --hdri city.exr --elevation 60 --azimuth 30 python -m src.render --all ``` 如果 `.glb` 尚不存在,渲染器将首先构建它。 **渲染模式** — `--mode`: | 模式 | 行为 | |------|-----------| | `auto` *(默认)* | 如果模型具有 `engine_*` / `laserpoint_*` 节点,则追踪点并写入 `Coords.json`;否则进行普通渲染。 | | `ship` | 强制进行点追踪 + `Coords.json`。 | | `item` | 普通渲染(矿石、物品,如 `lf4`、……)—— 没有点追踪,没有 `Coords.json`。 | **旋转台** | 参数 | 默认值 | 描述 | |----------|---------|-------------| | `--frames N` | 72 | 帧数。 | | `--total-degrees D` | 360 | 总扫描角度;每帧步长 = `D / frames`。 | | `--deg-per-frame D` | — | 显式步长,覆盖 `total-degrees / frames`。 | | `--start-angle D` | 90 | 第 1 帧时的旋转台旋转角度(正面朝向屏幕右侧)。 | | `--frame-start N` | 1 | 文件名中的第一个帧编号(`_1.png`)。 | **输出 / 质量** | 参数 | 默认值 | 描述 | |----------|---------|-------------| | `--resolution PX` | 256 | 正方形渲染分辨率。 | | `--samples N` | 96 | EEVEE 渲染采样数。 | | `--engine NAME` | `BLENDER_EEVEE` | 渲染引擎。 | | `--view-transform NAME` | `Standard` | 色彩管理(`Standard` / `AgX` / `Filmic`)。 | | `--no-crop` | off | 禁用全局稳定裁剪。 | | `--no-transparent` | off | 在不透明背景上渲染。 | | `--origin MODE` | `TOP_LEFT` | 坐标原点(`TOP_LEFT` / `BOTTOM_LEFT`)。 | **相机 / 光照** | 参数 | 默认值 | 描述 | |----------|---------|-------------| | `--hdri FILE` | `studio.exr` | 内置的世界 HDRI(见下文)。 | | `--world-strength F` | 0.8 | 世界/HDRI 强度。 | | `--sun-energy F` | 1.5 | 太阳灯能量。 | | `--emission F` | 0.6 | 发光/自发光贴图乘数。 | | `--elevation D` | 55 | 相机高出地平线的高度角。 | | `--azimuth D` | -90 | 绕 Z 轴的相机方位角。 | | `--persp` | ortho | 使用透视相机代替正交相机。 | | `--margin F` | 1.15 | 取景填充系数(> 1 缩小)。 | 内置 HDRI:`studio` · `city` · `courtyard` · `forest` · `interior` · `night` · `sunrise` · `sunset`。 所有默认值都位于 [`src/config.py`](src/config.py) 的 `RENDER_DEFAULTS` 中。 ## 输出结构 ``` out// model/ .glb # primary, self-contained (textures embedded) textures/ # decoded source PNGs (diffuse/normal/specular/glow) gltf/ .gltf + .bin + textures obj/ .obj + .mtl sprites/ _1.png … _N.png _Coords.json # per-frame reference-point screen positions work/ # intermediates (scene / config / meta JSON) ``` `_Coords.json` 是一个平铺映射,包含 `engine_*` / `laserpoint_*` 点的每帧屏幕位置: ``` { "engine_0": [[19, 107], [20, 113], "OFF", ...], "laserpoint_leftFrontOuter": [[168, 119], ...] } ``` `"OFF"` 标记该点位于屏幕之外的帧。坐标是相对于裁剪后的精灵的。 ## 工作原理 ``` .awd ──▶ AWD2 parser ──▶ geometry + named nodes + transforms ─┐ ├─▶ scene JSON ──▶ Blender ──▶ glb/gltf/obj .atf ──▶ ATF decoder ──▶ PNG (diffuse/normal/specular/glow) ──┘ │ └──▶ turntable render ──▶ sprites + Coords.json ``` 系统端的 Python(NumPy / imagecodecs)负责解码和解析;Blender 以 headless 模式运行,仅使用 `bpy` + 标准库。两侧通过 JSON 进行通信,因此彼此不依赖对方的库。 ## 项目布局 ``` src/ cli.py unified CLI (do3d / python -m src) __main__.py `python -m src` entry point atf/ ATF texture decoder (DXT1 + DXT5) awd/ AWD2 mesh parser + scene model blender/ headless scene builder + sprite renderer (run inside Blender) fx/ .awp particle parser + 2D billboard renderer config.py paths + render defaults pipeline.py conversion orchestrator render.py mesh turntable render orchestrator fx_render.py particle-effect render orchestrator server.py local web UI backend (stdlib http.server) web/ single-page UI (index.html) tools/ standalone inspection/preview helpers docs/ format research, architecture, roadmap ``` ## 文档 | 文档 | 内容 | |-----|----------| | [`docs/00_overview.md`](docs/00_overview.md) | 目标、手动与自动化工作流 | | [`docs/01_formats.md`](docs/01_formats.md) | AWD 和 ATF 二进制格式发现 | | [`docs/02_architecture.md`](docs/02_architecture.md) | Pipeline 架构与决策 | | [`docs/03_roadmap.md`](docs/03_roadmap.md) | 分阶段实施状态 | | [`docs/04_blender_scripts.md`](docs/04_blender_scripts.md) | 关于原始 Blender 脚本的说明 | | [`docs/05_open_questions.md`](docs/05_open_questions.md) | 已解决的决策与悬而未决的问题 | ## Web UI 一个最小的本地 Web UI(Python 标准库 `http.server` + 单个原生 JS 页面 —— 无框架,无构建步骤): ``` do3d ui # serves http://127.0.0.1:8765 and opens the browser ``` 在侧边栏中浏览网格 / fx 网格 / 特效,检查资源(对象、参考点、片段、纹理),然后直接从页面转换为 glb 或渲染精灵 / 粒子特效。渲染后的旋转台将作为动画预览内联播放,并提供 glb 和 `Coords.json` 的下载链接。长时间的 Blender 渲染作为后台任务运行,将实时进度(已用时间 + 日志)流式传输到页面。它调用与 CLI 相同的函数,因此 UI 所做的任何操作都可以在命令行上重现。 **手动纹理。** 自动检测会根据文件名映射 `__512.atf`;当资源不遵循该约定时(许多 `fx_*.awd` 网格,或命名奇特的网格),UI 的逐通道纹理字段允许您按名称选择任何 `.atf`(从 `textures/` 和 `fx/` 中的每个纹理自动补全)。选取的纹理会覆盖每个通道自动检测到的纹理并重新构建 glb。在 CLI 上,可以通过向 `pipeline.convert` / `render.render` 传递 `textures` 字典来实现相同的功能。(粒子特效将纹理携带在 `.awp` 内部,因此它们会自动解析。) ## FX / 粒子资源 `fx/` 文件夹包含粒子特效资源:`fx_*.awd` 网格、`.atf` 纹理以及每个包含单个 `.awp` 的 `.zip` 存档。`.awp` 是描述 Away3D 粒子特效的**纯 JSON**(`particleEvents`、`animationDatas`、`nodes`、材质/几何体引用)。 **将特效渲染为精灵帧** — `python -m src.fx_render`: ``` python -m src.fx_render explosion0 # -> out/fx/explosion0/sprites/ python -m src.fx_render explosion0 --frames 24 --resolution 256 python -m src.fx_render --all ``` 粒子在 3D 中进行模拟,并作为带有图层混合模式(加法 / alpha)的相机朝向公告板进行合成。引用的纹理直接从 `.atf` 资源中解码(DXT1 + DXT5)。`.zip` 会自动解压;如果只想提取 JSON: ``` python tools/extract_awp.py # unpack every fx/*.zip into fx/awp/ and validate JSON ``` | 参数 | 默认值 | 描述 | |----------|---------|-------------| | `name` | — | 特效名称(不带 `.zip`/`.awp`)。使用 `--all` 时省略。 | | `--all` | — | 渲染每个 `fx/*.zip`。 | | `--frames N` | 30 | 跨越特效持续时间的帧数。 | | `--resolution PX` | 256 | 正方形精灵分辨率。 | | `--margin F` | 1.2 | 画布填充系数。 | 支持的粒子节点:时间、位置、速度、加速度、缩放、分段/初始颜色、旋转、公告板、轨道、振荡器、sprite-sheet(flip-book)以及 UV 滚动。(在独立播放中 Follow 为无操作,因为发射器固定在原点。) `fx/` 中普通的 `fx_*.awd` 网格(环、球体、碎片等)使用 `--fx` 标志进行转换和渲染,该标志会从 `fx/` 获取网格和纹理,并写入到 `out/fx//` 下: ``` python -m src.pipeline fx_crystal_shard --fx # -> out/fx/fx_crystal_shard/model/ python -m src.render fx_ring --fx # turntable sprites python -m src.pipeline --all --fx # every fx_*.awd ``` ## 测试 单元测试在合成的 AWD/ATF 字节流上运行,因此不需要游戏资源: ``` pip install -r requirements-dev.txt pytest ``` 测试涵盖 AWD2 解析器(两种头部变体、几何体/实例/材质解码、孤立项名称恢复、属性跳过回归)、ATF 解码器(头部解析、DXT1 重建、完整的编码→解码往返)、中间模型、渲染稳定裁剪 / 坐标逻辑以及 `.awp` 粒子解析器(值采样器、分段颜色、特效加载)。Blender 脚本(需要 `bpy`)会进行语法检查,而不是执行。 CI 通过 GitHub Actions([`.github/workflows/c.yml`](.github/workflows/ci.yml))在 Python 3.10–3.12 上运行测试套件。 ## 许可证 [MIT](LICENSE) © 2026 Samet Ozturk ## 免责声明 仅供教育和个人使用。游戏资源(`meshes/`、`textures/`)归各自所有者所有,**不**包含在此存储库中。
标签:3D模型转换, Blender, Python, 数据解码, 无后门, 游戏资产处理, 逆向工具