Three.js 动画循环学习记录

在上一篇文章中,我们学习了Three.js 坐标系系统与单位理解教程:

Three.js 坐标系系统与单位理解教程

接下来我们要学习的是Three.js 的动画循环

一、动画循环基础原理

1. 什么是动画循环?

        动画循环是连续更新场景状态并重新渲染的过程,通过快速连续的画面变化产生动态效果。在Three.js中,这通常以与显示器刷新率同步的频率执行。

2. 核心代码结构

function animate() {requestAnimationFrame(animate); // 1. 请求下一帧updateScene();                 // 2. 更新场景状态renderer.render(scene, camera); // 3. 渲染场景
}
animate(); // 启动循环

二、Vue3 中的完整实现与解析

1. 组件基础结构(Composition API)

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import * as THREE from 'three';// DOM 引用
const container = ref(null);// Three.js 核心对象
let scene, camera, renderer, cube;
let animationId = null; // 存储动画ID用于取消
const clock = new THREE.Clock(); // Three.js内置时钟
</script>

2. 初始化函数详解

function initThreeScene() {// 场景初始化scene = new THREE.Scene();scene.background = new THREE.Color(0x111111);// 相机配置(透视相机)camera = new THREE.PerspectiveCamera(75,                         // 视野角度(FOV)window.innerWidth/innerHeight, // 宽高比0.1,                        // 近裁剪面1000                        // 远裁剪面);camera.position.z = 5;        // 相机位置// 渲染器配置renderer = new THREE.WebGLRenderer({ antialias: true,           // 开启抗锯齿alpha: true                // 开启透明度});renderer.setPixelRatio(Math.min(2, window.devicePixelRatio)); // 合理设置像素比renderer.setSize(window.innerWidth, window.innerHeight);container.value.appendChild(renderer.domElement);// 添加测试物体addTestCube();// 启动动画循环startAnimationLoop();
}

3. 动画循环的完整实现

基础版本:

function animate() {// 递归调用形成循环animationId = requestAnimationFrame(animate);// 物体旋转动画cube.rotation.x += 0.01;cube.rotation.y += 0.01;// 渲染场景renderer.render(scene, camera);
}

专业版本(带时间控制):

let previousTime = 0;function animate(currentTime) {animationId = requestAnimationFrame(animate);// 计算时间增量(毫秒转换为秒)const deltaTime = (currentTime - previousTime) / 1000;previousTime = currentTime;// 使用时间增量确保动画速度一致const rotationSpeed = 2; // 弧度/秒cube.rotation.x += rotationSpeed * deltaTime;cube.rotation.y += rotationSpeed * deltaTime;// 弹跳动画(使用正弦函数)const bounceHeight = 0.5;const bounceSpeed = 2;cube.position.y = Math.sin(currentTime * 0.001 * bounceSpeed) * bounceHeight;renderer.render(scene, camera);
}

4. 动画控制与管理

