@tinypoly/cpu-rendererv0.3.0MIT

CPU renderer for Three.js

An open source package that renders a THREE.Scene on the CPU, splitting the work across a pool of workers. It runs in the browser and in Node, and never creates a WebGL context. It is the renderer behind Tinypoly's render mode.

npm install @tinypoly/cpu-renderer three
Loading the renderer

Image rendered in this browser when the page opened.

Features

Features the renderer supports today, from materials and geometry to camera effects.

Materials

Standard and Physical, with clearcoat, sheen, transmission and anisotropy. Also Lambert, Phong, Toon and Basic.

GLSL Shaders

ShaderMaterial runs in a built-in interpreter that compiles GLSL to closures. It does not use eval.

Shadows

Shadows ray traced against a BVH, with penumbrae, and ambient occlusion.

Global Illumination

Diffuse indirect light with an irradiance cache, an SVGF-style denoiser and environment importance sampling.

Camera and Post-Processing

HDR or gradient environments, depth of field, fog, vignette, grain and ACES, AgX or Neutral tone mapping.

Progressive Output

Pixels arrive as each bucket finishes, ready to be painted on a canvas. A frame can be paused and resumed.

Browser and Node

No DOM dependency: it takes a size and hands back the pixels. In Node the workers run on worker_threads and share memory with no extra header; in the browser that needs cross-origin isolation.

Lines, Sprites and Points

Line, LineSegments, LineLoop, dashed lines, sprites and PointsMaterial, with a perspective or orthographic camera.

How it works

The main thread only coordinates. The computation happens in the workers.

  1. 01

    Serialization

    Geometry, materials, textures, lights and camera are copied into the renderer's own format, in the background.

  2. 02

    Preparation

    The first worker runs the vertex shaders and builds the triangles and the BVH. The others reuse that result through shared memory.

  3. 03

    Buckets

    With global illumination, the indirect light cache is computed first. Then the image is split into buckets, and each worker takes the next free one.

  4. 04

    Finishing

    Each bucket is handed over as it finishes, and the canvas adapter paints it. At the end, the denoiser is applied to the indirect light.

Usage

Pass the size, the objects and the camera, then call render(). It resolves with the finished pixels, and attachCanvas paints each bucket on a canvas as it finishes.

main.ts
import * as THREE from "three";
import { CpuRenderer, attachCanvas } from "@tinypoly/cpu-renderer";

const scene = new THREE.Scene();
scene.add(new THREE.Mesh(
  new THREE.SphereGeometry(1, 48, 24),
  new THREE.MeshPhysicalMaterial({ color: "tomato" }),
));

const camera = new THREE.PerspectiveCamera(40, 1, 0.1, 100);
camera.position.set(0, 1, 5);
camera.lookAt(0, 0, 0);

const canvas = document.querySelector("canvas")!;
const renderer = new CpuRenderer({
  width: canvas.clientWidth,
  height: canvas.clientHeight,
  pixelRatio: window.devicePixelRatio,
  scene,
  camera,
  environment: { kind: "gradient", topColor: "#9cb8d8", bottomColor: "#2b2a28" },
  settings: { globalIllumination: { enabled: true } },
});

attachCanvas(renderer, canvas);
renderer.on("progress", ({ completed, total }) => {
  console.log(`${completed} / ${total}`);
});

const { width, height, data } = await renderer.render();

In Tinypoly

This is the renderer used in Tinypoly's render mode.

Open the editor