Perlin Noise Generation

Watch the continuously generating Perlin noise animation:



← Back to All Cases

Explanation

How It Works

This demo shows how to create smooth, natural-looking noise using the Perlin noise algorithm. Unlike random white noise, Perlin noise generates coherent patterns that resemble natural phenomena like clouds, terrain, or waves.

View Utility Functions
// Smoothstep interpolation function (fade)
function fade(t) {
    return t * t * t * (t * (t * 6 - 15) + 10);
}

// Linear interpolation between two values
function lerp(a, b, t) {
    return a + t * (b - a);
}
View Perlin Noise Class
class PerlinNoise {
    constructor() {
        // Create permutation table
        this.p = new Uint8Array(512);
        for (let i = 0; i < 256; i++) this.p[i] = i;

        // Shuffle for randomness
        for (let i = 255; i > 0; i--) {
            const j = Math.floor(Math.random() * (i + 1));
            [this.p[i], this.p[j]] = [this.p[j], this.p[i]];
        }

        // Duplicate for wrapping
        for (let i = 0; i < 256; i++) this.p[i + 256] = this.p[i];
    }

    // Gradient function
    grad(hash, x, y, z = 0) {
        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);
    }

    // Main noise function
    noise(x, y, z = 0) {
        // Find unit cube containing the point
        const X = Math.floor(x) & 255;
        const Y = Math.floor(y) & 255;
        const Z = Math.floor(z) & 255;

        // Get relative position within cube
        x -= Math.floor(x);
        y -= Math.floor(y);
        z -= Math.floor(z);

        // Compute fade curves
        const u = fade(x);
        const v = fade(y);
        const w = fade(z);

        // Hash coordinates of cube corners
        const A = this.p[X] + Y;
        const AA = this.p[A] + Z;
        const AB = this.p[A + 1] + Z;
        const B = this.p[X + 1] + Y;
        const BA = this.p[B] + Z;
        const BB = this.p[B + 1] + Z;

        // Blend results from 8 corners
        return lerp(
            lerp(
                lerp(this.grad(this.p[AA], x, y, z),
                     this.grad(this.p[BA], x - 1, y, z), u),
                lerp(this.grad(this.p[AB], x, y - 1, z),
                     this.grad(this.p[BB], x - 1, y - 1, z), u),
                v
            ),
            lerp(
                lerp(this.grad(this.p[AA + 1], x, y, z - 1),
                     this.grad(this.p[BA + 1], x - 1, y, z - 1), u),
                lerp(this.grad(this.p[AB + 1], x, y - 1, z - 1),
                     this.grad(this.p[BB + 1], x - 1, y - 1, z - 1), u),
                v
            ),
            w
        );
    }
}
View Animation Code
const canvas = document.getElementById("noiseCanvas");
const ctx = canvas.getContext("2d");
const perlin = new PerlinNoise();

const scale = 0.05; // Controls noise detail
let time = 0; // Animation time offset

function animate() {
    const imgData = ctx.createImageData(canvas.width, canvas.height);
    const data = imgData.data;

    // Increment time for smooth animation
    time += 0.01;

    for (let y = 0; y < canvas.height; y++) {
        for (let x = 0; x < canvas.width; x++) {
            // Use time as z-axis for 3D animated noise
            const value = (perlin.noise(x * scale, y * scale, time) + 1) * 0.5;
            const color = Math.floor(value * 255);

            const index = (y * canvas.width + x) * 4;
            data[index] = color;     // Red
            data[index + 1] = color; // Green
            data[index + 2] = color; // Blue
            data[index + 3] = 255;   // Alpha
        }
    }

    ctx.putImageData(imgData, 0, 0);
    requestAnimationFrame(animate);
}

animate();