WebWorker、ThreeJs的渲染和控制

news2024/10/6 2:25:49

在这里插入图片描述

ios16.4 版本中已经开始支持了 OffscreenCanvas ,那看样子,是时候再把Three做一波优化了

背景介绍

在之前的项目经验中,如果使用threejs加载比较大的3d场景,那么在创建 threejs 的对象和绘制的时候,会占用浏览器线程执行一个大时长的任务,导致页面卡住,不能交互。

那有什么即可以绘制 canvas 又不占用主线程的方法吗?

今天它来了(其实已经来了很久了)

使用WebWorker + OffscreenCanvas 就可以实现在另外的线程中绘制canvas ,从而做到不影响主线程。

本文不会主要介绍 WebWorkerThreejs 基础知识,只是一篇实操(辛酸史),但是在必要的时候会提供相关的链接

WebWorker 可以在后台启动一个线程执行js脚本,并且不会影响到主线程。

关于Webworker: 使用 Web Workers - Web API 接口参考 | MDN

OffscreenCanvas 是一个可以脱离屏幕渲染的canvas 对象,在串口环境和 WebWorker 环境都可以使用

关于OffscreenCanvas: OffscreenCanvas - Web API 接口参考 | MDN

项目开始

接下来就实践一下WebWorker + Threejs 渲染3d场景,并使用 OrbitControls 实现人机交互

Demo使用 vue3 开发

app.vue

<template> 
    <canvas ref="canvas"></canvas>
</template>
<script setup>
import { ref } from 'vue'
canvas = ref()
</script>

WebWorker 中只能使用 OffscreenCanvas,不能直接操作 DOM,所以需要把 canvas 元素转成 OffscreenCanvas 对象,在传递给 WebWorker 中使用

vue 的组件 onMounted 生命周期中,可以访问 DOM 元素

App.vue

import {
  // ...other 
  onMounted 
} from 'vue'
import Worker from './worker?worker'
const worker = new Worker()
onMounted(() =>{
  const offCanvas = canvas.value.transferControlToOffscreen()  
})

WebWorker 的通信通过 postMessageonmessage

worker.postMessage({
	type: 'init',
	data: {
		offCanvas
	}
}, [offCanvas])    

webworker 中接收传入的 OffscreenCanvas 对象

worker.js

self.onmessage = function ({ data }) {
    switch (data.type) {
        case 'init':
            init(data.data)
            break;
    }
}

使用传入的 OffscreenCanvas 对象做threejs的渲染

import {
    OrthographicCamera,
    Scene,
    WebGLRenderer,
    BoxGeometry,
    MeshLambertMaterial,
    Mesh,
    AmbientLight
} from 'three'


self.onmessage = function ({ data }) {
    switch (data.type) {
        case 'init':
            init(data.data)
            break;
    }
}

let scene;
let camera;
let renderer;

function init(data) {
    const { offCanvas } = data
    const { width, height } = offCanvas
    initScene()
    initLight()
    initCamera(width, height)
    initRenderer(offCanvas, width, height)
    render()
}
// 初始化相机
function initCamera(w, h) {
    const k = w / h;
    const s = 300;
    camera = new OrthographicCamera(-s * k, s * k, s, -s, 1, 1000)
    camera.position.set(550, 600, 100);
    camera.lookAt(scene.position);
}

// 初始化渲染器
function initRenderer(canvas, w, h) {
    renderer = new WebGLRenderer({canvas})
    renderer.setSize(w, h, false);
    renderer.setClearColor(0xb9d3ff, 1)
}

// 初始化场景
function initScene() {
    scene = new Scene();
    function createMesh(i) {
        // 立方体网格模型
        const geometry1 = new BoxGeometry(10, 10, 10);
        const material1 = new MeshLambertMaterial({
            color: 0x0000ff
        })
        const mesh1 = new Mesh(geometry1, material1);
        mesh1.translateZ(i * 10)
        scene.add(mesh1);
    }

    for (let i = 0; i < 10; i ++) {
        createMesh(i)
    }
}

// 初始化环境光
function initLight() {
    const ambient = new AmbientLight(0x444444)
    scene.add(ambient)
}

// 开始渲染
function render() {
    function _render() {
        renderer.render(scene, camera);
        self.requestAnimationFrame(_render)
    }
    _render()
}

在这里插入图片描述

写完 worker.js之后,3d场景已经可以渲染但是不能交互,threejs 中有提供OrbitControls 轨道控制器做交互

OrbitControls 需要在页面上绑定DOM事件 实现人机交互

