SkinnedMesh克隆后骨骼动画出错
在生产中,碰到这样的一个需求,就是对于同一个模型文件,加载多次。在最初的想法中,考虑到材质的复用和效率问题,多次加载某个模型直接使用Object3D.clone()
方法实现。
但是,在某次加载拥有骨骼动画的模型时出了问题,我们现在先来简单的复现一下这个问题:基于Three.js官方的例子进行修改
创建SkinnedMesh
的代码如下
function initBones() {
// ...
const geometry = createGeometry(sizing);
const bones = createBones(sizing);
mesh = createMesh(geometry, bones);
mesh.scale.multiplyScalar(1);
// scene.add( mesh );
const cmesh = mesh.clone()
scene.add(cmesh)
console.log(mesh.skeleton === cmesh.skeleton)
}
mesh
没有被添加到场景中,取而代之的是它的克隆对象cmesh
。现在,再通过GUI
修改Bone
对象的位置,会发现场景中的cmesh
不会发生变化。
一定是在clone
时出了问题,看一下clone()
方法。在SkinnedMesh
中,没有clone()
方法,它的clone()
方法继承自Object3D.clone()
// Object3D
clone(recursive) {
return new this.constructor().copy(this, recursive);
}
可以看到,clone()
间接的调用了copy()
方法,在SkinnedMesh
中,重写了这个方法:
// SkinnnedMesh
copy(source, recursive) {
super.copy(source, recursive);
this.bindMode = source.bindMode;
this.bindMatrix.copy(source.bindMatrix);
this.bindMatrixInverse.copy(source.bindMatrixInverse);
this.skeleton = source.skeleton;
return this;
}
骨骼可以控制模型,是因为Skeleton.update()
方法。具体的内容可以通过骨骼原理及源码 - 掘金 (juejin.cn)了解到。
现在通过GUI
做一些改动。把Bone 0
的position.x
改为10.
然后在
Skeleton.update()
中打个断点。
可以看到,
Bone 0
的matrix
并没有更新。
因为Skeleton.bones
中的Bone
,都是mesh
的子对象,只有当mesh
在场景中时,才会更新matrix
(Three.js更新矩阵的策略是在绘制时更新,而不是每次修改position
就会更新)。
function createMesh(geometry, bones) {
// ...
mesh.add(bones[0]);
mesh.bind(skeleton);
// ...
return mesh;
}
对mesh
进行clone
,这些Bone
也应该被clone
,但是,对于这些Bone
来说,是深拷贝,对于mesh.skeleton
是浅拷贝,这就出现了错乱:
skeleton
依赖mesh.children
中的Bone
更新矩阵。在mesh.clone()
时,对children
进行深拷贝,cmesh.children
中的Bone
和mesh.children
中的Bone
不是相同的对象;而skeleton
使用浅拷贝,skeleton.bones
需要依赖于mesh
进行矩阵更新,而mesh
没有出现在场景中,就导致了skeleton.bones
的矩阵没有更新,从而cmesh
没有办法被Bone
控制。