Endless Procedural Terrain in Three.js · Part 2 of 3
THREE.JS TERRAIN HEIGHTMAP
FROM NOISE, PART 2
Plain noise makes boring bumps. Part 2 shapes the ground with fBm octaves, ridged mountains, domain warping, carved valleys and flat combat zones, all from one seed, and shows what that could mean for Super Soldier Battle.
By Shane Brumback ·
What Is a Terrain Heightmap? (Beginner Definition)
A Three.js terrain heightmap is a grid of heights that gives every (x, z) point a y value, which you write into the vertices of a flat PlaneGeometry. It can come from a grayscale image or from a function like heightAt(x, z). A function built from noise works at any coordinate, so it never runs out, which is exactly what endless terrain needs.
A Three.js terrain heightmap is a grid of heights that turns a flat mesh into land. You can paint it as a grayscale image, or you can write it as a function, heightAt(x, z), that returns a height for any point in the world. This is Part 2 of my series Endless Procedural Terrain in Three.js. Part 1 made the ground endless. Today I make it worth fighting on, with ridged mountains, winding valleys and flat combat areas, for my browser shooter Super Soldier Battle.
What Is a Three.js Terrain Heightmap?
The recipe works for an image or for math:
- Make a
PlaneGeometrywith a grid of vertices and lay it flat. - For every vertex, set
yto the height at that vertex's world position. - Recompute the normals so light falls across the slopes correctly.
An image heightmap has a fixed size. A function heightmap built from noise works at any coordinate, which is what endless chunks need. But one layer of noise gives soft, samey bumps, so the rest is about shaping it.
| Technique | What it does to the terrain | Cost per height sample |
|---|---|---|
| Single noise layer | Soft, even bumps | 1 noise call |
| fBm (5 octaves) | Big hills with smaller detail on top | 5 noise calls |
| Ridged noise | Sharp mountain crests | 5 noise calls |
| Domain warp | Bends ridges and valleys into natural curves | 2 to 6 extra calls |
| Masks (valleys, flat zones) | Shapes the land for gameplay | A few math ops |
How Do Perlin Noise Octaves (fBm) Build Noise Terrain?
fBm, short for fractal Brownian motion, means stacking layers of noise called octaves. Each octave runs at double the frequency and half the height of the one before. The first octave lays out the big hills, the second adds smaller hills on top of those, and the later octaves add rocks and bumps. Four to six octaves is the sweet spot; past that you pay for detail smaller than your mesh can show. Whether the base is Perlin, simplex or your own gradient noise, octave stacking is what makes it look like land.
How Does Ridged Noise Make Mountains?
Noise swings from negative to positive and crosses zero along smooth, wandering lines. Ridged noise flips that around: 1 - |noise|. Every zero crossing becomes a sharp crest, and you get mountain ridges instead of round hills. Square the result for crisper peaks, and weight each octave by the one before it so the fine detail lands on the ridges while the slopes stay smooth. A very slow noise acts as a mountain mask, so there are open plains in some places and ranges in others.
What Is Domain Warping in a Terrain Heightmap?
Noise has a giveaway grid-like regularity. Domain warping hides it by nudging the coordinates before you sample: instead of heightAt(x, z) you sample at (x + warpX, z + warpZ), where the warp itself comes from a slower noise. Straight ridges bend, valleys twist, and the land looks eroded instead of generated.
How Do You Carve Valleys and Flat Combat Areas?
This is where noise terrain stops being pretty and starts being a game map.
- Valleys. Take the absolute value of another slow noise. Near zero it forms a long, winding line; pull the height down along it and you carve a valley with a flat bed, a natural route with cover.
- Flat combat zones. Divide the world into big cells. Each cell has a seeded chance of holding a flat disc, and the terrain blends smoothly from the hills down (or up) to a level arena. Arenas hold bases, spawns and pads, and the spawn cell always gets one.
How Do Seeds Give Every Multiplayer Player the Same Map?
Everything above uses a seed. A small seeded random generator shuffles the noise tables and decides where arenas go, so the same seed always produces the same world. In multiplayer, the server sends one number and every client builds identical mountains locally. A new seed means a new battlefield.
How Super Soldier Battle's Terrain Works Today
I read the current Super Soldier Battle code. Here's what it actually does:
- A hand-built island. The live map loads as one pre-made terrain model split into 16 visible pieces, plus a separate invisible collision mesh with a BVH so raycasts are fast.
- Raycast heights. To find the ground, the game casts a ray straight down from high above onto that collision mesh.
- A fixed boundary. The land spans roughly x -1,380 to 1,350 and z -1,250 to 1,265, and the player is clamped to ±1,350.
- Painted and listed details. Beaches come from a painted mask texture (pebble inlets, white-sand coast), and a terrain info file lists crater positions that the ground shader darkens as scorch marks.
- Noise already in the sky. The game's sky clouds are drawn with fBm and domain warping, the same tools this article uses, just not on the ground yet.
So the terrain ships as a designed asset: great for a hand-tuned map, but it can't grow, and a new map means a new download.
What Noise Terrain Could Do for Super Soldier Battle
Procedural noise terrain is planned, not live. Here's what it could improve:
- New maps from a seed. One number instead of a big model download, and every player gets the same battlefield.
- Designed fights. Flat zones for bases and pads, valleys as attack lanes, and ridged mountains as natural walls, instead of hand-placing all of it.
- Cheaper height lookups. Ground height from the same function the mesh uses, instead of a raycast for every query.
How It Connects to the Endless Grass
The endless grass in my rigged capybara tutorial runs on flat ground today. In Part 3 that grass samples this terrain's height, so it can follow these hills and valleys.
Want the Code? The Part 2 Tutorial
The premium Three.js noise terrain tutorial builds directly on the Part 1 chunk code. It keeps the chunks, trees and character and swaps in one new height function: seeded noise, fBm, ridged mountains, domain warping, valleys and flat zones. Add ?seed=42 for a whole new world. If you'd rather skip code, see no-code endless terrain in STEM Studio.
Coming Next in the Series
- Part 3: LOD, performance and texturing. Faster distant chunks, and the endless grass growing on this terrain. (Planned, not yet published.)
Frequently Asked Questions
What is a terrain heightmap in Three.js?
A terrain heightmap is a grid of heights that shapes a flat mesh into hills. In Three.js you take a PlaneGeometry, lay it flat, and set each vertex's y from either a grayscale image or a height function like heightAt(x, z). A function-based heightmap built from noise never runs out, so it suits endless terrain.
What is fBm in noise terrain?
fBm (fractal Brownian motion) means adding several layers of noise, called octaves. Each octave usually has double the frequency and half the height of the one before, so the first octave makes big hills and later octaves add smaller and smaller detail. Four to six octaves is typical for game terrain.
How does ridged noise make mountains?
Ridged noise takes 1 minus the absolute value of the noise. Normal noise crosses zero along smooth lines, and that flip turns each zero line into a sharp crest, so you get mountain ridges instead of round hills. Squaring the result and weighting each octave by the previous one makes the ridges crisper.
What is domain warping?
Domain warping offsets the coordinates you feed into the noise using another, slower noise. Instead of heightAt(x, z) you sample at (x + warpX, z + warpZ). Straight, repetitive features bend into twisting ridges and winding valleys, which looks much more natural.
How do multiplayer games give every player the same procedural map?
They share a seed instead of the map. The noise and every random choice are driven by a seeded generator, so the same seed always produces the same heights. The server sends one number, and each client builds identical terrain locally without downloading a heightmap.
Does Super Soldier Battle use procedural terrain?
Not yet. Super Soldier Battle's live map is a hand-built island loaded as a 3D model, with an invisible collision mesh for height lookups. Procedural noise terrain is planned, and this series is the groundwork for it.
Play the Game, Then Picture the Mountains
Play Super Soldier Battle free in your browser. Fight across today's island, then picture those battles moving through ridged mountains and valleys that change with every seed.
Building a Three.js game and need terrain, gameplay or performance help? I'm available for freelance and contract work: contact me.
Play Super Soldier Battle free →