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

使用worker执行Three.js中耗时的步骤

在Three.js开发3D应用时,我们常常会遇到一个棘手的问题:当执行一些高耗时操作(比如海量顶点生成、复杂模型解析、物理碰撞计算等)时,主线程会被阻塞,导致3D场景卡顿、帧率下降,甚至出现页面无响应的情况。

这是因为JavaScript是单线程语言,主线程既要负责Three.js的渲染循环,又要处理耗时计算,两者抢占资源就会引发性能瓶颈。

面对海量计算时,常见的一些处理方式会考虑如下几个方向:

  1. 缓存可复用的元素
  2. 看看有没有批量处理的方法
  3. 使用worker并行计算

下面介绍下使用woker运行第三方库的一些踩坑经验

一、问题描述

在我们需要将海量顶点生成时,最简单的一个代码如下:

将下面串行的代码改为多线程执行,使用worker来分担计算压力。很容易注意到,我们需要在worker中引入three.js 库

第一个问题就是:怎么在worker中引用第三方库。

import * as THREE from "three"; import { TextGeometry } from "three/addons/geometries/TextGeometry.js"; // list 数组内可能有几千个数据 const elementList = list.map(item => { // 定义文字几何参数 // 这个TextGeometry的生成比较耗费时间 const textGeometry = new TextGeometry(text, { font: this.font, size: 0.2, // 字体大小 depth: 0.00001, // 字体厚度 curveSegments: 12, bevelEnabled: true, bevelThickness: 0.0001, bevelSize: 0.00008, bevelSegments: 3, }); const textMaterial = new THREE.MeshBasicMaterial({ color: TEXT_COLOR }); const textMesh = new THREE.Mesh(textGeometry, textMaterial); return textMesh })

二、踩坑分享

踩坑描述:

需要找到在vite环境下,正确使用worker的示例。

网上问worker的使用,给的示例基本都是没有考虑vite的示例,有说设置{ type: 'module' }就可以 import,有说用importScripts()加载三分脚本的,其实都不对。

最佳实践:

问AI: "javascript的worker,在vite下引入three.js 的最佳实践"

vite 对 worker 中引入依赖做了处理,完美支持项目node_model中依赖。

vite + worker 核心实现:

worker.js
// 直接 import,完美支持 import * as THREE from "three"; import { TextGeometry } from "three/addons/geometries/TextGeometry.js"; // 监听主线程发来的消息 self.onmessage = (e) => { const { fontData, text, size, height } = e.data; // 模拟三方依赖的复杂计算 const textGeo = new TextGeometry(1, 1, 1); self.postMessage({ textGeo, }); };
main.js
import TextWorker from "./worker.js?worker"; // 注意这里的引入方式, 用new URL()的方式也是可以的 const worker = new TextWorker(); worker.postMessage({ fontData: "fontData", text: item, size: 0.8, height: 0.2, }); worker.onmessage = (e) => { const { position, normal, index } = e.data; console.log(e, "onmessage"); };

三、workerpool.js 的使用

Web Worker 线程池(复用多个子线程),提供了一种自然的、基于Promise的代理方式来访问工作线程,就像它们直接在主应用程序中可用一样。

要是你的程序需要完美的import的支持,可以考虑使用 workerpool.js 来管理线程池。

说明文档:链接

使用案例

worker.js

import * as THREE from "three"; import { TextGeometry } from "three/addons/geometries/TextGeometry.js"; import workerpool from "workerpool"; // 要暴露给主线程调用的方法 function createText(text) { const textGeometry = new THREE.BoxGeometry(1, 1, 1); const textMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 }); const textMesh = new THREE.Mesh(textGeometry, textMaterial); // console.log(textMesh, "textMesh"); return { textMesh: textMesh.geometry, // 如果运行报错了,可以看看是不是回传的对象太复杂了。 }; } // 注册方法,让主线程可以调用 workerpool.worker({ createText, });

main.js

