在three.js的实际应用中,动画总是绕不过去的功能。常见应用场景:
本文将深入探讨 Three.js 动画的核心概念、技术实现及优化策略,并结合实际代码示例进行解析。
requestAnimationFrame
的动画循环Three.js 的动画本质是通过逐帧更新场景实现的。requestAnimationFrame
是浏览器提供的高性能动画 API,能够根据屏幕刷新率自动调整帧率(通常为 60 FPS),确保动画流畅性。
示例代码:基础旋转动画
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
renderer.render(scene, camera);
}
animate();
此代码让立方体绕 X 轴持续旋转,每次渲染前更新角度并重绘场景。
THREE.Clock
为避免高刷新率设备导致动画速度不一致,Three.js 提供了 Clock
类,通过计算时间差(deltaTime
)同步动画:
const clock = new THREE.Clock();
function animate() {
const delta = clock.getDelta(); // 获取时间差(秒)
cube.rotation.y += 1 * delta; // 每秒旋转 1 弧度
renderer.render(scene, camera);
}
getElapsedTime()
还可用于周期性动画(如圆周运动)。
Three.js 的动画系统由以下核心组件构成:
负责管理动画的播放、暂停及混合。每个动画对象(如模型)需绑定一个 AnimationMixer
:
const mixer = new THREE.AnimationMixer(model);
const action = mixer.clipAction(animationClip);
action.play();
控制动画的播放细节,如循环次数、速度、淡入淡出等:
action.setLoop(THREE.LoopRepeat, 3); // 重复播放 3 次
action.timeScale = 2; // 双倍速播放
GLTFLoader
加载。FBXLoader
加载,适用于复杂角色动画。GLTFLoader
或 FBXLoader
加载含骨骼数据的模型。AnimationMixer
。AnimationClip
并创建 AnimationAction
。示例代码:1、加载并播放骨骼动画
loader.load('model.glb', (gltf) => {
const model = gltf.scene;
const mixer = new THREE.AnimationMixer(model);
const action = mixer.clipAction(gltf.animations[0]);
action.play();
});
示例代码:2、利用three.js创建骨骼动画
import * as THREE from 'three';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 10;
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 创建骨骼
const bones = [];
const bone1 = new THREE.Bone();
bones.push(bone1);
const bone2 = new THREE.Bone();
bones.push(bone2);
bone1.add(bone2);
scene.add(bone1);
// 创建蒙皮网格
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
const skinnedMesh = new THREE.SkinnedMesh(geometry, material, bones);
scene.add(skinnedMesh);
// 绑定骨骼和蒙皮网格
const bindMatrix = new THREE.Matrix4();
skinnedMesh.bind(new THREE.Skeleton(bones), bindMatrix);
// 动画函数
function animate() {
requestAnimationFrame(animate);
bone1.rotation.x += 0.01;
bone2.rotation.x += 0.01;
skinnedMesh.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
action.crossFadeTo()
实现由动画1转为动画2的平滑过渡,例如人物由走路转为奔跑。// 创建两个动画动作
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
const loader = new GLTFLoader();
loader.load('model.glb',
(gltf) => {
const model = gltf.scene; scene.add(model); // 获取动画剪辑数组
const mixer = new THREE.AnimationMixer(model); // 获取第一个动画剪辑
const action1 = mixer.clipAction(clips[0]); // 动画1
const action2 = mixer.clipAction(clips[1]); // 动画2
// 播放动画1
action1.play(); // 在2秒内淡入动画2
action1.crossFadeTo(action2, 2);
action2.play();
}
);
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
const loader = new GLTFLoader();
loader.load('model.glb',
(gltf) => {
const model = gltf.scene; scene.add(model); // 获取动画剪辑数组
const clips = gltf.animations; // 创建动画混合器
const mixer = new THREE.AnimationMixer(model); // 获取第一个动画剪辑
const action1 = mixer.clipAction(clips[0]); // 动画1
const action2 = mixer.clipAction(clips[1]); // 动画2
// 设置权重
action1.setEffectiveWeight(0.7); // 动画1占70%
action2.setEffectiveWeight(0.3); // 动画2占30%
// 播放动画
action1.play();
action2.play();
// 例如,在 5 秒后同步停止两个动画
setTimeout(() => {
action1.stop();
action2.stop();
}, 5000);
}
);
是Mesh对象的一个属性,它包含一组权重值,范围从0到1。这些权重指定了变形目标(morph targets)应用的比例。默认情况下,这些权重是未定义的(undefined),但在调用updateMorphTargets方法后会重置为一个空数组。通过调整这些权重,可以实现物体形态的平滑变换。
关于变形动画,你可以理解为多组顶点数据,从一个状态变化到另一个状态,比如人的面部表情,哭的表情用一系列的顶点表示,笑的表情用一系列的顶点表示,从哭的表情过渡到笑的表情,就是表情对应的两组顶点之间的过渡,几何体的顶点的位置坐标发生变化,从一个状态过渡到另一个状态自然就产生了变形动画。
一般项目开发,变形动画和骨骼动画一样,由3D美术编辑好变形动画的模型数据,然后程序员通过Threejs相关的API解析渲染变形动画。我们这边写一个例子
// 创建一个几何体具有8个顶点
var geometry = new THREE.BoxGeometry(20, 20, 20); //立方体几何对象
console.log(geometry.vertices);
// 为geometry提供变形目标的数据
var box1 = new THREE.BoxGeometry(10, 5, 10); //为变形目标1提供数据
var box2 = new THREE.BoxGeometry(5, 20, 5); //为变形目标2提供数据
// 设置变形目标的数据
geometry.morphTargets[0] = {name: 'target1',vertices: box1.vertices};
geometry.morphTargets[1] = {name: 'target2',vertices: box2.vertices};
var material = new THREE.MeshLambertMaterial({
morphTargets: true, //允许变形
color: 0x0000ff
}); //材质对象
var mesh = new THREE.Mesh(geometry, material); //网格模型对象
scene.add(mesh); //网格模型添加到场景中
//启用变形目标并设置变形目标影响权重,范围一般0~1
// 设置第一组顶点对几何体形状影响的变形系数
mesh.morphTargetInfluences[0] = 0.5;
// 设置第二组顶点对几何体形状影响的变形系数
mesh.morphTargetInfluences[1] = 1;
gsap.to(cube.position, { x: 5, duration: 2, ease: "power2.inOut" });
AnimationMixer
的 stop
和 reset
方法来及时停止和重置不再需要的动画,以释放资源。