WebGL · Three.js

Rings: A WebGL Playground and Why Three.js Is Magic—and Merciless

May 18, 2026

Source repo
webgl
threejs
graphics
javascript
open-source
creative-coding
Rings: A WebGL Playground and Why Three.js Is Magic—and Merciless

Rings


There is a moment in every WebGL project when the canvas stops being a gray rectangle and becomes a small universe—light, motion, reflection, something you want to show someone without saying “trust me, run npm install.”

Rings is that moment for me: a focused Three.js scene I extracted from an older project (globex), deployed at rings-two.vercel.app. Twenty-four procedural toruses on an elliptical path. A mirror sphere that samples the world every frame with ping-pong cube cameras. A Preetham sky dome. A magenta light doing laps in ten seconds while the main camera takes ninety.

It is not a product. It is a playground—and playgrounds are how you actually learn graphics.

See it live

Drag to orbit, scroll to zoom, right-drag to pan. The demo runs entirely in the browser—no plugin, no native binary.

If the embed is blank (some browsers block third-party WebGL), open the demo in a new tab.

Tiling still works in production—handy if you ever point multiple screens at one giant virtual canvas (a leftover from video-wall experiments):

https://rings-two.vercel.app/?fullWidth=1920&fullHeight=1080&x=0&y=0

Change fullWidth, fullHeight, x, and y to show a window into a larger render target—camera.setViewOffset under the hood.

Why WebGL is still impressive

We are spoiled by CSS and video. WebGL is different: you are scheduling work on the GPU every frame—transforms, materials, lights, sometimes custom shaders that run per pixel.

What still feels like magic:

CapabilityWhat it buys you
Hardware accelerationThousands of vertices and complex fragment math at 60fps—on a laptop, in a tab
No installSend a URL; the GPU does the rest
Interactive 3DOrbit, zoom, pan—users explore instead of watching a loop
ShadersSkies, water, stylized materials impossible to fake with DOM alone
Dynamic environmentsReal-time reflections via cube maps—Rings updates two cube cameras alternately so the chrome sphere sees a moving world

The scene is not photorealistic film VFX. It is immediate: change a uniform, refresh, see. That feedback loop is why graphics people fall in love with the browser as a platform. (The hero image above is a frame from the live build—open the embed if you want motion.)

What Rings is doing (without drowning in jargon)

Boot is simple: index.htmlMain.js → dynamic import of Rings.js. From there:

  • Sky — analytic Preetham scattering in SkyShader.js; sun position drives a spotlight that tracks the dome.
  • Donuts — 24 TorusGeometry instances on an Ellipse curve; each material is procedural canvas noise (Perlin, patterns from tooloud), random UV scale per mesh.
  • Mirror sphere — hide the sphere, render the scene into cube map A or B, assign to envMap, show the sphere again—every frame.
  • Motion — two tweens: 90s camera path around the ellipse, 10s loop for the glow ball’s PointLight.
  • Floor — tiled metal texture, shadows on.

Lights and legacy camera hints live in Scenario.json. OrbitControls with damping so the thing feels tactile.

A Playwright smoke test (npm run test:webgl) loads the page headlessly and fails if the framebuffer is basically black—because “it works on my machine” is not a strategy for WebGL CI.

Why Three.js is hard anyway

Three.js is the good news: it wraps WebGL’s ceremony. It is also the honest news: you are still thinking like a graphics programmer, just with better names.

1. Mental model overload

Scene graph, cameras, render targets, materials, lights, gamma, color spaces, UVs, normals… The docs are fine; the integration is where beginners drown. One wrong lookAt and your sun points into the floor for a week.

2. The render loop is your real main()

Everything reactive eventually boils down to:

update state → maybe render to target → render scene → repeat

Miss an order—update cube map after moving the object that was hidden for that pass—and you get shimmering lies instead of reflections.

3. Shaders are a second programming language

GLSL looks small until you need branching, precision, or to debug why the sky is purple at noon. Rings’ sky shader came from established Preetham examples; even then, uniform naming and Three.js version drift cost real time.

4. Performance is always lurking

Rings renders two extra cube faces × six directions × 512² per frame for the mirror ball, alternately. Fine on a desktop. On a tired phone tab, next to Slack and Spotify, you learn humility fast. The README lists sensible next steps: lower cube resolution on mobile, pause env updates when the tab is hidden, code-split the ~760KB chunk.

5. Debugging is vibes until it isn’t

Black screen? Wrong near/far plane. Z-fighting? Camera inside mesh. envMap frozen? You forgot to hide the reflective mesh during the cube pass. stats.js in the corner is not decoration—it is a lifeline.

6. Legacy and ecosystem scars

This repo still aliases old tween imports to @tweenjs/tween.js. Scenario JSON carries lights from an earlier era. That is normal in graphics code: you do not rewrite; you coax forward.

The advantages that keep me coming back

Despite the pain:

  • Reach — One URL beats shipping a desktop binary for a visual experiment.
  • Iteration speed — Vite hot reload + browser devtools beat many native compile cycles.
  • Expressiveness — Procedural textures in Canvas, slap on a mesh, instant art direction.
  • Portfolio gravity — People feel 3D in a way they do not feel another REST API diagram.

Rings is MIT-licensed on GitHub. Clone it, break the sky, add a ?debug=1 tube along the path—the README lists grounded improvements (GUI for Preetham params, visibility pause, TypeScript types).

How this fits my other rabbit holes

Not every project needs WebGL. My ERP for La Esquina needed receipts that print and stock that matches góndola. Elliott needed privacy and IndexedDB. Rings needed wonder—the kind you get when math becomes light.

That diversity is the point. WebGL will not replace forms and databases. It reminds you why you learned to code: to make something alive on screen.

Try it yourself

git clone https://github.com/maggiben/rings.git
cd rings
npm install
npm run dev

Open http://localhost:5173/, wait for the donuts, orbit until dizzy. Read src/Rings/Rings.js with the loop in one hand and the Three.js docs in the other.

If you only do one thing: change the ellipse radius and watch the whole composition change. That is the lesson—small parameters, large poetry, and a GPU willing to draw it sixty times a second.


Live demo: rings-two.vercel.app. Code: github.com/maggiben/rings. MIT © 2026.