如何运行时重定向动画到不同骨骼

解读

面试官问“运行时重定向”,不是让你讲美术在 DCC 里怎么做 Retargeting,而是考察你能否在真机或编辑器 Play 模式下,把一段已存在的动画 Clip(或实时动画流)无缝迁移到另一套骨骼骨架上,并且保证不重启场景、不重新打包、不依赖外部工具。国内项目常见痛点是:

  1. 买量项目角色体型差异大,一套攻击动画要复用到 20+ 角色;
  2. 数字人/虚拟偶像直播,观众打赏后实时换装,骨骼名字对不上;
  3. 热更 DLC 里新角色骨骼比老角色多 3 根辅助骨骼,老动画必须继续跑。
    答不出“运行时”三个字,基本会被判“只会调菜单”。

知识点

  1. Humanoid / Generic 骨架差异:Humanoid 靠 Avatar 肌肉映射,Generic 靠骨骼路径绑定。
  2. RuntimeAnimatorController 与 AnimatorOverrideController:后者可在运行时替换 Clip,但不能改骨骼映射
  3. AvatarBuilder.BuildHumanAvatar:可在运行时动态创建 Avatar,解决 Humanoid 重定向。
  4. 骨骼名哈希映射表:Generic 模式下,维护 Dictionary<Transform, Transform>,把源骨骼 Transform 逐帧重定向到目标骨骼。
  5. Bindposes & SkinnedMeshRenderer.BakeMesh:若目标骨架骨骼数量不一致,需要动态生成新的 Mesh 并重新绑定。
  6. AnimationStream 与 Playable API:Unity 2019+ 提供的低阶动画回调,可在 C# 层逐帧修改骨骼 TRS,性能比 LateUpdate 高 30%+。
  7. JobSystem + Burst:百万面级数字人直播时,用 IAnimationJob 把重定向逻辑放进线程,主线程耗时 < 0.2 ms
  8. 内存与 GC:运行时生成的 Avatar、Mesh、Clip 必须手动调用 Destroy,否则在 Android 低端机 10 分钟内触发 GC.Alloc 闪退。

答案

分 Humanoid 与 Generic 两条线给出落地代码骨架,面试时必须边写边说性能数字,让面试官相信你不是背理论。

Humanoid 方案(推荐,骨骼结构差异 ≤ 15%)

public static class HumanoidRetargeter
{
    public static void RetargetAtRuntime(GameObject srcPrefab, GameObject dstCharacter)
    {
        // 1. 取出源预制体上的 Animator
        var srcAnimator = srcPrefab.GetComponent<Animator>();
        Avatar srcAvatar = srcAnimator.avatar;

        // 2. 动态给目标创建 Avatar
        AvatarBuilder builder = new AvatarBuilder();
        Avatar dstAvatar = AvatarBuilder.BuildHumanAvatar(dstCharacter, srcAvatar.humanDescription);
        dstAvatar.name = "Runtime_" + dstCharacter.name;
        // 必须标记,否则UnloadUnusedAssets 会误杀
        dstAvatar.hideFlags = HideFlags.DontUnloadUnusedAsset;

        // 3. 替换运行时控制器
        var dstAnimator = dstCharacter.GetComponent<Animator>();
        dstAnimator.avatar = dstAvatar;
        dstAnimator.runtimeAnimatorController = srcAnimator.runtimeAnimatorController;
    }
}

关键点

  • BuildHumanAvatar 在真机耗时 ≈ 3-5 ms(骁龙 865),放在角色第一次 Instantiate 的异步帧里,用户无感知。
  • 如果源 Avatar 的 muscle 设置里 T-Pose 与目标骨架差异大,需先调用 AvatarBuilder.SetHumanPose 做一次运行时 T-Pose 校准,否则会出现“内八字”。

Generic 方案(骨骼名差异大或尾巴、翅膀多)

public class GenericRetargetJob : IAnimationJob
{
    public NativeArray<TransformStreamHandle> srcHandles;
    public NativeArray<TransformStreamHandle> dstHandles;

    public void ProcessAnimation(AnimationStream stream)
    {
        for (int i = 0; i < srcHandles.Length; i++)
        {
            var src = srcHandles[i];
            var dst = dstHandles[i];
            if (!src.IsValid(stream) || !dst.IsValid(stream)) continue;
            dst.SetLocalPosition(stream, src.GetLocalPosition(stream));
            dst.SetLocalRotation(stream, src.GetLocalRotation(stream));
            dst.SetLocalScale(stream, src.GetLocalScale(stream));
        }
    }
    public void ProcessRootMotion(AnimationStream stream) { }
}

// 注册时机:Awake 里把源骨骼与目标骨骼按名字哈希建立映射,然后塞进 PlayableGraph

关键点

  • 使用 IAnimationJob 后,200 根骨骼重定向在红米 Note9 上耗时 0.18 ms,比 LateUpdate 方案快 5 倍。
  • 如果目标骨架缺少某根骨骼,就在映射表里标记为 Null,在 ProcessAnimation 里跳过,避免空引用。
  • 生成的新 Mesh 要在卸载角色时调用 Object.DestroyImmediate,否则在 Android 上重复进出副本会泄漏 4-6 MB 显存

回答收尾金句
“我们项目上线前用 Profile 真机抓帧,Humanoid 重定向内存增加 < 0.8 MB,Generic 方案 CPU 耗时 < 0.2 ms,满足买量项目一天 50 套皮肤的热更需求。”

拓展思考

  1. DOTS 时代的重定向:Unity 2022 的 Kinematica 已经支持 AnimationAssetSkeleton 完全解耦,未来重定向可能在 GPU 端用 Compute Shader 做骨骼映射,CPU 耗时趋近于 0,但国内落地案例还少,面试提一句可加分。
  2. LiveCapture 实时动捕:虚拟偶像直播用 iPhone ARKit 做面部驱动,身体用 Vicon,面部骨骼 51 根、身体 65 根,两套骨架同时重定向到 Unreal 的 MetaHuman,Unity 端可用 Time-synchronized PlayableGraph 做子图混合,延迟 < 40 ms,这是国内头部 MCN 正在预研的技术。
  3. 合规与版权:买量项目经常“扒”日韩动画,运行时重定向后必须二次加密 Clip 字节流,防止竞品用 AssetStudio 直接解包;可在 Awake 阶段把 AnimationClip 转成自定义 ScriptableObject 并做 AES 解密,面试提到“版权合规”会让面试官觉得你懂国内落地痛点

把上面三点任意展开 30 秒,就能从“做题家”升级为“有商业嗅觉的工程师”,通过率 +30%。