翻译《threejsfundamentals》离屏渲染+web-worker一篇

Three.js OffscreenCanvas   

        OffscreenCanvas是一种相对较新的浏览器功能,目前仅在Chrome中可用,但显然也即将适用于其他浏览器。

        OffscreenCanvas使web worker能够渲染canvas。这是一种办法把繁重的工作(如渲染复杂的3D场景)交给web worker,为了避免降低浏览器的响应速度。这还意味着数据是在worker中加载和解析的,因此在页面加载时可能会减少卡顿(jank)   

        初步的使用非常简单,让我们移植3旋转立方体示例从the article on responsiveness。worker通常将他们的代码分离到另一个脚本文件中,而这个站点上的大多数示例都将他们的脚本嵌入到他们所在页面的HTML文件中。 在本例中,我们将创建一个名为offscreencanvas-cubes.js的文件,并将响应式example中的所有JavaScript复制到该文件中。然后,我们将进行必要的更改,使其在辅助进程中运行。

        我们仍然需要写一些JavaScript代码在HTML页面中。我们需要做的第一件事是找到canvas,然后通过调用 canvas.transferControlToOffscreen 将该画布的控制权转移到屏幕外。

function main() {
  const canvas = document.querySelector('#c');
  const offscreen = canvas.transferControlToOffscreen();
 
  ...

        然后,我们可以用新的 worker(pathToScript,{type:'module'} 启动我们的worker。并将 offscreen 对象传递给它。

function main() {
  const canvas = document.querySelector('#c');
  const offscreen = canvas.transferControlToOffscreen();
  const worker = new Worker('offscreencanvas-cubes.js', {type: 'module'});
  worker.postMessage({type: 'main', canvas: offscreen}, [offscreen]);
}
main();

        需要注意的是,worker不能访问DOM。他们不能查看HTML元素,也不能接收鼠标事件或键盘事件。通常他们能做的唯一一件事就是响应发送给他们的消息,并将消息发送回页面。

        要向worker发送消息,我们调用 worker.postMessage 并向其传递1或2个参数。第一个参数是一个JavaScript对象,它将被 clone 并发送给worker。第二个参数是一个可选的对象数组,它是第一个对象的一部分,第一个对象是我们想要传递给worker的对象。这些对象将不会被克隆。相反,它们将被转移,并将不再存在于主页中。“不存在”可能是错误的描述,相反,它们是中性的。只能传输某些类型的对象,而不能克隆。它们包括 OffscreenCanvas ,因此一旦将 OffscreenCanvas 传输回主页,就没有用了。

        worker从他们的 onmessage 处理程序接收消息。我们传递给 postMessage 的对象在 event.data 传递给worker上的 onmessage 处理程序时到达。上面的代码在传递给worker的对象中声明了一个类型:“main”。此对象对浏览器没有意义。这完全是我们自己用的。我们将创建一个处理程序,他基于类型调用worker中不同函数。然后,我们可以根据需要添加函数,并从主页面轻松调用它们。

const handlers = {
  main,
};
 
self.onmessage = function(e) {
  const fn = handlers[e.data.type];
  if (!fn) {
    throw new Error('no handler for type: ' + e.data.type);
  }
  fn(e.data);
};

        调用一个不同的函数,您可以在上面看到,我们只是根据类型查找处理程序,并将从主页发送的数据传递给它,因此,现在我们只需要开始更改我们从the article on responsiveness 粘贴到 offscreencanvas-cubes.js 中的 main

        我们将从事件数据接收canvas,而不是从DOM中查找canvas。(canvas可以传递吗)

//function main() {
//  const canvas = document.querySelector('#c');
function main(data) {
  const {canvas} = data;
  const renderer = new THREE.WebGLRenderer({canvas});
 
  ...

        记住worker根本看不到DOM,我们遇到的第一个问题是 ResizeRenderToDisplaySize 不能查看 canvas.clientWidthcanvas.ClientHight ,因为它们是DOM值。这是原始代码

function resizeRendererToDisplaySize(renderer) {
  const canvas = renderer.domElement;
  const width = canvas.clientWidth;
  const height = canvas.clientHeight;
  const needResize = canvas.width !== width || canvas.height !== height;
  if (needResize) {
    renderer.setSize(width, height, false);
  }
  return needResize;
}

        相反,我们需要在 size 变化时将其发送给worker。所以,让我们添加一些全局状态,并将宽度和高度放置在那里。

const state = {
  width: 300,  // canvas default
  height: 150,  // canvas default
};

        然后,让我们添加一个 “size” 处理程序来更新这些值。

function size(data) {
  state.width = data.width;
  state.height = data.height;
}
 
const handlers = {
  main,
  size,
};

        现在我们可以将 ResizeRenderToDisplaySize 更改为使用 state.widthstate.height

function resizeRendererToDisplaySize(renderer) {
  const canvas = renderer.domElement;
  // const width = canvas.clientWidth;
  // const height = canvas.clientHeight;
  const width = state.width;
  const height = state.height;
  const needResize = canvas.width !== width || canvas.height !== height;
  if (needResize) {
    renderer.setSize(width, height, false);
  }
  return needResize;
}

         在我们计算aspect 的地方,我们需要类似的变化

function render(time) {
  time *= 0.001;
 
  if (resizeRendererToDisplaySize(renderer)) {
    // camera.aspect = canvas.clientWidth / canvas.clientHeight;
    camera.aspect = state.width / state.height;
    camera.updateProjectionMatrix();
  }
 
  ...

        回到主页,我们将在页面大小更改时发送大小事件。

const worker = new Worker('offscreencanvas-picking.js', {type: 'module'});
worker.postMessage({type: 'main', canvas: offscreen}, [offscreen]);
 
function sendSize() {
  worker.postMessage({
    type: 'size',
    width: canvas.clientWidth,
    height: canvas.clientHeight,
  });
}
 
window.addEventListener('resize', sendSize);
sendSize();

        我们还可以调用它一次,以发送初始大小。

        仅做了这些改动,假设您的浏览器完全支持Offscreencanvas,它应该可以正常工作。在我们运行它之前,让我们检查一下浏览器是否真的支持OffscreenCanvas,如果不支持,则显示一个错误。首先,让我们添加一些HTML来显示错误。

<body>
  <canvas id="c"></canvas>
  <div id="noOffscreenCanvas" style="display:none;">
    <div>no OffscreenCanvas support</div>
  </div>
</body>

        和一些css:

#noOffscreenCanvas {
    display: flex;
    width: 100%;
    height: 100%;
    align-items: center;
    justify-content: center;
    background: red;
    color: white;
}

        然后我们可以检查 transferControlToOffscreen 是否存在,以查看浏览器是否支持 OffscreenCanvas 

function main() {
  const canvas = document.querySelector('#c');
  if (!canvas.transferControlToOffscreen) {
    canvas.style.display = 'none';
    document.querySelector('#noOffscreenCanvas').style.display = '';
    return;
  }
  const offscreen = canvas.transferControlToOffscreen();
  const worker = new Worker('offscreencanvas-picking.js', {type: 'module});
  worker.postMessage({type: 'main', canvas: offscreen}, [offscreen]);
 
  ...

        如果你的浏览器支持Offscreencanv,那么这个例子就可以了(官方页面中有,我没粘过来,在博客园弄js不太方便)

        click here to open in a separate window

        这个例子很不错,但因为目前不是每个浏览器都支持OffscreenCanvas,所以让我们将代码更改为同时使用 OffscreenCanvascanvas ,如果不支持 OffscreenCanvas ,则退回到主页中使用 canvas ,就像正常情况一样

        顺便说一句,如果你需要OffscreenCanvas来让你的页面更具响应性,那么现在还不清楚回退的意义何在。可能基于您是否最终在主页面上运行或在worker中运行,您可能会调整完成的工作量,以便在worker中运行时可以比在主页面上运行时做得更多。你做什么真的取决于你自己。

        我们应该做的第一件事可能是将three.js代码与特定于worker的代码分开。这样我们就可以在主页和工作页面上使用相同的代码。换句话说,我们现在将有3个文件

    1. our html file: threejs-offscreencanvas-w-fallback.html 
    2. a JavaScript that contains our three.js code: shared-cubes.js 
    3. our worker support code: offscreencanvas-worker-cubes.js 

        shared-cubes.js 和 offscreencanvas-worker-cubes.js 基本上是我们之前 offscreencanvas-cubes.js 文件的拆分。首先,我们将 offscreencanvas-cubes.js 复制到 shared-cube.js 。然后我们将 main 重命名为 init ,因为我们的HTML文件中已经有一个 main ,我们需要导出 init 和 state 

import * as THREE from './resources/threejs/r132/build/three.module.js';
 
const state = {
export const state = {
  width: 300,   // canvas default
  height: 150,  // canvas default
};
 
function main(data) {
export function init(data) {
  const {canvas} = data;
  const renderer = new THREE.WebGLRenderer({canvas});

(课业太忙了,暂时没时间写了,未完待续。。。)

 

posted @ 2021-11-06 14:39  dou_fu_gan  阅读(1041)  评论(0编辑  收藏  举报