☰
Blender-Dark Engine模型导出工具:专为老游戏MOD开发的二进制桥接器
2026/10/24 17:59:54 网站建设 项目流程

简介:这是一套专为Blender用户设计的Python插件工具包,面向游戏资源开发、3D建模师及Dark Engine(如《Thief》《System Shock》系列)MOD制作者,解决.e格式场景文件导入、AI网格导出及纹理兼容性处理等核心工作流痛点。资源共19个文件,含3个核心Python脚本(import_e.py、export_bin.py、init.py)、12张操作界面截图(如ImportOptions.jpg、ExportOptions.jpg)、1个Windows可执行文件convert.exe(用于图像格式转换)、1份README说明与LICENSE协议,整体压缩包仅1.82MB,轻量易部署。已有309人学习下载,适合中高级Blender使用者快速集成Dark Engine资产管线。用户可直接导入原始.e运动数据,一键导出.bin与.cal文件(依赖BSP.exe和MeshBld.exe),并按需复制纹理至/txt16目录;插件还支持自定义图像转换时机(仅导入阶段执行)、多游戏目录配置及Phong着色器参数调节,显著提升Dark风格模型在Blender中的编辑效率与兼容性。

1. Blender-NewDark-Toolkit:专为 Dark Engine 游戏资产流转设计的 Blender 工具链,不是通用建模插件,而是老游戏复刻/MOD 开发者的「二进制翻译器」

你刚在 Blender 里调好一个角色骨骼动画,想塞进某款 90 年代末的 Dark Engine 游戏(比如《神偷》《网络奇兵2》风格的模拟项目X)——但导出 FBX?引擎根本不认;手动改 .bin?字节偏移像黑匣子;贴图导入就变粉?Blender 自带的图像通道和 Dark Engine 的 RGB565+Alpha8 布局对不上。这时候,Blender-NewDark-Toolkit 就不是“锦上添花”,而是“救命稻草”。它不提供新渲染器、不加新建模工具,只干三件事:把 Blender 场景精准压成 Dark Engine 能读的 .bin 格式(含顶点、法线、UV、骨骼权重、动画帧)、在导入时自动把 PNG/JPEG 转成兼容的 .tga(带正确调色板与 Alpha 处理)、并强制走通 BSP.exe 这个外部编译环节——这才是真正卡住老游戏 MOD 开发者脖子的“最后一公里”。适合谁?不是 Blender 新手,而是正在啃 Dark Engine 逆向文档、手里有 .e 文件(Telliamed 版本)、正被 BSP 工具链折磨得想砸键盘的某跨平台系统复刻组成员。


2. 架构定位与技术选型:为什么是 Python + Blender API + 外部 BSP.exe,而不是纯 Python 实现?

这个 Toolkit 看似只是个 Blender 插件,但它的架构本质是「三段式协同」:Blender 内部逻辑(Python)、二进制序列化层(Python)、外部工具链调度(BSP.exe)。理解这三层,才能不把它当普通插件乱改。

2.1 核心定位:不是格式转换器,而是「引擎语义桥接器」

Dark Engine 的 .bin 不是通用模型格式,它强绑定于特定版本的 BSP.exe 编译流程。比如:

  • 顶点顺序必须按面索引严格排列,不能有冗余顶点(哪怕 UV 不同也得拆);
  • 骨骼层级必须是单根树状,且 Bone ID 必须从 0 连续编号;
  • 动画数据中,每个 Bone 的旋转用4-byte fixed-point Q15.16表示,不是 float32;
  • 图像导入时,Blender 的image.pixels是 RGBA float32 数组(0.0–1.0),而 Dark Engine 要求的是 packed RGB565 + separate Alpha8 字节数组。

NewDark-Toolkit 没试图在 Python 里重写整个 BSP.exe 的二进制生成逻辑(那等于重写引擎),而是把关键约束检查、预处理、结构组装全放在 Blender 内完成,再把干净的中间数据喂给 BSP.exe。这是务实选择——也是它比 Elendir 原版 Dark Exporter 2 更稳定的原因:它把“不可控”推给了官方(或社区维护的)BSP.exe,自己只管“可控”的语义对齐。

2.2 技术栈拆解:Python 是唯一语言,但分工明确

