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, 数据解码, 无后门, 游戏资产处理, 逆向工具