26 Jan 2021 (updated 6 Aug 2021)
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 (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.
Notes from "Curl-Noise for Procedural Fluid Flow" by Bridson et al.
Given a potential field , its curl in is given by:
The resulting curl field is divergence free, i.e.
Thus the curl field is incompressible (no sources nor drains). In this demo, we're working with vector fields over , so let and define the curl operator in by
When 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
Nevertheless, this ended up yielding some interesting results.
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,
where the constants and were times the domain as suggested.
The potential field is sampled from a 3D Perlin noise with the components being the 2D position (or screen coordinates) and the component corresponds to some function of the run-time; taking a 2D slice from the 3D noise.
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 noise yields a scalar, not a vector. This Perlin vector field is generated by computing the noise at the position and one at for a constant offset
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.