Appearance
翻译官
背景:外部动作进不来——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 让它们叠加不覆盖。
宁可不译,不可错译:空白可以修,错误只能猜。