Back in April I wrote about my first week with forge3d, and I admitted something a little funny: my favorite image had nothing to do with the GPU. “No interactive viewer. No dramatic camera flythrough.” Just Python, elevation, light and composition.
Six months later, I finally went and made the dramatic camera flythrough.
Humphreys Peak, Arizona · 12,633 ft · USGS 3DEP 10 m elevation · USGS National Map aerial imagery · 1.6× vertical exaggeration · rendered with forge3d
Why Humphreys
Humphreys Peak is the highest point in Arizona, the top of the San Francisco Peaks just north of Flagstaff. The Peaks are what’s left of a big stratovolcano. Collapse and erosion hollowed its top out into a horseshoe-shaped valley called the Inner Basin, and the summits you see today — Humphreys, Agassiz, Fremont, Doyle — are pieces of its old rim.
That makes it a great subject for a camera that goes all the way around. From one side it’s a single mountain. Come round to the other and the whole thing opens up, and you’re looking straight down into the old volcano.
How It’s Built
The whole thing is a small pixi project, four steps from nothing to a finished video:
- Elevation. A 20 km square of USGS 3DEP around the summit at 10 m, requested straight from the USGS ImageServer in UTM zone 12N, so every pixel is square and in metres.
- Imagery. The USGS National Map aerial layer for exactly the same square, stitched from sixteen 1,024-pixel tiles into one 4,096-pixel image — about 5 m a pixel. Both sources are public domain.
- The orbit. forge3d’s viewer loads the terrain, drapes the imagery, and renders one frame per camera position: 601 frames, one full turn in 20 seconds, lit by a late-afternoon sun.
- The finish. A pass in plain Python that swaps in a sky, adds the labels, and hands the frames to ffmpeg.
The camera path is just 37 keyframes. It starts low, rises as it comes round, and drifts in a little, with a cosine ease so it never lurches:
for i in range(37):
u = i / 36
ease = 0.5 - 0.5 * cos(pi * u) # gentle in, gentle out
anim.add_keyframe(
time=u * seconds,
phi=225 + 360 * u, # one full turn
theta=74 - 10 * ease, # rise as it goes round
radius=15000 - 3500 * ease, # metres from the summit
fov=40, target=summit)
The orbit centres on the highest cell in the DEM, so pointing it at a different mountain is just a different GeoTIFF.
Three Things I Got Wrong
My first renders were a blank white screen. The forge3d docs describe the viewer’s camera distances in “terrain-width world units,” so I sized everything in pixels: a 2,000-pixel DEM, a camera 840 units out. Nothing showed up. The viewer’s own stats gave it away: its camera anchor was sitting at easting 438,384, northing 3,911,670. The viewer had read the GeoTIFF’s coordinates and was working in real metres, so my camera was parked somewhere inside the mountain. Once I switched to metres, exaggeration became a plain number too: 1.0 is true scale.
The built-in labels floated. forge3d can draw labels, and my first version used them. They came out small, a little soft, and hanging well above the summits. So I moved them out of the renderer. forge3d’s orbit camera is simple enough to reproduce exactly — the eye sits at the target plus r·(sinθ cosφ, cosθ, sinθ sinφ), looking back with a vertical field of view — so for every frame I can project each summit onto the image myself and draw the label in Roboto with a proper dark halo. To make sure the maths matched, I painted a red dot on the test imagery at the summit and checked that my projected point landed on it. It landed right on it.
Owning the labels bought two more things. A label now hides when the mountain passes in front of it, because each frame marches a line of sight from the camera to the summit across the DEM. And when two labels collide as the ridge lines up with the camera, the higher peak wins and the other fades out instead of stacking on top.
The sky wouldn’t turn blue. forge3d has a background colour setting, but I couldn’t get it to take in the viewer mode I was using for shadows. The background it does draw is a perfectly flat off-white, though, so the finishing pass keys it out — background-coloured pixels connected to the top edge — and puts a gradient sky behind the mountain. Growing that mask by a single pixel swallows the anti-aliased rim, so there’s no white fringe on the ridgeline.
A Note on GPUs
I built and tested all of this on a cloud machine with no graphics card, using Mesa’s software Vulkan driver. It works, which says something nice about forge3d, but it takes about 25 seconds a frame, which is most of an afternoon for one orbit. On my laptop’s GPU it’s far faster. Same code either way.
Back to April
In the first post I said that once I got the hang of forge3d, I felt mostly limited by my own ideas and by how good the open data was. That held up. Everything in this video is public: USGS elevation, USGS imagery, an open-source renderer and an open font. The hard part was a camera, a sky and five labels.
The April post ended with a promise to try Rainier with Washington’s 1 m LiDAR. That’s still on the list. Now it can orbit.
Data: USGS 3D Elevation Program; USGS The National Map imagery (public domain). Rendering: forge3d by Milos Popovic. Labels: Roboto.