十六、关键帧动画
关键帧动画
- 创建关键帧动画
AnimationClip
- 1.1 给需要设置关键帧动画的模型命名
- 1.2 设置关键帧数据
KeyframeTrack - 1.3 基于关键帧数据
KeyframeTrack,创建关键帧动画AnimationClip
2.播放关键帧动画AnimationClip
- 2.1
AnimationMixer播放关键帧动画AnimationClip - 2.2
mixer.update()更新播放器AnimationMixer时间
const geometry = new THREE.BoxGeometry(15, 15, 15);
const material = new THREE.MeshLambertMaterial({
color: 0xff0000
});
const mesh = new THREE.Mesh(geometry, material);
// 1. 第1步创建一个关键动画AnimationClip
// 1.1 给需要设置关键帧动画的模型命名
mesh.name = "Box";
// 1.2 给名为Box的模型对象的设置关键帧数据KeyframeTrack
const times = [0, 3, 6]; //时间轴上,设置三个时刻0、3、6秒
// times中三个不同时间点,物体分别对应values中的三个xyz坐标
const values = [0, 0, 0, 100, 0, 0, 0, 0, 100];
// 创建关键帧,把模型位置和时间对应起来
// 0~3秒,物体从(0,0,0)逐渐移动到(100,0,0),3~6秒逐渐从(100,0,0)移动到(0,0,100)
const posKF = new THREE.KeyframeTrack('Box.position', times, values);
// 时间轴上选择两个时刻2秒、5秒,分别对应物体两个颜色值(1, 0, 0)、(0, 0, 1)
// 从2秒到5秒,物体从红色逐渐变化为蓝色
const colorKF = new THREE.KeyframeTrack('Box.material.color', [2, 5], [1, 0, 0, 0, 0, 1]);
// 上面是在时间轴上,随机编辑了两个关键帧posKF、colorKF,你可以根据需要随意改变场景中物体位置、颜色、角度等属性
// 1.3 AnimationClip表示一个关键帧动画,可以基于关键帧数据产生动画效果
// 创建一个clip关键帧动画对象,命名"test",动画持续时间6s
// AnimationClip包含的所有关键帧数据都放到参数3数组中即可
const clip = new THREE.AnimationClip("test",6,[posKF, colorKF]);
// 2. 第2步播放关键帧动画AnimationClip
//2.1 动画播放器AnimationMixer播放关键帧动画AnimationClip
//包含关键帧动画的模型对象作为AnimationMixer的参数创建一个播放器mixer
const mixer = new THREE.AnimationMixer(mesh);
//AnimationMixer的`.clipAction()`返回一个AnimationAction对象
const clipAction = mixer.clipAction(clip);
//.play()控制动画播放,默认循环播放
clipAction.play();
// 2.2 如果想播放动画开始变化,需要周期性执行`mixer.update()`更新AnimationMixer时间数据
// 你可以通过requestAnimationFrame提供一个周期性执行的函数
const clock = new THREE.Clock();//辅助获取时间数据
function loop() {
requestAnimationFrame(loop);
//clock.getDelta()方法获得loop()两次执行时间间隔
const frameT = clock.getDelta();
// 更新播放器相关的时间
mixer.update(frameT);
}
loop();动画播放(暂停、倍速、循环)
循环.loop
通过AnimationAction的循环属性.loop可以控制动画是否循环播放。
- THREE.LoopOnce- 只执行一次
- THREE.LoopRepeat- 重复次数为repetitions的值, 且每次循环结束时候将回到起始动作开始下一次循环。
- THREE.LoopPingPong- 重复次数为repetitions的值, 且像乒乓球一样在起始点与结束点之间来回循环。
const clipAction = mixer.clipAction(clip);
//.play()控制动画播放,默认循环播放
clipAction.play();
//不循环播放
clipAction.loop = THREE.LoopOnce;
// 物体状态停留在动画结束的时候
clipAction.clampWhenFinished = true;你通过clipAction.loop = THREE.LoopOnce设置播放模式为非循环模式的时候,你会发现关键帧动画执行完成一个后,模型回到了关键帧动画开头状态,如果你希望模型停留在关键帧动画结束的状态,可以设置.clampWhenFinished属性实现,.clampWhenFinished属性默认是false,设置为true即可
停止.stop()
执行AnimationAction的.stop()方法,动画会停止,并结束,模型回到动画开始状态,注意不是暂停,是动画彻底终止,回到初始状态。
//动画停止结束,回到开始状态
clipAction.stop();暂停播放.paused
.paused默认值false,动画正常执行,如果你想暂停正在执行的动画可以把.paused设置为true,对于暂停执行的动画,你把.paused设置为false,动画会接着暂停的位置继续执行。
<div id="bu"class="bu">暂停</div>const bu = document.getElementById('bu');
bu.addEventListener('click',function(){
// AnimationAction.paused默认值false,设置为true,可以临时暂停动画
if (clipAction.paused) {//暂停状态
clipAction.paused = false;//切换为播放状态
bu.innerHTML='暂停';// 如果改变为播放状态,按钮文字设置为“暂停”
} else {//播放状态
clipAction.paused = true;//切换为暂停状态
bu.innerHTML='继续';// 如果改变为暂停状态,按钮文字设置为“继续”
}
})倍速播放.timeScale
clipAction.timeScale = 1;//默认
clipAction.timeScale = 2;//2倍速拖动条调整播放速度
gui辅助快速创建一个可交互拖动条,实际开发,可以用vue或react创建的拖动条。
const gui = new GUI(); //创建GUI对象
// 0~6倍速之间调节
gui.add(clipAction, 'timeScale', 0, 6);动画播放(拖动任意时间状态)
控制动画播放特定时间段
从时间轴上选择时间段播放动画,开始时刻AnimationAction.time,结束时刻AnimationClip.duration。
// 给名为Box的模型对象设置关键帧动画AnimationClip
mesh.name = "Box";
const times = [0, 3, 6];
const values = [0, 0, 0, 100, 0, 0, 0, 0, 100];
const posKF = new THREE.KeyframeTrack('Box.position', times, values);
const colorKF = new THREE.KeyframeTrack('Box.material.color', [2, 5], [1, 0, 0, 0, 0, 1]);
const clip = new THREE.AnimationClip("test", 6, [posKF, colorKF]);
// console.log('clip.duration',clip.duration);
// 播放器AnimationMixer播放动画AnimationClip
const mixer = new THREE.AnimationMixer(mesh);
//AnimationMixer的`.clipAction()`返回一个AnimationAction对象
const clipAction = mixer.clipAction(clip);
//.play()控制动画播放,默认循环播放
clipAction.play();
// AnimationAction对象用来控制播放规则
clipAction.loop = THREE.LoopOnce; //不循环播放
// 物体状态停留在动画结束的时候
clipAction.clampWhenFinished=true;
// 从时间轴上选择时间段播放动画,开始时刻`clipAction.time`,结束时刻`clip.duration`。
clipAction.time = 1; //AnimationAction设置开始播放时间:从1秒时刻对应动画开始播放
clip.duration = 5;//AnimationClip设置播放结束时间:到5秒时刻对应的动画状态停止注意.loop和.clampWhenFinished对播放效果的影响,如果需要上面代码完全起作用,要设置非循环模式,同时动画播放完,物体停留在结束状态,而不是回到开始状态。
查看时间轴上任意时间动画状态
把动画设置为暂停状态,然后你可以通过AnimationAction.time把动画定格在时间轴上任何位置。
//在暂停情况下,设置.time属性,把动画定位在任意时刻
clipAction.paused = true;
clipAction.time = 1;//物体状态为动画1秒对应状态
clipAction.time = 3;//物体状态为动画3秒对应状态拖动条拖动显示动画任意时刻模型状态
默认是播放的,可以预先暂停,再通过拖动条控制。
//在暂停情况下,设置.time属性,把动画定位在任意时刻
clipAction.paused = true;gui辅助快速创建一个可交互拖动条,调整模型停留在任何选定时间点状态。实际开发时候,你可以通过vue或react的UI组件库实现拖动条。
import {GUI} from 'three/addons/libs/lil-gui.module.min.js';
const gui = new GUI(); //创建GUI对象
gui.add(clipAction, 'time', 0, 6).step(0.1);解析外部模型关键帧动画
加载gltf模型,如果存在帧动画数据的话,可以通过加载返回gltf对象的动画属性.animations获取。
gltf.animations是一个数组,如果没有帧动画数据,就是一个空数组,有帧动画数据的情况下,里面可能1个或多个Clip动画对象AnimationClip。
let mixer = null; //声明一个播放器变量
loader.load("../工厂.glb", function (gltf) {
model.add(gltf.scene);
//包含帧动画的模型作为参数创建一个播放器
mixer = new THREE.AnimationMixer(gltf.scene);
// 获取gltf.animations[0]的第一个clip动画对象
const clipAction = mixer.clipAction(gltf.animations[0]);//创建动画clipAction对象
clipAction.play();//播放动画
})
// 创建一个时钟对象Clock
const clock = new THREE.Clock();
function render() {
requestAnimationFrame(render);
if (mixer !== null) {
//clock.getDelta()方法获得两帧的时间间隔
// 更新播放器相关的时间
mixer.update(clock.getDelta());
}
}
render();变形动画
- 为geometry提供变形目标的顶点数据
.morphAttributes设置几何体变形目标顶点数据 注意:给一个几何体geometry设置顶点变形数据.morphAttributes时候要注意,在执行代码new THREE.Mesh()之前设置,否则报错。.morphTargetInfluences权重系数控制变形程度 设置为关键帧- 设置变形目标影响权重,范围一般0~1。
- mesh在geometry原始形状和变形目标1顶点对应形状之间变化。
- 一个网格模型的几何体geometry可以有多个变形目标,只要对应权重不为0,每个变形目标的形状都会影响模型的形状。
//权重0:物体形状对应geometry.attributes.position表示形状
mesh.morphTargetInfluences[0] = 0.0;
//权重1:物体形状对应target1表示形状
mesh.morphTargetInfluences[0] = 1.0;
//权重0.5:物体形状对应geometry和target1变形中间状态
mesh.morphTargetInfluences[0] = 0.5;- 生成变形动画
//几何体两组顶点一一对应,位置不同,然后通过权重系数,可以控制模型形状在两组顶点之间变化
const geometry = new THREE.BoxGeometry(50, 50, 50);
// 为geometry提供变形目标的顶点数据(注意和原始geometry顶点数量一致)
const target1 = new THREE.BoxGeometry(50, 200, 50).attributes.position; //变高
const target2 = new THREE.BoxGeometry(10, 50, 10).attributes.position; //变细
// 几何体顶点变形目标数据,可以设置1组或多组
geometry.morphAttributes.position = [target1, target2];
const material = new THREE.MeshLambertMaterial({
color: 0x00ffff
});
const mesh = new THREE.Mesh(geometry, material);
// 创建变形动画权重系数的关键帧数据
mesh.name = "Box";//关键帧动画控制的模型对象命名
// 设置变形目标1对应权重随着时间的变化
const KF1 = new THREE.KeyframeTrack('Box.morphTargetInfluences[0]', [0, 5], [0, 1]);
// 设置变形目标2对应权重随着时间的变化
const KF2 = new THREE.KeyframeTrack('Box.morphTargetInfluences[1]', [5, 10], [0, 1]);
// 创建一个剪辑clip对象
const clip = new THREE.AnimationClip("t", 10, [KF1, KF2]);
// 播放变形动画
const mixer = new THREE.AnimationMixer(mesh);
const clipAction = mixer.clipAction(clip);
clipAction.play();
clipAction.loop = THREE.LoopOnce; //不循环播放
clipAction.clampWhenFinished = true // 物体状态停留在动画结束的时候
const clock = new THREE.Clock();
function loop() {
requestAnimationFrame(loop);
const frameT = clock.getDelta();
// 更新播放器时间
mixer.update(frameT);
}
loop();骨骼关节Bone
骨骼关节Bone是threejs的一个类,父类是Object3D,你可以类似人或动物的骨骼关节,来理解骨骼关节Bone对象。
骨骼关节Bone的作用就是模拟人或动物的关节运动,控制身体表面变形,来生成骨骼动画(可以查看下节课的案例)。
骨骼关节Bone树结构
人或动物实际的骨骼关节结构往往是比较复杂的,一般可以用一个层级树结构表达。
你可以把骨骼关节当做一个模型对象Object3D,构建一个简单的层级模型。
const Bone1 = new THREE.Bone(); //关节1,用来作为根关节
const Bone2 = new THREE.Bone(); //关节2
const Bone3 = new THREE.Bone(); //关节3
// 设置关节父子关系 多个骨头关节构成一个树结构
Bone1.add(Bone2);
Bone2.add(Bone3);设置关节模型的位置和姿态角度
//根关节Bone1默认位置是(0,0,0)
Bone2.position.y = 60; //Bone2相对父对象Bone1位置
Bone3.position.y = 30; //Bone3相对父对象Bone2位置
//平移Bone1,Bone2、Bone3跟着平移
Bone1.position.set(50,0,50);
// 骨骼关节旋转
Bone1.rotateX(Math.PI / 6);
Bone2.rotateX(Math.PI / 6);SkeletonHelper可视化骨骼关节
// 骨骼关节可以和普通网格模型一样作为其他模型子对象,添加到场景中
const group = new THREE.Group();
group.add(Bone1);
// SkeletonHelper会可视化参数模型对象所包含的所有骨骼关节
const skeletonHelper = new THREE.SkeletonHelper(group);
group.add(skeletonHelper);注意SkeletonHelper的可视化效果,SkeletonHelper几个线段并不表示Bone,每个Bone你可以理解为一个虚拟关节点,SkeletonHelper几个线段是每个虚拟Bone关节点连线。
拖动条控制骨骼关节旋转
import {GUI} from 'three/addons/libs/lil-gui.module.min.js';
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');