因为 WebWorker 中不能操作 DOM 所以 OrbitControls 不能直接在 WebWorker 中使用,要在主线程中使用

用法很简单,只需要传入一个 Camera 对象,和一个绑定事件用的 DOM元素 就可以了

import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
new OrbitControls(camera, canvas)

主线程中已经有了一个canvas 元素,现在还需要一个 camera 对象,第一考虑直接把 WebWorker 中创建的 Camera 传到主线程中

worker.js

// 初始化相机
function initCamera(w, h) {
    // ...other
    self.postMessage({
        type: 'create-camera',
        data: { camera }
    })
}

App.vue

let camera;
function createCamera(data) {
	camera = data.camera;
}
worker.onmessage = function ({data}) {
	switch (data.type) {
		case "create-camera":
			createCamera(data.data);
			break;
	}
}

控制台出现报错,不可行
在这里插入图片描述

原因是因为 postMessage 方法传递的参数,必须是可以被结构化克隆算法处理的JavaScript对象。

Function 对象和 Dom 对象是不能被结构化克隆算法复制的

关于WebWorker.postMessage:Worker.postMessage() - Web API 接口参考 | MDN

关于结构化克隆算法:结构化克隆算法 - Web API 接口参考 | MDN

既然不能直接传 Camera 对象,那就传递创建 Camera 使用的参数,在主线程中创建一个一样的 Camera 对象

worker.js

// 通知主线程camera创建成功
function dispatchCreateCamera(data) {
    self.postMessage({
        type: 'create-camera',
        data: data
    })
}
// 初始化相机
function initCamera(w, h) {
    // ...other
    dispatchCreateCamera({
        args: [-s * k, s * k, s, -s, 1, 1000],
        position: [550, 600, 100],
        lookAt: [scene.position.x, scene.position.y, scene.position.z]
    })
}

App.vue

import { OrthographicCamera, Vector3 } from 'three'
function createCamera(data) {
	const { args, position, lookAt} = data;
	camera = new OrthographicCamera(...args)
	camera.position.set(...position);
	camera.lookAt(new Vector3(...lookAt));
}
new OrbitControls(camera, canvas.value)

这样就创建了控制器了,但是现在控制器还是没有实现交互,因为现在修改的是主线程的Camera ,而canvas绘制是用的 WebWorker 中的 Camera,所以还需要把控制器对 Camera 的修改同步到 WebWorker

OrbitControls 的代码(这里就不展开看了)发现在事件处理中,通过调用 scope.update 完成对 Camera 的修改

OrbitControlsupdate 方法中,除了 对 Cameraposition 的修改外,还调用了 CameralookAt 方法,所以这里我们做一个投机取巧的操作。

Camera 做代理,每次调用 lookAt 的时候,就把 CamerapositionzoomlookAt 的参数,传递给 WebWorker ,对 WebWorker 中的 Camera 做一样的操作,完成交互

App.vue

function dispatchCameraUpdate(data) {
	worker.postMessage({
		type: 'update-camera',
		data
	})
}
function createCamera(data) {
	// ...other
	const $camera = new Proxy(camera, {
		get(target, key, receiver) {
			const value = Reflect.get(target, key, receiver)
			if (key === 'lookAt') {
				return function ($target) {
					value.call(target, $target)
					dispatchCameraUpdate({
						position: [camera.position.x, camera.position.y, camera.position.z],
						lookAt: [$target.x, $target.y, $target.z],
						zoom: camera.zoom
					})
				}
			}
			return value
		}
	})
	new OrbitControls($camera, canvas.value)
}

worker.js

import {
    // ...other
    Vector3
} from 'three'
self.onmessage = function ({ data }) {
    switch (data.type) {
        // ...other
        case 'update-camera':
            updateCamera(data.data)
            break;
    }
}
function updateCamera(data) {
    const { position, lookAt, zoom } = data;
    camera.zoom = zoom;
    camera.position.set(...position)
    camera.lookAt(new Vector3(...lookAt))
    // 修改了zoom之后需要调用updateProjectionMatrix
    camera.updateProjectionMatrix()
}

在这里插入图片描述

现在已经完成了在 WebWorker 中操作 Three ,在做一个绘制大量元素的场景,看一下浏览器是否还会有大时长任务阻塞

worker.js

// 初始化场景
function initScene() {
    // ...other

    for (let i = 0; i < 10000; i ++) {
        createMesh(i)
    }
}

在这里插入图片描述

可以看到在threejs绘制期间,浏览器的渲染并没有被阻塞,在WebWorker 中有一个 2.43s的长任务,这个任务的执行,并不会阻塞浏览器的渲染,这就是 WebWorker的后台渲染

