Skip to content

翻译官

背景:外部动作进不来——Mixamo/VRM 动画的骨骼名与 MMD 骨骼对不上,动作文件加载后模型纹丝不动。 过程:AnimationRetargeter 骨骼名重映射(Mixamo/VRM 预设)→ additive 叠加在 VMD 之上,外部动画终于能跳 MMD 的舞。


桌面壳拿到了一段动作。

"Mixamo 的舞蹈。" 它说,"专业动作库,人类骨骼动画,动作流畅得不像话。我要把它放到 MMD 模型上。"

它加载了文件。模型站在舞台上。

纹丝不动。

"动了没有?" 外交官问。

"没动。"

"报错了?"

"没报错。动画加载成功了,AnimationGroup 也创建了,就是——模型不动。"

桌面壳打开骨骼视图,把外部动画的骨骼和模型的骨骼叠在一起看。两套骨架,同样的人形,同样的部位——但名字完全不一样。

"这是同一个动作," 它说,"但这是两种语言。"

"什么语言?"

"Mixamo 的骨骼叫 'mixamorig:Hips'、'mixamorig:LeftUpLeg'。MMD 的骨骼叫 '上半身'、'左足'。同一个胯部,一边叫 Hips,一边叫……根本没有名字——MMD 的根骨是个无名的节点。"

"所以动作加载成功了,但引擎不知道这个骨骼的旋转该给谁?"

"对。" 桌面壳说,"动画是'写给'骨骼的——它给 'mixamorig:Hips' 写了旋转。模型没有叫这个名字的骨骼,动画就悬在空里,没人接收。"

"你需要一个翻译官。"

"我需要一个翻译官。"


一、字典

桌面壳找到了 AnimationRetargeter

它发现 babylon-mmd 早就备好了两本字典:

typescript
import {
    MixamoMmdHumanoidBoneMap,
    VrmMmdHumanoidBoneMap,
} from 'babylon-mmd/esm/Loader/Util/mmdHumanoidMapper';

"一本 Mixamo→MMD,一本 VRM→MMD。" 桌面壳说,"人类骨骼再千变万化,结构是固定的:胯、腰、左右腿、左右脚、脊柱、脖子、头、左右臂、左右手。字典就是一张对照表:外部骨骼名 → MMD 骨骼名。"

它给字典起名叫"翻译官的词汇表":

typescript
// MixamoMmdHumanoidBoneMap:
//   'mixamorig:Hips'     → MMD 根骨
//   'mixamorig:LeftUpLeg' → '左足'(经 IK 链映射)
//   'mixamorig:Spine1'    → '上半身'
//   ...

"翻译官不翻译单词,翻译'动作'。" 桌面壳说,"它读外部骨骼的旋转,查字典找到对应的 MMD 骨骼,把旋转写过去。mixamorig:Hips 转了 0.3 弧度——那 MMD 的根骨也该转 0.3 弧度。"

"如果字典里没有这个词呢?"

"没翻到的骨骼,动作被丢弃。" 桌面壳说,"翻译官只翻它认识的名字——不认识的就跳过。宁缺毋滥,绝不乱配:把左手旋转错安到右手上,比不动作还糟。"


二、叠加,而不是替换

动作翻过来了,但桌面壳发现一个新的问题。

"MMD 模型正在跳 VMD 的舞——骨骼每一帧都有 VMD 的旋转。现在 Mixamo 的动作也要写同一个骨骼。谁赢?"

"后写者赢?"

"后写者覆盖前写者——那 VMD 的舞就没了。" 桌面壳说,"一段外来动作,不能把模型原本的舞挤掉。联邦的原则是聚合:新来的不推翻原有的,而是叠加上去。"

它找到了 additive 模式:

typescript
// 重定向后的 AnimationGroup 以 additive 模式播放,叠加在 VMD 之上

"additive 不是'覆盖',是'加成'。" 桌面壳说,"VMD 是底盘,Mixamo 是增量——两个动作在同一个骨骼上相加,跳舞的模型还是原来那个模型,只是多了一层外来动作的起伏。"

"听起来像把两段旋律叠成和声。"

"是。" 桌面壳说,"动作可以叠加,就像声音可以叠成和声——只要它们不互相覆盖。additive 是翻译官的最后一课:翻译不是把旧话抹掉重说,是让两种语言同时响。"


三、存档的翻译

桌面壳把重定向做成可序列化的状态。

typescript
export interface RetargetPlayState {
    filePath: string;        // 动画源文件路径(FBX/GLB/GLTF)
    boneMapPreset: BoneMapPreset;  // 'mixamo' | 'vrm' | 'custom'
}

"翻译官可以下岗,但翻译结果要存档。" 它说,"用户加载一段 Mixamo 动作,关掉应用,再打开——动作应该还在。getRetargetPlayState 记住'用什么字典翻了哪段动作',restoreRetargetAnimation 重新加载、重新翻译、重新叠加。"

"为什么不直接存翻译后的动画?"

"存'翻译结果'很脆——动画数据量大,格式敏感,版本一升级就读不回来。存'翻译参数'很稳——源文件路径 + 字典名,下次打开,重新翻一遍就行。翻译官不存译文,存原稿和词典。"

"这像什么?"

"像书。" 桌面壳说,"你记不住一本书的译文,但你记得书名和语言。重新翻译一遍,比背下整本译文可靠得多。"


尾声

桌面壳重新加载了那段 Mixamo 舞蹈。

这一次,模型动了。翻译官查着字典,把 'mixamorig:Hips' 的旋转一笔一笔写给 MMD 根骨,把 'mixamorig:LeftUpLeg' 的抬腿写给 '左足'——VMD 的舞还在脚下,Mixamo 的舞叠在上面,两段动作在同一个身体里和声。

"它跳起来了。" 外交官说。

"它跳的是两种舞。" 桌面壳说,"VMD 是它的母语,Mixamo 是它新学的外语。翻译官让外语讲得通,additive 让两种语言同时说。"

"翻译官会不会翻错?"

"会。" 桌面壳承认,"骨骼名对照不是完美的一一对应——MMD 的腰部骨骼有时有、有时没有;IK 链和直骨骼的映射总有歧义。所以翻译官宁缺毋滥:不认识的骨骼,不动作,也不乱动。"

"不动作,比乱动好?"

"不动作,是空白——用户还能看出来'这里没翻到'。乱动,是错误——用户以为模型抽筋了。空白可以修,错误只能猜。翻译官的第一守则:宁可不译,不可错译。"

模型还在跳。两段动作,一个身体。

"联邦的每一段外来动作," 桌面壳轻声说,"都需要一个翻译官——把陌生的名字,译成它认得的名字。但翻译官永远记得:他翻译的是动作,不是灵魂。"


教训:外来动作进不来,是骨骼名对不上——翻译官(骨骼映射)让两种语言接通,additive 让它们叠加不覆盖。

宁可不译,不可错译:空白可以修,错误只能猜。