从零开始学习three.js(13):完整的动画指南

在three.js的实际应用中,动画总是绕不过去的功能。常见应用场景:

  1. 角色动画 :在游戏开发中,可以使用骨骼动画来创建角色的行走、奔跑、跳跃等动画,使角色的动作更加自然和流畅。
  2. 场景切换动画 :在网页设计中,可以使用 Three.js 的动画功能来创建场景切换的过渡动画,如淡入淡出、旋转切换等,提升用户体验。
  3. 数据可视化动画 :在数据可视化领域,可以借助 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. 骨骼动画实现步骤

  1. 加载模型 :使用 GLTFLoaderFBXLoader 加载含骨骼数据的模型。
  2. 初始化动画混合器 :将模型绑定到 AnimationMixer
  3. 播放动画 :提取 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 动画的优化

  1. 减少不必要的计算 :在动画循环中,尽量避免进行复杂的数学计算和逻辑判断,只更新必要的属性,以提高动画的性能。
  2. 合理使用缓存 :对于一些不经常变化的数据,如物体的几何形状和材质等,可以将其缓存起来,避免每次动画帧都重新创建和更新。
  3. 控制物体数量 :过多的物体会影响动画的性能,因此在设计场景时,要根据实际需求合理控制物体的数量,避免不必要的物体出现在场景中。
  4. 使用动画混合器的优化方法 :在使用关键帧动画时,可以使用 AnimationMixerstopreset 方法来及时停止和重置不再需要的动画,以释放资源。
相关推荐
Senar42 分钟前
如何判断浏览器是否开启硬件加速
前端·javascript·数据可视化
HtwHUAT1 小时前
实验四 Java图形界面与事件处理
开发语言·前端·python
利刃之灵1 小时前
01-初识前端
前端
codingandsleeping1 小时前
一个简易版无缝轮播图的实现思路
前端·javascript·css
天天扭码1 小时前
一分钟解决 | 高频面试算法题——最大子数组之和
前端·算法·面试
全宝2 小时前
🌏【cesium系列】01.vue3+vite集成Cesium
前端·gis·cesium
拉不动的猪2 小时前
简单回顾下插槽透传
前端·javascript·面试
烛阴2 小时前
Fragment Shader--一行代码让屏幕瞬间变黄
前端·webgl
爱吃鱼的锅包肉3 小时前
Flutter路由模块化管理方案
前端·javascript·flutter
风清扬雨3 小时前
Vue3具名插槽用法全解——从零到一的详细指南
前端·javascript·vue.js