Shader concept
Hash functions in shaders: random numbers without random()
Shaders have no random() function, so they use a hash: a small formula that turns a position into a number that looks random but is always the same for the same input. It is the building block of every noise.
Running live in your browser. The code below is exactly what draws it.
Why a shader cannot just pick a random number
A fragment shader runs once for every pixel, all at the same time, with no memory between pixels. There is nowhere to keep a "current random state", and two pixels would pick the same value anyway. So instead you compute a value from the pixel's own position: the same position always gives the same result, and nearby positions give unrelated results. That is a hash.
How the one above works
It multiplies the input by a constant, takes the fractional part (frac), mixes the three numbers with a dot product, and takes the fractional part again. Each step scrambles the digits, so a tiny change in the input changes the output completely. There is no sin in it on purpose: the old frac(sin(dot(p, ...)) * 43758.5453) trick gives different results on different graphics cards, because sin of large numbers is not exact. This version is stable.
Using it
- Static grain or noise: hash the pixel's cell, as in the picture.
- Random per object: hash a cell index to pick a size, colour or phase for that tile.
- Smooth noise: hash the four corners around a point and blend them: that is value noise.
Common mistakes
- Hashing the raw
uvinstead offloor(uv * n): you get a different value at every pixel, which is just fine grain. - Expecting a different result each frame. A hash of the position never changes. To animate, add
timeinto the input.
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);
}
float4 frag(float2 uv)
{
// a 48 x 36 grid of cells, each one a random grey level
float2 cell = floor(uv * float2(48.0, 36.0));
float v = hash21(cell);
return float4(v, v, v, 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);
}
vec4 frag(vec2 uv)
{
// a 48 x 36 grid of cells, each one a random grey level
vec2 cell = floor(uv * vec2(48.0, 36.0));
float v = hash21(cell);
return vec4(v, v, v, 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 frag(uv: vec2f) -> vec4f
{
// a 48 x 36 grid of cells, each one a random grey level
let cell = floor(uv * vec2f(48.0, 36.0));
let v = hash21(cell);
return vec4f(v, v, v, 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);
}
vec4 frag(vec2 uv)
{
// a 48 x 36 grid of cells, each one a random grey level
vec2 cell = floor(uv * vec2(48.0, 36.0));
float v = hash21(cell);
return vec4(v, v, v, 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);
}
float4 frag(float2 uv)
{
// a 48 x 36 grid of cells, each one a random grey level
float2 cell = floor(uv * float2(48.0, 36.0));
float v = hash21(cell);
return float4(v, v, v, 1.0);
} Practise it in the lessons
- Randomness without random()Make TV-static blocks: a 20×15 grid where each cell is a grey level from hash(cell).
- Repeating: floor & fracDraw an 8×6 checkerboard of black and white.
- Smooth randomness, step 1: blend two valuesPlot the curve n as a thin cyan line on the dark background (8 cells across, hash from the Toolbox).
Questions
Is there a random function in GLSL or HLSL?
No. Shaders have no built-in random function. You pass in a random number from the CPU, read it from a noise texture, or compute one with a hash function.
Why does the sin-based random function look different on different devices?
It depends on sin() being accurate for very large inputs, and graphics cards differ in precision there. Hash functions built from frac and dot products give the same result everywhere.