本章主要学习知识点
- 了解什么是关键帧动画
- 掌握动画的各种控制方法
- 学会如何解析并播放外部模型自带的动画
关键帧动画
关键帧动画就像给 3D 模型制作“动态剧本”——通过在时间轴上设定关键动作点,让模型自动完成从起点到终点的平滑过渡。
在时间轴上选择几个关键时间点,定义模型在这些时刻的状态(如位置、颜色、旋转角度等)。例如:
- 第 0 秒:模型在原点,颜色为红色
- 第 3 秒:模型移动到 (10,0,0),颜色变为蓝色
Three.js会根据关键帧之间的状态自动计算中间过渡效果,我们无需手动逐帧设置
下面是一个简单的示例
先创建关键帧轨道
const times = [0,1,3,5];
const values = [0,0,0, 10,0,0, 0,0,10, 0,0,0];
const keyframeTrack = new THREE.KeyframeTrack(
'cube.position', // 属性名称
times, // 时间序列
values, // 属性值序列
);
// 创建一个颜色关键帧轨道
const colorKeyframeTrack = new THREE.ColorKeyframeTrack(
'cube.material.color', // 属性名称
[0.5,2,4], // 时间序列
[0.2,1,0.4, 0.3,0.2,0.5, 0.1,0.4,0.8], // 属性值序列
);
然后,我们需要用到AnimationClip
创建动画剪辑, 将多个关键帧轨道打包成一个动画片段
const clip = new THREE.AnimationClip(
'Action', // 动画名称
5, // 动画持续时间
[keyframeTrack,colorKeyframeTrack] // 关键帧轨道数组
);
创建动画混合器(AnimationMixer),这相当于动画播放器,控制动画的播放逻辑
const mixer = new THREE.AnimationMixer(cube);
最后播放动画
const action = mixer.clipAction(clip);
// 播放动画
action.play();
不要忘记在循环动画函数中更新动画混合器,推进动画时间轴
const frameT = clock.getDelta();
mixer.update(frameT);
动画播放控制
Three.js 提供了stop
停止,play
播放,paused
暂停,timeScale
调速等方法来控制播放
在页面上添加一个面板,实现上述功能看看
stopBtn.addEventListener('click', () => {
action.stop();
})
playBtn.addEventListener('click', () => {
action.play();
})
pauseBtn.addEventListener('click', () => {
if(action.paused){
action.paused = false;
pauseBtn.innerText = 'PAUSE';
} else {
action.paused = true;
pauseBtn.innerText = 'CONTINUE';
}
})
speedBtn.addEventListener('click', () => {
action.timeScale = 2;
})
逐帧播放
通过设置动画的time
来实现逐帧播放,这很简单
const next = document.getElementById('next');
next.addEventListener('click', () => {
action.time += 0.1;
})
效果如下
解析外部模型动画
要解析外模模型动画,必须保证模型自带动画 , 我们导入模型,打印naimations
便可查看模型是否自带动画
loader.load('model/animated_butterflies/scene.gltf', function (gltf) {
scene.add(gltf.scene);
console.log(gltf.naimations);
//包含关键帧动画的模型作为参数创建一个播放器
const mixer = new THREE.AnimationMixer(gltf.scene);
//获取gltf.animations[0]的第一个clip动画对象
const action = mixer.clipAction(gltf.animations[0]);
action.play()
const clock = new THREE.Clock();
function loop() {
const delta = clock.getDelta();
mixer.update(delta);
requestAnimationFrame(loop);
}
loop()
}, undefined, function (error) {
console.error(error);
});
效果如下
模拟机械拆装动画
加载自带动画的模型,通过gui可视化修改参数查看效果
const duration = action.getClip().duration;
const gui = new GUI();
gui.add(action, 'paused').name('暂停');
gui.add(action, 'play').name('播放');
gui.add(action, 'stop').name('停止');
gui.add(action, 'clampWhenFinished').name('结束是否停止');
gui.add(action, 'repetitions', 0, 10).name('重复次数');
gui.add(action, 'timeScale', 0, 10).name('时间缩放');
gui.add(action, 'time',0,duration).step(0.01).name('拖动');
变形动画
变形动画(Morph Animation)是一种通过改变模型顶点位置实现形状过渡的技术,类似于“魔法面团”被捏成不同造型的效果。
变形原理
-
顶点变形
每个 3D 模型由顶点(Vertex)构成,变形动画通过预定义多组顶点坐标(称为变形目标),在不同目标之间平滑过渡。例如:- 人脸模型定义「笑」和「哭」两组顶点数据
- 通过权重控制从「哭」到「笑」的渐变效果
-
权重混合
每个变形目标有一个权重值(0到1),控制其对最终形状的影响。例如:- 权重 0:完全保持原始形状
- 权重 1:完全变为目标形状
- 权重 0.5:原始和目标各占一半
定义原始几何体和目标几何体
const geometry = new THREE.BoxGeometry( 1, 1, 1 );
const target1 = new THREE.BoxGeometry( 1, 2, 1 ).attributes.position;
const target2 = new THREE.BoxGeometry( 0.2, 1, 0.2 ).attributes.position;
geometry.morphAttributes.position = [target1, target2]; // 变形目标数组
创建模型并设置权重
const material = new THREE.MeshBasicMaterial( { color: 'deeppink' } );
cube = new THREE.Mesh( geometry, material );
cube.morphTargetInfluences[ 0 ] = 0.5; // 变形目标权重
cube.morphTargetInfluences[ 1 ] = 0.5;
生成变形动画
cube.name = 'Box';
const KF1 = new THREE.KeyframeTrack('Box.morphTargetInfluences[0]', [0, 1], [0, 1]);
const KF2 = new THREE.KeyframeTrack('Box.morphTargetInfluences[1]', [0, 3], [0, 1]);
const clip = new THREE.AnimationClip('t', 3, [KF1, KF2]);
const mixer = new THREE.AnimationMixer(cube);
const action = mixer.clipAction(clip);
action.play();
action.loop = THREE.LoopOnce;
action.clampWhenFinished = true;
骨骼操作
在three.js 中 通过new THREE.Bone()
创建骨骼对象,
const Bone1 = new THREE.Bone(); // 创建骨骼Bone1
const Bone2 = new THREE.Bone(); // 创建骨骼Bone2
const Bone3 = new THREE.Bone(); // 创建骨骼Bone3
// 设置骨骼的父子关系
Bone1.add(Bone2);
Bone2.add(Bone3);
// 设置骨骼的初始位置
Bone2.position.y = 5;
Bone3.position.y = 2;
Bone1.position.set(5,0,5);
// 骨骼关节旋转
Bone1.rotateX(Math.PI / 4);
Bone2.rotateX(Math.PI / 4);
const group = new THREE.Group();
group.add(Bone1);
接下来我们添加gui进行参数控制
const gui = new GUI();
gui.add(Bone1.rotation, 'x', 0, Math.PI / 3 ).name('关节1');
gui.add(Bone2.rotation, 'x', 0, Math.PI / 3 ).name('关节2');
骨骼动画
先看效果
这是一个全骨骼控制案例,细致到手指头,下面是代码
const loader = new GLTFLoader();
loader.load('model/bone_boi/scene.gltf', gltf => {
scene.add(gltf.scene);
// 创建一个骨骼助手,用于显示骨骼动画
const skeletonHelper = new THREE.SkeletonHelper(gltf.scene);
scene.add(skeletonHelper);
let boneArr = [];
const gui = new GUI();
gltf.scene.traverse((child) => {
if(child.isMesh){
console.log(child.skeleton);
console.log(child.skeleton.bones);
boneArr = child.skeleton.bones;
for(let i = 0; i < boneArr.length; i++){
gui.add(boneArr[i].rotation, 'x').min(-10).max(10).step(0.01).name(boneArr[i].name + 'x');
gui.add(boneArr[i].rotation, 'y').min(-10).max(10).step(0.01).name(boneArr[i].name + 'x');
gui.add(boneArr[i].rotation, 'z').min(-10).max(10).step(0.01).name(boneArr[i].name + 'x');
}
}
})
// 将模型设置在原点
const boundingBox = new THREE.Box3().setFromObject(gltf.scene);
const center = boundingBox.getCenter(new THREE.Vector3());
gltf.scene.position.x += (gltf.scene.position.x - center.x);
gltf.scene.position.y += (gltf.scene.position.y - center.y);
gltf.scene.position.z += (gltf.scene.position.z - center.z);
})