如何在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); }
方案二:完全隔离头部骨骼与动画混合器
如果不需要头部保留动画动作,可以禁用动画混合器对头部骨骼的更新:
- 找到头部骨骼后,禁用其矩阵自动更新,这样动画混合器不会修改它的变换:
// 在模型加载完成后执行一次 const headBone = group.current.children[0].children[2].skeleton.getBoneByName('mixamorig9Head'); headBone.matrixAutoUpdate = false; // 初始化头部矩阵 headBone.updateMatrix();
- 然后在
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
相关产品推荐
相关产品推荐

