当前位置: 首页 > article >正文

【three.js】动画系统完全指南 - 从事件循环到工业级动画架构

目录

1. 浏览器渲染机制底层原理

1.1 JavaScript事件循环与渲染帧

宏任务/微任务对动画的影响 

1.2 帧率控制科学

Delta Time标准化计算:

帧率波动补偿策略:

2. Three.js动画体系深度解析

2.1 原生动画循环优化

性能黑洞检测:

渲染节流技术:

2.2 补间动画工业级解决方案

2.2.1 Tween.js源码级剖析

2.2.2 高级路径动画

2.3 动画系统架构设计

状态机管理:

动画轨道系统:

动画混合与层叠

性能优化:


本篇文章适合基本掌握three.js基础知识的开发者,所以本文不过于重复之前的内容,但是也会适当回顾关键概念

1. 浏览器渲染机制底层原理

1.1 JavaScript事件循环与渲染帧

宏任务/微任务对动画的影响 

        setTimeout vs requestAnimationFrame(rAF):

        setTimeout是基于事件循环的宏任务调度,无法保证精确的动画时间间隔。

        requestAnimationFrame是专门为动画设计的API,与浏览器的渲染管线同步,能够提供更加平滑的动画效果。

浏览器渲染管线

        解析 → 样式计算 → 布局 → 绘制 → 合成

        以上步骤是浏览器渲染页面的关键流程

垂直同步(VSync)

        rAF如何与显示器刷新率同步(60Hz/120Hz):rAF 通常会尝试与显示器的刷新率同步,以减少画面撕裂和卡顿。

1.2 帧率控制科学

Delta Time标准化计算
const clock = new THREE.Clock();
let delta = 0;
const targetFPS = 60;

function animate() {
  delta = clock.getDelta(); // 获取精确到毫秒的时间差
  
  // 基于实际耗时计算运动增量
  cube.rotation.y += (Math.PI / 2) * delta; // 每秒旋转90度
  
  requestAnimationFrame(animate);
}
帧率波动补偿策略

        当delta > 0.1时启用插值算法来平滑动画

        丢帧处理:使用累积时间算法避免动画跳跃

let accumulator = 0;
const fixedStep = 1 / 60; // 固定步长

function animate() {
  accumulator += clock.getDelta();
  
  while (accumulator >= fixedStep) {
    updatePhysics(fixedStep); // 物理模拟需固定步长
    accumulator -= fixedStep;
  }
  
  render(accumulator / fixedStep); // 插值渲染
}

2. Three.js动画体系深度解析

2.1 原生动画循环优化

