Noise Functions
Perlin Noise§
My implementation is based on the Perlin code from “Simplex noise demystified” by Gustavson, and written using JavaScript.
Instead of using a predefined permutation table, I opted to use a randomly generated one. To do this, I filled a 256 element array with values corresponding with their indicies, then performed a 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.
Here, I used crypto
to generate a random integer; one consideration to keep in mind is to not use the modulo operator to generate a number between 0 and N - 1
as it does not preserve uniformity.
My implementation of the Perlin noise algorithm has a maximum throughput of about 8 million calls a second; which is less than noise.js’ 10M/s.
Fractional Brownian Motion (fBm)§
Fractional Brownian motion is used to modulate Perlin noise, it works by superimposing the noise at a certain point with a number of octaves with increasing frequency and 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 $\Psi = \langle \Psi_x, \Psi_y, \Psi_z \rangle$, its curl in $\mathbb{R}^3$ is given by:
$$ \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 motivation for taking the curl of a vector field is that the resultant curl field, $\vec{v}$, has the following property:
$$ \nabla \cdot \vec{v} = \nabla \cdot (\nabla \times \Psi) = 0. $$
The divergence of curl of the vector field (at least in $\mathbb{R^3}$) is always zero. Thus, the curl field contains neither sources nor drains, and is incompressible. These properties are desirable as they ensure uniformity in the field, which is good for noise functions.
In this demo we’re working with a vector field in $\mathbb{R}^2$, then if $\Psi$ takes the form $ \langle \Psi_x, \Psi_y\rangle$, then the curl is defined as
$$ \nabla \times \Psi = \frac{\partial \Psi_y}{\partial x} -\frac{\partial \Psi_x}{\partial y}. $$
Because $\nabla \times \Psi: \mathbb{R}^2 \to \mathbb{R}$, I created a vector function $\vec{v}$ by splitting the scalar as follows
$$ \vec{v} = \left\langle \frac{\partial \Psi_y}{\partial x}, -\frac{\partial \Psi_x}{\partial y} \right\rangle. $$
This made animation individual particles easier, as I could just set the velocity of the particle to the value of the field at that position. As opposed to making the angle some sort of function based on the scalar value. And the visual much more appealing — in my opinion.
Considerations§
- The partial derivatives are calculated using finite diference approximations, Bridson recommends a step value of $10^{-4}$ times the domain.
- Perlin noise, $N(x, y)$, can be used to construct the potential field, in $\mathbb{R}^2, \Psi = N$.
- Adding octaves of different scales can produce fields similar to physical turbulance.
- See: Kolmogorov turbulance spectrum on how to reduce the speed of small vortices.
- 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, then apply the Curl noise method over the Perlin + fBm field.
For the fBm, I ended up using 3 octaves, a lacunarity of 2, and gain of 0.5.
To calculate the partial derivatives for Curl noise, I used finite differences in $\mathbb{R}^2$ using the approximations listed on Wikipedia.
$$ f_x(x, y) \approx \frac{f(x + h, y) - f(x - h, y)}{2h}, $$
$$ f_y(x, y) \approx \frac{f(x, y + k) - f(x, y - k)}{2k}, $$
where the constants $h$ and $k$ were $10^{-4}$ times the domain as suggested.
To evolve the field over time, I took the delta between the current and start time of animation and put it through a sine function multiplied by a random constant. This number was then put into a 3D Perlin noise function as the third (z) component, and the x/y components were the usual screen coordiates transformed using a function that linearly generates a number within some specified range (in the Curl Noise demo from 0 to 8).
Process Visualization§
To visualize the curl field, I sampled a grid of points and computed the angle of flow at the point. Using HSL, I translated the angle into the line's color.
Demonstrations§
In the end, I used web workers in order to offload the computation for noise generation from the main thread, and to parallelize it. But, a much better option would be using WebGL shaders to take advantage of parallelization on the graphics processor.
The demonstrations below are rendered in real time, using particles originating in random positions on the xy-plane with velocities determined by the corresponding noise field.