该示例通过批量生成25个立方体,并添加到统一的动画组 AnimationObjectGroup
中,使用关键帧动画,对这些模型立方体进行动画控制。可以说,这个示例是对关键帧动画的综合应用,示例相对比较简单。通过对该示例的学习,我们可以更好地理解掌握three.js 动画的使用。
一、AnimationObjectGroup
AnimationObjectGroup
是接收共享状态的一组对象。多个对象形成一个整体,对整体加上动画,则里面元素将会以整齐划一的状态进行运动。
1. 构造函数
AnimationObjectGroup( obj1 : Object, obj2 : Object, obj3 : Object, ... )
obj 是共享同一动画状态的任意数量的网格。
2.属性
属性名 | 说明 |
---|---|
isAnimationObjectGroup | 只读标志,以检查给定对象是否属于 isAnimationObjectGroup 对象 |
stats | 一个包含此动画对象组的一些信息的对象(总数,使用中的数量,绑定到每个对象上的数量) |
uuid | 这个动画对象组的UUID。 他是自动分配的,不可被编辑 |
3.方法
方法名 | 说明 |
---|---|
add | 将任意数量的对象添加到这个动画对象组 |
remove | 将任意数量的对象从这个动画对象组中删除 |
uncache | 释放此对象组传递的对象的所有内存资源 |
二、Quaternion
Quaternion
是一个数学相关的类,可以用来计算或表示物体在3D空间中的旋转姿态角度。
1. 构造函数
Quaternion( x : Float, y : Float, z : Float, w : Float )
参数名 | 说明 |
---|---|
x | x坐标 |
y | y坐标 |
z | z坐标 |
w | w坐标 |
2. 四元素的一些常用方法
方法名 | 说明 |
---|---|
setFromAxisAngle() | 用来辅助生成表示特定旋转的四元数 |
setFromAxisAngle(axis, angle) | 生成的四元数表示绕 axis 旋转,旋转角度是 angle |
setFromAxisAngle() | 可以生成一个四元数,绕任意轴、旋转任意角度,并不局限于x、y、 z轴 |
3.区别物体角度属性 .rotation 和 四元属性 .quaternion
three.js 模型对象的角度 .ratation
和 四元数 .quaternion
属性都是用来表示物体姿态角度的,只是表达的形式不同而已。 rotation
和 quaternion
两个属性的值,一个改变,另一个也会同步改变。
三、关键帧动画
three.js 关键帧动画和css 的动画有些相似,如果使用过css 关键帧动画然后来学习three动画,相信很快能上手。
1. 创建关键帧动画一般步骤
- 给需要设置关键帧动画的模型命名
- 设置关键帧数据,使用
KeyframeTrack
- 基于关键帧数据
KeyframeTrack
, 创建关键帧动画AnimationClip
// 给需要设置关键帧动画的模型命名
mesh.name = "Box";
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秒,物体从红色逐渐变化为蓝色
const colorKF = new THREE.KeyframeTrack('Box.material.color', [2, 5], [1, 0, 0, 0, 0, 1]);
// 1.3 基于关键帧数据,创建一个clip关键帧动画对象,命名"test",持续时间6秒。
const clip = new THREE.AnimationClip("test", 6, [posKF, colorKF]);
1.1 KeyframeTrack 设置关键帧数据
KeyframeTrack 是由时间和相关值的列表组成,用来让一个对象的某个特定属性动起来。
// 构造函数:
KeyframeTrack( name : String, times : Array, values : Array, interpolation : [Constant]() )
参数名 | 说明 |
---|---|
name | 关键帧轨道得标识符 |
times | 关键帧得时间数组,被内部转化为Float32Array. |
values | 与时间数组中的时间点相关的值组成的数组,被内部转化为 Float32Array. |
inerpolation | 插值类型(用的比较少) |
1.2 AnimationClip 创建关键帧动画
AnimationClip( name : String, duration : Number, tracks : Array )
参数名 | 说明 |
---|---|
name | 名称 |
duration | 持续时间(单位秒)。如果传入负数,持续时间将会从传入的数组中计算得到 |
tracks | 一个由关键帧轨道(KeyframeTrack) 组成的数组 |
2. AnimationMixer
播放关键帧动画AnimationClip
//包含关键帧动画的模型对象作为AnimationMixer的参数创建一个播放器mixer
const mixer = new THREE.AnimationMixer(mesh);
//AnimationMixer的`.clipAction()`返回一个AnimationAction对象
const clipAction = mixer.clipAction(clip);
//.play()控制动画播放,默认循环播放
clipAction.play();
3. mixer.update()
更新播放器AnimationMixer
时间
const frameT = clock.getDelta();
// 更新播放器相关的时间
mixer.update(frameT);
4. 动画播放的常用API
- 是否循环播放(默认是循环播放)
// 设置不循环播放
clipAction.loop = THREE.LoopOnce;
- 播放完停在结束状态 (注意: 不循环播放,播放结束默认停在开头那一帧)
// 物体状态停留在动画结束的时候。
clipAction.clampWhenFinished = true;
- 动画结束
stop
//动画停止结束,回到开始状态
clipAction.stop();
- 是否暂停动画
paused
paused
默认值是false,代表正常执行;如果设为true,暂停。
clipAction.paused = true;//切换为暂停状态
- 倍速播放
clipAction.timeScale = 1;//默认
clipAction.timeScale = 2;//2倍速
5. 示例使用到的关键帧动画
// create some keyframe tracks
const xAxis = new THREE.Vector3( 1, 0, 0 );
// Quaternion:四元数,在three.js中用于表示旋转
const qInitial = new THREE.Quaternion().setFromAxisAngle( xAxis, 0 );
// Math.PI: 表示一个圆的周长和直径的比例,约为 3.14159
const qFinal = new THREE.Quaternion().setFromAxisAngle( xAxis, Math.PI );
// QuaternionKeyframeTrack: 四元数类型的关键帧轨道
const quaternionKF = new THREE.QuaternionKeyframeTrack( '.quaternion', [ 0, 1, 2 ], [ qInitial.x, qInitial.y, qInitial.z, qInitial.w, qFinal.x, qFinal.y, qFinal.z, qFinal.w, qInitial.x, qInitial.y, qInitial.z, qInitial.w ] );
// ColorKeyframeTrack: 反应颜色变化的关键帧轨道
const colorKF = new THREE.ColorKeyframeTrack( '.material.color', [ 0, 1, 2 ], [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ], THREE.InterpolateDiscrete );
// NumberKeyframeTrack: 数字类型的关键帧轨道
const opacityKF = new THREE.NumberKeyframeTrack( '.material.opacity', [ 0, 1, 2 ], [ 1, 0, 1 ] );
// create clip
// AnimationClip: 动画剪辑,是一个可重用的关键帧轨道集,他代表动画
const clip = new THREE.AnimationClip( 'default', 3, [ quaternionKF, colorKF, opacityKF ] );
// AnimationMixer: 动画混合器是用于场景中特定对象的动画的播放器。 当场景中的多个对象独立动画时,每个对象都可以使用同一个动画混合器
mixer = new THREE.AnimationMixer( animationGroup );
// clipAction: 返回锁传入的剪辑参数的 AnimationAction, 根对象参数可选,默认值为混合器的默认根对象。
// 第一个参数可以是动画剪辑对象或动画剪辑的名称
const clipAction = mixer.clipAction( clip );
clipAction.play();
四、示例实现思路
- 初始化场景、相机、动画组
- 使用双重循环,生成25个立方体模型,并设置模型的x y z 轴位置,添加到动画组对象中
- 对动画组设置关键帧动画,使得这25个立方体按照整齐划一的动起来。动画包括旋转、颜色、和透明度的变化
- 处理页面窗口大小改变(resize)
五、源码
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgl - animation - groups</title>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"
/>
<link type="text/css" rel="stylesheet" href="main.css" />
</head>
<body>
<div id="info">
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a>
webgl - animation - groups
</div>
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import Stats from "three/addons/libs/stats.module.js";
let stats, clock;
let scene, camera, renderer, mixer;
init();
animate();
function init() {
scene = new THREE.Scene();
//
camera = new THREE.PerspectiveCamera(
40,
window.innerWidth / window.innerHeight,
1,
1000
);
camera.position.set(50, 50, 100);
camera.lookAt(scene.position);
// all objects of this animation group share a common animation state
// AnimationObjectGroup: 接收共享动画的一组对象
const animationGroup = new THREE.AnimationObjectGroup();
// BoxGeometry: 立方缓冲集合体
const geometry = new THREE.BoxGeometry(5, 5, 5);
// MeshBasicMaterial:基础网格材质,这种材质不受光照的影响
const material = new THREE.MeshBasicMaterial({ transparent: true });
//
for (let i = 0; i < 5; i++) {
for (let j = 0; j < 5; j++) {
const mesh = new THREE.Mesh(geometry, material);
mesh.position.x = 32 - 16 * i;
mesh.position.y = 0;
mesh.position.z = 32 - 16 * j;
scene.add(mesh);
animationGroup.add(mesh);
}
}
// create some keyframe tracks
const xAxis = new THREE.Vector3(1, 0, 0);
// Quaternion:四元数,在three.js中用于表示旋转
const qInitial = new THREE.Quaternion().setFromAxisAngle(xAxis, 0);
// Math.PI: 表示一个圆的周长和直径的比例,约为 3.14159
const qFinal = new THREE.Quaternion().setFromAxisAngle(xAxis, Math.PI);
// QuaternionKeyframeTrack: 四元数类型的关键帧轨道
const quaternionKF = new THREE.QuaternionKeyframeTrack(
".quaternion",
[0, 1, 2],
[
qInitial.x,
qInitial.y,
qInitial.z,
qInitial.w,
qFinal.x,
qFinal.y,
qFinal.z,
qFinal.w,
qInitial.x,
qInitial.y,
qInitial.z,
qInitial.w,
]
);
// ColorKeyframeTrack: 反应颜色变化的关键帧轨道
const colorKF = new THREE.ColorKeyframeTrack(
".material.color",
[0, 1, 2],
[1, 0, 0, 0, 1, 0, 0, 0, 1],
THREE.InterpolateDiscrete
);
// NumberKeyframeTrack: 数字类型的关键帧轨道
const opacityKF = new THREE.NumberKeyframeTrack(
".material.opacity",
[0, 1, 2],
[1, 0, 1]
);
// create clip
// AnimationClip: 动画剪辑,是一个可重用的关键帧轨道集,他代表动画
const clip = new THREE.AnimationClip("default", 3, [
quaternionKF,
colorKF,
opacityKF,
]);
// AnimationMixer: 动画混合器是用于场景中特定对象的动画的播放器。 当场景中的多个对象独立动画时,每个对象都可以使用同一个动画混合器
mixer = new THREE.AnimationMixer(animationGroup);
// clipAction: 返回锁传入的剪辑参数的 AnimationAction, 根对象参数可选,默认值为混合器的默认根对象。
// 第一个参数可以是动画剪辑对象或动画剪辑的名称
const clipAction = mixer.clipAction(clip);
clipAction.play();
// WebGLRenderer: 用 WebGL 渲染出你精心制作的场景
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
//
stats = new Stats();
document.body.appendChild(stats.dom);
//Clock: 用于跟踪时间
clock = new THREE.Clock();
window.addEventListener("resize", onWindowResize);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
const delta = clock.getDelta();
if (mixer) {
mixer.update(delta);
}
renderer.render(scene, camera);
stats.update();
}
</script>
</body>
</html>