性能黑洞检测

        在动画循环中创建对象(如new THREE.Vector3()

        频繁修改几何体顶点数据(应使用geometry.verticesNeedUpdate = true标记)

渲染节流技术

        通过setTimeout实现非活动状态降频渲染

let isActive = true;

function conditionalAnimate() {
  if (isActive) {
    animate();
    setTimeout(conditionalAnimate, 1000 / 30); // 降为30FPS
  }
}

2.2 补间动画工业级解决方案

2.2.1 Tween.js源码级剖析

时间函数算法实现

        自定义三次贝塞尔缓动函数

function cubicBezier(t, p1x, p1y, p2x, p2y) {
  // 详细实现贝塞尔公式...
}

对象池优化

        复用Tween实例避免垃圾回收(GC)压力

const tweenPool = [];

function createTween(target) {
  return tweenPool.length > 0 
    ? tweenPool.pop().reset(target)
    : new TWEEN.Tween(target);
}
2.2.2 高级路径动画

贝塞尔曲线运动

const curve = new THREE.CubicBezierCurve3(
  new THREE.Vector3(0, 0, 0),
  new THREE.Vector3(2, 3, 1),
  new THREE.Vector3(-1, 2, 0),
  new THREE.Vector3(5, 0, 3)
);

new TWEEN.Tween({ t: 0 })
  .to({ t: 1 }, 3000)
  .onUpdate(({ t }) => {
    const point = curve.getPoint(t);
    object.position.copy(point);
  })
  .start();

跟随路径旋转

        使用curve.getTangent(t)计算朝向

2.3 动画系统架构设计

状态机管理

        定义IDLEPLAYINGPAUSED等状态,用于管理动画的生命周期

动画轨道系统

        通过THREE.AnimationClip实现多轨道融合

const clip = new THREE.AnimationClip('dance', 10, [
  new THREE.VectorKeyframeTrack(
    '.position', 
    [0, 3, 6], 
    [0,0,0, 2,1,0, 2,1,3]
  ),
  new THREE.QuaternionKeyframeTrack(
    '.quaternion',
    [0, 5],
    [0,0,0,1, 0.707,0,0.707,0] // 绕Y轴旋转180度
  )
]);

const mixer = new THREE.AnimationMixer(model);
const action = mixer.clipAction(clip);
action.play();
动画混合与层叠

        使用 THREE.AnimationMixer 和多个 THREE.AnimationClip 实现复杂的动画混合效果。

        通过调整 AnimationMixer 的权重来控制不同动画的混合比例。

案例:

性能优化:

        避免不必要的渲染和计算,使用 requestAnimationFrame 进行动画循环。

案例:

// 初始化场景、相机和渲染器
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 加载一个带有多个动画的GLTF模型
const loader = new THREE.GLTFLoader();
loader.load('path/to/your/model.glb', function (gltf) {
    const model = gltf.scene;
    scene.add(model);

    // 创建AnimationMixer
    const mixer = new THREE.AnimationMixer(model);

    // 获取动画剪辑
    const clipWalk = gltf.animations[0];
    const clipRun = gltf.animations[1];

    // 创建动画动作
    const actionWalk = mixer.clipAction(clipWalk);
    const actionRun = mixer.clipAction(clipRun);

    // 播放动画并设置权重
    actionWalk.play();
    actionRun.play();

    // 初始化权重
    let walkWeight = 1.0;
    let runWeight = 0.0;

    // 动画循环
    function animate() {
        requestAnimationFrame(animate);

        // 更新权重(这里只是示例,你可以根据需求动态调整权重)
        walkWeight = Math.cos(Date.now() * 0.001) * 0.5 + 0.5;
        runWeight = 1.0 - walkWeight;

        // 设置权重
        actionWalk.setWeight(walkWeight);
        actionRun.setWeight(runWeight);

        // 更新mixer
        mixer.update(0.01);

        // 渲染场景
        renderer.render(scene, camera);
    }
    animate();
}, undefined, function (error) {
    console.error(error);
});

// 设置相机位置
camera.position.z = 5;

        尽量减少对DOM的操作,使用CSS动画或WebGL进行高效的渲染。

        利用GPU加速,减少CPU的负担。

案例:

// 创建一个几何体和材质
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });

// 创建一个InstancedMesh
const count = 1000;
const instancedMesh = new THREE.InstancedMesh(geometry, material, count);

// 设置每个实例的变换矩阵
for (let i = 0; i < count; i++) {
    const matrix = new THREE.Matrix4().makeTranslation(
        Math.random() * 10 - 5,
        Math.random() * 10 - 5,
        Math.random() * 10 - 5
    );
    instancedMesh.setMatrixAt(i, matrix);
}

// 更新InstancedMesh的变换矩阵
instancedMesh.instanceMatrix.needsUpdate = true;

// 将InstancedMesh添加到场景中
scene.add(instancedMesh);

// 动画循环
function animate() {
    requestAnimationFrame(animate);

    // 更新每个实例的变换(这里只是简单的旋转示例)
    for (let i = 0; i < count; i++) {
        const matrix = instancedMesh.getMatrixAt(i);
        matrix.premultiply(new THREE.Matrix4().makeRotationY(0.01));
        instancedMesh.setMatrixAt(i, matrix);
    }
    instancedMesh.instanceMatrix.needsUpdate = true;

    // 渲染场景
    renderer.render(scene, camera);
}
animate();


 

码字不易,各位大佬点点赞呗


http://www.kler.cn/a/579605.html

相关文章:

  • MobileBERT: 一种适用于资源有限设备的紧凑型任务无关BERT
  • 关于C/C++语言的初学者在哪刷题,怎么刷题
  • 软件系统压力测试方案,压力测试报告模版(Word原件)
  • OSPF-单区域的配置
  • 反射是什么?
  • 数学建模-1:对变化建模
  • Python正则表达式完全指南:从入门到精通
  • 【Linux网络(一)】初始网络
  • Linux:多线程(单例模式,其他常见的锁,读者写者问题)
  • ESP8266UDP透传
  • 华为Mate 60 Pro+ 等机型适配支持运营商北斗卫星短信功能
  • C++:vector容器(下篇)
  • Milvus的匹配语法
  • 二维码识别OCR接口:开启高效信息提取的新篇章
  • RK Android14 在计算器内输入特定字符跳转到其他应用
  • 文件上传漏洞测试
  • Java 大视界 -- Java 大数据在智慧交通信号灯智能控制中的应用(116)
  • TCP/IP 5层协议簇:网络层(ICMP协议)
  • 论文阅读-秦汉时期北方边疆组织的空间互动模式与直道的定位(中国)
  • 自学微信小程序的第十二天