Hash & pseudo-random
Turn a coordinate into a value that looks random but never changes, for randomness per pixel or per cell.
How it works
GLSL has no Hash A function that scrambles its input into a value that looks random but is always the same for the same input. Shaders use it for per-pixel and per-cell randomness.random(). A shader runs on thousands of pixels at once with no shared state to advance, and it has to
draw the same image every frame, so randomness in a shader is a function: give it a coordinate and it returns a value
that looks random, the same value every time for the same input. That function is a hashA function that scrambles its input into a value that looks random but is always the same for the same input. Shaders use it for per-pixel and per-cell randomness.h = fract(sin(dot(p, k)) × big).
A good hash has three properties. Inputs that are close (pixel 10 and pixel 11) give outputs that look unrelated. The outputs spread evenly between 0 and 1, with no value more common than another. And it is cheap, because it often runs several times per pixel.
The sine hash
The shortest hash takes the sine of a number, multiplies it by a large constant and keeps the fractional part with
fract:
float hash21(vec2 p) {
return fract(sin(dot(p, vec2(41.37, 289.13))) * 24634.6345);
}
The dot folds the two coordinates into one number with different weights, so (1, 2) and (2, 1) don’t collide.
sin maps that to -1..1, the large multiplier spreads tiny differences in the sine across many whole numbers, and
fract throws the whole part away. What is left changes wildly from one input to the next. The constants are
arbitrary; any large, unrelated numbers work, and changing them gives a different random field. The name hash21
says what goes in and out: two values in, one out.
Output it per pixel and you get static, like an untuned TV:
float n = hash21(gl_FragCoord.xy);
fragColor = vec4(vec3(n), 1.0);
This hash is popular because it is one line, but it rests on how precisely the GPU computes sin of a large argument.
That precision isn’t specified, so the same shader can look fine on one GPU and show stripes or repeating blocks on
another, especially with large inputs such as world positions or a time that keeps growing. Keep its inputs small (under
a few hundred) and treat it as a quick tool for prototypes and visuals. The static above already breaks that rule:
gl_FragCoord runs into the thousands on a full-HD screen, so it can stripe on some GPUs. For per-pixel values you can
trust, hash the pixel with the integer hash below, hashCell(ivec2(gl_FragCoord.xy)).
Integer hashes
GLSL ES 3.00 has unsigned integers and bit operations, which allow hashes that give the same bits on every GPU. Integer multiplication wraps around at 2³², and shifts plus XOR mix the high bits back into the low ones. This mixing function is the finalizer of MurmurHash3, which its author placed in the public domain:
uint mix32(uint x) {
x ^= x >> 16;
x *= 0x85ebca6bu;
x ^= x >> 13;
x *= 0xc2b2ae35u;
x ^= x >> 16;
return x;
}
// One random value in 0..1 per integer cell.
float hashCell(ivec2 cell) {
uint h = mix32(uint(cell.x) ^ mix32(uint(cell.y)));
return float(h >> 8) / 16777216.0; // the top 24 bits fit a float exactly
}
Hashing y before combining it with x keeps the cells (1, 2) and (2, 1) apart. Converting a negative int to
uint keeps its bits, so negative cells work too. The study in the sources compares many GPU hashes for speed and
quality: integer hashes like this one cost about as much as the sine trick and are far more even.
To hash a float position directly, floatBitsToUint(p) gives its raw bits. That is exact, but two values that differ
only in rounding get unrelated results, so usually you hash a cell index instead.
Randomness per cell
Hashing every pixel gives static. Hashing the cell a pixel sits in gives one random value per cell, the basis of
random tiles, noise and scattered points. With floor from tiling:
vec2 grid = uv * 8.0;
ivec2 cell = ivec2(floor(grid));
vec3 color = vec3(hashCell(cell),
hashCell(cell + ivec2(17, 0)),
hashCell(cell + ivec2(0, 31)));
Each cell gets its own color, constant across the cell. Offsetting the input for each channel gives three different values from one hash function. The same random number can pick a rotation for a tile, decide whether a cell is on, or nudge a point inside the cell, which is where cellular noise starts.
Animated grain
A hash of the pixel and a frame counter gives grain that changes every frame. Count whole frames, so each frame is a fresh, uncorrelated pattern, and use the integer hash, because the counter keeps growing:
uint frame = uint(time * 24.0); // 24 new patterns per second
uvec2 q = uvec2(gl_FragCoord.xy);
uint h = mix32(q.x ^ mix32(q.y ^ mix32(frame)));
float grain = float(h >> 8) / 16777216.0;
Hashes are the bottom layer of everything procedural. On their own they are rough: neighboring outputs have nothing to do with each other. Smoothing them across space is the next step, value noise.