canvas
worker / offscreencanvas
What this shows
One hundred fogged, matcap-shaded icosahedra orbit slowly, with a jank toggle that busy-loops the main thread to show the render loop stalling.
Side by side with the three.js r186 original: webgl_worker_offscreencanvas.html
Differences from three.js
- The three.js page shows two canvases: one rendered on the main thread and one transferred to a Web Worker with transferControlToOffscreen. Both sides here mount only the main-thread canvas, because the example viewer takes a context on the stage canvas before setup, writes canvas.width on resize, and needs a synchronous first frame, all of which a transferred canvas cannot provide.
- EASEL's Renderer types its canvas option as HTMLCanvasElement, so passing an OffscreenCanvas inside a worker needs a type cast even though the renderer only calls width, height, getContext('2d') and putImageData.
- matcap-porcelain-white.jpg has no stated licence, so it is not copied; the three.js side uses MeshMatcapMaterial's built-in grey gradient instead of the porcelain matcap.
- EASEL has no MeshMatcapMaterial, so the port uses LambertMaterial lit only by a HemisphereLight whose sky and ground colours reproduce that built-in matcap gradient (0.2 below to 0.8 above in linear light), baked per vertex. The light has no upstream counterpart, so the usual 1/pi intensity scaling does not apply.
- The START JANK button becomes a jank select control. Both the three.js and EASEL sides start their own 10,000,000-iteration interval, so turning it on stalls the page about twice as long as the single upstream button, and the result number is not displayed.
- The upstream count of 100 icosahedra at detail 8 (162,000 triangles) is kept; at 640x360 the EASEL side measured a median of about 37 ms per frame in the Bun smoke test, over the 33 ms budget.
Loading scene…Key API excerpt
Key API excerpt
import * as EASEL from "@xsyetopz/easel";
scene.fog = new EASEL.Fog({ color: 0x444466, near: 100, far: 400 }).updateLut();
scene.add(new EASEL.HemisphereLight(0xe7e7e7, 0x7c7c7c, 1));
const geometry = new EASEL.IcosahedronGeometry(5, 8);
const material = new EASEL.LambertMaterial({ color: 0xaa24df, vertexColors: false });
for (let i = 0; i < 100; i++) {
const mesh = new EASEL.Mesh(geometry, material);
mesh.position.set(random() * 200 - 100, random() * 200 - 100, random() * 200 - 100);
group.add(mesh);
}
group.rotation.y = -Date.now() / 4000;
renderer.prepare(scene, camera);
renderer.render(scene, camera);