Rendering from Satellite and Elevation Data of London
I tested several heightmap formats as the source for the terrain mesh, from 8-bit greyscale JPEGs to GeoTIFFs. JPEG is fast to load and decode in the browser, but it's a poor fit for elevation data:
Low precision - 8 bits gives only 256 height levels, so terrain ends up stepped or "terraced".
Compression artefacts - lossy compression adds noise that shows up as bumps in the mesh.
No absolute scale - each image is scaled to its own min/max, so a pixel value doesn't map to a real elevation, and neighbouring tiles don't line up.
No georeferencing - there's no built-in link between pixels and world coordinates.
GeoTIFF fixes all of these. It stores 16- or 32-bit elevation values along with georeferencing metadata: coordinate reference system, geotransform, pixel scale and no-data values.
I decode GeoTIFFs on a Node.js server using geotiff.js. The client calls a REST endpoint. The server reads the raster and its GeoKeys metadata, samples elevation at the requested resolution, and returns each sample with its world coordinates, without blocking the event loop. Decoding on the server keeps the client light: the browser gets a ready-to-use grid of heights and coordinates, which goes straight into a WebGL vertex buffer. Because every vertex has real coordinates, it's easy to add map overlays, markers and other georeferenced data later.
Building a Miniature World: Mallorca
I then used the same pipeline to build a miniature 3D model of Mallorca from two inputs:
A heightmap - from elevation data, used for displacement mapping: each vertex of a subdivided plane is moved up by its sampled height, which recreates the island's topography (the Serra de Tramuntana ridge, coastal plains and bays).
A satellite image - used as the colour (albedo) texture, UV-mapped onto the displaced mesh so the surface colour lines up with the terrain beneath it.

By extracting elevation data from the satellite image, the miniature landscape's topographical features are recreated with depth.
Six Years On: Where This Goes Next
I built this experiment five years ago. The core idea still holds: decode elevation properly, keep every vertex georeferenced, and let the browser do the drawing. But the tools around it have changed enough that I want to pick it back up. Three things in particular.
Gaussian splats. A heightmap gives you the shape of the ground and nothing else. Buildings, trees and bridges are either missing or smeared into the terrain. Gaussian splatting captures a scene as millions of small, coloured, semi-transparent blobs, reconstructed from ordinary photos, and it renders in real time in a browser. The terrain mesh becomes the base layer, with splats providing the detail on top.
AI models that turn images into 3D. Photogrammetry used to mean hundreds of photos and hours of processing. Newer feed-forward models estimate depth, camera poses and geometry from a handful of images in seconds. That opens up a different pipeline: take satellite or street-level imagery, infer the 3D structure, and place it into the scene.
React Three Fiber, WebGPU and TSL. The original was imperative WebGL with hand-written shaders. React Three Fiber lets me build the scene from components: a terrain tile, a splat layer and an overlay each become a self-contained piece. Three.js now has a WebGPU renderer, and TSL (Three Shading Language) lets me write shaders as composable JavaScript nodes that compile to WGSL, with a WebGL fallback. Compute shaders mean work like displacement, normals and level-of-detail can move onto the GPU.
Note: WebGPU can't linearly filter 32-bit float textures by default. That needs the optional
float32-filterablefeature, which not every device supports. Where it's missing, the options are to interpolate between height samples manually in the shader, or to fall back to 16-bit floats, which still gives far more precision than the 8-bit JPEGs I started with.
What ties these together is the decision I made the first time round: everything has real coordinates. A splat or an AI-generated mesh has no idea where it is in the world. It comes out in its own arbitrary space and scale. The georeferenced terrain gives me a frame to align them to.
The next goals
Rebuild the terrain renderer as React Three Fiber components on WebGPU, with displacement done in TSL from a floating-point height texture.
Load a Gaussian splat of a London landmark and align it to the terrain using known ground control points.
Run an image-to-3D model over a small area and compare its output against the SRTM elevation.
Find the limits: how many splats, how many tiles, and what frame rate a mid-range laptop can hold.
I don't know yet how well these fit together. Sorting semi-transparent splats against a solid mesh, matching a 30 meter elevation grid to centimetre-scale captures, and keeping it all streaming smoothly are open problems. That's the point of the experiment.
You can visit the project here.