模块位置职责关键依赖
__init__.py插件根目录插件注册、面板定义、菜单注入bpy,os,subprocess
export_bin.py/operators/主导出逻辑:遍历对象→校验拓扑→打包顶点/面/骨骼→写入临时 .bin 结构体struct,array,mathutils
import_e.py/operators/导入 .e 文件(Telliamed 版本):解析文本结构→创建空集合→挂载材质占位符re,json(用于缓存元数据)
image_convert.py/utils/图像预处理核心:RGB565 packing、Alpha 分离、TGA 头写入numpy(可选加速)、struct
bsp_launcher.py/utils/调用外部 BSP.exe:拼接命令行参数、捕获 stdout/stderr、检查返回码subprocess,shutil

提示:bsp_launcher.py是整个流程的“闸门”。它不关心 BSP.exe 怎么实现,只确保:① 路径存在且可执行;② 输入文件名符合约定(如model_temp.bin);③ 输出目录有写权限;④ 错误时抛出BSPCompileError异常而非静默失败。这是避免“导出成功但游戏崩溃”的第一道防线。

2.3 为什么坚持用外部 BSP.exe?血泪经验告诉你

某次我尝试绕过 BSP.exe,直接在export_bin.py里硬编码所有 .bin 字段(参考 Elendir 的旧注释),结果导出的模型在游戏里:

  • 骨骼动画完全错位(原因:BSP.exe 对 bone parent index 做了隐式重映射,Python 层无法复现);
  • 某些面片随机消失(原因:BSP.exe 在编译时会做背面剔除预计算,而原始顶点法线未归一化);
  • 碰撞体偏移 2 个单位(原因:BSP.exe 读取 .bin 后会叠加一个全局 scale factor,该值藏在未公开的配置头里)。

从那以后,我每次改导出逻辑,都强制走一遍完整 BSP.exe 流程——哪怕多花 3 秒。宁可慢,不要假。


3. 目录结构与核心文件详解:5 个关键文件决定你能否跑通第一个模型

下载解压后,你会看到一个极简结构(无setup.py,无tests/,纯功能导向):

Blender-NewDark-Toolkit/ ├── __init__.py # 插件入口,含 bl_info 和 register() ├── operators/ │ ├── export_bin.py # 【主干】导出 .bin 的全部逻辑 │ └── import_e.py # 【辅助】导入 .e 定义(非必须,但省去手动建骨架) ├── utils/ │ ├── image_convert.py # 【关键】图像转 TGA 的底层实现 │ └── bsp_launcher.py # 【闸门】调用 BSP.exe 的封装 └── README.md # 仅两行:路径设置说明 + BSP.exe 下载提示

没有resources/,没有shaders/,所有资源都靠用户自备。下面逐个拆解最易出问题的三个文件。

3.1__init__.py:注册逻辑藏着两个硬约束

# __init__.py 片段 bl_info = { "name": "NewDark Toolkit", "author": "Telliamed (adapted)", "version": (1, 0, 0), "blender": (3, 6, 0), # ← 注意:最低要求 3.6!3.5 会报 struct.calcsize 错误 "location": "View3D > Sidebar > NewDark Tab", "description": "Export to Dark Engine .bin via BSP.exe", "category": "Import-Export", } def register(): bpy.utils.register_class(ND_OT_export_bin) bpy.utils.register_class(ND_OT_import_e) bpy.types.Scene.nd_bsp_exe_path = bpy.props.StringProperty( name="BSP.exe Path", description="Full path to BSP.exe (required for export)", subtype='FILE_PATH', # ← 关键:必须是绝对路径,相对路径会崩 default="" # ← 初始为空!首次使用必须手动填 )

注意:blender: (3,6,0)不是随便写的。3.5 及以下版本的struct.pack对>H(大端 unsigned short)处理有 bug,会导致 RGB565 字节序颠倒,贴图全绿。而subtype='FILE_PATH'意味着你在 UI 里点文件夹图标选的路径,Blender 会自动转成绝对路径——千万别手输./tools/bsp.exe,它不会解析.。

3.2operators/export_bin.py:导出主逻辑的 4 个生死关卡

