3D Simplex Noise Implementation
Simplex noise written from scratch, then pointed at a few thousand particles so you can watch it work.
Interactive, 2023
Why: I built this because I wanted to learn how to implement noise functions. Using a library would have taught me nothing, so I wrote the 3D simplex algorithm by hand and then needed something pretty to point it at.
A z-slice of the 3D field; z is time.
Every step is one frame of the third input.
What the noise function is doing
A noise function takes coordinates and returns a number that changes smoothly as you move. Games use it for terrain. I am using it for wind. The important property is that nearby inputs give nearby outputs while far-apart inputs look unrelated. Random numbers alone can't do that. Ken Perlin published simplex noise in 2001 as a faster, less grid-shaped follow-up to his original algorithm, and the version running on this page is one I wrote myself from his description rather than importing.
The third input is time. Every particle asks for n(x·s, y·s, t), treats the answer times 3π as a compass heading, and steps a couple of pixels that way. Because t keeps creeping upward, the whole field slides through a fourth dimension you never see. The Scale slider is the multiplier s. Push it up and the cells shrink. The Noise field view above paints the value at every sample directly, which is the function's own output; the Flow field view hands the same numbers to particles as compass headings, which is why their trails keep reorganising instead of freezing into a pattern.
I expected the hard part to be the maths. It was actually the bookkeeping: getting the six tetrahedron cases in the right order, and remembering that the permutation table needs to be doubled to 512 entries so the index arithmetic never wraps mid-lookup.
Skewing onto the simplex grid
Classic Perlin noise looks at the eight corners of the cube around a point. Simplex noise tiles space with tetrahedra instead, so a sample only ever touches four corners. The catch is that a tetrahedral grid is awkward to index, so the input gets skewed until the grid lines up with ordinary integer coordinates. Then the cell is just a floor().
s = (x + y + z) · F₃, F₃ = 1/3 i = ⌊x + s⌋, j = ⌊y + s⌋, k = ⌊z + s⌋
Which tetrahedron
Unskewing the cell origin gives the offset from the first corner. Sorting the three components of that offset (is x the biggest? is z?) picks one of the six tetrahedra a skewed cube splits into, and that fixes the remaining corners. This is the part I got wrong twice: the six cases are easy to transpose and the field still looks vaguely right when you do, just with faint seams along one diagonal.
t = (i + j + k) · G₃, G₃ = 1/6 (x₀, y₀, z₀) = (x − (i − t), y − (j − t), z − (k − t))
A gradient per corner
Every lattice corner is assigned a pseudo-random gradient. The lookup goes through a permutation table shuffled from a seed, which is what makes the same coordinates give the same answer every frame, and what the Reseed button changes. The gradients are the twelve edge midpoints of a cube: cheap dot products, and no axis is favoured.
gᵢ = grad3[ perm[i + perm[j + perm[k]]] mod 12 ]
Kernel and sum
Each corner contributes its gradient dotted with the vector to the sample point, faded by a radial falloff that hits zero before the next cell starts. The falloff is raised to the fourth power so its second derivative is continuous too; without that the trails show tiny kinks at cell boundaries. Add the four contributions and scale the total into roughly −1..1.
tᵢ = 0.6 − |dᵢ|² nᵢ = max(tᵢ, 0)⁴ · (gᵢ · dᵢ) n(x, y, z) = 32 · Σ nᵢ