Shader concept

Value noise: smooth randomness for shaders

Value noise is smooth randomness: random values are placed on a grid and the space between them is blended, so you get soft blobs instead of grain. It is the simplest kind of procedural noise.

Running live in your browser. The code below is exactly what draws it.

How it is built

Take the position and split it into a whole part (which grid cell you are in) and a fractional part (where you are inside the cell). Use a hash to get a random value for each of the four corners of that cell, then blend them: first along x, then along y. If you blend with the raw fraction, the seams between cells show as visible creases, so the fraction is first eased with the same S-curve smoothstep uses (f * f * (3 - 2 * f)).

What it looks like and what it is good for

Value noise is blobby with a slight grid feel. It is a fast way to get clouds, dirt, water movement and wobble. Stack several scales of it and you get fractal noise, which looks natural. If the grid pattern bothers you, gradient noise (Perlin or simplex) looks smoother, at a small extra cost.

Common mistakes

  • Skipping the ease on the fraction: the picture then shows diamond-shaped creases along the grid lines.
  • Using too high a frequency, which collapses back into grain. Scale the position up slowly (4 to 10 for a screen-sized pattern).

The code, in every shader language

The same example for each language family. Open one to see it.

HLSL Unity, Unreal
float hash21(float2 p)
{
    float3 p3 = frac(float3(p.x, p.y, p.x) * 0.1031);
    p3 += dot(p3, p3.yzx + 33.33);
    return frac((p3.x + p3.y) * p3.z);
}
float noise2(float2 p)
{
    float2 i = floor(p);
    float2 f = frac(p);
    f = f * f * (3.0 - 2.0 * f);
    float a = hash21(i);
    float b = hash21(i + float2(1.0, 0.0));
    float c = hash21(i + float2(0.0, 1.0));
    float d = hash21(i + float2(1.0, 1.0));
    return lerp(lerp(a, b, f.x), lerp(c, d, f.x), f.y);
}

float4 frag(float2 uv)
{
    float n = noise2(uv * float2(aspect, 1.0) * 6.0 + time * 0.3);
    return float4(n, n, n, 1.0);
}
GLSL Shadertoy, three.js, OpenGL
float hash21(vec2 p)
{
    vec3 p3 = fract(vec3(p.x, p.y, p.x) * 0.1031);
    p3 += dot(p3, p3.yzx + 33.33);
    return fract((p3.x + p3.y) * p3.z);
}
float noise2(vec2 p)
{
    vec2 i = floor(p);
    vec2 f = fract(p);
    f = f * f * (3.0 - 2.0 * f);
    float a = hash21(i);
    float b = hash21(i + vec2(1.0, 0.0));
    float c = hash21(i + vec2(0.0, 1.0));
    float d = hash21(i + vec2(1.0, 1.0));
    return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}

vec4 frag(vec2 uv)
{
    float n = noise2(uv * vec2(aspect, 1.0) * 6.0 + time * 0.3);
    return vec4(n, n, n, 1.0);
}
WGSL WebGPU, Bevy
fn hash21(p: vec2f) -> f32
{
    var p3 = fract(vec3f(p.x, p.y, p.x) * 0.1031);
    p3 += dot(p3, p3.yzx + 33.33);
    return fract((p3.x + p3.y) * p3.z);
}
fn noise2(p: vec2f) -> f32
{
    let i = floor(p);
    var f = fract(p);
    f = f * f * (3.0 - 2.0 * f);
    let a = hash21(i);
    let b = hash21(i + vec2f(1.0, 0.0));
    let c = hash21(i + vec2f(0.0, 1.0));
    let d = hash21(i + vec2f(1.0, 1.0));
    return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}

fn frag(uv: vec2f) -> vec4f
{
    let n = noise2(uv * vec2f(aspect, 1.0) * 6.0 + time * 0.3);
    return vec4f(n, n, n, 1.0);
}
Godot Godot 4 shading language
shader_type canvas_item;

float hash21(vec2 p)
{
    vec3 p3 = fract(vec3(p.x, p.y, p.x) * 0.1031);
    p3 += dot(p3, p3.yzx + 33.33);
    return fract((p3.x + p3.y) * p3.z);
}
float noise2(vec2 p)
{
    vec2 i = floor(p);
    vec2 f = fract(p);
    f = f * f * (3.0 - 2.0 * f);
    float a = hash21(i);
    float b = hash21(i + vec2(1.0, 0.0));
    float c = hash21(i + vec2(0.0, 1.0));
    float d = hash21(i + vec2(1.0, 1.0));
    return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}

vec4 frag(vec2 uv)
{
    float n = noise2(uv * vec2(aspect, 1.0) * 6.0 + TIME * 0.3);
    return vec4(n, n, n, 1.0);
}

void fragment()
{
    // Godot's UV starts at the top-left, the lessons use bottom-left
    COLOR = frag(vec2(UV.x, 1.0 - UV.y));
}
Metal iOS, macOS
float hash21(float2 p)
{
    float3 p3 = fract(float3(p.x, p.y, p.x) * 0.1031);
    p3 += dot(p3, p3.yzx + 33.33);
    return fract((p3.x + p3.y) * p3.z);
}
float noise2(float2 p)
{
    float2 i = floor(p);
    float2 f = fract(p);
    f = f * f * (3.0 - 2.0 * f);
    float a = hash21(i);
    float b = hash21(i + float2(1.0, 0.0));
    float c = hash21(i + float2(0.0, 1.0));
    float d = hash21(i + float2(1.0, 1.0));
    return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}

float4 frag(float2 uv)
{
    float n = noise2(uv * float2(aspect, 1.0) * 6.0 + time * 0.3);
    return float4(n, n, n, 1.0);
}

Practise it in the lessons

Start with "Smooth randomness, step 1: blend two values"

Questions

What is the difference between value noise and Perlin noise?

Value noise blends random values stored at grid points. Perlin (gradient) noise blends random directions stored at the grid points, which hides the grid better but costs a little more.

How do I animate noise?

Add time to the position, or use a 3D noise function and move through the third axis. Moving the position slowly looks like drifting; moving through the third axis looks like boiling.

Related concepts