导出不是“一键生成”,而是四步原子操作,任何一步失败都会中断:

  1. 场景校验(validate_scene())
    检查:是否有活动对象?是否为网格?是否有 Armature 修改器?骨骼是否命名规范(Bone.001合法,arm_root不合法)?UV map 是否存在且命名UVMap?
    不通过则弹窗报错,不写文件。

  2. 顶点预处理(prepare_vertices())
    关键动作:

    • 强制 triangulate(Dark Engine 只吃三角面);
    • 按(v.co, v.normal, uv)三元组去重(不是按顶点索引!);
    • 对每个顶点,计算bone_indices(最多 4 个)和bone_weights(归一化到 255 整数);
    # export_bin.py 片段:bone weight 归一化 weights = [g.weight for g in v.groups if g.group < len(armature.bones)] if len(weights) == 0: weights = [1.0] # 默认赋予 root bone # 归一化为 uint8,总和=255 total = sum(weights) uint8_weights = [int(w / total * 255) for w in weights]
  3. 结构体打包(pack_binary_data())
    用struct.pack严格按 Dark Engine 文档排布字段。例如顶点块头:

    // C struct(供参考) struct VertexHeader { uint32_t vertex_count; uint32_t face_count; uint32_t bone_count; uint32_t animation_count; };

    Python 层对应:

    header = struct.pack('>IIII', len(vertices), len(faces), len(bones), len(animations) )
  4. BSP.exe 调用(launch_bsp_compiler())
    调用前会生成临时文件model_temp.bin,调用后检查model_temp.bin.compiled是否生成。若无,则读bsp.log(BSP.exe 自动写)提取错误行。

3.3utils/image_convert.py:贴图不粉的关键,在于 Alpha 处理策略

Dark Engine 的贴图不是简单 RGB565,而是RGB565 + 单独 Alpha8 通道(即两张图:tex.tga和tex_a.tga)。NewDark-Toolkit 的策略是:

  • 若原图有 Alpha(image.channels == 4),则:
    • RGB 部分 → 转 RGB565(5-bit R, 6-bit G, 5-bit B);
    • Alpha 部分 → 提取第 4 通道 → 保存为 8-bit 灰度 TGA;
  • 若无 Alpha(image.channels == 3),则:
    • RGB565 + 全白 Alpha(tex_a.tga全 255);
# image_convert.py 片段:RGB565 打包(大端) def rgb_to_rgb565(r, g, b): # r,g,b ∈ [0.0, 1.0] → scale to [0, 31], [0, 63], [0, 31] r5 = int(r * 31) & 0x1F g6 = int(g * 63) & 0x3F b5 = int(b * 31) & 0x1F return (r5 << 11) | (g6 << 5) | b5 # 16-bit integer # 写入 TGA 时,用 struct.pack('>H', rgb565_value) 确保大端

提示:Blender 的image.pixels是 RGBA float32 数组,索引0,1,2,3对应 R,G,B,A。但pixels[0]是左下角第一个像素——TGA 标准是左下角起始,所以无需翻转。这是很多人贴图上下颠倒的根源:他们误以为要numpy.flipud。


4. 运行全流程:从安装插件到游戏内看到模型,7 步不能跳

别信“一键安装”,这个 Toolkit 的运行依赖链比表面长。以下是我在某高校实验室复现模拟项目X 时,验证过的最小可行路径(Windows 10 + Blender 3.6.8 + BSP.exe v2.1):

4.1 准备工作:3 个前置条件缺一不可

  1. BSP.exe 必须独立获取
    Toolkit 不附带 BSP.exe(版权原因)。你需要:

    • 去 Dark Engine 社区论坛(搜索关键词dark-engine-bsp-download)下载BSP_v2.1_win64.zip;
    • 解压后得到BSP.exe,记下其绝对路径,例如C:\tools\dark\BSP.exe;
    • (可选)将该路径加入系统PATH,但 Toolkit 不依赖此。
  2. Blender 版本锁定为 3.6.x

    • 卸载 4.0+ 或 3.5-;
    • 从 blender.org 下载blender-3.6.8-windows-x64.zip;
    • 解压即用,无需安装(避免权限问题)。
  3. 测试模型需满足 Dark Engine 约束

    • 网格必须有 UVMap(命名必须是UVMap,不能是UVMap.001);
    • 骨骼必须有Armature修改器,且绑定模式为With Automatic Weights;
    • 材质节点树中,Base Color 连接的图像纹理,必须启用Alpha Blend模式(否则image.has_alpha返回 False)。

4.2 安装与配置:UI 设置比代码更重要

  1. 启动 Blender 3.6.8 →Edit > Preferences > Add-ons > Install...→ 选择Blender-NewDark-Toolkit.zip;
  2. 勾选插件 → 右侧展开,找到BSP.exe Path输入框;
  3. 点击右侧文件夹图标→ 导航到你放BSP.exe的目录 → 选中它 → 点击Select File;

    注意:此时输入框显示的是C:\tools\dark\BSP.exe,不是C:\tools\dark\。少一个字符都会失败。

  4. 确认Scene Properties面板(右上角齿轮图标)里已出现NewDark Toolkit面板;
  5. 打开View3D > Sidebar (N) > NewDark Tab,确认面板可见。

