I am using offscreencanvas (https://threejs.org/manual/#en/offscreencanvas) to render react-three-fiber elements on a separate thread. There are not a lot of resources about this on the internet (however I found this issue: https://github.com/pmndrs/react-three-fiber/issues/280). I tried reproducing it but I get an error.
Main thread create canvas:
// main.jsx (main thread)
import React from "react";
export const Main = () => {
const canvasRef = React.useRef();
React.useEffect(() => {
const offscreen = canvasRef.current.transferControlToOffscreen();
const worker = new Worker('worker.js');
worker.postMessage({
canvas: offscreen,
}, [offscreen])
}, []);
return (
<canvas ref={canvasRef} />
);
}
Separate thread:
// worker.jsx (worker thread)
import { render } from 'react-three-fiber';
import { WebGLRenderer, Scene, PerspectiveCamera } from 'three';
self.onmessage = (event) => {
const { canvas } = event.data;
const scene = new Scene();
const renderer = new WebGLRenderer({ canvas });
const camera = new PerspectiveCamera( 75, canvas.width / canvas.height, 0.1, 1000 );
render(<gridHelper scale={[10, 10, 10]}/>, scene);
renderer.render(scene, camera);
}
When I render this, I get an error saying "Cannot read property of undefined". Could anyone help me identify why I get this error? Or provide a demo using offscreencanvas with react-three-fiber?
Demo: https://f-loat.github.io/offscreen-canvas-demo
GitHub: https://github.com/F-loat/offscreen-canvas-demo
// main.js
import React, { useEffect } from 'react';
const App = () => {
const canvasRef = React.useRef();
useEffect(() => {
const canvas = canvasRef.current;
const offscreen = canvasRef.current.transferControlToOffscreen();
const worker = new Worker(new URL('./worker.js', import.meta.url));
worker.postMessage( {
drawingSurface: offscreen,
width: canvas.clientWidth,
height: canvas.clientHeight,
pixelRatio: window.devicePixelRatio,
}, [ offscreen ] );
}, []);
return (
<canvas ref={canvasRef} />
);
}
export default App
// worker.js
import React, { useRef, useState } from 'react'
import * as THREE from 'three'
import { extend, useFrame, createRoot, events } from '@react-three/fiber'
extend(THREE)
function Cube(props) {
// This reference will give us direct access to the mesh
const mesh = useRef()
// Set up state for the hovered and active state
const [hovered, setHover] = useState(false)
const [active, setActive] = useState(false)
// Subscribe this component to the render-loop, rotate the mesh every frame
useFrame((state, delta) => {
mesh.current.rotation.x += 0.01
mesh.current.rotation.y += 0.01
})
// Return view, these are regular three.js elements expressed in JSX
return (
<mesh
{...props}
ref={mesh}
scale={active ? 1.5 : 1}
onClick={() => setActive(!active)}
onPointerOver={() => setHover(true)}
onPointerOut={() => setHover(false)}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />
</mesh>
)
}
const App = () => {
return (
<>
<ambientLight />
<pointLight position={[10, 10, 10]} />
<Cube position={[0, 0, 0]} />
</>
);
}
self.onmessage = (event) => {
const { drawingSurface: canvas, width, height, pixelRatio } = event.data;
const root = createRoot(canvas)
root.configure({
events,
size: {
width,
height,
},
dpr: pixelRatio, // important
})
root.render(<App />)
}
// @react-three/fiber/dist/index-1e2a4313.esm.js
+ var window = self || window
- gl.setSize(size.width, size.height);
+ gl.setSize(size.width, size.height, false);