03 - Noise Flow Fields

Simulating particle paths over a 2D Perlin noise vector field to create natural, organic flows.

← Back to Portfolio

Core Algorithm: Generating Continuous Paths

To plot efficiently, we want long, continuous lines so the pen doesn't have to constantly lift. For each starting point, we step forward based on the underlying Perlin noise angle, drawing a single unbroken line.

beginShape();
for (let j = 0; j < stepsPerPath; j++) {
    vertex(x, y);

    // Grab an angle from the noise grid at coordinates (x, y)
    let angle = noise(x * noiseScale, y * noiseScale) * TWO_PI * 2;
    
    // Move slightly along that angle
    x += cos(angle) * stepLength;
    y += sin(angle) * stepLength;

    // Terminate path early if it hits canvas edges
    if (x < 0 || x > A5_WIDTH || y < 0 || y > A5_HEIGHT) {
        break;
    }
}
endShape();

How does Perlin Noise create a smooth transition between pixels?

If you use a standard random() function, pixel 1 might be a value of 0.9 and pixel 2 right next to it could be 0.1. It results in harsh, jagged white noise. Perlin Noise solves this using interpolation (smooth averaging). Imagine a very wide grid spread over the canvas (called a lattice). The math algorithm assigns a random value only to the corners of the grid squares. If you ask for the noise value at a pixel that sits precisely in the middle of a grid square, the algorithm looks at the 4 corners, figures out how close the pixel is to each corner, and mathematically blends (interpolates) them using a smooth S-curve. Because adjacent pixels are physically super close to each other inside the same mathematical grid square, their blended results end up being virtually identical, which creates a smooth transition!

noiseScale

This is the most important variable in the sketch:

let noiseScale = 0.01;
noise(x * noiseScale, y * noiseScale)

The noise() function relies on those mathematical "grid squares" being a certain fixed distance apart (usually 1.0 units), if you ask for noise(1, 1) and then noise(2, 2), you are jumping entire grid squares at a time, and it will look chaotic. By multiplying by noiseScale = 0.01, you are tricking the function. You say "I'm at pixel 10, but please calculate the noise as if I'm at coordinate 0.10". The next pixel is "I'm at pixel 11, calculate coordinate 0.11". Because 0.10 and 0.11 are so remarkably close to each other inside the exact same mathematical grid square, the noise function returns an almost identically smooth angle. These videos on YouTube give a good overview of Perlin Noise.