Rendering Mountain Terrain with Three.js
After understanding the basics of heightmaps, vertices, and triangle meshes, I tried rendering a real mountain terrain in the browser using DEMNAS elevation data.
Pipeline
DEMNAS (.tif)
→ Crop target area with GDAL
→ Convert DEM into heightmap PNG
→ Read pixel brightness
→ Apply elevation to vertices
→ Generate triangle mesh terrain
→ Render in Three.js
1. Download DEMNAS Data
I downloaded DEMNAS raster elevation data (.tif) for the target area.
The DEM contains:
- elevation values
- geographic coordinates
- terrain resolution data
At this stage, the data is still raw elevation data.
2. Crop the Terrain Area
The original DEM tile was too large covering the whole Java island.
I cropped the raster using GDAL only the mountain area needed:
gdal_translate \
-projwin xmin ymax xmax ymin \
input.tif \
mountain.tif
This reduced the terrain to only the target mountain area.
3. Convert DEM into Heightmap PNG
Next, I converted the DEM into a grayscale heightmap.
gdal_translate \
-ot Byte \
-scale \
mountain.tif \
mountain_heightmap.png
In the heightmap:
- dark pixels represent lower elevation
- bright pixels represent higher elevation
The PNG is now a heightmap.
4. Load the heightmap in Three.js
In Three.js:
- the PNG brightness values were read pixel-by-pixel
- each pixel controlled vertex elevation
- vertices were connected into triangle meshes automatically by
PlaneGeometry
Load the PNG image in the browser.
const img = new Image()
img.src = "/mountain_heightmap.png"
5. Read pixel brightness values
Draw the image into a hidden canvas and read the pixel data.
const imageData = ctx.getImageData(...)
Each pixel brightness represents elevation.
Conceptually:
(x, z) → height
6. Create terrain geometry
Create a subdivided plane geometry.
new THREE.PlaneGeometry(
width,
height,
segmentsX,
segmentsY
)
This automatically creates:
- vertices
- triangles/faces
Initially the plane is flat.
7. Apply heightmap data to vertices
Loop through pixels and move vertices vertically.
vertices.setY(index, height)
Now the flat plane becomes a mountain terrain.
8. Recalculate normals
geometry.computeVertexNormals()
Normals are needed for proper lighting and smooth shading.
9. Create material and mesh
const terrain = new THREE.Mesh(
geometry,
material
)
This combines:
- geometry (shape)
- material (surface appearance)
into a renderable 3D object.
10. Render the scene
renderer.render(scene, camera)
The terrain is now rendered as a 3D mountain in the browser.
I also added:
- camera controls
- drag rotation
- zoom
- pan interaction
using OrbitControls.
Result
The result already looks like a recognizable 3D terrain of the mountain.
However, the surface still appears:
- rough
- stepped
- similar to terracing/contour layers
This is likely caused by elevation quantization from the PNG pipeline and limited height precision.
Next Steps
Things I still want to improve:
- smoother terrain surface
- better elevation precision
- satellite texture / realistic surface colors
- improved lighting and atmosphere
- more natural terrain shading
Key Insight
A 3D mountain is not a “solid object”.
It is essentially:
a grid of vertices whose heights are driven by elevation data and connected into many small triangles.