Field guide · Survey 04
How this atlas
was built
EPHEMERIS renders five different worlds with one sphere, one shader, and zero texture files. Everything you saw, the oceans, the lava, the rings, is mathematics evaluated live on your GPU. This page explains the whole machine, briefly, so you can build your own.
One planet, five climates
The obvious way to build this site is five planet models with five sets of textures. EPHEMERIS does something better: there is a single procedural planet, and each "world" is just a preset, a small bundle of about fourteen numbers and five colors fed into the shader. Scrolling does not swap anything in or out. It retunes the planet, the way turning a dial retunes a radio.
That is why the transitions feel alive rather than like page changes: between two sections you are watching an ocean world literally become a lava world, every value interpolating mid-flight.
| Uniform | What it controls |
|---|---|
| uColA · uColB · uColC | A three-stop color ramp the surface noise is mapped through |
| uNoiseScale · uWarp | How zoomed-in and how swirled the weather pattern is |
| uBands | Blends toward latitude stripes: 0 for rocky worlds, 1 for the gas giant |
| uDisplace | Physical bumpiness of the crust (vertex displacement) |
| uNightGlow · uGlowColor | Emission on the dark side: lava rivers, plankton, auroras |
| uSpec | Specular glint, high for oceans and glass dunes |
| uAtmoColor + strength | The fresnel rim and the halo shell around the planet |
The surface: warped noise
The terrain pattern is fbm, fractal Brownian motion. Think of noise as smooth random static; fbm stacks several layers of it (called octaves), each one twice as detailed and half as strong. One layer looks like blobs. Five layers look like geography.
The signature move is domain warping: before sampling the noise, we bend its input coordinates using more noise. That bending is what makes Vela Thalassa's currents curl and Pyrrhos Cinder's lava run in filaments instead of sitting in polite round patches.
// bend space with noise, then sample noise in bent space
vec3 warp = vec3(fbm3(q), fbm3(q + 5.2), fbm3(q + 9.1));
float n = fbm(q + uWarp * warp);
// map the result through a three-stop color ramp
vec3 col = mix(uColA, uColB, smoothstep(0.08, 0.52, n));
col = mix(col, uColC, smoothstep(0.55, 0.95, n));
The gas giant adds one more trick: a sine wave across latitude creates cloud bands, and the same noise shears them so the stripes storm into each other instead of staying ruler-straight.
The light: day, night, and things that glow
Lighting is a single fixed "sun" direction with wrap lighting, a softened version of the usual dot product so the terminator (the day-night line) rolls off gently instead of cutting hard.
The emotional trick is on the night side. Where the surface noise is at its highest values, the dark hemisphere emits light: orange for Pyrrhos Cinder's lava script, teal for Thalassa's phosphorescent sea, pale green for the aurora light on Hiems Corona. A planet whose night side answers back reads as alive.
Two finishing touches: a specular glint (strong on the ocean world, absent on charred rock) and a fresnel rim, brightness that rises as the surface turns edge-on to you, which is what an atmosphere actually does.
Atmosphere and rings
The halo is a second, slightly larger sphere rendered inside-out
(side: BackSide) with additive blending, so it only shows as a soft glowing shell
around the limb of the planet. Cost: almost nothing. Effect: the whole mood.
Noctua Pale's rings are a flat ring mesh whose density comes from one-dimensional value noise sampled along the radius, wide soft bands from low-frequency noise, fine grooves from high-frequency noise, multiplied together and faded at both edges. The rings' opacity is itself a preset value, so they simply are not there until the gas giant's section fades them in.
The scroll engine
The page is ordinary HTML sections, one per world. Each frame, the current scroll position is converted to a fractional world index, 2.4 means "40 percent of the way from Pyrrhos to Noctua", and every preset value is interpolated at that fraction.
Then, instead of applying that target directly, the live state chases it with exponential smoothing:
const chase = 1 - Math.exp(-dt * 4.2);
current[k] += (target[k] - current[k]) * chase;
That one line is why the planet feels like it has mass. Scroll fast and it leans into the change, then settles. It also makes the animation frame-rate independent, the same feel at 60 or 120 Hz.
The design tokens
The visual identity is a star-atlas: parchment ink on a void, engraved gold rules, and monospaced survey data. Three typefaces, each with one job: Italiana for engraved display capitals, Instrument Sans for prose, IBM Plex Mono for anything a surveyor would have typed.
Each world section carries its accent as a CSS custom property
(style="--accent:#5FD4C0"), so the card chrome, the epithet, and the constellation
nav all re-tint from a single attribute.
Build your own
- Vendor everything. Fonts as local woff2 files, Three.js as a single module file. No CDNs means the template works offline and never breaks under someone else's outage.
- Start with the sphere and two colors. Get fbm mapped to a ramp on a lit sphere first. Every other feature is one uniform added to a working planet.
- Design worlds as presets, not scenes. Keep every world as a flat object of numbers. Tuning a world becomes editing nine lines, and blending comes free.
- Let scroll drive a fraction, not events. A fractional index plus exponential chase gives you inertia, interruptibility, and frame-rate independence in three lines of code.
- Respect the quiet paths.
prefers-reduced-motionfreezes rotation and parallax and lets presets snap. WebGL failure falls back to a gradient. Keyboard focus is visible throughout. - Write the copy like it matters. The planet is the spectacle, but the survey notes are why you scroll to the end. Fictional worlds earn real feelings when the data grid plays it straight.