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.
EASEL (Canvas2D)
This example needs a canvas-capable browser.

Loading scene…

three.js r186 (WebGL)
This example needs a canvas-capable browser.

Loading scene…

Loading scene…

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);