<script setup> import workerpool from "workerpool"; import { onMounted } from "vue"; import workerURL from "./text.worker.js?worker&url"; onMounted(() => { // 1. 创建线程池:最大并发 4 个 Worker const pool = workerpool.pool(workerURL, { maxWorkers: 4, workerType: "web", workerOpts: { type: "module", // 必须开启 module 模式 }, }); setTimeout(async () => { const tasks = [ "37.13", "37.12", "37.13", "37.13", "37.12", "37.13", "37.13", "37.12", "35.13", "35.13", ]; // 模拟批量计算 const promises = tasks.map((item) => pool.exec("createText", [item])); Promise.all(promises) .then((data) => { console.log(data, "data"); }) .catch((err) => { console.log(err, "err"); }); }, 1 * 1000); }); </script>

四、复杂对象回传

问题描述:

因为worker的能力限制,worker 只能回传一些简单的对象,当我们回传了一个复杂对象时,会报错误。

text.worker.js:16 Uncaught DataCloneError: Failed to execute 'postMessage' on 'DedicatedWorkerGlobalScope': function onRotationChange() { quaternion.setFromEuler(rotation, false); } could not be cloned. at self.onmessage (text.worker.js:16:8)

解决方法:

  1. 不要回传整改对象,可以考虑回传关键的几个子属性,再在主线程中,重新构建对象
  2. 使用 ArrayBuffer + Transferable 零拷贝高速传大数据
http://www.jsqmd.com/news/856663/

相关文章:

  • 3分钟掌握B站视频转文字:bili2text完整指南与效率提升方案
  • 智慧树刷课插件:如何用自动化工具解放你的学习时间
  • 告别官方镜像:手把手教你用Armbian Build系统为树莓派5定制专属Debian系统
  • 5月精选!市面上口碑好的不锈钢离心泵源头厂家推荐分析,不锈钢无负压供水设备/灌溉泵,离心泵直销厂家哪个好 - 品牌推荐师
  • 杂木半成品定制厂家哪家好,云松木业口碑出众 - mypinpai
  • 口碑好的郑州医考机构推荐
  • 导师不会告诉你的秘密:9款免费AI神器,30分钟生成高信度问卷论文 - 麟书学长
  • ArcGIS Pro 3.0 加载天地图WMTS服务,解决偏移问题的保姆级教程(附最新Key申请流程)
  • Gemini 3.5 Flash 实测报告:快4倍、编程跑分超自家Pro,这6类场景到底该不该换?
  • 超越基础采集:用STC89C51和ADC0832打造简易数据记录仪(串口绘图/Excel分析)
  • Ccursor安装使用
  • 波卡XCMP深度解析:跨链通信的核心标准与实战指南
  • Vivado ILA核的‘高级玩法’:用多个比较器实现复杂触发,告别简单边沿抓取
  • 别再写一堆if-else了!用状态机重构你的嵌入式C代码(附3种实现对比)
  • ESP32-C3 I²S实战:手把手教你驱动ES8311音频编解码器实现回声消除
  • 从ResNet到Res2Net:手把手教你理解ECAPA-TDNN中的多尺度特征提取(附PyTorch代码)
  • 2026断桥铝门窗十大品牌揭晓!装修选窗认准这几家,闭眼入不踩坑!
  • 手把手教你用Arduino+CAN总线模块DIY一个OBD升窗器(附代码与调试心得)
  • 【Perplexity本地新闻查询实战指南】:零配置部署+实时数据源接入,3步搞定离线新闻检索系统
  • 若依框架:自定义接口与权限验证实践
  • c语言循环结构-for
  • Python 实现电脑垃圾自动清理工具(附完整源码)
  • 思科Packet Tracer 7.4 生成树协议(STP)配置与安全防护上机讲义
  • 告别手动!用J-Flash批处理脚本+USB-HUB,实现多Jlink同时烧录STM32(附完整脚本)
  • 深入解析Cosmos IBC:跨链通信的核心标准、实战应用与未来展望
  • 从‘动物叫’到‘电机转’:我的Codesys面向对象编程踩坑实录与避坑指南
  • MXM-ACMA模块化GPU:AI边缘计算的高性能可升级解决方案
  • NISP的社会价值和高含金量!
  • CANape标定窗口被锁?三步排查工程配置陷阱
  • csp信奥赛C++高频考点专项训练之前缀和差分 --【一维前缀和】:“非常男女”计划