// 启动动画循环
function startAnimationLoop() {if (!animationId) {clock.start(); // 启动Three.js时钟previousTime = performance.now(); // 记录初始时间animate(); // 开始循环}
}// 停止动画循环
function stopAnimationLoop() {if (animationId) {cancelAnimationFrame(animationId);animationId = null;clock.stop(); // 停止时钟}
}// 重置动画状态
function resetAnimation() {cube.rotation.set(0, 0, 0);cube.position.set(0, 0, 0);previousTime = performance.now(); // 重置时间记录
}

三、高级动画技巧

1. 性能优化策略

条件渲染(只在需要时渲染)

let needsUpdate = false;// 当场景变化时调用
function triggerRender() {needsUpdate = true;
}function animate() {animationId = requestAnimationFrame(animate);if (needsUpdate) {renderer.render(scene, camera);needsUpdate = false;}
}

分帧处理(复杂场景优化)

let frameCount = 0;function animate() {animationId = requestAnimationFrame(animate);// 每帧只更新部分元素if (frameCount % 2 === 0) updatePhysics();if (frameCount % 3 === 0) updateBackground();updateMainObjects(); // 主要物体每帧都更新renderer.render(scene, camera);frameCount++;
}

2. 混合动画技术

function animate() {animationId = requestAnimationFrame(animate);const time = clock.getElapsedTime();// 旋转动画cube.rotation.x = time * 1; // 持续旋转// 脉动动画(缩放)const pulseSpeed = 3;const pulseIntensity = 0.2;cube.scale.setScalar(1 + Math.sin(time * pulseSpeed) * pulseIntensity);// 颜色变化cube.material.color.setHSL(Math.sin(time * 0.5) * 0.5 + 0.5, // 色相 (0-1)0.8, // 饱和度0.5  // 亮度);renderer.render(scene, camera);
}

四、Vue3 组件完整实现

<template><!-- 容器用于挂载 Three.js 渲染器 --><div ref="container" class="three-container"></div><!-- 控制面板,包含播放/暂停、重置按钮和 FPS 显示 --><div class="control-panel"><!-- 播放/暂停按钮,点击切换动画状态 --><button @click="toggleAnimation">{{ isPlaying ? '⏸ 暂停' : '▶ 播放' }}</button><!-- 重置动画按钮 --><button @click="resetAnimation">↻ 重置</button><!-- 显示当前 FPS 值 --><span class="fps-counter">FPS: {{ fps.toFixed(1) }}</span></div>
</template><script setup>
// 导入 Vue 的响应式 API
import { ref, onMounted, onBeforeUnmount } from 'vue';
// 导入 Three.js 库
import * as THREE from 'three';// 创建响应式引用:容器 DOM 元素
const container = ref(null);
// 创建响应式引用:动画播放状态
const isPlaying = ref(true);
// 创建响应式引用:FPS 值
const fps = ref(0);// 声明 Three.js 相关对象变量
let scene, camera, renderer, cube;
// 动画帧 ID,用于取消动画循环
let animationId = null;
// Three.js 时钟对象,用于计算时间差
const clock = new THREE.Clock();// FPS 计算相关变量
let lastFpsUpdate = 0;  // 上次更新 FPS 的时间戳
let frameCount = 0;     // 帧计数器// 初始化 Three.js 场景
function initScene() {// 创建场景对象scene = new THREE.Scene();// 设置场景背景颜色scene.background = new THREE.Color(0x222222);// 创建透视相机camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 0.1, 100);// 设置相机位置camera.position.set(0, 1, 5);// 相机看向原点camera.lookAt(0, 0, 0);// 创建 WebGL 渲染器,开启抗锯齿renderer = new THREE.WebGLRenderer({ antialias: true });// 设置渲染器像素比率renderer.setPixelRatio(window.devicePixelRatio);// 设置渲染器尺寸renderer.setSize(window.innerWidth, window.innerHeight);// 将渲染器的 DOM 元素添加到容器中container.value.appendChild(renderer.domElement);// 创建环境光const ambientLight = new THREE.AmbientLight(0x404040);// 创建方向光const directionalLight = new THREE.DirectionalLight(0xffffff, 1);// 设置方向光位置directionalLight.position.set(1, 1, 1);// 将灯光添加到场景中scene.add(ambientLight, directionalLight);// 添加动画立方体到场景addAnimatedCube();// 添加坐标轴辅助器scene.add(new THREE.AxesHelper(3));// 添加网格辅助器scene.add(new THREE.GridHelper(10, 10));// 开始动画循环startAnimation();
}// 创建并添加动画立方体
function addAnimatedCube() {// 创建立方体几何体const geometry = new THREE.BoxGeometry(1, 1, 1);// 创建标准材质并设置属性const material = new THREE.MeshStandardMaterial({ color: 0x00ff00,      // 颜色roughness: 0.3,       // 粗糙度metalness: 0.7        // 金属度});// 创建网格对象cube = new THREE.Mesh(geometry, material);// 将立方体添加到场景中scene.add(cube);
}// 动画循环函数
function animate(timestamp) {// 请求下一帧动画animationId = requestAnimationFrame(animate);// 更新 FPS 计数器updateFpsCounter(timestamp);// 获取时间差和总时间const delta = clock.getDelta();const elapsed = clock.getElapsedTime();// 更新动画状态updateAnimations(elapsed, delta);// 渲染场景renderer.render(scene, camera);
}// 更新所有动画效果
function updateAnimations(time, delta) {// 立方体绕 X 轴旋转cube.rotation.x = time * 0.5;// 立方体绕 Y 轴旋转cube.rotation.y = time * 0.3;// 立方体上下弹跳效果cube.position.y = Math.sin(time * 2) * 0.5;// 立方体颜色随时间变化cube.material.color.setHSL((Math.sin(time * 0.3) + 1) / 2,  // 色相0.8,                              // 饱和度0.6                               // 亮度);
}// 更新 FPS 计数器
function updateFpsCounter(timestamp) {// 帧数递增frameCount++;// 每秒更新一次 FPS 值if (timestamp >= lastFpsUpdate + 1000) {fps.value = (frameCount * 1000) / (timestamp - lastFpsUpdate);// 重置帧计数器和更新时间frameCount = 0;lastFpsUpdate = timestamp;}
}// 控制动画播放的方法
function startAnimation() {// 如果动画未运行则开始动画if (!animationId) {clock.start();animationId = requestAnimationFrame(animate);isPlaying.value = true;}
}// 停止动画
function stopAnimation() {// 如果动画正在运行则停止if (animationId) {cancelAnimationFrame(animationId);animationId = null;clock.stop();isPlaying.value = false;}
}// 切换动画播放状态
function toggleAnimation() {if (isPlaying.value) stopAnimation();else startAnimation();
}// 重置动画状态
function resetAnimation() {// 重置立方体旋转cube.rotation.set(0, 0, 0);// 重置立方体位置cube.position.set(0, 0, 0);// 重置立方体缩放cube.scale.set(1, 1, 1);// 重置立方体颜色cube.material.color.set(0x00ff00);// 重启时钟clock.start();
}// 窗口大小调整处理函数
function onResize() {// 更新相机宽高比camera.aspect = window.innerWidth / window.innerHeight;// 更新相机投影矩阵camera.updateProjectionMatrix();// 更新渲染器尺寸renderer.setSize(window.innerWidth, window.innerHeight);
}// 组件挂载时执行
onMounted(() => {// 初始化场景initScene();// 添加窗口大小调整事件监听器window.addEventListener('resize', onResize);
});// 组件卸载前执行清理工作
onBeforeUnmount(() => {// 停止动画stopAnimation();// 移除窗口大小调整事件监听器window.removeEventListener('resize', onResize);// 释放渲染器资源if (renderer) {renderer.dispose();renderer.forceContextLoss();}
});
</script><style>
/* Three.js 容器样式 */
.three-container {position: fixed;top: 0;left: 0;width: 100%;height: 100%;outline: none;
}/* 控制面板样式 */
.control-panel {position: fixed;bottom: 20px;left: 50%;transform: translateX(-50%);display: flex;gap: 10px;align-items: center;background: rgba(0, 0, 0, 0.7);padding: 10px 15px;border-radius: 20px;color: white;
}/* 控制面板按钮样式 */
.control-panel button {background: rgba(255, 255, 255, 0.1);border: 1px solid rgba(255, 255, 255, 0.3);color: white;padding: 5px 12px;border-radius: 15px;cursor: pointer;transition: all 0.2s;
}/* 按钮悬停效果 */
.control-panel button:hover {background: rgba(255, 255, 255, 0.2);
}/* FPS 计数器样式 */
.fps-counter {font-family: monospace;min-width: 80px;display: inline-block;text-align: center;
}
</style>

实现效果

three.js动画

五、关键知识点总结

  1. 动画循环三要素

    • requestAnimationFrame 递归调用

    • 场景状态更新

    • 渲染器执行渲染

  2. 时间控制的重要性

    • 使用 performance.now() 或 THREE.Clock

    • 通过 delta time 确保动画速度一致

  3. 性能优化方向

    • 条件渲染(只在需要时渲染)

    • 分帧处理(复杂场景优化)

    • 合理使用 dispose() 释放资源

  4. Vue3 集成要点

    • 在 onMounted 中初始化

    • 在 onBeforeUnmount 中清理

    • 使用 ref 管理DOM元素引用

  5. 调试技巧

    • 添加FPS计数器监控性能

    • 使用坐标辅助器查看空间关系

    • 实现动画控制按钮方便调试

       这个实现展示了专业级的Three.js动画循环管理,包含了性能优化、时间控制、状态管理等关键要素,可以直接用于生产环境项目。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/93754.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/93754.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ktg-mes 改造成 Saas 系统

ktg-mes 改造成 Saas 系统 快速检验市场&#xff0c;采用最简单的方案&#xff0c;即添加表字段 截止2025年8月16日上传的ktg-mes搭建存在一些问题&#xff0c;搭建可看文章&#xff1a; 搭建ktg-mes 改造 1. 添加租户表 create table sys_tenant (tenant_id bigint au…

【新手易混】find 命令中 -perm 选项的知识点

find 命令是 Linux/Unix 系统中强大的文件查找工具&#xff0c;广泛用于根据文件名、类型、时间、权限等条件搜索文件。其中&#xff0c;-perm 选项用于按文件权限查找文件&#xff0c;而在 -perm /mode 中出现的斜杠 / 是一种特殊的语法&#xff0c;表示“按位或&#xff08;O…

gdb的load命令和传给opeocd的monitor flash write_image erase命令的区别

问&#xff1a; "monitor flash write_image erase ${workspaceFolder}/obj/ylad_led_blink.elf", 和 "load", "executable" : "${workspaceFolder}/obj/ylad_led_blink.elf", 的区别&#xff1f;答&#xff1a; 你提到的 "monit…

1. Docker的介绍和安装

文章目录1. Docker介绍核心概念核心优势与虚拟机的区别一句话总结2. Docker的安装Windows 10/11 安装 Docker Desktop&#xff08;推荐 WSL2 方式&#xff09;Linux&#xff08;以 Ubuntu / Debian 系为例&#xff09;Docker 是一个开源的容器化平台&#xff0c;它允许开发者将…

fastdds.ignore_local_endpoints 属性

Fast DDS 的 fastdds.ignore_local_endpoints 属性用于控制同一 DomainParticipant 下的本地端点&#xff08;即 DataWriter 和 DataReader&#xff09;是否自动匹配。以下是对该功能的详细解释&#xff0c;并翻译为中文&#xff0c;结合其上下文、实现原理和使用场景&#xff…

华清远见25072班C语言学习day11

重点内容:函数&#xff1a;定义&#xff1a;返回值类型 函数名(参数列表) { //函数体 }函数的参数列表中可以有多个数据返回值&#xff1a;如果函数没有返回值可以写成void 返回值的作用&#xff0c;函数的结果用来返回给主调函数的&#xff0c;如果主调函数处不需要函数的结果…

视觉语言导航(7)——VLN的数据集和评估方法 3.2

这是课上做的笔记&#xff0c;因此很多记得比较急&#xff0c;之后会逐步完善&#xff0c;每节课的逻辑流程写在大纲部分。成功率(SR)导航误差(NE)成功加权路径长度&#xff08;SucceedPLength&#xff09;轨迹长度&#xff08;TL&#xff09;先知成功率&#xff08;OS&#xf…

ElasticSearch不同环境同步索引数据

目的&#xff1a;在生产环境把一个索引的数据同步到测试环境中1、在生产环境导出json数据curl -u "adims_user:xkR%cHwR5I9g" -X GET "http://172.18.251.132:9200/unify_info_mb_sp_aggregatetb_0004/_search?scroll1m" -H Content-Type: applicatio…

咨询进阶——解读咨询顾问技能模型

适应人群为咨询行业从业者、咨询团队管理者、想提升咨询技能的职场人士及咨询公司培训人员。主要内容围绕咨询顾问技能模型展开,核心包括五大核心能力(解决问题能力,涵盖洞察力、分析技巧、问题构建等,从识别问题实质到构建新分析方法分层次阐述;管理能力,涉及管理他人与…

2025年- H98-Lc206--51.N皇后(回溯)--Java版

1.题目描述2.思路 二维数组集合 (1&#xff09;N皇后规则 1&#xff09;不能同行&#xff08;同一行不能出现2个皇后&#xff09; 2&#xff09;不能同列&#xff08;同一列不能出现2个皇后&#xff09; 3&#xff09;不能说45度或135度&#xff08;斜对角线不能出现2个皇后&am…

5G + AI + 云:电信技术重塑游戏生态与未来体验

在数字娱乐蓬勃发展的今天&#xff0c;游戏产业已然成为科技创新的前沿阵地。电信网络也经历了一场深刻的蜕变&#xff0c;从最初仅仅是 “内容传输管道”&#xff0c;摇身一变成为与游戏深度绑定的技术共生体。5G 不断刷新着体验的边界&#xff0c;AI 彻底颠覆传统的创作模式&…

【React Hooks】封装的艺术:如何编写高质量的 React 自-定义 Hooks

【React Hooks】封装的艺术&#xff1a;如何编写高质量的 React 自-定义 Hooks 所属专栏&#xff1a; 《前端小技巧集合&#xff1a;让你的代码更优雅高效》 上一篇&#xff1a; 【React State】告别 useState 滥用&#xff1a;何时应该选择 useReducer 作者&#xff1a; 码力…

华为GaussDB的前世今生:国产数据库崛起之路

在数据库领域&#xff0c;华为GaussDB已成为一颗耀眼的明星&#xff0c;为企业核心业务数字化转型提供坚实的数据底座。但这并非一蹴而就&#xff0c;其背后是长达二十余年的技术沉淀、战略投入与持续创新。本文将深入探寻华为GaussDB的历史沿革与核心技术细节&#xff0c;展现…

数据结构初阶(16)排序算法——归并排序

2.4 归并排序 归并排序&#xff08;Merge Sort&#xff09;是基于分治思想的经典排序算法。核心逻辑&#xff1a; 分而治之——把复杂排序问题拆分成简单子问题解决&#xff0c;再合并子问题的结果。联系链表的合并&#xff1a;两个有序链表l1、l2创建新链表l3&#xff08;带头…

MATLAB实现匈牙利算法求解二分图最大匹配

MATLAB实现匈牙利算法求解二分图最大匹配 匈牙利算法&#xff08;也称为Kuhn-Munkres算法&#xff09;是解决二分图最大匹配问题的经典算法。 代码 function [matching, max_match] hungarian_algorithm(adjMatrix)% HUNGARIAN_ALGORITHM 实现匈牙利算法求解二分图最大匹配% 输…

自定义table

更好<!DOCTYPE html> <html lang"zh-CN"><head><meta charset"utf-8"><title>数据表格</title><style>* {margin: 0;padding: 0;box-sizing: border-box;font-size: 14px;}html,body {width: 100%;height: 100%…

面向R语言用户的Highcharts

如果您喜欢使用 R 进行数据科学创建交互式数据可视化&#xff0c;那么请你收藏。今天&#xff0c;我们将使用折线图、柱状图和散点图来可视化资产回报。对于我们的数据&#xff0c;我们将使用以下 5 只 ETF 的 5 年月回报率。 SPY (S&P500 fund)EFA (a non-US equities fun…

【测试工具】OnDo SIP Server--轻松搭建一个语音通话服务器

前言 Ondo SIP Server 是一款基于 SIP(Session Initiation Protocol)协议的服务器软件&#xff0c;主要用于实现 VoIP(Voice over IP)通信&#xff0c;支持语音通话、视频会议等多媒体会话管理&#xff0c;非常适合学习和测试VoIP的基本功能。本文介绍Ondo SIP Server的安装、…

疯狂星期四文案网第42天运营日记

网站运营第42天&#xff0c;点击观站&#xff1a; 疯狂星期四 crazy-thursday.com 全网最全的疯狂星期四文案网站 运营报告 今日访问量 今日搜索引擎收录情况 网站优化点 优化一些发现的seo错误 增加颜文字栏目 增加了一些tag

使用空模型实例调用辅助函数,确定在量化过程中哪些层会被跳过(43)

在Facebook的OPT-350M中,模型的头部(lm_head)与解码器的嵌入标记层(decoder.embed_tokens)共享其权重。 print(model.model.decoder.embed_tokens) print(model.lm_head)输出结果 Embedding(50272, 512