3D Biome Terrain Generator

Generate and explore procedurally created 3D biomes using Perlin noise!



Adjust settings and generate!
← Back to All Cases

Explanation

How It Works

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.

View Perlin Noise Class
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))));
    }
}
View Terrain Generation Process
// 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);
        }
    }
}