Noise Functions


§ Perlin Noise

My implementation is based on the Perlin code from "Simplex noise demystified" by Stefan Gustavson and is written with vanilla JavaScript.

The implementation uses a randomly generated permutation table rather than a predefined one. To do this, a 256 element index array is shuffled using the Fisher-Yates shuffle.

const p = [...new Array(256).keys()]
    .map((v, _, a, j = random(255)) => [a[j], a[j] = v][0])

const perm = new Array(512)
    .fill(0)
    .map((_, i) => p[i & 255])

The random(N) function generates a uniformly random integer between 0 and N, inclusive; note not to use the modulo operator on a random integer as that induces a bias and thus does not preserve uniformity.

My implementation of Perlin noise has a max throughput of ~8 million calls a second; which is less than noise.js' 10M/s.

§ Fractional Brownian Motion

Fractional Brownian motion (fBm) is a method to modulate a noise function by superimposing the noise with a number of octaves with increasing frequency and decreasing amplitude.

The Book of Shaders has a good tutorial on how fBm can be used to modulate waves, and its implementation. Another good resource is this article by Inigo Quilez.

§ "Curl" Noise

Notes from "Curl-Noise for Procedural Fluid Flow" by Bridson et al.

Given a potential field Ψ=Ψx,Ψy,Ψz\Psi = \langle \Psi_x, \Psi_y, \Psi_z \rangle, its curl in R3\mathbb{R}^3 is given by:

v(x,y,z)=×Ψ=(ΨzyΨyz,ΨxzΨzx,ΨyxΨxy). \vec{v}(x, y, z) = \nabla \times \Psi = \left( \frac{\partial\Psi_z}{\partial y} - \frac{\partial\Psi_y}{\partial z}, \frac{\partial\Psi_x}{\partial z} - \frac{\partial\Psi_z}{\partial x}, \frac{\partial\Psi_y}{\partial x} - \frac{\partial\Psi_x}{\partial y} \right).

The resulting curl field c\vec{c} is divergence free, i.e.

v=(×Ψ)=0.\nabla \cdot \vec{v} = \nabla \cdot (\nabla \times \Psi) = 0.

Thus the curl field is incompressible (no sources nor drains). In this demo, we're working with vector fields over R2\mathbb{R}^2, so let ΨΨx,Ψy\Psi \triangleq \langle \Psi_x, \Psi_y\rangle and define the curl operator in R2\mathbb{R}^2 by

×Ψ=ΨyxΨxy. \nabla \times \Psi = \frac{\partial \Psi_y}{\partial x} -\frac{\partial \Psi_x}{\partial y}.

When ×Ψ\nabla \times \Psi is positive we have anticlockwise rotation, if it is negative then clockwise rotation, otherwise it is irrotational.

(2026 Update) At the time of writing the article I hadn't yet taken vector calculus and, as a result, I hadn't learned how taking the gradient of the scalar curl field gave me a vector field. Instead, I made a vector field by splitting the terms of the 2D curl

v=Ψyx,Ψxy. \vec{v} = \left\langle \frac{\partial \Psi_y}{\partial x}, -\frac{\partial \Psi_x}{\partial y} \right\rangle.

Nevertheless, this ended up yielding some interesting results.

§ Considerations

  1. The partial derivatives are calculated using finite diference approximations, Bridson recommends a step value of 10410^{-4} times the domain.
  2. The potential field can be generated via Perlin noise, i.e. Ψ=N\Psi = N.
  3. Adding octaves of varying scales produces fields similar to physical turbulence.
    • See: Kolmogorov turbulence spectrum on how to reduce the speed of small vortices.
  4. Ideally, the field should vary with time, possibly by using FlowNoise.

§ Process

I ended up combining all three of the aforementioned methods. First, I generate Perlin noise, then modulate the field using Fractional Brownian Motion (fBm), then apply the curl noise method over the Perlin + fBm field.

The fBm parameters were 3 octaves, lacunarity of 2, and gain of 0.5.

The partial derivatives used in the curl noise were computed using finite differencing,

fx(x,y)f(x+h,y)f(xh,y)2h, f_x(x, y) \approx \frac{f(x + h, y) - f(x - h, y)}{2h},

fy(x,y)f(x,y+k)f(x,yk)2k, f_y(x, y) \approx \frac{f(x, y + k) - f(x, y - k)}{2k},

where the constants hh and kk were 10410^{-4} times the domain as suggested.

The potential field Ψ\Psi is sampled from a 3D Perlin noise N(x,y,z)N(x, y, z) with the (x,y)(x, y) components being the 2D position (or screen coordinates) and the zz component corresponds to some function of the run-time; taking a 2D slice from the 3D noise.


§ Process Visualization

From left to right: generate the perlin noise, apply fBm, compute the "curl" field.


§ Demonstrations

The implementation uses web workers are used to offload noise generation from the main thread, however, using compute shaders and WebGL would be a much better approach.

The demos below are rendered in real time with particles randomly sampled on the $xy-$plane and velocities sampled from the corresponding vector field.

§ Perlin Vector Field

Perlin noise yields a scalar, not a vector. This Perlin vector field is generated by computing the noise at the position (x,y)(x, y) and one at (x+a,y+b)(x + a, y + b) for a constant offset (a,b)R2.(a, b) \in \mathbb{R}^2.

§ Curl Noise

This curl noise demo superimposes the path of every particle, the brightness of a spot is directly correlated to how many times a particle has visited that spot.

The above demo shows how particles travel within the velocity field generated by "curl" noise. The color of the particle's path is dependent on the HSL color at the angle it is travelling.