从符合 MetaHuman 官方拓扑的 Mesh 构建 MetaHuman 绑定与 DNA 的 Maya 工具。
日常拿到的只有一套模型(头部 9 个 LOD0 网格 + 身体网格),拿不到 DNA。本工具把 Epic 官方
MetaHumanForMaya 插件里原本只存在于交互式 UI 的 conform 流程串成可脚本化的流水线:
拓扑校验 → 骨骼重定位 → 表情重算 → 输出 DNA → 装配 Maya 绑定(头身骨架合并)
| 依赖 | 说明 |
|---|---|
| Maya | 2024 / 2025 / 2026 / 2027(对应 Python 3.10 / 3.11 / 3.11 / 3.13) |
MetaHumanForMaya |
Epic 官方插件,需已安装。工具从它加载 dna / dnacalib2 / rdf / frt_api / mh_assemble_lib / nls 与六个 .mll |
| 身体模板 DNA | 插件不自带 MHB.1 原型;仓库自带 Assets/body.dna,可换成你自己体型的,见下文 |
mhrigcreator.env.setup() 负责定位插件、解析 env/<platform>-<maya版本>.json、注入
sys.path 与 DLL 目录、加载插件。它按当前解释器的 Python 版本挑选配置,所以在 Maya、
mayapy、以及版本匹配的独立 CPython 里都能工作(独立解释器下只有 frt_api /
mh_assemble_lib 不可用,它们依赖 maya.cmds)。
如果插件不在 ~/Documents/maya/modules/MetaHumanForMaya,用环境变量指定:
set MHRIGCREATOR_MH4M_ROOT=<MetaHumanForMaya 根目录>
不要引入 OpenRigLogic。 它的
dna是 13.2.5,与插件的 10.1.4 冲突 —— 这正是MetaHumanForMaya.py:dependency_checks()警告的场景。它只适合作为格式文档与离线交叉验证。
from mhrigcreator import ui
ui.show()三个按钮对应流水线三步:校验拓扑 → 构建 DNA → 装配绑定。第三步会新建场景,面板会先弹确认框, 且在当前场景有未保存改动时额外警告。
from mhrigcreator import assemble, character, env
env.setup()
# 步骤 1+2:校验拓扑并从当前场景网格构建头身 DNA
result = character.build_character("D:/out", character_name="my_char")
print(result.format())
assert result.ok
# 步骤 3:装配可动绑定(注意:会新建场景)
asm = assemble.assemble_character(result.head_dna, result.body_dna)
print(asm.format())只想校验拓扑:
from mhrigcreator import env, topology
env.setup()
head_report, body_report = topology.check_scene()
print(head_report.format())
if not head_report.ok:
raise RuntimeError("拓扑不匹配,无法构建绑定")整条流水线不依赖 GUI,可以用 mayapy 跑(tests/check_assemble.py 就是这么验证的):
"C:/Program Files/Autodesk/Maya2024/bin/mayapy.exe" your_batch_script.pyMH.6 头部(870 骨骼 / 1673 表情 / 50 网格 / 8 LOD)+ MHB.1 身体(342 骨骼),Maya 2024:
| 阶段 | 耗时 |
|---|---|
| 拓扑校验(10 个网格逐面比对) | ~1 s |
| 身体:骨骼定位 + 写入 + LOD 重生成 | 4.9 s |
| 头部:骨骼定位 | 0.5 s |
头部:构造 RigDataHandler |
2.0 s |
| 头部:写中性骨骼(含 1673 个表情的 sculpt 物化) | 5.2 s |
| 头部:全表情重算 | 19.6 s |
| 头部:LOD 重生成(ONNX) | 0.2 s |
| 头部:导出 + 自检 | 1.5 s |
| 构建 DNA 合计 | ~36 s |
| 装配绑定(头 + 身 + 接缝重建 + RigLogic) | ~31 s |
JM 关节匹配优化(默认关闭)会额外增加数十分钟,量级完全不同。
| 产物 | 说明 |
|---|---|
<name>_head.dna |
MH.6 头部 DNA,可直接导入 UE |
<name>_body.dna |
MHB.1 身体 DNA |
| Maya 场景 | 1185 根骨骼的单一骨架(342 身体 + 870 头部 − 27 共享)、54 个 skinCluster、blendShape、head_rl4Embedded / body_rl4Embedded 两个 RigLogic 节点、178 个 faceboard 控制器 |
装配产物已验证可动:把 CTRL_C_jaw.ty 拨到 1.0,头部 24049 个顶点里有 20824 个发生位移,最大 3.65 cm。
输入网格不全也能产出完整 DNA —— 缺失的装饰网格会用蒙皮补出来,见输入网格不全时怎么办。
产出 DNA 会比原型小(实测 43.4 MB vs 51.8 MB)。这是正常的:表情重算按 config.json 的剪枝
阈值(corrective blend shape 0.002 cm)丢掉了视觉无关的小增量,实测保留约 79% 的 CBS 增量与 99%
的关节增量。export.verify_dna() 会核对这两个比例,真正塌掉的 behavior 层会被拦下来。
前三个不依赖 pytest,可直接运行;在 Maya 里 import 后调 main() 会额外跑 Maya 相关项。
uv run python tests/check_foundation.py # 环境注入、模板资源、DNA 读写
uv run python tests/check_joint_math.py # 纯 Python 矩阵与 Maya 一致性、世界↔局部往返
uv run python tests/check_joint_fit.py # 骨骼定位精度(头部用真值 DNA,身体用合成仿射变形)另两个需要 frt_api(依赖 maya.cmds),用 mayapy 跑:
# 缺失网格路径:角色分级、拒绝/补齐行为、补出网格的质量。不碰 Maya 场景
"C:/Program Files/Autodesk/Maya2024/bin/mayapy.exe" tests/check_partial_meshes.py
# 装配自检。注意它会新建场景,别在有未保存改动的 GUI 会话里跑
"C:/Program Files/Autodesk/Maya2024/bin/mayapy.exe" tests/check_assemble.py| 文件 | 角色 |
|---|---|
SM_MH_test.ma |
测试输入网格:9 个头部 LOD0 网格 + body_lod0_mesh,无骨骼 |
head.dna |
MH.6 角色 DNA。头部精度打分的真值 |
body.dna |
MHB.1 身体 DNA。身体流水线的默认模板 |
两份 DNA 是配套的同一角色(27 根共享骨骼世界位置一致到 3.5e-5 cm)。
head.dna 当真值用:它与插件自带的原型 res/MH.6/template.dna 结构完全一致(870 骨骼 /
50 网格 / 8 LOD / 782 BS 通道 / 545 PSD / 38 RBF pose)但几何不同(网格差 2.87 cm、骨骼差
4.01 cm),是一份货真价实的"另一个角色"。于是头部打分可以做到:从原型拟合到它的网格,再逐骨骼
对比它自己的骨骼位置 —— 骨骼位置由 Epic 管线产出,不是我们造的。
body.dna 当模板用:MHB.1 是参数化体型,没有唯一"原型"。这份是身高约 154.7 cm(head 骨骼
高度)、网格高 142.4 cm 的体型。身体精度打分改用合成仿射变形 + 解析真值,见下节。
MetaHumanForMaya 不带 MHB.1 身体原型(官方假定身体来自 MetaHuman Creator 导出);
mh_pose_editor/resources/reference/{mh6,mhb1}.dna 只是结构校验参考,几何被清零,不能当
模板用。
mhrigcreator.resources.find_body_archetype_dna() 按以下顺序查找:
- 环境变量
MHRIGCREATOR_BODY_ARCHETYPE_DNA resources/body_archetype.dnaAssets/body.dna(默认)
换模板安全,因为流水线会覆盖全部与体型相关的数据:LOD0 全部 30455 个顶点、342 根骨骼的 局部平移、LOD1–3 网格。模板保留下来的部分实测与体型无关或无风险:
| 保留项 | 换模板的风险 | 实测依据 |
|---|---|---|
| 骨骼名 / 层级 / 网格名 / LOD 映射 | 无 | 两份 MHB.1 完全一致 |
| 中性骨骼旋转 | 无 | 旋转矩阵逐元素差 2.6e-06,与体型无关(注意必须比矩阵,比欧拉角会得到假的 360° 差) |
| behavior 层(176 raw control / 215 RBF pose / 72 solver / 72 joint group) | 无 | 计数与关节增量值数量完全一致 |
| UV | 无 | 拓扑一致 |
| 蒙皮权重 | 有 | 两份不同体型的 MHB.1 抽样 4351 顶点中 3708 个(85%)权重不同,最大差 0.165 |
蒙皮那条是唯一要留意的:差异成片分布(pelvis / spine_02-04 / 各 *_twist_* 100% 不同,
脚部 73%),因为 Epic 是按体型分别调的权重,而流水线没有重算蒙皮的手段。
所以请选一份体型接近你日常绑定目标的 MHB.1,不必纠结"是不是官方默认"。
只需要 4 个网格就能构建:head_lod0_mesh、teeth_lod0_mesh、eyeLeft_lod0_mesh、
eyeRight_lod0_mesh(身体另需 body_lod0_mesh)。其余 5 个是"装饰网格",缺了照样能建。
网格角色从数据推导(topology.mesh_roles()),不是硬编码 —— REQUIRED 取自官方顶点映射表
实际引用的网格,EYE 取自眼球球心拟合的配置,换 rig 版本会自动跟着变:
| 角色 | 网格 | 缺失后果 |
|---|---|---|
REQUIRED |
head_lod0_mesh、teeth_lod0_mesh |
拒绝构建 —— 官方映射表依赖它们定位 812 根骨骼 |
EYE |
eyeLeft_lod0_mesh、eyeRight_lod0_mesh |
警告。眼球球心拟合跳过,眼球骨骼链退化(整体平均 0.0073 → 0.0092 cm),仍可构建 |
FILLABLE |
saliva、eyeshell、eyelashes、eyeEdge、cartilage |
警告。对骨骼定位几乎零影响,网格用蒙皮权重自动补出来 |
实测它们的蒙皮骨骼独占数全为 0 —— 每根骨骼的证据都能从别的网格拿到:
| 网格 | 顶点 | 映射表骨骼 | 有蒙皮骨骼 | 独占蒙皮 |
|---|---|---|---|---|
head_lod0_mesh |
24049 | 800 | 639 | 584 |
teeth_lod0_mesh |
4246 | 12 | 17 | 15(舌头) |
eyeLeft / eyeRight |
770 | 0 | 2 | 2(EyeParallel / Pupil) |
saliva |
660 | 0 | 2 | 0 |
eyeshell |
552 | 0 | 21 | 0 |
eyelashes |
2144 | 0 | 21 | 0 |
eyeEdge |
268 | 0 | 21 | 0 |
cartilage |
386 | 0 | 55 | 0 |
只给 4 个网格 vs 全 9 个:整体平均 0.053687 vs 0.053715 cm(差 2.8e-5 cm),受影响最大的单根
是 head,偏移 0.24 mm。不是严格零 —— head 与眼周骨骼在装饰网格上也有权重 —— 但比本项目最差
骨骼的误差还小一个数量级。
不补的后果很实在:输出 DNA 里它们会保留原型几何,睫毛会浮在眼睛外面。因为流水线只改骨骼 平移,线性混合蒙皮退化成一个加权平移和,可以直接算出来:
p'_v = p_v + Σ_j w_vj · (T'_j − T_j) / Σ_j w_vj
实测(缺 5 个装饰网格,与真实网格对比):
| 网格 | 保留原型 | 蒙皮补出 | 改善 |
|---|---|---|---|
eyelashes_lod0_mesh |
1.6902 cm | 0.1218 cm | 13.9x |
eyeEdge_lod0_mesh |
1.6584 cm | 0.1378 cm | 12.0x |
eyeshell_lod0_mesh |
1.6614 cm | 0.1540 cm | 10.8x |
cartilage_lod0_mesh |
1.6694 cm | 0.1692 cm | 9.9x |
saliva_lod0_mesh |
1.8310 cm | 0.2111 cm | 8.7x |
默认开启(HeadBuildOptions.fill_missing_meshes=True)。补出来的网格终究是推测而非艺术家做的
模型,所以报告里会明确列出哪些是补的、蒙皮覆盖率多少 —— 不让它悄悄发生。关掉开关则会警告
"输出 DNA 里它们保留原型几何、与新角色对不上"。
DNA 的中性骨骼只存局部平移 + 局部旋转。"网格变了骨骼该去哪"没有单一答案,按证据强度 分级叠加,一根骨骼由第一个能定位它的策略接管:
| 优先级 | 策略 | 依据 |
|---|---|---|
| 0 | 外部硬约束 | 头身共享骨骼,由另一侧拟合结果回灌 |
| 1 | 官方顶点映射 | Epic 自带 vertex_joint_mapping/*.csv |
| 2 | 眼球球心拟合 | 眼球网格的代数球面最小二乘 |
| 3 | 链上插值 | 从原型 DNA 自身反解 lerp(A, B, w) |
| 4 | 蒙皮权重迁移 | 骨骼跟着它影响的皮肤走 |
| 5 | 子树相似拟合 | 已定位子孙的相似变换(旋转 + 均匀缩放 + 平移);无子孙则继承最近已定位祖先的变换 |
| 6 | 随父刚性跟随 | 兜底。实测头身两侧都已归零 —— 342 / 870 根骨骼全部有正向依据 |
只改平移,不改旋转 —— 与 Epic 一致。DNA behavior 层里每个表情的关节变换都是相对中性 姿态的增量,改动中性旋转会让全部表情失效。
绑定锚点(位于世界原点的根骨骼,如 MHB.1 的 root)被排除在所有策略之外:它是地面/世界
锚点而非解剖关节。不排除的话子树拟合会把整套骨架拖偏 5.09 cm。而 MH.6 头部骨架的根
spine_04 是真会动的解剖关节(原型 y=117.62),必须参与拟合。
子树拟合必须带缩放项。 早期版本只求旋转(纯刚性 Procrustes),在体型缩放场景下系统性偏移。
用受控实验隔离过:把 thigh_l 的全部子孙放到精确位置、只让该策略反推 thigh_l ——
| 形变 | 仅旋转 | 旋转 + 缩放 |
|---|---|---|
| 等比 1.04 | 2.0553 cm | 0.0000 cm(s 正确解出 1.04000) |
| 非等比 1.02/1.09/1.05 | 4.6391 cm | 0.6471 cm |
没有子孙的骨骼继承最近已定位祖先的变换。 MHB.1 的 head 是叶子骨骼(面部骨骼在头部 DNA
里),neck_02 唯一子孙就是 head,neck_01 的子孙全是这两根 —— 整条颈链拿不到样本,原先只能
落到"随父跟随"(等于假设该段一点没缩放)。改为继承 spine_05 的 (s, R) 后(链式传递
neck_01←spine_05 → neck_02 → head,实测 s=1.0399):
| 骨骼 | 随父跟随 | 继承祖先变换 |
|---|---|---|
head |
1.057 cm | 0.048 cm |
neck_01 |
0.652 cm | 0.041 cm |
neck_02 |
0.848 cm | 0.045 cm |
头部:原型 res/MH.6/template.dna → Assets/head.dna 的网格,以它自己的骨骼位置为真值,
870 根骨骼,逐骨骼欧氏距离(cm):
| 策略 | 骨骼数 | 平均 | 中位 | p95 | 最大 |
|---|---|---|---|---|---|
| 0 外部约束(头身共享) | 27 | 0.0000 | 0.0000 | 0.0000 | 0.0000 |
| 1 官方映射 surface | 770 | 0.0001 | 0.0000 | 0.0000 | 0.0046 |
| 1 官方映射 volumetric | 42 | 0.0568 | 0.0597 | 0.1077 | 0.1629 |
| 2 眼球球心拟合 | 2 | 0.0159 | 0.0172 | 0.0172 | 0.0172 |
| 3 链上插值 | 16 | 0.1641 | 0.0520 | 0.6969 | 0.6969 |
| 4 蒙皮权重迁移 | 11 | 0.0709 | 0.0483 | 0.3190 | 0.3190 |
| 5 子树相似拟合 | 2 | 0.2386 | 0.3179 | 0.3179 | 0.3179 |
| 合计 | 870 | 0.0073 | 0.0000 | 0.0483 | 0.6969 |
| 不拟合的基线 | 870 | 1.8045 | 1.7757 | 2.2596 | 4.0144 |
身体:合成仿射变形 + 解析真值(见 自检脚本),342 根骨骼:
| 策略 | 骨骼数 | 平均 | 中位 | p95 | 最大 |
|---|---|---|---|---|---|
| 3 链上插值 | 32 | 0.0597 | 0.0545 | 0.1465 | 0.1465 |
| 4 蒙皮权重迁移 | 285 | 0.0631 | 0.0281 | 0.2442 | 0.6613 |
| 5 子树相似拟合 | 24 | 0.0708 | 0.0540 | 0.1480 | 0.1480 |
| 锚点(有意不动) | 1 | 0.0000 | 0.0000 | 0.0000 | 0.0000 |
| 合计 | 342 | 0.0632 | 0.0358 | 0.1856 | 0.6613 |
| 不拟合的基线 | 342 | 3.9408 | 4.2703 | 5.3981 | 6.1867 |
以上身体数据用的是等比 1.04 的形变 —— 对应我手上两份 MHB.1 之间约 4.4% 的真实体型差。 把形变加严到非等比 1.03/1.09/1.05 + 剪切 0.02 时,合计平均 0.1909、最大 3.9615 cm,误差集中在 子树相似拟合那 24 根上(平均 1.2689):非等比形变超出了"均匀缩放"的表达能力,这是该策略的 固有上限。
读这些数字时注意:头部基线是 1.80 cm 而不是更早文档里的 5.18 cm,原因是换了一份更接近 原型的真值角色(网格差 2.87 cm,而非 7.01 cm),不是算法变好了。跨真值比较绝对值没有意义, 要比的是「改善倍数」与「按策略分解」。
头部 DNA 与身体 DNA 有 27 根同名骨骼(spine_04~upperarm_* 与颈链)。实测配套的一对
DNA 里它们的世界位置一致到 3.5e-5 cm —— 官方管线本身就满足这个约束。所以流水线先拟合身体、
再把这 27 根位置作为优先级最高的硬约束回灌头部(character.build_character 自动完成)。
头部网格几乎覆盖不到肩背,光靠它推断这批骨骼很不准。收益(同一真值下对比):
| 指标 | 无约束 | 有约束 |
|---|---|---|
| 870 根整体平均 | 0.0537 cm | 0.0073 cm |
| 870 根整体最大 | 3.3761 cm | 0.6969 cm |
| 靠蒙皮迁移兜底的骨骼数 | 31 根(平均 1.0698 cm) | 11 根(平均 0.0709 cm) |
| 靠子树拟合兜底的骨骼数 | 9 根(平均 0.9342 cm) | 2 根(平均 0.2386 cm) |
加约束后误差最大的是 FACIAL_C_Jaw / FACIAL_C_LowerLipRotation(0.70 cm)—— 下颌旋转中心
在头骨内部,没有网格直接对应,只能靠链上插值推断。它在官方 user_defined_joints 里,本来就是
要人工摆的。
- 用一环邻域极分解旋转 volumetric 偏移。 CSV 里的
delta实测就是模板空间的joint_ws - vertex_ws(吻合到平均 0.045 cm),而真值里这个偏移会随局部形状转动 (平均差 0.27 cm),所以"跟着局部帧转"看起来很合理。实测明显更差:平均 0.54 cm、 最大 2.08 cm(额头与脸颊内侧最糟)。密集网格上的局部帧对形状变化过于敏感。 → 保留 Epic 原始规则。
- 只改骨骼平移,不改朝向。 输入网格与原型比例差异过大时会有轻微蒙皮滑动。
- UV 沿用模板。
dnacalib2没有写纹理坐标的命令;输入网格若改了 UV,拓扑校验只给 warning,输出 DNA 的 UV 仍是原型的。 - 身体骨骼定位无官方映射表,靠蒙皮权重迁移 + 链上插值 + 子树相似拟合三级策略。真实体型差 量级下平均 0.06 cm、最大 0.66 cm;形变越偏离"均匀缩放"(非等比、剪切)误差越大,因为子树 相似拟合只能表达均匀缩放。若日后拿到官方身体映射表,替换策略 4/5 即可。
- 绑定 DBName 固定 MH.6 / MHB.1。
resources.py已按 DBName 分目录,扩展 MH.4 需另备template.dna(插件的 MH.4 目录缺这个文件)。
-
Maya 内代码必须兼容 Python 3.10(Maya 2024)。
pyproject.toml的requires-python是>=3.10;venv 只用于离线 DNA 工具与类型检查。 -
dnareader 的生命周期。 SWIG 包装层用__del__调 C++destroy(),被隐式回收的 reader 会让进程段错误:db = dna_io.read(path).getDBName() # 崩溃:reader 是临时对象
所以统一走
mhrigcreator.dna_io:它把(reader, stream)登记进注册表防止隐式回收,用完 调dna_io.release(reader),或用with dna_io.opened(path) as reader:。 -
dna/dnacalib2/rdf不抛异常,只设全局Status位。用dna_io.check_status(dna, dnacalib, context="...")包住调用,否则错误会静默传播。 -
additional assemble script(AAS)有三个版本,只有一个是对的。
路径 签名 能用吗 mh_expression_editor/res/<rig>/rig_editing/additional_assemble_script.py2 个位置参数 ✗ lib 用 3 个参数调用,直接报错 mh_assemble_lib/resources/<rig>/ass_json.py3 个位置参数 △ 只摆位,不接 GUI → CTRL_expressions,RigLogic 不会被驱动mh_character_assembler/assets/<rig>/ass_json.py3 个位置参数 ✓ 摆位 + connect_expressions(),这才是装配该用的resources.HeadResources.aas_character_json指向正确的那个。另外 AAS 是尽力执行的: 它失败只降级成警告,不会中断装配 ——mh_assemble_lib的参考 AAS 在只装头部的场景里会因为 对GRP_C_eyesAim调cmds.parent(..., world=True)(该节点本就在世界层级)而报错,那是它对 场景结构的假设问题,不值得为此让整次装配失败。 -
embeddedRL4的cs参数要写逗号分隔的方向全名("left,up,front")。写缩写会让插件报Unknown direction然后静默退回默认值。 -
批处理下
cmds.refresh()不推动 DG。 要验证形变必须显式cmds.dgdirty(allPlugs=True)+cmds.dgeval("<mesh>Shape.outMesh"),否则读到的是缓存,会误判 RigLogic 没生效。