Noise

This shader compares multiple noise generation strategies, including Gradient noise and Simplex noise. It can be used to create a variety of textures and patterns, from natural landscapes to abstract designs.

Noise shaders are the secret ingredient for natural-looking textures and motion - clouds, fire, wood grain — made from pure math.

Random Noise

Random noise is the simplest type: sample a value at each point in space to get a speckled field often used as a base for more advanced noise. Despite the name, the values are rarely truly random - they come from deterministic math that only appears random, which is more than enough for generating patterns.

There are many ways to generate pseudo-random noise. Some rely on trigonometric functions (sine/cosine), but I prefer a hash-based method that returns pseudo-random integers and is much cheaper to compute. The trade-off is that it only accepts discrete inputs, so you need to pass integer grid coordinates (or quantize your inputs) to get correct results.

// Hugo Elias style hash function
int iHash(int n) {
  n = (n << 13) ^ n;
  n = (n * (n * n * 15731 + 789221) + 1376312589);
  return n;
}

you can easily convert this to float numbers in range [-1, 1] by:

float hash(int n) {
  n = iHash(n);
  return -1.0 + 2.0 * float(n & 0x0fffffff) / float(0x0fffffff);
}

For 2D coordinates, you can use a similar approach by combining the x and y coordinates into a single integer:

float hash2D(int2 p) {
  return hash(p.x * 37 + p.y * 57); // just put some random prime number
}

Gradient Noise

Gradient noise assigns a pseudo-random gradient vector to each point on a lattice. To evaluate the noise at some position, you take the vector from each nearby lattice point to that position, dot it with the lattice point’s gradient, and then smoothly blend those contributions using a “fade” curve (typically a quintic polynomial). This produces a field that’s continuous with smooth first derivatives, which avoids the blocky look of value noise and yields a cleaner frequency spectrum.

First we need hashing function that takes 2D coordinates and returns a gradient vector:

float2 hash2(int2 v) {
  int n = iHash(v) >> 16;

  // Gradient vectors made Perlin style
  n &= 7;
  float2 result = float2(n & 1, n >> 1) * 2.0 - 1.0;
  return (n >= 6) ? float2(0.0, result.x) : (n >= 4) ? float2(result.x, 0.0) : result;
}

Hash function above returns only vectors containing directions so x and y values can only be -1.0, 0.0 or 1.0.

Now we can us this function in our gradient noise calculations:

float2 cubic(float2 t) { return t * t * (3. - 2. * t); }
float2 quintic(float2 t) { return t * t * t * (t * (t * 6. - 15.) + 10.); }

float gnoise(float2 uv) {
  int2 i = int2(floor(uv));
  float2 f = frac(uv);

  float2 u = quintic(f);
  float a = dot(hash2(i + int2(0, 0)), f - float2(0, 0));
  float b = dot(hash2(i + int2(1, 0)), f - float2(1, 0));
  float c = dot(hash2(i + int2(0, 1)), f - float2(0, 1));
  float d = dot(hash2(i + int2(1, 1)), f - float2(1, 1));

  float ab = lerp(a, b, u.x);
  float cd = lerp(c, d, u.x);
  return lerp(ab, cd, u.y);
}

Fractal Brownian Motion (fBm)

Fractal Brownian Motion (fBm) is what you get when you stack several layers of the same noise at different scales and strengths. The low-frequency layers give you big, overall shapes; higher-frequency layers add finer and finer detail. The result feels “fractal” and natural - think mountain ranges with both broad silhouettes and tiny ridges, or clouds with soft billows and wispy edges.

float gnoiseFbm(float2 uv, float scalarScale = 2.0, float ampScale = 0.5) {
  const int octaves = 4;
  float v = 0.0;
  float amp = 0.5;

  for (int i = 0; i < octaves; i++) {
    v += amp * gnoise(uv);
    uv *= scalarScale;
    amp *= ampScale;
  }
  return v;
}

Simplex noise

Simplex noise is a smoother, cleaner-looking cousin of Perlin noise (variant of gradient noise). It produces natural, organic patterns without the grid-aligned streaks you sometimes see in classic gradient noise, so textures feel more even and isotropic - great for clouds, terrain, fire, and watery surfaces.

In practice it shines in higher dimensions: 3D and 4D noise (e.g., when you add time) stays fluid and less “swimmy”, making it ideal for animated effects. It’s fast, consistent, and a solid base for layering techniques like fBm, turbulence, and domain warping to add rich detail and complexity.

simplex(float2 uv) {
  const float skewFactor = 0.366025403784;        //(sqrt(N+1.0)-1.0)/N
  const float reverseSkewFactor = 0.211324865405; // (sqrt(N+1.0)-1.0)/N

  float2 skew = floor(uv + (uv.x + uv.y) * skewFactor);

  int2 cell = int2(floor(skew));

  float2 p0 = uv - skew + (skew.x + skew.y) * reverseSkewFactor;
  float m = step(p0.y, p0.x);
  float2 o = float2(m, 1. - m);
  float2 p1 = p0 - o + reverseSkewFactor;
  float2 p2 = p0 - 1. + 2. * reverseSkewFactor;

  float d0 = dot(p0, p0);
  float d1 = dot(p1, p1);
  float d2 = dot(p2, p2);

  float3 w = max(0.5 - float3(d0, d1, d2), 0.0);
  w *= w * w * w;

  float3 g;
  g.x = dot(p0, hash2(cell));
  g.y = dot(p1, hash2(cell + int2(o)));
  g.z = dot(p2, hash2(cell + 1));

  return dot(w * g, float3(50));
}