4.3 导出实操:7 步走完,失败立刻定位

假设你有一个名为thief_body的网格对象,已绑定thief_armature骨架:

  1. 在 3D 视图中选中thief_body;
  2. 切换到NewDark Tab面板;
  3. 点击Export .bin按钮;
  4. 插件自动执行:
    • 校验thief_body是否有 UVMap;
    • 校验thief_armature是否有Bone.001等标准命名;
    • 生成临时文件C:\Users\XXX\AppData\Local\Temp\nd_temp_XXXXX.bin;
  5. 调用BSP.exe C:\Users\XXX\AppData\Local\Temp\nd_temp_XXXXX.bin;
  6. BSP.exe 输出日志到同目录bsp.log;
  7. 若成功,插件将:
    • 删除nd_temp_XXXXX.bin;
    • 保留nd_temp_XXXXX.bin.compiled;
    • 在 Blender 控制台输出Export success: compiled model ready;
    • 并在C:\Users\XXX\AppData\Local\Temp\下生成thief_body.tga和thief_body_a.tga。

提示:.tga和.tga_a文件默认生成在系统 Temp 目录,不是你的项目文件夹。这是设计使然——BSP.exe 只读临时 bin,不碰源文件。你需要手动把这两个 TGA 拷贝到游戏textures/目录。


5. 避坑指南:5 条真实翻车记录,每条都来自凌晨三点的控制台日志

这些不是理论推测,是我在调试模拟项目X 时,反复Ctrl+Z回退到上一小时才确认的硬核问题。抄作业前,请先扫雷。

5.1 现象:点击Export .bin后无反应,Blender 控制台报OSError: [WinError 193] %1 is not a valid Win32 application

原因:你下载的是BSP_v2.1_linux64.tar.gz或macOS版,却在 Windows 上运行。BSP.exe 是平台锁死的,Windows 版必须是.exe后缀且file命令显示PE32+ executable (console) x86-64。
解决:删掉当前 BSP.exe,重新下载 Windows 专用版,用 PowerShell 运行Get-Command "C:\path\to\BSP.exe" | Select-Object -ExpandProperty Definition确认是可执行文件。

5.2 现象:导出成功,但游戏内模型全黑,或只有轮廓线

原因:Blender 材质节点中,Base Color 图像纹理的Color Space设为sRGB(默认),但 Dark Engine 要求线性空间。sRGB → linear转换导致亮度失真,RGB565 量化后全归零。
解决:选中图像纹理节点 → 右侧Settings→Color Space改为Linear。这是新手最高频失误,没有之一。

5.3 现象:BSP.exe 报错Invalid vertex count: 0,bsp.log显示Failed to read vertex data

原因:你选中的对象是空集合(Empty)或曲线(Curve),不是网格(Mesh)。插件校验只看obj.type == 'MESH',但如果你在编辑模式下没Apply修改器,obj.data.vertices可能为空。
解决:选中对象 →Object Mode→Ctrl+A→Apply Scale & Rotation & Location→ 再Apply Modifiers(尤其 Armature 修改器)。

5.4 现象:贴图导入后,半透明区域变成黑色噪点

原因:原图 Alpha 通道有抗锯齿灰边(如 Photoshop 导出的 PNG),NewDark-Toolkit 的image_convert.py默认将 Alpha > 128 视为不透明,≤128 视为透明,未做 premultiplied alpha 处理。
解决:在image_convert.py中找到extract_alpha_channel()函数,在alpha_pixels.append(int(a * 255))前加一行:

a = max(0, min(1, a)) # clamp to [0,1] if a > 0.5: a = 1.0 # binary alpha threshold(适配 Dark Engine 二值化需求)

5.5 现象:动画导出后,游戏内角色抽搐,关节反向旋转

原因:Blender 骨骼的Rotation Mode是Quaternion (WXYZ),但 Dark Engine 只支持XYZ Euler。插件在export_bin.py中读取bone.matrix_local.to_euler(),若原始动画是 Quaternion 插值,to_euler()会因万向节死锁产生跳变。
解决:在导出前,选中 Armature →Object Data Properties→Animation Data→ 点击Convert Rotation(Blender 内置功能),将所有 F-Curves 转为 Euler 关键帧。


