在 Three.js Shader 中从 OffscreenCanvas 渲染动态纹理时出现问题

问题描述 投票:0回答:1

我正在开发一个 React Three Fiber 项目,在该项目中,我尝试从在 WebWorker 中更新的 OffscreenCanvas 渲染动态纹理。然而,我得到的只是网格上的黑色渲染,我正在努力找出问题所在。

我使用 THREE.CanvasTexture 作为纹理,使用自定义着色器作为材质,并使用 useFrame 循环来更新纹理。以下是我的代码的简化和相关部分:

组件(component.ts):

import { useRef, useEffect, useState } from "react";
import { useFrame } from "@react-three/fiber";
import * as THREE from 'three';
import TextureShader from "./TextureShader";

const MyComponent = () => {
  const [texture, setTexture] = useState<THREE.Texture>();
  const meshRef = useRef<THREE.Mesh>(null);

  useEffect(() => {
    const offscreenCanvas = new OffscreenCanvas(100, 100);
    const canvasTexture = new THREE.CanvasTexture(offscreenCanvas);
    setTexture(canvasTexture);

    // Initialize and post message to worker (not shown for brevity)

    // ... other initialization code ...
  }, []);

  useFrame(() => {
    if (texture) {
      texture.needsUpdate = true;
    }
  });

  return (
    <mesh ref={meshRef}>
      <boxBufferGeometry args={[1, 1, 1]} />
      <TextureShader uTexture={texture} />
    </mesh>
  );
};

export default MyComponent;

着色器(TextureShader.ts):

import { shaderMaterial } from '@react-three/drei';
import * as THREE from 'three';

const vert = /* glsl */`
varying vec2 vUv;
void main() {
  vUv = uv;
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;

const frag = /* glsl */`
uniform sampler2D uTexture;
varying vec2 vUv;
void main() {
  gl_FragColor = texture2D(uTexture, vUv);
}
`;

const TextureShader = shaderMaterial({ uTexture: new THREE.Texture() }, vert, frag);

export default TextureShader;

WebWorker(worker.ts):

// Simplified worker logic for drawing on the OffscreenCanvas
self.onmessage = (event) => {
  if (event.data.canvas) {
    const canvas = event.data.canvas;
    const ctx = canvas.getContext('2d');

    // Drawing logic (e.g., ctx.fillRect(...))
  }
};

纹理应由 WebWorker 动态更新,但当使用自定义着色器应用于网格时,它仅呈现黑色。着色器可以很好地处理静态颜色,因此我怀疑问题在于动态纹理更新过程或 OffscreenCanvas 的管理方式。

我尝试过的:

确保 useFrame 循环中的纹理的 needUpdate 设置为 true。 验证 WebWorker 是否在 OffscreenCanvas 上正确绘制。 使用更简单的材质来排除着色器问题。 问题:

问题是否与 WebWorker 中处理 OffscreenCanvas 的方式有关? 有没有一种特定的方法可以从 WebWorker 中绘制的 OffscreenCanvas 更新 THREE.CanvasTexture? 任何见解或建议将不胜感激

reactjs three.js shader offscreen-canvas
1个回答
0
投票

就像当我们传输一个

ArrayBuffer
实例时,它会被分离,并且它的
length
设置为
0
并且你基本上不能再用它做任何事情了,当传输
OffscreenCanvas
实例时,它也是分离的,它的
width
height
设置为
0
,您将无法再访问其绘图缓冲区。

const canvas = new OffscreenCanvas(50, 50);
console.log("before", canvas.width, canvas.height); // 50, 50
// Transfer the OffscreenCanvas (even to the same JS context)
(new MessageChannel()).port1.postMessage("", [canvas]);
console.log("after", canvas.width, canvas.height); // 0, 0

可以做的是,使用它的

OffscreenCanvas
方法从
<canvas>
元素中获取
transferControlToOffscreen()
。这样做,占位符
<canvas>
内容将在每个绘画帧中更新(基本上与
requestAnimationFrame
相同的速度,并且您将能够从主要上下文中绘制它。

(使用 2D 上下文以便于演示,但

CanvasTexture
应该具有相同的作用。)

const placeholder = document.createElement("canvas");
const offscreen = placeholder.transferControlToOffscreen();
const worker = new Worker(
  URL.createObjectURL(new Blob(
    [document.querySelector("[type=worker-script]").textContent],
    { type: "text/javascript" }
  ))
);
worker.postMessage(offscreen, [offscreen]);
worker.onmessage = (evt) => {
  const ctx = document.querySelector("canvas").getContext("2d");
  ctx.drawImage(placeholder, 0, 0);
};
<script type=worker-script>
onmessage = ({data: canvas}) => {
  const ctx = canvas.getContext("2d");
  ctx.fillStyle = "green"
  ctx.fillRect(0, 0, 300, 150);
  // wait 'till next paint befre saying we're done
  requestAnimationFrame(() => postMessage("done"));
};
</script>
<canvas></canvas>

但是,这样做,你总是会晚一帧,并且可能会遇到浏览器错误(例如我的 Firefox 无法正确使用占位符画布作为源)。

因此,您可能想从工作线程传输

ImageBitmap
对象,调用
canvas.transferToImageBitmap()
并记住在完成将其加载为 close()
 时对其进行 
CanvasTexture

© www.soinside.com 2019 - 2024. All rights reserved.