今后的几篇郭先生主要说说three.js骨骼动画。three.js骨骼动画十分有意思,但是对于初学者来说,学起来要稍微困难一些,官方文档比较少,网上除了用圆柱体的例子就是引用外部模型的,想要熟练使用骨骼动画就需要不断地探索和练习。这篇是初探three.js骨骼动画,也不深入讲解,先说说它的实现和原理,然后一点一点解读官网案例,。
1. 骨骼动画的实现和原理 1. 骨骼动画的实现骨骼动画主要有以下三个部分构成:
(1) 几何体–在新版本中这个几何体要求必须是一个BufferGeometry而非Geometry,而骨骼动画需要的几何体还有两个十分重要的属性,
skinWeights : Array
当在处理一个 SkinnedMesh 时,每个顶点最多可以有 4 个相关的 bones 来影响它。 skinWeights 属性是一个权重队列,顺序同几何体中的顶点保持一致。因而,队列中的第一个 skinWeight 就对应几何体中的第一个顶点。由于每个顶点可以被 4 个 bones 营销,因而每个顶点的 skinWeights 就采用一个 Vector4 表示。skinWeight 矢量中每个元素的取值范围应该在 0 到 1 之间。例如,当设置为 0,骨骼对该顶点的位置没有影响。当设置为 0.5, 则对顶点的影响为 50%。 当设置为 100% 则对顶点的影响是 100%。如果矢量中只有一个骨骼与顶点相关联,则你只需要关注矢量中的第一个元素, 剩余的元素可以忽略,他们的值可以都设置为 0。
skinIndices : Array
就如同 skinWeights 属性一样。skinWeights 的值也是与几何体的顶点相对应。每个顶点可以最多有 4 个骨骼与之相关联。 因而第一个 skinIndex 就与几何体的第一个顶点相关联,skinIndex 的值就指明了影响该顶点的骨骼是哪个。例如,第一个顶点的值是 ( 10.05, 30.10, 12.12 ),第一个 skinIndex 的值是( 10, 2, 0, 0 ),第一个 skinWeight 的值是 ( 0.8, 0.2, 0, 0 )。上述值表明第一个顶点受到mesh.bones[10]骨骼的影响有 80%, 受到 skeleton.bones[2] 的影响是 20%,由于另外两个 skinWeight 的值是 0,因而他们对顶点没有任何影响。
(2) 其材质必须支持蒙皮,并且已经启用了蒙皮,既skinning = true;
(3) 创建骨骼和骨架
骨骼(Bone)其实就是一个Object3D对象,可以把骨架看成是人体骨架,假如脊柱的根节点,那么大腿就是下一级节点,小腿就是更下一级的节点,如果大腿转动,那么小腿在世界坐标系必然会动,而小腿动,不一定影响大腿。
现在我们假如有一个几何体(这个几何体加上带蒙皮的材质就是我们的腿的网格),想让这个几何体跟着这个骨骼运动,那么这个动画就是骨骼动画,现在我们假设bones[0]为大腿上端点,bones[1]为大小腿关节点,bones[2]为小腿下端点,这里如果我们把腿看成是圆柱体(官方案例就是这样做的),将极大的降低了难度,让heightSegments为2(就是分两段)也就生成了沿高度分布的3层点。
我们将最上层点对应的skinIndices设置成0,skinWeights设置成1。中间层点对应的skinIndices设置成1,skinWeights设置成1。最下层点对应的skinIndices设置成2,skinWeights设置成1。这样几何体的顶点就和骨骼的端点建立了联系。
2. 官网上的骨骼动画 1. 初始化蒙皮网格//这是生成蒙皮网格的主方法 initBones() { //下面是一些会用到的参数 var segmentHeight = 8; //每段的高度 var segmentCount = 4; //段数 var height = segmentHeight * segmentCount; //总高度 var halfHeight = height * 0.5; //一般高度 var sizing = { segmentHeight: segmentHeight, segmentCount: segmentCount, height: height, halfHeight: halfHeight }; var geometry = this.createGeometry( sizing ); //这是生成几何体的方法,主要是根据顶点生成对应的skinIndex和skinWeight属性 var bones = this.createBones( sizing ); //这是生成骨骼的方法 mesh = this.createMesh( geometry, bones ); //这是生成蒙皮网格的方法 mesh.scale.multiplyScalar( 1 ); scene.add( mesh ); this.render(); document.getElementById("loading").style.display = "none"; },