Perlin noise = structured randomness. No hard jumps, just smooth, flowing variation.

Instead of random values at points, it assigns random gradients to a grid. Noise emerges from interpolation.

Fade function (smoothstep-like) ensures transitions aren’t janky.

More octaves = richer detail. Stack ‘em at different frequencies/amplitudes for better texture.

Used in: procedural textures, terrain gen, cloud sims, motion effects.

Key Takeaway: Perlin noise isn’t chaos; it’s controlled, organic variation.

Algorithm

  • Generate a grid of pseudo-random gradient vectors.
  • Compute dot products between gradients and distance vectors.
  • Smooth with a fade function.
  • Interpolate to get final noise value.

Math Formula

Noise at (x,y) - built from stacked octaves.

i = octave number (controls detail level).

Each octave doubles frequency (2^i) and halves amplitude (1/(2^i)).

Base noise function provides raw values.

const randomGradient = () => {
  const angle = Math.random() * Math.PI * 2
  return [Math.cos(angle), Math.sin(angle)]
}
 
const dotGridGradient = (gradients, ix, iy, x, y) => {
  if (!gradients[`${ix},${iy}`]) {
    gradients[`${ix},${iy}`] = randomGradient()
  }
  const [gx, gy] = gradients[`${ix},${iy}`]
  return (x - ix) * gx + (y - iy) * gy
}
 
const fade = (t) => t * t * t * (t * (t * 6 - 15) + 10)
const lerp = (a, b, t) => a + t * (b - a)
 
const perlinNoise = (x, y, gradients = {}) => {
  const x0 = Math.floor(x),
    x1 = x0 + 1
  const y0 = Math.floor(y),
    y1 = y0 + 1
 
  const sx = fade(x - x0)
  const sy = fade(y - y0)
 
  const n0 = dotGridGradient(gradients, x0, y0, x, y)
  const n1 = dotGridGradient(gradients, x1, y0, x, y)
  const ix0 = lerp(n0, n1, sx)
 
  const n2 = dotGridGradient(gradients, x0, y1, x, y)
  const n3 = dotGridGradient(gradients, x1, y1, x, y)
  const ix1 = lerp(n2, n3, sx)
 
  return lerp(ix0, ix1, sy)
}
 
console.log(perlinNoise(1.3, 2.1))
// Output perlin at (1.3, 2.1)