我正在开发一个 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? 任何见解或建议将不胜感激
就像当我们传输一个
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
。