Lane · Professional · Web & Vision

The browser is a 3D engine now.

A modern browser can render real-time 3D scenes that would have needed a game engine a decade ago. No plugin, no download, no install — paste a URL, wait two seconds, and you're inside the simulation. The demo below is one 260 KB HTML file. Drag to orbit. Scroll to zoom. Click any planet to fly to it.

01 Discipline What this is and why it's hard

Real-time 3D is budget management.

Every frame of a WebGL scene has a budget — roughly 16 milliseconds to compute positions, run shaders, and push pixels to the screen. Miss the budget and the frame rate halves. Miss it repeatedly and the experience drops from "smooth" to "broken" faster than the visitor can blame their laptop. The discipline is not about cramming in more — it's about knowing what costs what, so you spend the frame budget on things that visibly matter.

A solar system is a useful subject because the physics is well-known (Kepler's laws since 1619) and the visual expectations are universal. Anyone can spot a planet in the wrong place. Anyone can tell when Saturn's rings are cartoonish. The challenge is rendering 8 planets, a dozen moons, Saturn's rings, an asteroid belt, a comet with a procedural tail, and a Milky Way skybox — all at 60 fps on a five-year-old laptop — while staying in a single HTML file that loads in two seconds over slow Wi-Fi.

02 Toolkit What's in the rendering stack

Three.js, a few shaders, no backend.

The stack is deliberately thin. A heavy framework would triple the page weight without adding anything the simulation actually needs. Three.js handles the low-level WebGL plumbing so the interesting code is about astronomy, not graphics APIs.

Engine
Three.js r128
WebGL abstraction. Scene graph, camera, renderer, mesh factories, orbit controls — the industry-standard plumbing so we can spend the frame budget on physics and shaders.
Surfaces
Procedural shaders
No texture files. Planet surfaces, the sun's corona, Saturn's rings, and the comet tail are all generated in GLSL from simplex noise and analytical gradients. Zero external assets.
Physics
Keplerian orbits
Real semi-major axes, eccentricities, and orbital periods. Mercury laps Neptune 660× in the same simulation interval — just like the real thing, only faster.
Crowds
Instanced meshes
The asteroid belt is ~1,500 individually-orbiting rocks. Naive rendering would tank the frame rate; a single instanced draw call keeps it under 1 ms.
Controls
OrbitControls + raycasting
Drag, scroll, pinch. Click a planet — a raycast from the mouse picks the body, the camera smoothly interpolates to a framing distance, an info card flies in.
Deployment
Single HTML file
Zero build step. Zero npm. Zero backend. Drop the file on any static host and the simulation runs. Three.js loads from Cloudflare CDN (the only external dependency).
03 The simulation Drag · scroll · click a planet

Eight planets, one frame budget.

Wait for the scene to load (~2 s on first view — Three.js is fetched from Cloudflare), then the intro animation pulls the camera out from inside the Sun's corona until the whole system is framed. After that, everything is interactive: drag to orbit the camera, scroll to zoom, right-click to pan. Click any planet and the camera flies to it. Use the bottom controls to speed up or reverse time.

Loading Three.js · initialising scene
Solar system · live
Three.js · procedural shaders · Keplerian orbits · no backend.
Drag · orbit
Scroll · zoom
Click · focus
View
Orbit paths
Planet labels
Asteroid belt
Halley's comet
Voyager 1 · 2
Kuiper belt
d/s
fps
04 Data All 8 planets · real parameters

The numbers driving the scene.

The simulation is parameter-driven — every planet's size, orbital radius, period, axial tilt, and shader colour scheme comes from this table. Click any row to fly the camera to that body. The same values drive both "compressed" and "realistic" scale modes — the transform just changes the interpretation of the numbers.

Planet Radius (km) Semi-major axis (AU) Orbital period Day length Axial tilt Moons
05 Featured challenge The asteroid belt problem

"1,500 rocks at 60 frames per second."

The asteroid belt is the single most instructive part of this build — the gap between the naive solution (bad) and the correct solution (good) is larger than anywhere else in the scene, and the technique that fixes it is the same technique that makes every large-scale WebGL scene viable.

v0
The naive approach

First version: for (let i=0; i<1500; i++) scene.add(new THREE.Mesh(geometry, material)). Looks correct. Runs at 6 fps on a MacBook Air. Every asteroid is a separate draw call, every draw call crosses the JavaScript-to-GPU boundary, and that boundary is the bottleneck. The GPU itself is bored; the bus between CPU and GPU is on fire.

v1
BufferGeometry merge

Merge all asteroids into a single geometry — one mesh, one draw call. Frame rate jumps to 58 fps. But now every asteroid moves as one rigid blob — you can't orbit them individually. The visual suffers badly; the belt looks static.

v2
InstancedMesh

Three.js's InstancedMesh. One geometry, one material, one draw call — but each instance has its own per-instance matrix, so every asteroid can orbit at its own angular velocity. Update the matrices in JavaScript each frame, the GPU consumes them. Frame rate: 60 fps (capped). CPU cost of matrix updates: ~0.8 ms. The belt feels alive; the performance budget is barely touched.

v3
The refinement

Replace the sphere geometry with a 6-sided icosahedron. 20 triangles per asteroid instead of 128. Nobody can tell the difference at belt-viewing distance — but 1,500 × 108 fewer triangles is 162,000 triangles saved per frame. GPU budget on the asteroid belt drops from 2.2 ms to 0.3 ms. That's 1.9 ms given back to the rest of the scene — enough for a second comet, richer sun shaders, or four more moons.

Why this generalises. The same three questions apply to almost every large-count problem in real-time 3D: one draw call or many? Unique geometry per instance or shared? How many triangles does a viewer actually perceive? Get those three right and the frame budget takes care of itself. Get any one wrong and the simulation stutters on anything older than last year's flagship.