React+Three.js动画切换卡顿及THREE.PropertyBinding警告求助
React + Three.js 3D动画切换卡顿与警告问题解决
问题描述
使用React和Three.js开发3D网站时,切换动画会卡顿数秒,同时出现警告:THREE.PropertyBinding: No target node found for track: Armature.quaternion。尝试用fadeIn/fadeOut实现平滑过渡未达预期效果。
解决方案
1. 修复Armature.quaternion警告
原因:FBX动画文件中的骨骼根节点名称为Armature,但GLTF模型的根骨骼节点是Hips,导致动画轨道找不到目标节点。
解决方法:重映射动画轨道的节点路径,将Armature替换为模型中的根节点Hips:
// 封装重映射函数 function remapAnimationTracks(animation, oldRootPath, newRootPath) { animation.tracks.forEach(track => { track.name = track.name.replace(oldRootPath, newRootPath); }); } // 在加载FBX动画后调用 const { animations: typingAnimation } = useFBX(typing); remapAnimationTracks(typingAnimation[0], "Armature", "Hips"); const { animations: standingAnimation } = useFBX(standing); remapAnimationTracks(standingAnimation[0], "Armature", "Hips"); const { animations: fallingAnimation } = useFBX(falling); remapAnimationTracks(fallingAnimation[0], "Armature", "Hips");
2. 实现平滑动画过渡并解决卡顿
原因:原代码中直接reset动画并在清理函数中立即停止,导致过渡中断,引发卡顿。
解决方法:保存当前激活的动画引用,实现新旧动画的交叉淡入淡出:
// 添加ref保存当前激活的动画 const currentActionRef = useRef(null); useEffect(() => { const newAction = actions[animation]; if (!newAction) return; // 旧动画淡出,新动画淡入 if (currentActionRef.current) { currentActionRef.current.fadeOut(0.5); } newAction.reset().fadeIn(0.5).play(); currentActionRef.current = newAction; return () => { newAction.fadeOut(0.5); // 延迟停止动画,确保过渡完成 setTimeout(() => newAction.stop(), 500); }; }, [animation, actions]);
3. 性能优化减少卡顿
- 确保动画与模型的骨骼绑定完全匹配,避免运行时额外计算
- 使用
Preload组件预加载模型和动画资源,避免切换时加载延迟 - 关闭未使用的
morphTarget(如果模型不需要),减少渲染开销
修改后的完整代码
import { useAnimations, useFBX, useGLTF, Preload } from "@react-three/drei"; import { useFrame } from "@react-three/fiber"; import { useControls } from "leva"; import React, { useEffect, useRef } from "react"; import * as THREE from "three"; import avatar from '../assets/3D/avatar.glb' import typing from '../animations/Typing.fbx'; import standing from '../animations/Standing.fbx'; import falling from '../animations/Falling.fbx'; // 重映射动画轨道节点路径 function remapAnimationTracks(animation, oldRootPath, newRootPath) { animation.tracks.forEach(track => { track.name = track.name.replace(oldRootPath, newRootPath); }); } export function Avatar(props) { const { animation } = props; const { headFollow, cursorFollow, wireframe } = useControls({ headFollow: false, cursorFollow: false, wireframe: false, }); const avatarRef = useRef(); const currentActionRef = useRef(null); const { nodes, materials } = useGLTF(avatar); const { animations: typingAnimation } = useFBX(typing); remapAnimationTracks(typingAnimation[0], "Armature", "Hips"); typingAnimation[0].name = "Typing"; const { animations: standingAnimation } = useFBX(standing); remapAnimationTracks(standingAnimation[0], "Armature", "Hips"); standingAnimation[0].name = "Standing"; const { animations: fallingAnimation } = useFBX(falling); remapAnimationTracks(fallingAnimation[0], "Armature", "Hips"); fallingAnimation[0].name = "Falling"; const { actions } = useAnimations( [typingAnimation[0], standingAnimation[0], fallingAnimation[0]], avatarRef ); useFrame((state) => { if (headFollow) { avatarRef.current.getObjectByName("Head")?.lookAt(state.camera.position); } if (cursorFollow) { const target = new THREE.Vector3(state.mouse.x, state.mouse.y, 1); avatarRef.current.getObjectByName("Spine2")?.lookAt(target); } }); useEffect(() => { const newAction = actions[animation]; if (!newAction) return; if (currentActionRef.current) { currentActionRef.current.fadeOut(0.5); } newAction.reset().fadeIn(0.5).play(); currentActionRef.current = newAction; return () => { newAction.fadeOut(0.5); setTimeout(() => newAction.stop(), 500); }; }, [animation, actions]); useEffect(() => { Object.values(materials).forEach((material) => { material.wireframe = wireframe; }); }, [wireframe]); return ( <> <Preload /> <group {...props} ref={avatarRef} dispose={null}> <group rotation-x={-Math.PI / 2}> <primitive object={nodes.Hips} /> <skinnedMesh name="EyeLeft" geometry={nodes.EyeLeft.geometry} material={materials.Wolf3D_Eye} skeleton={nodes.EyeLeft.skeleton} morphTargetDictionary={nodes.EyeLeft.morphTargetDictionary} morphTargetInfluences={nodes.EyeLeft.morphTargetInfluences} /> <skinnedMesh name="EyeRight" geometry={nodes.EyeRight.geometry} material={materials.Wolf3D_Eye} skeleton={nodes.EyeRight.skeleton} morphTargetDictionary={nodes.EyeRight.morphTargetDictionary} morphTargetInfluences={nodes.EyeRight.morphTargetInfluences} /> <skinnedMesh name="Wolf3D_Head" geometry={nodes.Wolf3D_Head.geometry} material={materials.Wolf3D_Skin} skeleton={nodes.Wolf3D_Head.skeleton} morphTargetDictionary={nodes.Wolf3D_Head.morphTargetDictionary} morphTargetInfluences={nodes.Wolf3D_Head.morphTargetInfluences} /> <skinnedMesh name="Wolf3D_Teeth" geometry={nodes.Wolf3D_Teeth.geometry} material={materials.Wolf3D_Teeth} skeleton={nodes.Wolf3D_Teeth.skeleton} morphTargetDictionary={nodes.Wolf3D_Teeth.morphTargetDictionary} morphTargetInfluences={nodes.Wolf3D_Teeth.morphTargetInfluences} /> <skinnedMesh geometry={nodes.Wolf3D_Hair.geometry} material={materials.Wolf3D_Hair} skeleton={nodes.Wolf3D_Hair.skeleton} /> <skinnedMesh geometry={nodes.Wolf3D_Glasses.geometry} material={materials.Wolf3D_Glasses} skeleton={nodes.Wolf3D_Glasses.skeleton} /> <skinnedMesh name="Wolf3D_Outfit_Top" geometry={nodes.Wolf3D_Outfit_Top.geometry} material={materials.Wolf3D_Outfit_Top} skeleton={nodes.Wolf3D_Outfit_Top.skeleton} morphTargetDictionary={nodes.Wolf3D_Outfit_Top.morphTargetDictionary} morphTargetInfluences={nodes.Wolf3D_Outfit_Top.morphTargetInfluences} /> <skinnedMesh name="Wolf3D_Outfit_Bottom" geometry={nodes.Wolf3D_Outfit_Bottom.geometry} material={materials.Wolf3D_Outfit_Bottom} skeleton={nodes.Wolf3D_Outfit_Bottom.skeleton} morphTargetDictionary={nodes.Wolf3D_Outfit_Bottom.morphTargetDictionary} morphTargetInfluences={nodes.Wolf3D_Outfit_Bottom.morphTargetInfluences} /> <skinnedMesh name="Wolf3D_Outfit_Footwear" geometry={nodes.Wolf3D_Outfit_Footwear.geometry} material={materials.Wolf3D_Outfit_Footwear} skeleton={nodes.Wolf3D_Outfit_Footwear.skeleton} morphTargetDictionary={nodes.Wolf3D_Outfit_Footwear.morphTargetDictionary} morphTargetInfluences={nodes.Wolf3D_Outfit_Footwear.morphTargetInfluences} /> <skinnedMesh name="Wolf3D_Body" geometry={nodes.Wolf3D_Body.geometry} material={materials.Wolf3D_Body} skeleton={nodes.Wolf3D_Body.skeleton} morphTargetDictionary={nodes.Wolf3D_Body.morphTargetDictionary} morphTargetInfluences={nodes.Wolf3D_Body.morphTargetInfluences} /> </group> </group> </> ); }
内容的提问来源于stack exchange,提问作者Anubhav Singh
相关产品推荐
相关产品推荐

