DEV Community

INTFRAME
INTFRAME

Posted on Originally published at intframe.com

A planet in a browser tab

Our site now has a chapter where you scroll a camera through low orbit, visiting the four places we operate from. It looks like it should weigh fifty megabytes. It ships in roughly 1.5, and it is built almost entirely from public-domain science data.

Real data, baked hard

  • The planet is NASA Blue Marble imagery, resized and recompressed by a build-time generator. Never hand-edited: when we want a change, we change the generator and bake again.
  • City lights are not a texture. A script samples a NASA night-lights raster into about 46,000 weighted points packed into a small binary buffer, rendered as glowing sprites. Crisp at any zoom, because they are geometry, not pixels.
  • Close-ups use a two-stage patch trick: whole-earth imagery is about 10 km per pixel, hopeless at 200 km altitude, so the generator bakes high-resolution patches (down to 76 m per pixel, from Sentinel-2 cloudless data) only around the four cities the camera actually visits.

The patches blend in the fragment shader, feathered at the edges so no seam ever shows, and the procedural detail noise fades out underneath them so photography is not fighting synthesis:

float fe  = smoothstep(edge, edge - feather, dist);   // patch edge feather
vec3  col = mix(base, patchTexel, fe * uPatch);
col      *= 1.0 - patchAmt * 0.75;                    // damp noise under imagery
Enter fullscreen mode Exit fullscreen mode

One line of trigonometry fixed the camera

Scroll drives a great-circle flight between stops. The obvious way to aim the camera, normalize(next - pos), collapses to a zero vector exactly at the endpoints, which is why naive fly-over cameras snap on arrival. The fix is the analytic tangent of the spherical interpolation itself, stable everywhere including t = 0 and t = 1:

// bearing along slerp(a, b) at parameter t, W = angle between a and b
tangent(t) = normalize( -cos((1 - t) * W) * a  +  cos(t * W) * b )
Enter fullscreen mode Exit fullscreen mode

Arrivals settle north-up, hold while the camera descends, then rotate toward the next destination and depart with a slight bank, all of it driven by signed angles between that tangent and local north. Lighting is deliberately cinematic: the sun rises as you arrive so the terrain reads, and sinks back to a dawn band for the night flight.

Honest performance

ema = ema * 0.9 + frameMs * 0.1;            // frame-time moving average
if (ema > 24.0 && px > FLOOR) {
  px -= 0.2;                                 // shed resolution before jank
  renderer.setPixelRatio(px);
}
Enter fullscreen mode Exit fullscreen mode

A governor watches the running frame time and steps internal resolution down before a weak GPU starts dropping frames, with a floor so text and coastlines stay sharp. Assets load only when the chapter approaches, and the whole thing idles at zero cost when off-screen. Awards-bait visuals are allowed. Awards-bait jank is not.


Originally published on the INTFRAME engineering blog. The companion code lives at github.com/intframe/scroll-rig.

Top comments (1)

Collapse
 
timetate profile image
Yevhen Lysenko

The 46k weighted points instead of a night-lights texture is the part I'll be stealing. Emissive city textures fall apart exactly where you need them most — mip-blur turns them into orange smudges on approach, and the only fix is paying for resolution you don't need at orbit.

Question about the terminator: do the sprites give you trouble in the narrow band where lit and unlit meet? I blend day/night in the fragment shader on the sphere itself, and that band is where anything additive on top starts reading as if it floats above the surface instead of sitting on it.

The slerp tangent is a good catch too. normalize(next - pos) degenerating at t=0 and t=1 is the kind of bug that only shows on the first and last frame of a flight, which is the worst possible place to be debugging one.