Generate and explore procedurally created 3D biomes using Perlin noise!
This WebGL demo generates 3D terrain using Perlin noise and procedural biome blending. It simulates realistic landscapes by combining multiple noise layers and mapping height values to different biomes such as ocean, beach, plains, mountains, and snow peaks. Users can customize terrain features in real time using the sliders and seed input to generate unique worlds each time.
The terrain is built as a grid of vertices in 3D space. Each vertex's height is determined by combining multiple layers of Perlin noise. The resulting height value is then assigned a color based on biome thresholds. The scene is shaded using Phong lighting to simulate realistic light and shadow effects.
class PerlinNoise {
constructor(seed = Math.random()) {
this.seed = seed;
this.p = this.generatePermutation();
}
generatePermutation() {
const p = [];
for (let i = 0; i < 256; i++) p[i] = i;
let rng = this.seed;
for (let i = 255; i > 0; i--) {
rng = (rng * 9301 + 49297) % 233280;
const j = Math.floor((rng / 233280) * (i + 1));
[p[i], p[j]] = [p[j], p[i]];
}
return [...p, ...p];
}
fade(t) { return t * t * t * (t * (t * 6 - 15) + 10); }
lerp(t, a, b) { return a + t * (b - a); }
grad(hash, x, y, z) {
const h = hash & 15;
const u = h < 8 ? x : y;
const v = h < 4 ? y : h === 12 || h === 14 ? x : z;
return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
}
noise(x, y, z) {
const X = Math.floor(x) & 255;
const Y = Math.floor(y) & 255;
const Z = Math.floor(z) & 255;
x -= Math.floor(x);
y -= Math.floor(y);
z -= Math.floor(z);
const u = this.fade(x), v = this.fade(y), w = this.fade(z);
const A = this.p[X] + Y, AA = this.p[A] + Z, AB = this.p[A + 1] + Z;
const B = this.p[X + 1] + Y, BA = this.p[B] + Z, BB = this.p[B + 1] + Z;
return this.lerp(w,
this.lerp(v,
this.lerp(u, this.grad(this.p[AA], x, y, z),
this.grad(this.p[BA], x - 1, y, z)),
this.lerp(u, this.grad(this.p[AB], x, y - 1, z),
this.grad(this.p[BB], x - 1, y - 1, z))),
this.lerp(v,
this.lerp(u, this.grad(this.p[AA + 1], x, y, z - 1),
this.grad(this.p[BA + 1], x - 1, y, z - 1)),
this.lerp(u, this.grad(this.p[AB + 1], x, y - 1, z - 1),
this.grad(this.p[BB + 1], x - 1, y - 1, z - 1))));
}
}
// Generate height based on multi-octave noise
function generateTerrain(waterLevel, mountainFreq, smoothness) {
const size = 50;
const resolution = 100;
const vertices = [];
const colors = [];
const perlin = new PerlinNoise();
for (let z = 0; z <= resolution; z++) {
for (let x = 0; x <= resolution; x++) {
const nx = x / resolution - 0.5;
const nz = z / resolution - 0.5;
let height = 0;
let amp = 1, freq = 0.03;
for (let o = 0; o < smoothness; o++) {
height += perlin.noise(nx * freq, nz * freq, 0) * amp;
amp *= 0.5;
freq *= 2;
}
height *= mountainFreq;
vertices.push([nx * size, height, nz * size]);
// Assign color based on height
let color;
if (height < waterLevel) color = [0.1, 0.3, 0.6];
else if (height < waterLevel + 0.05) color = [0.9, 0.85, 0.6];
else if (height < 0.4) color = [0.3, 0.7, 0.2];
else if (height < 0.6) color = [0.5, 0.5, 0.5];
else color = [0.9, 0.9, 0.95];
colors.push(color);
}
}
}