在这里插入图片描述

问题

  1. ios16.4支持了 OffscreenCanvas 但是并没有支持 3d 的应用,OffscreenCanvas 获取 webgl 的上下文返回的是 null

  2. 现在的主流设备并没有完全支持 OffscreenCanvas ,所以开发中还需要考虑好兼容性

参考链接

使用 Web Workers - Web API 接口参考 | MDN

OffscreenCanvas - Web API 接口参考 | MDN

Worker.postMessage() - Web API 接口参考 | MDN

结构化克隆算法 - Web API 接口参考 | MDN

代码地址

GitHub - wukang0718/webworker-three: 在webworker中渲染three的Demo

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

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

相关文章

认识C++《共、枚、指1》

目录 前言: 1.共用体的基本知识 2.匿名共用体 3.枚举 3.1设置枚举值 3.2枚举的应用场景 3.3枚举变量的取值范围 4.地址和自由存储空间 5.指针的思想 6.指针的声明和初始化 前言: 指针内容比较多&#xff0c;还需要再出一篇。久等了&#xff01;&#xff01;我看了我的…

数据库中的视图及三级模式结构

文章目录一、视图二、数据库三级模式结构一、视图 简单地说&#xff0c;视图可以看成是一个窗口&#xff0c;它所反映的是一个表或若干表的局部数据&#xff0c;可以简化查询语句。视图一经定义&#xff0c;用户就可以把它当作表一样来查询数据。 但视图和基本表不同&#xf…

Python算法设计 - Karatsuba乘法

版权声明&#xff1a;原创不易&#xff0c;本文禁止抄袭、转载&#xff0c;侵权必究&#xff01; 目录一、Karatsuba 乘法二、算法思路三、Python算法实现四、作者Info一、Karatsuba 乘法 当你在纸上做两个数字的乘法时&#xff0c;一般我们都是用小时候学到的方法&#xff1a…

22.SSM-JdbcTemplate总结

目录 一、JdbcTemplate对象。 &#xff08;1&#xff09;Spring产生JdbcTemplate对象。 &#xff08;2&#xff09;JdbcTemplate常用操作。 &#xff08;3&#xff09;知识要点。 一、JdbcTemplate对象。 &#xff08;1&#xff09;Spring产生JdbcTemplate对象。 这个是Sp…

AIGC大模型时代下,该如何应用高性能计算PC集群打造游戏开发新模式?

ACT | SIM | ETC | FTG | RAC AVG | RPG | FPS | MUG | PUZ ACT、SIM、ETC、FTG、RAC、RTS、STG、AVG、RPG、FPS、MUG、PUZ、SLG、SPG等游戏类型&#xff0c;需要高性能的计算机来支持运行。为了满足这些游戏的需求&#xff0c;国内服务器厂商不断推出新的产品&#xff0c;采用…

定点数加减运算

定点数加减运算 文章目录定点数加减运算格式相同位宽相同但不同格式运算位宽不同的定点数运算1.转换为S5.10格式的相同位宽2.统一转换为S10.5格式的相同位宽定点数运算可直接通过处理器内置的整数单元实现格式相同 加减法就是对应二进制形式的有符号整数的加减运算 例如 2.71…

[Date structure]时间/空间复杂度

⭐作者介绍&#xff1a;大二本科网络工程专业在读&#xff0c;持续学习Java&#xff0c;努力输出优质文章 ⭐作者主页&#xff1a;逐梦苍穹 ⭐所属专栏&#xff1a;数据结构。数据结构专栏主要是在讲解原理的基础上拿Java实现&#xff0c;有时候有C/C代码。 ⭐如果觉得文章写的…

手写锚点,且随着滚动屏幕自动高亮

入上图效果 通过判断几个id对应的dom离滚动区域上方的高度跟滚动区域高度对比高亮锚点 <template><div v-loading"totalLoading" class"define-target-container"><el-radio-group v-model"ucatsType" class"ucats-setti…

AUTOSAR Gateway介绍

概述 熟悉整车电子架构的朋友们都知道,Gateway(网关)在整车网络架构中协调不同物理链路数据的交换,发挥着数据中枢作用。本文为大家介绍AUTOSAR架构中Gateway的应用方式。 在AUTOSAR架构中,Signal、Signal Group或者PDU从一个源总线接收可以发送到一个或相同或不同协议的总…

ChatGPT可以做WebRTC音视频质量性能优化,惊艳到我了

