Three.js骨骼动画(SkinnedMesh
)
关于骨骼动画,如果你有一定了解,可以直接阅读本文,如果从来没有关于骨骼动画的任何概念,建议可以先学习本站发布的three.js视频教程第十二章,打开源码案例查看下课件源码案例效果,这样现有一个直观的感性认识,然后再阅读本文关于骨骼动画的知识点总结,这样效果应该会更好。
Threejs骨骼动画需要通过骨骼网格模型SkinnedMesh
来实现,一般来说一个骨骼动画模型都是美术创建,然后程序员通过threejs引擎加载解析,为了让大家更深入理解骨骼动画,下面就通过一个通过threejs程序创建一个具有骨骼动画的骨骼网格模型SkinnedMesh
。
程序创建一个骨骼动画
下面的的代码通过SkinnedMesh
构造函数创建一个骨骼动画模型,如果你想深入理解骨骼动画可以研究一下下面的代码,如果不想深入研究,可以大致看下,应用的时候,只需要会加载解析骨骼动画模型就可以。
/**
* 创建骨骼网格模型SkinnedMesh
*/
// 创建一个圆柱几何体,高度120,顶点坐标y分量范围[-60,60]
var geometry = new THREE.CylinderGeometry(5, 10, 120, 50, 300);
geometry.translate(0, 60, 0); //平移后,y分量范围[0,120]
console.log("name", geometry.vertices); //控制台查看顶点坐标
//
/**
* 设置几何体对象Geometry的蒙皮索引skinIndices、权重skinWeights属性
* 实现一个模拟腿部骨骼运动的效果
*/
//遍历几何体顶点,为每一个顶点设置蒙皮索引、权重属性
//根据y来分段,0~60一段、60~100一段、100~120一段
for (var i = 0; i < geometry.vertices.length; i++) {
var vertex = geometry.vertices[i]; //第i个顶点
if (vertex.y <= 60) {
// 设置每个顶点蒙皮索引属性 受根关节Bone1影响
geometry.skinIndices.push(new THREE.Vector4(0, 0, 0, 0));
// 设置每个顶点蒙皮权重属性
// 影响该顶点关节Bone1对应权重是1-vertex.y/60
geometry.skinWeights.push(new THREE.Vector4(1 - vertex.y / 60, 0, 0, 0));
} else if (60 < vertex.y && vertex.y <= 60 + 40) {
// Vector4(1, 0, 0, 0)表示对应顶点受关节Bone2影响
geometry.skinIndices.push(new THREE.Vector4(1, 0, 0, 0));
// 影响该顶点关节Bone2对应权重是1-(vertex.y-60)/40
geometry.skinWeights.push(new THREE.Vector4(1 - (vertex.y - 60) / 40, 0, 0, 0));
} else if (60 + 40 < vertex.y && vertex.y <= 60 + 40 + 20) {
// Vector4(2, 0, 0, 0)表示对应顶点受关节Bone3影响
geometry.skinIndices.push(new THREE.Vector4(2, 0, 0, 0));
// 影响该顶点关节Bone3对应权重是1-(vertex.y-100)/20
geometry.skinWeights.push(new THREE.Vector4(1 - (vertex.y - 100) / 20, 0, 0, 0));
}
}
// 材质对象
var material = new THREE.MeshPhongMaterial({
skinning: true, //允许蒙皮动画
// wireframe:true,
});
// 创建骨骼网格模型
var SkinnedMesh = new THREE.SkinnedMesh(geometry, material);
SkinnedMesh.position.set(50, 120, 50); //设置网格模型位置
SkinnedMesh.rotateX(Math.PI); //旋转网格模型
scene.add(SkinnedMesh); //网格模型添加到场景中
/**
* 骨骼系统
*/
var Bone1 = new THREE.Bone(); //关节1,用来作为根关节
var Bone2 = new THREE.Bone(); //关节2
var Bone3 = new THREE.Bone(); //关节3
// 设置关节父子关系 多个骨头关节构成一个树结构
Bone1.add(Bone2);
Bone2.add(Bone3);
// 设置关节之间的相对位置
//根关节Bone1默认位置是(0,0,0)
Bone2.position.y = 60; //Bone2相对父对象Bone1位置
Bone3.position.y = 40; //Bone3相对父对象Bone2位置
// 所有Bone对象插入到Skeleton中,全部设置为.bones属性的元素
var skeleton = new THREE.Skeleton([Bone1, Bone2, Bone3]); //创建骨骼系统
// console.log(skeleton.bones);
// 返回所有关节的世界坐标
// skeleton.bones.forEach(elem => {
// console.log(elem.getWorldPosition(new THREE.Vector3()));
// });
//骨骼关联网格模型
SkinnedMesh.add(Bone1); //根骨头关节添加到网格模型
SkinnedMesh.bind(skeleton); //网格模型绑定到骨骼系统
console.log(SkinnedMesh);
/**
* 骨骼辅助显示
*/
var skeletonHelper = new THREE.SkeletonHelper(SkinnedMesh);
scene.add(skeletonHelper);
// 转动关节带动骨骼网格模型出现弯曲效果 好像腿弯曲一样
skeleton.bones[1].rotation.x = 0.5;
skeleton.bones[2].rotation.x = 0.5;
程序实现骨骼动画
通过骨骼骨骼系统代码实现骨骼动画效果
var n = 0;
var T = 50;
var step = 0.01;
// 渲染函数
function render() {
renderer.render(scene, camera);
requestAnimationFrame(render);
n += 1;
if (n < T) {
// 改变骨关节角度
skeleton.bones[0].rotation.x = skeleton.bones[0].rotation.x - step;
skeleton.bones[1].rotation.x = skeleton.bones[1].rotation.x + step;
skeleton.bones[2].rotation.x = skeleton.bones[2].rotation.x + 2 * step;
}
if (n < 2 * T && n > T) {
skeleton.bones[0].rotation.x = skeleton.bones[0].rotation.x + step;
skeleton.bones[1].rotation.x = skeleton.bones[1].rotation.x - step;
skeleton.bones[2].rotation.x = skeleton.bones[2].rotation.x - 2 * step;
}
if (n === 2 * T) {
n = 0;
}
}
render();
解析外部骨骼动画模型
开发的时候,3D美术如果导出一个包含骨骼动画数据的三维模型,你可以通过下面的代码进行加载解析,查看骨骼动画的运动效果。
骨骼动画除了需要创建一个骨骼动画模型SkinnedMesh
外,还需要通过帧动画存储相关的关节动画数据。
var mixer = null; //声明一个混合器变量
loader.load("./marine_anims_core.json", function(obj) {
console.log(obj)
scene.add(obj); //添加到场景中
//从返回对象获得骨骼网格模型
var SkinnedMesh = obj.children[0];
//骨骼网格模型作为参数创建一个混合器
mixer = new THREE.AnimationMixer(SkinnedMesh);
// 查看骨骼网格模型的帧动画数据
// console.log(SkinnedMesh.geometry.animations)
// 解析跑步状态对应剪辑对象clip中的关键帧数据
var AnimationAction = mixer.clipAction(SkinnedMesh.geometry.animations[1]);
// 解析步行状态对应剪辑对象clip中的关键帧数据
// var AnimationAction = mixer.clipAction(SkinnedMesh.geometry.animations[3]);
AnimationAction.play();
// 骨骼辅助显示
// var skeletonHelper = new THREE.SkeletonHelper(SkinnedMesh);
// scene.add(skeletonHelper);
})
// 创建一个时钟对象Clock
var clock = new THREE.Clock();
// 渲染函数
function render() {
renderer.render(scene, camera);
requestAnimationFrame(render);
if (mixer !== null) {
//clock.getDelta()方法获得两帧的时间间隔
// 更新混合器相关的时间
mixer.update(clock.getDelta());
}
}
render();
骨骼动画模型SkinnedMesh
骨骼动画模型SkinnedMesh
的基类是网格模型Mesh
,你也可以把SkinnedMesh
理解为一个网格模型对象,只是模型表面可以随着骨骼系统产生骨骼动画效果。关于骨骼动画模型SkinnedMesh
类的属性和方法除了查找文档中关于SkinnedMesh
的介绍,还可以查找基类Mesh
和Object3D
。
SkinnedMesh
构造函数和Mesh
一样两个参数分别是几何体和材质对象。网格模型Mesh
的材质对象,骨骼动画网格模型SkinnedMesh
都是适用的。
骨骼动画几何体皮肤顶点权重属性.skinWeights
.skinWeights
表示的是几何体顶点权重数据,当使用骨骼动画网格模型SkinnedMesh的时候, 每个顶点最多可以有4个骨骼对象Bone影响它. skinWeights属性是一个权重值数组,对应于几何体中顶点的顺序. 因此,例如,第一个skinWeight将对应于几何体中的第一个顶点. 由于每个顶点可以被4个骨骼Bone修改,因此Vector4用于表示该顶点的皮肤skin的权重weights.
向量vector分量的值通常应在0和1之间. 例如,当设置为0时,骨骼变换bone transformation将不起作用. 设置为0.5时,将产生50%的影响. 设置为100%时,会产生100%的影响. 如果只有一个骨骼与顶点关联,那么您只需要考虑设置向量的第一个分量大小,其余的可以忽略并设置为0.
骨骼动画几何体皮肤顶点索引属性.skinIndices
.skinIndices
就像skinWeights属性一样,skinIndices的值对应于几何体的顶点. 每个顶点最多可以有4个与之关联的骨骼. 因此,如果您查看第一个顶点和第一个皮肤骨骼索引skinIndex,它将告诉您与该顶点关联的骨骼. 例如,第一个顶点坐标( 10.05, 30.10, 12.12 ). 第一个皮肤骨骼索引skin index值是( 10, 2, 0, 0 ). 第一个皮肤权重skin weight值是( 0.8, 0.2, 0, 0 ). 这两个皮肤骨骼索引skin index和皮肤权重skin weight数据表达的意思是骨骼10mesh.bones[10]对第一个顶点坐标影响权重80%. 骨骼2skeleton.bones[2]对第一个顶点的影响权重20%. 接下来的两个骨骼权重值的权重为0,因此对顶点坐标没有任何影响.
骨骼动画顶点数据
骨骼动画的相关的一些顶点数据,主要是描述几何体表面的顶点数据是如何受骨骼系统关节运动影响的。加载外部模型你可以访问骨骼动画网格模型的几何体对象查看骨骼动画相关顶点数据。网格模型的几何体类型可能是Geometry
,也可能是BufferGeometry
,一般是缓冲类型的几何体BufferGeometry
比较常见。
Geometry
的骨骼动画顶点数据,直接查看.skinWeights
和.skinIndices
属性
console.log('骨骼动画皮肤顶点权重数据',SkinnedMesh.geometry.skinWeights);
console.log('骨骼动画皮肤顶点索引数据',SkinnedMesh.geometry.skinIndices);
BufferGeometry
的骨骼动画顶点数据,如果你熟悉BufferGeometry
的结构,应该都知道该几何体通过.attributes
属性存储各种顶点数据。
console.log('骨骼动画皮肤顶点权重数据',SkinnedMesh.geometry.attributes.skinWeights);
console.log('骨骼动画皮肤顶点索引数据',SkinnedMesh.geometry.attributes.skinIndices);
骨骼系统
Bone
的基类是Object3D
,通过Bone
构造函数来创建一个骨头关节,所有的骨头关键对象Bone
集合构成一个骨骼动画模型的骨骼系统对象Skeleton
。
Bone
和Skeleton
骨骼系统的主要作用就是控制模型表面随着骨骼转动变化,就像人体的皮肤随着自身的骨骼发生变化一样。
/**
* 骨骼系统
*/
var Bone1 = new THREE.Bone(); //关节1,用来作为根关节
var Bone2 = new THREE.Bone(); //关节2
var Bone3 = new THREE.Bone(); //关节3
// 设置关节父子关系 多个骨头关节构成一个树结构
Bone1.add(Bone2);
Bone2.add(Bone3);
// 设置关节之间的相对位置
//根关节Bone1默认位置是(0,0,0)
Bone2.position.y = 60; //Bone2相对父对象Bone1位置
Bone3.position.y = 40; //Bone3相对父对象Bone2位置
// 所有Bone对象插入到Skeleton中,全部设置为.bones属性的元素
var skeleton = new THREE.Skeleton([Bone1, Bone2, Bone3]); //创建骨骼系统
骨骼系统属性.skeleton
通过SkinnedMesh
的骨骼系统属性.skeleton
可以访问骨骼动画模型的骨骼系统数据。对于程序员而言一般不需要程序创建这些东西,大多数情况下这个工作都是美术来做,程序员只需要会加载解析控制就可以。
console.log('查看骨骼动画网格模型的骨骼系统数据',SkinnedMesh.skeleton);
本文转载地址:我的个人技术博客