Creating rolling terrain with 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!