Skip to content

十六、关键帧动画

更新: 12/19/2025字数: 0 字阅读: 0 分钟

关键帧动画

  1. 创建关键帧动画AnimationClip
  • 1.1 给需要设置关键帧动画的模型命名
  • 1.2 设置关键帧数据KeyframeTrack
  • 1.3 基于关键帧数据KeyframeTrack,创建关键帧动画AnimationClip

2.播放关键帧动画AnimationClip

  • 2.1AnimationMixer播放关键帧动画AnimationClip
  • 2.2mixer.update()更新播放器AnimationMixer时间
javascript
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的值, 且像乒乓球一样在起始点与结束点之间来回循环。
javascript
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()方法,动画会停止,并结束,模型回到动画开始状态,注意不是暂停,是动画彻底终止,回到初始状态。

javascript
//动画停止结束,回到开始状态
clipAction.stop();

暂停播放.paused

.paused默认值false,动画正常执行,如果你想暂停正在执行的动画可以把.paused设置为true,对于暂停执行的动画,你把.paused设置为false,动画会接着暂停的位置继续执行。

javascript
<div id="bu"class="bu">暂停</div>
javascript
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

javascript
clipAction.timeScale = 1;//默认
clipAction.timeScale = 2;//2倍速

拖动条调整播放速度

gui辅助快速创建一个可交互拖动条,实际开发,可以用vue或react创建的拖动条。

javascript
const gui = new GUI(); //创建GUI对象
// 0~6倍速之间调节
gui.add(clipAction, 'timeScale', 0, 6);

动画播放(拖动任意时间状态)

控制动画播放特定时间段

从时间轴上选择时间段播放动画,开始时刻AnimationAction.time,结束时刻AnimationClip.duration

javascript
// 给名为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把动画定格在时间轴上任何位置。

javascript
//在暂停情况下,设置.time属性,把动画定位在任意时刻
clipAction.paused = true;
clipAction.time = 1;//物体状态为动画1秒对应状态
clipAction.time = 3;//物体状态为动画3秒对应状态

拖动条拖动显示动画任意时刻模型状态

默认是播放的,可以预先暂停,再通过拖动条控制。

javascript
//在暂停情况下,设置.time属性,把动画定位在任意时刻
clipAction.paused = true;

gui辅助快速创建一个可交互拖动条,调整模型停留在任何选定时间点状态。实际开发时候,你可以通过vue或react的UI组件库实现拖动条。

javascript
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

javascript
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();

变形动画

  1. 为geometry提供变形目标的顶点数据
  2. .morphAttributes设置几何体变形目标顶点数据 注意:给一个几何体geometry设置顶点变形数据.morphAttributes时候要注意,在执行代码new THREE.Mesh()之前设置,否则报错。
  3. .morphTargetInfluences权重系数控制变形程度 设置为关键帧
    • 设置变形目标影响权重,范围一般0~1。
    • mesh在geometry原始形状和变形目标1顶点对应形状之间变化。
    • 一个网格模型的几何体geometry可以有多个变形目标,只要对应权重不为0,每个变形目标的形状都会影响模型的形状。
javascript
//权重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;
  1. 生成变形动画
javascript
//几何体两组顶点一一对应,位置不同,然后通过权重系数,可以控制模型形状在两组顶点之间变化
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,构建一个简单的层级模型。

javascript
const Bone1 = new THREE.Bone(); //关节1,用来作为根关节
const Bone2 = new THREE.Bone(); //关节2
const Bone3 = new THREE.Bone(); //关节3

// 设置关节父子关系   多个骨头关节构成一个树结构
Bone1.add(Bone2);
Bone2.add(Bone3);

设置关节模型的位置和姿态角度

javascript
//根关节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可视化骨骼关节

javascript
// 骨骼关节可以和普通网格模型一样作为其他模型子对象,添加到场景中
const group = new THREE.Group();
group.add(Bone1);

// SkeletonHelper会可视化参数模型对象所包含的所有骨骼关节
const skeletonHelper = new THREE.SkeletonHelper(group);
group.add(skeletonHelper);

注意SkeletonHelper的可视化效果,SkeletonHelper几个线段并不表示Bone,每个Bone你可以理解为一个虚拟关节点,SkeletonHelper几个线段是每个虚拟Bone关节点连线。

拖动条控制骨骼关节旋转

javascript
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');

查看外部模型骨骼动画

骨骼动画不同动作切换