1-Bit World Viewer
← Project Index 1-Bit World Viewer

1-Bit World Viewer

A single-file WebGL viewer that renders navigable 3D worlds through real-time 1-bit dithering -; switchable between ordered Bayer, screen-space noise, and Atkinson error-diffusion, so you can watch why error-diffusion "boils" under motion while the screen-space methods stay put.

Exploring Started: May 2026 Updated: May 2026

Overview

Open the page, load any GLB/GLTF mesh -; or use the built-in demo scene -; and fly around a world rendered entirely in two colors. It's one HTML file, no install, three dither algorithms with live controls. The point is partly aesthetic and partly a little experiment: putting Bayer, screen-space noise, and Atkinson side by side under the same camera motion makes the difference between them obvious in a way a still image never will.

It came out of having a pile of meshes exported from WorldLabs Marble and wanting to do something with them that wasn't just another smooth-shaded preview.

How It Works

The 3D scene renders to a small offscreen buffer (256-;640px wide). Those pixels get pulled back to the CPU, converted to luminance, and thresholded to 1-bit there, then upscaled nearest-neighbor for the chunky edges. The dithering is the interesting bit, and the three methods behave very differently under motion:

  • Bayer (ordered 8×8) and IGN (interleaved gradient noise) threshold purely as a function of screen position. Deterministic per pixel and temporally solid -; the pattern is welded to the screen while the world slides through it.
  • Atkinson re-runs full error diffusion from scratch every frame. That sequential dependency chain -; each pixel's error feeding its neighbors -; is exactly what makes it unstable under motion. The result is the classic temporal "boil."
// screen-space thresholds: welded to the screen, stable under motion
bayer8[(y & 7) * 8 + (x & 7)]                              // Bayer
(52.9829189 * frac(0.06711056*x + 0.00583715*y)) % 1     // IGN

It's built on Three.js r160, with OrbitControls and GLTFLoader from the addons, all loaded from CDN. The full pipeline is: 3D scene → low-res offscreen WebGL target → CPU readback → luminance → 1-bit dither → nearest-neighbor upscale → 2D canvas. The CPU readback every frame is the performance ceiling, which is why the resolution drops help when Atkinson gets heavy on a big mesh.

Panel controls let you swap the algorithm, change the offscreen resolution, dial exposure and gamma into the right range before thresholding (which matters a lot when you only have two values), invert to paper mode, and set autorotate speed.

Current Status

Live and working -; there's a demo scene (a ring of columns around a central icosahedron head, a small Manhole/Myst nod, with an obelisk on the horizon for the dither to chew on at distance), a sample Marble mesh you can download and load, and a recorded GIF in the repo. Loads any GLB; splats aren't supported (mesh only).

  • Three dither algorithms with live exposure/gamma/resolution controls.
  • Auto-demo scene plus drag-and-drop GLB/GLTF loading.
  • Known ceiling: per-frame CPU readback -; drop to 256px if Atkinson lags on a large mesh.