You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Three.js中忽略特定骨骼的混合器动画,实现头部平滑追踪

解决Three.js中骨骼动画与手动头部旋转的冲突问题

问题分析

动画混合器(AnimationMixer)每次调用update()时,会根据动画剪辑更新所有骨骼的变换,这会覆盖你手动设置的头部骨骼旋转。另外你的slerpRotateObject函数中lookAt的参数使用错误,导致旋转计算逻辑不正确。

修正slerp旋转逻辑

首先修复lookAt的参数错误,Matrix4.lookAt需要传入骨骼世界位置、目标点位置和向上向量,这样才能正确计算头部朝向目标的旋转矩阵:

function slerpRotateObject(headWorldPos, targetWorldPos, object, dt) {
  const startRotation = object.quaternion.clone();

  // 计算正确的朝向矩阵:从头部位置看向目标点
  const rotationMatrix = new THREE.Matrix4().lookAt(
    headWorldPos, 
    targetWorldPos, 
    new THREE.Vector3(0, 1, 0) // 保持头部竖直向上
  );

  const targetQuaternion = new THREE.Quaternion().setFromRotationMatrix(rotationMatrix);
  
  // 平滑插值因子,dt为帧间隔时间
  const LERP_SPEED = 5; // 调整这个值控制平滑速度,越大越快
  const t = Math.min(dt * LERP_SPEED, 1);

  // 从当前旋转插值到目标旋转
  startRotation.slerp(targetQuaternion, t);
  object.quaternion.copy(startRotation);
}

解决动画冲突的两种方案

方案一:保留动画对头部的自然影响(推荐)

让动画混合器正常更新头部骨骼,之后基于动画后的头部旋转,再平滑过渡到目标点。这样头部会保留动画中的自然动作(比如行走时的轻微摆动),同时响应手动追踪:

useFrame((_, deltaTime) => {
  // 先让动画混合器更新所有骨骼(包括头部)
  mixer.update(deltaTime);
  // 再执行头部追踪逻辑
  rotateHeadTowardsMouse(mouse, deltaTime);
});

function rotateHeadTowardsMouse(beginHeadTracking, group, canvasRef, mouse, deltaTime) {
  if (!beginHeadTracking) return;

  const headBone = group?.current?.children?.[0]?.children?.[2]?.skeleton?.getBoneByName('mixamorig9Head');
  if (!headBone) return;

  // 获取头部骨骼的世界位置
  const headWorldPos = headBone.getWorldPosition(new THREE.Vector3());
  // 计算鼠标对应的世界目标点(根据你的需求调整坐标转换逻辑)
  const targetWorldPos = new THREE.Vector3(mouse.x, mouse.y - 250, 1500);

  // 基于动画更新后的头部旋转,平滑过渡到目标旋转
  slerpRotateObject(headWorldPos, targetWorldPos, headBone, deltaTime);
}

方案二:完全隔离头部骨骼与动画混合器

如果不需要头部保留动画动作,可以禁用动画混合器对头部骨骼的更新:

  1. 找到头部骨骼后,禁用其矩阵自动更新,这样动画混合器不会修改它的变换:
// 在模型加载完成后执行一次
const headBone = group.current.children[0].children[2].skeleton.getBoneByName('mixamorig9Head');
headBone.matrixAutoUpdate = false;
// 初始化头部矩阵
headBone.updateMatrix();
  1. 然后在useFrame中,先更新动画混合器,再手动控制头部旋转并更新矩阵:
useFrame((_, deltaTime) => {
  mixer.update(deltaTime);
  rotateHeadTowardsMouse(mouse, deltaTime);
});

function rotateHeadTowardsMouse(beginHeadTracking, group, canvasRef, mouse, deltaTime) {
  if (!beginHeadTracking) return;

  const headBone = group?.current?.children?.[0]?.children?.[2]?.skeleton?.getBoneByName('mixamorig9Head');
  if (!headBone) return;

  const headWorldPos = headBone.getWorldPosition(new THREE.Vector3());
  const targetWorldPos = new THREE.Vector3(mouse.x, mouse.y - 250, 1500);

  slerpRotateObject(headWorldPos, targetWorldPos, headBone, deltaTime);
  
  // 手动更新头部骨骼的矩阵和矩阵世界
  headBone.updateMatrix();
  headBone.updateMatrixWorld(true);
}

额外提示

  • 调整slerpRotateObject中的LERP_SPEED可以控制头部追踪的平滑程度,值越大追踪越快,越小越平缓。
  • 鼠标坐标转世界坐标的逻辑可能需要根据你的相机设置调整,确保targetWorldPos在正确的空间中。

内容的提问来源于stack exchange,提问作者mepley

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 13:17:45