前言
骨骼动画是一种计算机动画技术,它通过将虚拟模型的结构分解为许多骨骼并随着动画的播放而移动这些骨骼,从而为观众呈现出更为逼真的动画效果。骨骼动画通常使用三维计算机图形软件来创建,可以应用于各种动画领域,包括游戏制作、电影制作、视觉特效等。
一、骨骼动画
1.简介
Three.js是一个JavaScript库,用于在Web浏览器中创建3D图形和动画。它提供了骨骼动画的支持,可以使用Three.js的骨骼系统来创建复杂的动画效果。
骨骼动画是基于骨骼系统的动画技术,通过在模型上创建多个关节和骨骼,然后对这些骨骼进行动画变换,来实现模型的动画效果。在Three.js中,使用SkinnedMesh和Skeleton类来实现骨骼动画。
SkinnedMesh是一个具有骨骼系统的网格对象。它包含一个Geometry对象和一个Material对象,可以使用它来创建复杂的角色动画。Skeleton类代表骨骼系统,包含了多个骨骼节点。通过设置骨骼节点的权重、旋转和偏移量,可以实现复杂的动画效果。
在使用Three.js创建骨骼动画时,需要先构建模型的骨骼系统,然后创建SkinnedMesh和Skeleton对象,并将它们关联起来。然后,使用Tween.js等动画库或手动控制骨骼节点的变换,来实现模型的动画效果。
总之,Three.js提供了强大的骨骼动画支持,可以用于创建各种复杂的动画效果。
2.骨骼动画相关概念
2.1 SkinnedMesh骨骼网络模型
SkinnedMesh是Three.js中的一个重要概念,它可以用于模拟动态物体的动画,例如人物、动物或机器人。SkinnedMesh是基于骨骼动画的,其基本原理是将一个3D模型分成多个部分,然后对每个部分定义一个骨骼和一个权重,通过改变骨骼的位置和角度来改变模型的形状及动画。
具体来说,SkinnedMesh由以下几个组成部分:
-
Geometry(几何体):描述模型的形状、构造和纹理等信息。
-
Skeleton(骨架):由多个Bone(骨骼)组成的层级结构,用于描述模型的动态变化。
-
Material(材质):定义模型的表面颜色、纹理和光照等信息。
-
Animations(动画):定义模型的动态行为,包括运动、表情和姿势等。
SkinnedMesh通过将骨骼和权重应用到几何体上来控制模型的形状和动画。每个顶点都被分配到一个或多个骨骼上,并指定了它们所对应的权重,这些权重决定了每个骨骼对顶点的影响程度。当骨骼的位置和角度发生变化时,模型的形状也会相应地发生变化,从而实现动态的动画效果。
2.2 Bone骨骼关节
在Three.js中,Bone(骨骼)是一种对象,用于控制网格模型的变形动画。Bone通常作为骨骼层次结构的一部分出现,其中包括多个骨骼对象,这些骨骼对象共同组成了骨架。
每个Bone对象都有一个本地坐标系,该坐标系相对于其父骨骼或基础骨骼(如果它是根骨骼)定义了其位置和方向。Bone对象还包括权重属性,用于与Mesh对象中的顶点进行绑定,以便在对骨骼进行旋转和变换时,可以将顶点相应地移动。
Bone对象也可以包含子骨骼。这些子骨骼连接到父骨骼的本地坐标系中,在父骨骼移动时相对移动。这些子骨骼可以用于创建更复杂的变形动画,例如人物的手臂和腿的弯曲。
在Three.js中,可以使用Skeleton对象来管理Bone对象和它们的骨骼层次结构。Skeleton对象还可以设置蒙皮权重,以便将顶点绑定到骨骼上。
2.2 Skeleton骨架
Skeleton是Three.js中的一个动画对象,它由骨骼和骨骼的关节组成,并且可以在屏幕上移动和旋转。Skeleton通过将动画应用于每个骨骼来实现骨骼动画。
在Three.js中使用Skeleton对象时,需要创建一个包含Mesh对象和Skeleton对象的SkinnedMesh对象。SkinnedMesh对象连接网格和骨骼,允许骨骼动画应用于网格。在创建SkinnedMesh对象后,将骨骼和网格添加到Skeleton对象中即可。
Skeleton对象有许多方法可用于控制骨骼的动画和变换,例如:updateMatrixWorld()方法可以更新骨骼和网格的全局变换矩阵,applyMatrix()方法可以应用变换矩阵到骨骼和网格中。
使用Skeleton对象可以创建非常复杂的动画,例如人物动画,驾驶员动画等。
3.案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
*{
margin: 0;
padding: 0;
}
</style>
</head>
<body>
</body>
</html>
<script type="module">
import * as THREE from 'https://cdn.skypack.dev/[email protected]';
const clock = new THREE.Clock()
// 创建一个场景
const scene = new THREE.Scene();
// 创建一个相机 视点
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
// 设置相机的位置
camera.position.set(100,100,0);
camera.lookAt(new THREE.Vector3(0,0,0));
// 创建一个渲染器
const renderer = new THREE.WebGLRenderer();
// 设置渲染器尺寸
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 添加灯光
const spotLight = new THREE.SpotLight(0xffffff);
spotLight.position.set(2000,8000,4000);
scene.add(spotLight);
// 圆柱体
const geometry = new THREE.CylinderGeometry(2, 2, 40, 8, 12)
const material = new THREE.MeshPhongMaterial();
// 蒙皮 - 皮肤
const mesh = new THREE.SkinnedMesh(geometry, material)
scene.add(mesh);
// 首先,创建一个起点. 创建骨骼系统
let b1 = new THREE.Bone();
b1.position.set(0, -20, 0);
let b2 = new THREE.Bone();
b1.add(b2)
b2.position.set(0, 10, 0);
let b3 = new THREE.Bone();
b2.add(b3)
b3.position.set(0, 10, 0)
let b4 = new THREE.Bone();
b3.add(b4)
b4.position.set(0, 10, 0)
let b5 = new THREE.Bone();
b4.add(b5)
b5.position.set(0, 10, 0)
// 创建骨架
const skeleton = new THREE.Skeleton([b1, b2, b3, b4, b5])
mesh.add(b1)
mesh.bind(skeleton)
// 添加权重 设置的就是蒙皮的权重, 顶点的蒙皮索引
const index = [] // 索引
const weight = [] // 权重
const arr = geometry.attributes.position.array;
for (let i = 0; i < arr.length; i += 3) {
const y = arr[i + 1] + 20
// const index = Math.floor(y / 10);
const weightValue = (y % 10) / 10
index.push(Math.floor(y / 10), Math.floor(y / 10) + 1, 0, 0)
weight.push(1 - weightValue, weightValue, 0, 0);
}
geometry.setAttribute('skinIndex', new THREE.Uint16BufferAttribute(index, 4));
geometry.setAttribute('skinWeight', new THREE.Float32BufferAttribute(weight, 4));
let step = 0.1;
const animation = () => {
// 渲染
renderer.render(scene, camera);
// 添加边界
if (
mesh.skeleton.bones[0].rotation.x > 0.3 ||
mesh.skeleton.bones[0].rotation.x < -0.3
) {
step = -step
}
for (let i = 0; i < mesh.skeleton.bones.length; i++) {
mesh.skeleton.bones[i].rotation.x += step * Math.PI / 180;
}
requestAnimationFrame(animation);
}
animation()
</script>