Behind the scenes
How Starfall was made
Starfall is a narrated, planetarium style trip through the solar system. Every world in it was rendered from real spacecraft imagery in Blender, on my own PC, by Python scripts rather than by hand. It also lets you leave the story and fly over the real terrain of Earth, the Moon and Mars yourself. This page explains how it was built and what went wrong along the way.
Rubab Ibn Hasib

The goal
I wanted the feeling of sitting in a planetarium: dark, slow, a calm voice telling a story. The visual reference was the kind of cinematic space imagery seen on high end brand sites, with huge planets lit from one side, real city lights on Earth's night side, and a thin glowing atmosphere. A first version used planets painted by code, and it looked like a toy. Real imagery and real lighting were the only way to get there.
The pipeline
- Real imageryNASA Visible Earth (Blue Marble surface, Black Marble city lights, cloud cover), NASA 3D Resources for Io, Europa and Titan, and Solar System Scope maps (CC BY 4.0) for the other worlds.
- Blender, driven by PythonOne script builds any world from scratch: sphere, surface material, night lights, clouds, a camera-only atmosphere glow, rings, sun angle and camera framing. No manual editing in the Blender interface.
- GPU renderingCycles path tracing on an AMD Radeon RX 7900 XTX through HIP. 4K stills at 256 samples, 1080p loops and flights at 64 samples with denoising.
- A static websitePlain HTML, CSS and JavaScript. Three.js for the live 3D solar system and the Web Audio API for music generated in the browser.
- A real narrator voiceEvery chapter and every explorer location is read by Kokoro, an open source text to speech model (Apache 2.0), generated on my own PC with Python. Each clip ships with sentence timings, so the captions follow the voice.
- A 3D explorerCesiumJS streams real terrain for Earth, the Moon and Mars, so you can fly over them like a game. Sunlight follows the real time of day, and the International Space Station flies on its live orbit.
- An encyclopediaIn depth pages for the most important worlds, written as data files and turned into pages by a small Python build script, so new worlds can be added without touching the design. Every fact is checked by a separate review against NASA and other agency sources, and every place mentioned links straight into the explorer.
- CloudflareServed as static assets from Cloudflare's network, deployed with Wrangler.
Getting Earth right
Most of the look was found by rendering small test images, studying them, and changing one thing at a time.




Problems solved
The Earth map was too big for the GPU
SymptomThe first render failed with a GPU image size error.
FixNASA's Blue Marble is 21,600 pixels wide, beyond the 16,384 pixel texture limit. Blender itself resized it to 16K once, keeping far more detail than the usual 8K maps.
Renders suddenly ten times slower
SymptomA 40 second Earth render took over 9 minutes.
FixWindows GPU memory counters showed another app's Python process holding 13 GB of the card's 24 GB and competing for compute. Closing it restored full speed.
The overnight batch kept disappearing
SymptomThe video batch stopped after about 11 minutes, with no crash in the Windows event logs.
FixThe processes were being cleaned up along with the terminal session that launched them. Starting the batch through Windows' own process service made it independent. Each video now renders to a temporary file and is only renamed when Blender exits cleanly, so a broken file can never be mistaken for a finished one.
Four times faster, with identical images
SymptomVideo frames took 5 to 8 seconds each, much slower than the stills suggested.
FixBlender was rebuilding the scene and re-uploading every texture on each frame. Persistent Data plus GPU denoising cut that to under 2 seconds. A pixel by pixel comparison of the same frame both ways showed a largest difference of 1 out of 255, which is invisible.
A pink Sun
SymptomThe Sun rendered salmon pink instead of fiery gold.
FixThe AgX colour transform shifts very bright oranges toward pink. Rendering the Sun alone with the Khronos PBR Neutral transform kept its true colour.
Real data, not decoration
In the 3D view, every planet sits where it actually is on the chosen date. Positions come from NASA JPL's published Keplerian orbital elements, solved with Kepler's equation in the browser. You can scrub from 1800 to 2050, jump to any date, and switch to true scale, where the distances become real and the planets shrink to specks. The weight and age calculator uses each world's surface gravity and orbital period.
// position of a planet, in astronomical units, for a given date
let E = M + e * Math.sin(M); // mean to eccentric anomaly
for (let i = 0; i < 8; i++) E -= (E - e * Math.sin(E) - M) / (1 - e * Math.cos(E));
const x = a * (Math.cos(E) - e), y = a * Math.sqrt(1 - e * e) * Math.sin(E);
Flying over real worlds
The explorer started from one question: could you drag the Moon around and fly across it like a video game? Pre-rendered video can't do that, so this part runs live in the browser on CesiumJS, which streams real elevation data and imagery as you move.
- Earth uses NASA Blue Marble and Black Marble at a distance, sharper aerial imagery as you descend, and Google's photorealistic 3D cities and mountains near the ground. Clouds and city lights are separate layers lit by the real Sun for the current moment.
- The Moon and Mars use NASA's own elevation models (LRO and Mars Global Surveyor data, via Cesium). Their sunlight is calculated from orbital positions and each body's real rotation, so a crater can be in shadow or daylight depending on when you visit.
- The ISS is NASA's 3D model, placed on its real orbit from the latest published tracking data. You can ride alongside it.
- 51 places, from Everest and the Grand Canyon to the Apollo landing sites and Olympus Mons, each with its own narration. The scripts were fact checked separately, and six mistakes were corrected before release.
Keyboard and mouse on a computer, a joystick and up and down buttons on a phone.
More problems solved
Clouds on the wrong side of the planet
SymptomThe cloud and city light layers sat exactly half a world away from the land beneath them.
FixThe sphere's texture coordinates started at a different longitude than the maps. Shifting them half a turn lined every coastline up.
Cities glowing in daylight
SymptomThe night lights showed across the sunlit side, and the ground stayed bright after sunset.
FixThe built in day and night blending didn't behave as documented, so city lights became their own layer that only shows where the real Sun has set.
A giant wedge instead of a space station
SymptomThe ISS model appeared stretched into a huge shape.
FixIts orientation was built from an unnormalised velocity. A proper frame from its direction of travel and the local up direction made it fly level, the right size.
The narrator was one place behind
SymptomThe story about Calgary was still playing when the camera reached Mount Fuji.
FixThe queue now keeps only the newest place, jumping somewhere new stops the current clip, and places passed on the way don't interrupt.
Security versus a 3D engine
SymptomThe explorer stayed black on the live site.
FixThe strict security policy blocks the code generation a 3D engine needs. The policy was split per page, so only the explorer gets that allowance and the rest of the site stays locked down.
By the numbers
Tools
- Blender 5.2
- Python
- Cycles + HIP
- Three.js
- Web Audio API
- CesiumJS
- satellite.js
- Kokoro TTS
- Playwright
- PowerShell
- Cloudflare Workers
- Claude (AI pair programmer)
Credits
Imagery: NASA Visible Earth (Blue Marble, Black Marble, cloud cover), NASA 3D Resources, and Solar System Scope textures under CC BY 4.0. Orbital elements: NASA JPL Solar System Dynamics.
Explorer: CesiumJS and Cesium ion (World Terrain, Moon and Mars terrain from NASA LRO and Mars Global Surveyor data), Google Photorealistic 3D Tiles, Microsoft Bing aerial imagery, the NASA ISS 3D model, ISS orbit data from wheretheiss.at and CelesTrak. Narration voice: Kokoro (Apache 2.0).