在three.js的实际应用中,动画总是绕不过去的功能。常见应用场景:
- 角色动画 :在游戏开发中,可以使用骨骼动画来创建角色的行走、奔跑、跳跃等动画,使角色的动作更加自然和流畅。
- 场景切换动画 :在网页设计中,可以使用 Three.js 的动画功能来创建场景切换的过渡动画,如淡入淡出、旋转切换等,提升用户体验。
- 数据可视化动画 :在数据可视化领域,可以借助 Three.js 的动画效果来展示数据的变化趋势,如柱状图的生长动画、折线图的绘制动画等,使数据更加直观和易于理解。
本文将深入探讨 Three.js 动画的核心概念、技术实现及优化策略,并结合实际代码示例进行解析。
一、动画基础原理与实现方式
1. 基于 requestAnimationFrame
的动画循环
Three.js 的动画本质是通过逐帧更新场景实现的。requestAnimationFrame
是浏览器提供的高性能动画 API,能够根据屏幕刷新率自动调整帧率(通常为 60 FPS),确保动画流畅性。
示例代码:基础旋转动画
scss
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
renderer.render(scene, camera);
}
animate();
此代码让立方体绕 X 轴持续旋转,每次渲染前更新角度并重绘场景。
2. 时间同步与 THREE.Clock
为避免高刷新率设备导致动画速度不一致,Three.js 提供了 Clock
类,通过计算时间差(deltaTime
)同步动画:
ini
const clock = new THREE.Clock();
function animate() {
const delta = clock.getDelta(); // 获取时间差(秒)
cube.rotation.y += 1 * delta; // 每秒旋转 1 弧度
renderer.render(scene, camera);
}
getElapsedTime()
还可用于周期性动画(如圆周运动)。
二、动画系统核心组件
Three.js 的动画系统由以下核心组件构成:
1. 动画片段(AnimationClip)
- 定义:存储模型某段动画的关键帧数据,如角色行走、跳跃等动作。
- 关键帧轨道(KeyframeTrack) :描述单个属性随时间变化的数据(如骨骼位置、材质透明度)。
2. 动画混合器(AnimationMixer)
负责管理动画的播放、暂停及混合。每个动画对象(如模型)需绑定一个 AnimationMixer
:
ini
const mixer = new THREE.AnimationMixer(model);
const action = mixer.clipAction(animationClip);
action.play();
3. 动画行为(AnimationAction)
控制动画的播放细节,如循环次数、速度、淡入淡出等:
ini
action.setLoop(THREE.LoopRepeat, 3); // 重复播放 3 次
action.timeScale = 2; // 双倍速播放
4. 支持的模型格式与加载器
- glTF :推荐格式,支持骨骼动画和变形目标,通过
GLTFLoader
加载。 - FBX :通过
FBXLoader
加载,适用于复杂角色动画。 - 注意事项:OBJ 格式不支持动画,3ds Max 和 Maya 导出时需注意时间线同步。
三、骨骼动画与蒙皮技术
1. 骨骼动画实现步骤
- 加载模型 :使用
GLTFLoader
或FBXLoader
加载含骨骼数据的模型。 - 初始化动画混合器 :将模型绑定到
AnimationMixer
。 - 播放动画 :提取
AnimationClip
并创建AnimationAction
。
示例代码:1、加载并播放骨骼动画
ini
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创建骨骼动画
ini
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();
2. 蒙皮(Skinning)
- 骨骼层级:骨骼通过父子关系形成层级结构,驱动顶点变形。
- 顶点权重:每个顶点可绑定多个骨骼,权重决定各骨骼对其影响程度。
四、高级动画技术
1. 动画混合与同步
- 交叉渐变(Crossfade) :通过
action.crossFadeTo()
实现由动画1转为动画2的平滑过渡,例如人物由走路转为奔跑。
ini
// 创建两个动画动作
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();
}
);
- 同步动画:即同时播放几个动画:
ini
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);
}
);
2. 变形目标动画(Morph Targets):mesh.morphTargetInfluences
是Mesh对象的一个属性,它包含一组权重值,范围从0到1。这些权重指定了变形目标(morph targets)应用的比例。默认情况下,这些权重是未定义的(undefined),但在调用updateMorphTargets方法后会重置为一个空数组。通过调整这些权重,可以实现物体形态的平滑变换。
关于变形动画,你可以理解为多组顶点数据,从一个状态变化到另一个状态,比如人的面部表情,哭的表情用一系列的顶点表示,笑的表情用一系列的顶点表示,从哭的表情过渡到笑的表情,就是表情对应的两组顶点之间的过渡,几何体的顶点的位置坐标发生变化,从一个状态过渡到另一个状态自然就产生了变形动画。 一般项目开发,变形动画和骨骼动画一样,由3D美术编辑好变形动画的模型数据,然后程序员通过Threejs相关的API解析渲染变形动画。我们这边写一个例子
csharp
// 创建一个几何体具有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;
3. 第三方动画库集成
- GSAP:实现复杂补间动画,支持时间轴和缓动函数:
php
gsap.to(cube.position, { x: 5, duration: 2, ease: "power2.inOut" });
- Tween.js:轻量级补间库,适合简单动画序列,使用与gsap类似。
五、Three.js 动画的优化
- 减少不必要的计算 :在动画循环中,尽量避免进行复杂的数学计算和逻辑判断,只更新必要的属性,以提高动画的性能。
- 合理使用缓存 :对于一些不经常变化的数据,如物体的几何形状和材质等,可以将其缓存起来,避免每次动画帧都重新创建和更新。
- 控制物体数量 :过多的物体会影响动画的性能,因此在设计场景时,要根据实际需求合理控制物体的数量,避免不必要的物体出现在场景中。
- 使用动画混合器的优化方法 :在使用关键帧动画时,可以使用
AnimationMixer
的stop
和reset
方法来及时停止和重置不再需要的动画,以释放资源。