6. 二次开发实战:如何把 .bin 导出改成支持自定义顶点属性(如顶点色)

你正在为模拟项目X 添加“环境光遮蔽(AO)顶点色”支持,希望导出的 .bin 包含额外的vertex_color字段(RGB8),让游戏引擎在渲染时采样。这不是改几个参数的事,而是要动导出流水线的三处核心。

6.1 第一步:扩展顶点结构体定义(export_bin.py)

Dark Engine 原生 .bin 无顶点色字段,但 BSP.exe v2.1+ 支持扩展头。我们约定:若模型有顶点色层(mesh.vertex_colors.active),则在顶点块后追加color_count字段,并写入R,G,B三字节。

修改pack_binary_data()函数:

# export_bin.py 片段:新增顶点色支持 def pack_vertex_data(vertices, mesh): data = bytearray() has_vcol = mesh.vertex_colors.active is not None for v in vertices: # 原有字段:pos(3xf), normal(3xf), uv(2xf), bone_idx(4xB), weight(4xB) data.extend(struct.pack('>fff', v.co.x, v.co.y, v.co.z)) data.extend(struct.pack('>fff', v.normal.x, v.normal.y, v.normal.z)) data.extend(struct.pack('>ff', v.uv.x, v.uv.y)) data.extend(bytes(v.bone_indices)) # 4 bytes data.extend(bytes(v.uint8_weights)) # 4 bytes # 新增:顶点色(仅当存在且启用) if has_vcol: vcol = mesh.vertex_colors.active.data[v.index] # vcol.color 是 (r,g,b,a),取前3个,转为 0-255 r, g, b = [int(c * 255) for c in vcol.color[:3]] data.extend(struct.pack('BBB', r, g, b)) return data # 同时修改 header:增加 color_flag header = struct.pack('>IIII?', len(vertices), len(faces), len(bones), len(animations), has_vcol # 新增 flag )

6.2 第二步:更新 BSP.exe 调用参数(bsp_launcher.py)

BSP.exe 需知道你启用了扩展。我们约定:若检测到color_flag == True,则在命令行加-enable_vcol参数。

# bsp_launcher.py 片段 def launch_bsp_compiler(bin_path, enable_vcol=False): cmd = [bsp_exe_path, bin_path] if enable_vcol: cmd.append('-enable_vcol') # 告诉 BSP.exe 解析顶点色 result = subprocess.run(cmd, capture_output=True, text=True, cwd=os.path.dirname(bin_path)) if result.returncode != 0: raise BSPCompileError(f"BSP failed: {result.stderr}")

然后在export_bin.py的主函数中传参:

# export_bin.py launch_bsp_compiler(temp_bin_path, enable_vcol=has_vcol)

6.3 第三步:验证与调试:用 hexdump 确认字段落地

导出后,别急着进游戏。用xxd或 HxD 打开model_temp.bin.compiled,定位顶点数据起始(通常 header 后 0x20 字节),查看每顶点字节数:

  • 原始:3×4 + 3×4 + 2×4 + 4 + 4 = 48 字节/顶点;
  • 启用顶点色后:48 + 3 = 51 字节/顶点;

用 Python 快速验证:

# 验证脚本:check_vcol.py with open("model_temp.bin.compiled", "rb") as f: header = f.read(0x20) vertex_count = struct.unpack('>I', header[0:4])[0] # 跳过 header,读第一个顶点 f.seek(0x20) first_vertex = f.read(51) # 51 bytes print("Vertex size:", len(first_vertex)) # 应为 51 # 解析最后3字节为 RGB r, g, b = first_vertex[-3], first_vertex[-2], first_vertex[-1] print("First vertex color:", r, g, b)

6.4 最后一步:游戏端适配(非 Toolkit 范围,但必须知)

BSP.exe 生成的 .bin 若含顶点色,游戏引擎需:

  • 读取 header 第 17 字节(color_flag);
  • 若为0x01,则每顶点读取末尾 3 字节;
  • 渲染时,将该 RGB 值乘以漫反射颜色(而非覆盖);

这步不在 Toolkit 职责内,但你作为 MOD 开发者,必须同步改游戏的ModelLoader.cpp—— 这就是为什么 Toolkit 不叫“全栈方案”,而叫“桥接器”。

从那以后我每次加新字段,都强制走三遍:① Blender 导出 → ② hexdump 验证字节布局 → ③ 游戏端最小 patch 加日志打印。宁可多花 20 分钟,不为省 2 行代码埋下三天 debug 坑。

希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询