SCENE.LOG
scenenode[01]
2026-06-06 · 5 min

Creating rolling terrain with Three.js

stylized nature geometry three.js

The first thing I want to tackle for the project is the ground: I want rolling terrain, with relief. I chose to generate it directly with Three.js rather than modeling it in Blender.

The idea is to create a flat surface with a PlaneGeometry, then move its vertices through its BufferAttribute attributes.position to build up relief.

const geometry = new THREE.PlaneGeometry(32, 32, 64, 64);
geometry.rotateX(-Math.PI / 2); // lay the surface flat

const material = new THREE.MeshStandardMaterial({
  color: "#704a39",
  flatShading: true, // lowpoly look
});
const mesh = new THREE.Mesh(this.geometry, this.material);
this.scene.add(mesh);

Noise for natural-looking relief

To create relief, the idea is to ripple the surface across several octaves driven by noise, for a more natural result. I use the createNoise2D function to generate 2D simplex noise. Unlike pure randomness, noise varies smoothly and continuously: two neighbouring points have close values, which gives smooth, non-chaotic relief.

import { createNoise2D } from "simplex-noise";

this.noise2D = createNoise2D(alea(this.params.seed));

The seed parameter lets me try out several different noise patterns, and keep, thanks to that seed, the generated terrain I like.

We then define our octaves to ripple the terrain (I tuned the values with the tweaks until I got what suited me).

this.params = {
  octaves: [
    { frequency: 0.091, amplitude: 0.65 }, // large hills
    { frequency: 0.067, amplitude: 0.6 }, // medium bumps
    { frequency: 0.018, amplitude: 0.3 }, // small details
  ],
};

Applying the relief to the geometry

Finally, we modify the positions of the BufferAttribute attributes.position by combining the octaves and the noise:

const pos = geometry.attributes.position;
 for (let i = 0; i < pos.count; i++) {
      const x = pos.getX(i);
      const z = pos.getZ(i);

      // Combine several noise "octaves" for rich relief
      let height = 0;
      for (const octave of this.params.octaves) {
        height +=
          this.noise2D(x * octave.frequency, z * octave.frequency) *
          octave.amplitude;
      }

      pos.setY(i, height);
    }

    pos.needsUpdate = true;
    this.geometry.computeVertexNormals(); // so normals account for the changes
  }

Here’s the result:

Feel free to play with the tweaks to change the noise or the octave values.

That wraps up the first step of my project: creating rolling terrain. Next up, I’ll add grass to it!

← back to scene