Skip to content
Fundamental · NoiseBeginner

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 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 hash.

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.

Further reading