Skip to content

154. 骨骼动画的重定向(二)

Published:

上节加载了人物模型,播放了各自的骨骼动画:

2026-03-15 19.52.49.gif

如何把骨骼动画应用到另一个呢?

首先,我们需要在女孩的模型那里把这个动画导出:

image.png

let commonAnimation = null;
let pendingResolve = null;

export function waitForCommonAnimation() {
  if (commonAnimation) {
    return Promise.resolve(commonAnimation);
  }
  return new Promise((resolve) => {
    pendingResolve = resolve;
  });
}

image.png

const clip0 = gltf.animations[0];
if (clip0) {
    // 存为公共动画,给其他模型重定向使用
    commonAnimation = clip0;
    if (pendingResolve) {
      pendingResolve(clip0);
      pendingResolve = null;
    }

    // 自己也播放这个动画
    const clipAction = mixer.clipAction(clip0);
    clipAction.play();
}

在另一个模型那里引入,播放一下:

image.png

waitForCommonAnimation().then((clip) => {
    const clipAction = mixer.clipAction(clip);
    clipAction.play();
});

2026-03-15 20.33.41.gif

这样就可以了。

当然,这里是因为两个人物的骨骼是一致的,所以可以直接用。

有时候骨骼动画直接在另一个模型播放有问题的时候,就要用 SkecthUtils.retargetClip 了

在 mesh2.js 里导出 mesh 和动画:

image.png

export let sourceRoot = null;
export let sourceClip = null;

function findSkinnedMesh(root) {
  let skinned = null;
  root.traverse((obj) => {
    if (!skinned && obj.isSkinnedMesh) {
      skinned = obj;
    }
  });
  return skinned;
}
// 源:Michelle 的 SkinnedMesh + 第 0 个动画
sourceSkin = findSkinnedMesh(gltf.scene);
sourceClip = gltf.animations[0] || null;

if (sourceClip) {
    const clipAction = mixer.clipAction(sourceClip);
    clipAction.play();
}

在 mesh.js 用重定向的方式来修改这个动画:

image.png

image.png

function findSkinnedMesh(root) {
  let skinned = null;
  root.traverse((obj) => {
    if (!skinned && obj.isSkinnedMesh) {
      skinned = obj;
    }
  });
  return skinned;
}
function tryPlayRetarget() {
    if (!soldierSkin || !sourceSkin || !sourceClip) {
      requestAnimationFrame(tryPlayRetarget);
      return;
    }

    try {
      const retargetedClip = SkeletonUtils.retargetClip(
        sourceSkin, // 源:Michelle 的 SkinnedMesh
        soldierSkin, // 目标:Soldier 的 SkinnedMesh
        sourceClip,
        {
          hip: 'mixamorigHips',
          scale: 1,
          getBoneName: (bone) => bone.name,
        },
      );

      danceMixer = new THREE.AnimationMixer(soldierSkin);
      const danceAction = danceMixer.clipAction(retargetedClip);
      danceAction.setLoop(THREE.LoopRepeat);
      danceAction.play();
    } catch (err) {
      console.warn('跳舞动画重定向失败:', err);
    }
}

tryPlayRetarget();

当然,因为现在动画没啥错乱的,所以直接播放另一个模型的动画也行。

如果骨骼动画有错乱就可以用这种方式来做一些局部修改。

案例代码上传了小册仓库

总结

这节我们实现了骨骼动画的复用。

如果两者骨骼模型一样,那可以直接用另一个模型播放这个模型的骨骼动画。

如果播放的时候有一些错乱,就需要用 SkeletonUtils.retargetClip 做一些局部修改了。

评论