摘要 随着GPT-4的发布&#xff0c;AI的风越吹越旺。GPT-4可以回答问题&#xff0c;可以写作&#xff0c;甚至可以基于一张草图生成html代码搭建一个网站。即构社区的一位开发者倪同学就基于目前在研究的WebRTC QoS技术点对GPT-3.5跟GPT-4进行一场实验&#xff0c;ChatGPT会取代…

HTTP与HTTPS的区别;TLS握手过程

一、HTTP协议与HTTPS 我们都知道当客户端与服务端需要进行通信时&#xff0c;需要根据一套协议来进行通信。 HTTP全程是超文本传输协议&#xff08;Hyper Text Transfer Protocol&#xff0c;HTTP&#xff09;是一个简单的请求-响应协议&#xff0c;它通常运行在TCP之上。它指…

Redis应用问题及解决

目录 一.缓存穿透 1.1 问题描述 1.2 解决方案 二.缓存击穿 2.1 问题描述 2.2 解决方案 三.缓存雪崩 3.1 问题描述 3.2 解决方案 当数据库压力变大&#xff0c;导致服务访问数据库响应变慢&#xff0c;导致服务的压力变大&#xff0c;最终可能导致服务宕机。 一.缓存穿透 1.1 …

Linux编译器——gcc/g++使用

前言&#xff1a; 在上一篇&#xff0c;我们学习了关于文本编辑器 vim 的全部知识&#xff0c;今天给大家带来的是关于Linux编译器—gcc/使用的详细介绍。 本文目录 &#xff08;一&#xff09;温习程序的产生的过程 1、前言 2、程序的产生过程 3、&#x1f31c;初步认识 gc…

深度学习中的算法学习与记忆,利用故事联想帮助大家记忆,每个人都会

大家好&#xff0c;我是微学AI&#xff0c;大家看过我的文章&#xff0c;想必是对深度学习有了一定的了解了&#xff0c;但是对于初学者来说&#xff0c;深度学习中有很多名词和数学知识、原理还是不太清楚&#xff0c;记忆的不牢固&#xff0c;用起来不熟练&#xff0c;今天就…

网络安全 - Web应用防护墙(WAF)

什么WAF Web应用防护墙&#xff08;Web Application Firewall&#xff09;简称WAF。是一种特定形式的应用程序防火墙&#xff0c;用于过滤、监控和阻断通过网页服务的HTTP流量。通过监察HTTP流量&#xff0c;它可以防止利用网页应用程序已知漏洞的攻击&#xff0c;例如SQL 注入…

适配器模式C++用法示例

五.适配器模式一.适配器模式1.原理2.适用场景3.代理、桥接、装饰器、适配器区别4.分类&#xff08;类适配器模式、对象适配器模式&#xff09;二.C程序示例1.类适配器2.对象适配器一.适配器模式 1.原理 适配器模式的原理是将一个类的接口转换成客户希望的另一个接口。适配器模…

权威认证!腾讯云数据安全中台入选工信部商用密码典型应用方案

近日&#xff0c;工业和信息化部、国家密码管理局发布了《关于公布工业和信息化领域商用密码典型应用方案名单的通知》&#xff0c;腾讯云“基于商用密码的数据安全中台”在众多方案中脱颖而出&#xff0c;成功入选工业和信息化领域商用密码典型应用方案名单。 密码可以实现信息…

在springboot项目中使用rocketmq消息队列实战

rocketmq环境搭建 在docket环境下安装部署rocketmq的方法记录在上一篇文章中。 (31条消息) docker环境下搭建rocketmq集群_haohulala的博客-CSDN博客 这种方式不一定是最好的&#xff0c;但是我用这种方式可以成功搭建rocketmq开发环境。 项目架构 我们需要在springboot中…

CSDN 周赛填空题,充满恶意的嘲讽

CSDN 周赛填空题&#xff0c;充满恶意的嘲讽41期的填空题44期的填空题45期的填空题再说题型老顾最近一直在玩 csdn 周赛&#xff0c;没啥想法&#xff0c;就是想票点小玩意&#xff0c;之前从第四十一期开始&#xff0c;题型进行了扩展&#xff0c;增加了填空、判断、单选。扩展…

C#调试与测试 | Assert(断言)

Assert(断言) 文章目录Assert(断言)前言什么是Assert适用场景使用示例检查传入的参数是否为空检查循环变量是否在规定范围内检查方法返回值是否为null结束语前言 今天我要和大家聊聊C#调试和测试中的一种神器——断言&#xff08;Assert&#xff09;。如果你还不知道什么是断言…