Three.js中追踪动画对象移动:回调触发但对象位置未更新问题
问题:glTF动画修改Object3D位置但属性值未更新,无法直接检测
加载glTF文件后,我遍历所有动画,寻找修改object.position的动画轨道。尝试直接给对应Object3D添加回调但未触发,于是找到下游第一个Mesh并添加回调,回调成功触发,但动画引用的原Object3D的位置值从未发生改变。请问我忽略了什么?
for (let ai = 0, al = gltf.animations.length; ai < al; ai++ ) { for (let ti = 0, tl = gltf.animations[ai].tracks.length; ti < tl; ti++ ) { let parts = gltf.animations[ai].tracks[ti].name.split('.'); // name is 'uuid'.position if ( parts && parts.length > 1 && parts[1] == 'position') { var varObj = scene.getObjectByProperty('uuid', parts[0]); // 找到下游第一个Mesh来添加回调 let c = varObj.children[0]; let d = 1; while ( !c.isMesh ) { c = c.children[0]; d = d + 1; } c.onBeforeRender = function() { // 向上遍历找到位置变化的父对象 let p = this.parent; let x = this.d - 1; while ( x ) { p = p.parent; x = x - 1; } this.b4pos.set(p.position.x, p.position.y, p.position.z); }; c.onAfterRender = function() { // 向上遍历找到位置变化的父对象 let p = this.parent; let x = this.d - 1; while ( x ) { p = p.parent; x = x - 1; } let delta = (p.position.x - this.b4pos.x) + (p.position.y - this.b4pos.y) + (p.position.z - this.b4pos.z); if ( delta != 0 ) { console.log('Object3D associated with mesh is moved'); } }; c.b4pos = new THREE.Vector3(0, 0, 0); c.d = d; } } }
解答
核心原因
- 动画直接操作矩阵而非属性:Three.js的
AnimationMixer处理glTF动画时,默认直接修改对象的本地变换矩阵(matrix属性),不会自动同步position、rotation这类直观属性。只有手动修改这些属性时才会反向更新矩阵,动画修改矩阵时属性值不会自动刷新,所以你读取到的p.position始终是旧值。 - 空节点不触发渲染回调:
onBeforeRender/onAfterRender仅对Mesh、Line等可渲染对象生效,普通空节点Object3D不会触发回调,这也是你直接给目标Object3D加回调无效的原因。
解决方案
1. 同步矩阵到属性(直观但有开销)
在读取目标对象位置前,强制将矩阵中的变换同步到position等属性:
// 在获取p.position前添加这行,分解矩阵到属性 p.matrix.decompose(p.position, p.quaternion, p.scale);
修改后的回调示例:
c.onBeforeRender = function() { let p = this.parent; let x = this.d - 1; while ( x ) { p = p.parent; x = x - 1; } // 同步矩阵到position属性 p.matrix.decompose(p.position, p.quaternion, p.scale); this.b4pos.set(p.position.x, p.position.y, p.position.z); }; c.onAfterRender = function() { let p = this.parent; let x = this.d - 1; while ( x ) { p = p.parent; x = x - 1; } // 同步矩阵到position属性 p.matrix.decompose(p.position, p.quaternion, p.scale); let delta = (p.position.x - this.b4pos.x) + (p.position.y - this.b4pos.y) + (p.position.z - this.b4pos.z); if ( delta != 0 ) { console.log('Object3D associated with mesh is moved'); } };
2. 直接从矩阵提取位置(更高效)
如果不需要同步属性,可直接从矩阵中提取位置值,避免分解整个矩阵的开销:
// 获取本地位置 this.b4pos.setFromMatrixPosition(p.matrix); // 如果需要世界位置,先更新世界矩阵 p.updateMatrixWorld(true); this.b4pos.setFromMatrixPosition(p.matrixWorld);
额外优化
提前缓存目标Object3D,不用每次回调都向上遍历父节点,提升性能:
// 在外部缓存目标对象 const targetObj = varObj; c.targetObj = targetObj; // 回调中直接使用 let p = this.targetObj;
内容的提问来源于stack exchange,提问作者MarkTheMule
相关产品推荐
相关产品推荐

