Terrain elevation with noise layers
Resulting terrain elevation with multi-scale noise layers and mountain peaks

This post continues from Part II, where we established the paint map foundation and mountain ridge system. Now we’ll add detailed noise layers, distance-based mountain peaks, and do blending to create the final terrain elevation.

Paint Map (recap)

Before applying noise layers, we start with the foundation established in Part I - the paint map that defines our base land/water distribution:

Paint map from Part I
The paint map from Part I - our starting elevation values before noise enhancement

For visualization throughout this post, we’ll be using the magma palette from matplotlib, which I patched to artificially darken the ocean areas to highlight the coastline:

Paint map sampled at triangle centroids
Paint map values sampled at the centroids of Delaunay triangles

Note that we’ll be sampling the paint map per Delaunay triangle (at each triangle’s centroid):

Central portion of triangulated paint map
Central portion of the above image showing the per-triangle sampling more clearly

Remember that the paint map provides the broad strokes: positive values for land, negative for ocean, with smooth transitions between them. Now we’ll enhance it with noise layers to create realistic terrain detail.

Multi-Scale Noise Layers

We will layer multiple octaves of Simplex noise at different frequencies over the broad strokes provided by the paint map. Each will contribute different detail scales to the final terrain.

mapgen4 by @redblobgames in particular uses six layers:

Noise field visualization
All six noise fields at different frequencies (1x, 2x, 4x, 16x, 32x, 64x) shown in a 3x2 grid
Triangulated noise field
Top-left corner of n2 - Note that we are sampling the noises at the (centroids of the) triangles.
Layer Frequency Description
n₀ 1x Lowest frequency
n₁ 2x Low frequency
n₂ 4x Medium-low frequency
n₄ 16x Medium-high frequency
n₅ 32x High frequency
n₆ 64x Highest frequency

Notice the gap in numbering (n₃ is missing). This would correspond to frequency 8x, which we don’t use.

Coastal Noise Enhancement

mapgen4 starts with coastal noise enhancement. This provides control over the variation at coastlines while keeping inland elevation unaffected:

\[e = \text{Paint map from Part I}\] \[e_{coast} = e + \alpha \cdot (1 - e^4) \cdot \left(n_4 + \frac{n_5}{2} + \frac{n_6}{4}\right)\]

The term \((1 - e^4)\) creates a bell curve that peaks at \(e=0\) (coastline) and decreases rapidly for \(\lvert e \rvert > 0\). This modulates an fBm-like combination of our three highest frequency noise layers.

What matters here isn’t the exact formula or amplitudes, but the core principle: applying high-frequency detail specifically where land meets water.

\[e_{tmp} = \begin{cases} e & \text{if } e_{coast} > 0 \\ e_{coast} & \text{if } e_{coast} \leq 0 \end{cases}\]
etmp with varying α - Showing its effect on coastline and seabed complexity

Mountain Distance Field

Mountains need special pre-processing. If you remember from Part I in the swarm of seed points we tagged some as mountain peaks. Here we will pre-compute a distance field from every regular seed point to the closest mountain peak point.

We compute distance through the mesh topology of the Delaunay triangulation using BFS (breadth-first search). i.e., we don’t use Euclidean distance. This creates more organic mountain shapes that follow the terrain’s natural connectivity.

The algorithm spreads outward from mountain peaks:

  1. Start at triangles containing mountain seed points (distance = 0)
  2. Visit neighboring triangles, incrementing distance by a randomized amount
  3. The randomization creates natural ridge patterns instead of perfect cones

Here’s the magic formula used for distance increment in each step:

\[\Delta = s \cdot (1 + j \cdot r)\]

Where:

  • \(s\) = spacing between triangles (uses configured Poisson disk separation)
  • \(j\) = jaggedness parameter (0 = true topological distance, 1 = very irregular)
  • \(r \in [-1,1]\) = random factor using triangular distribution

The triangular distribution rand() - rand() clusters values near zero while allowing occasional larger variations. This looks more natural than uniform randomness.

I implemented Fisher-Yates shuffling when visiting neighbor triangles. Instead of processing neighbors in a fixed order (which would create directional bias), the order is randomly shuffled each time. This ensures mountain ridges branch out organically in all directions rather than following predictable patterns.

After computing distances this way, we normalize them (by the max dist, for example):

dm - Normalized mountain distance field with varying jaggedness parameter

Elevation Blending

The final elevation combines all components through weighted blending:

\[e_{final} = \begin{cases} \text{lerp}(e_{hill}, e_{mountain}, e_{coast}^2) & \text{if } e_{coast} > 0 \\ e_{coast} \cdot (\rho + n_1) & \text{if } e_{coast} \leq 0 \end{cases}\]

Where:

  • \(e_{hill} = h \cdot \bigl(1 + \text{lerp}(n_2, n_4, \tfrac{1 + n_0}{2})\bigr)\) => hill elevation with noise-modulated height
  • \(e_{mountain} = 1 - \frac{\mu}{2^\sigma} \cdot d_m\) => mountain elevation from distance field

The quadratic blend weight produces smooth transitions from hills near the coast through mixed terrain at mid-elevations to pure mountains at peaks.

With (editable) parameters:

  • \(\alpha\): Coastal noise strength (0.01)
  • \(h\): Hill height scale (0.02)
  • \(\rho\): Ocean depth multiplier (1.5)
  • \(\mu\): Mountain slope (17.6)
  • \(\sigma\): Mountain sharpness (9.8)

Interactive Parameter Exploration

Terrain elevation with varying mountain sharpness σ
Terrain elevation with varying mountain jaggedness j
Terrain elevation with varying hill height scale h
Terrain elevation with varying ocean depth multiplier ρ

Region (vs. Triangle) Elevation

So far we’ve computed elevation for triangles. But our Voronoi regions (from Part I) also need elevations for certain stages in the rest of the series.

Triangle vs Region elevation animation
Animation comparing triangle elevation vs region elevation (1 second each)

Each seed point defines a Voronoi region and serves as a vertex in multiple Delaunay triangles. To assign elevation to a Voronoi region, we average the elevations of all triangles that share its seed point as a vertex.

Triangle vs Region elevation animation
Central detail animating between triangle elevation and region elevation (1 second each)

Next Steps

With elevation complete, our island has shape but lacks the defining features carved by water. Part IV will simulate the hydrological cycle: rainfall patterns influenced by topography, rivers flowing from peaks to ocean, and valleys carved by erosion.

Valuable Resources