Randomness from a Hash
by GameDev.net · GLSL ES 3.00 (WebGL2) · 25 Aug 2026
Run the shader to adjust these controls.
What it demonstrates
Procedural shaders need randomness, but a fragment shader has no random number generator and no memory of earlier frames. This shader builds repeatable randomness from coordinates alone: every grid cell asks a hash function for a number, and that number sets the cell brightness and the size of the dot stamped inside it.
Shader inputs
void mainImage(out vec4 fragColor, in vec2 fragCoord)
Called once per pixel. Write the colour to fragColor.
-
iResolutionvec3 - Viewport size in pixels (z is the pixel aspect ratio).
-
iTimefloat - Seconds since the shader started.
-
iTimeDeltafloat - Seconds since the previous frame.
-
iFrameRatefloat - Frames per second, smoothed.
-
iFrameint - Frames rendered since the start.
-
iMousevec4 - Mouse position: xy while held, zw of the last click.
-
iDatevec4 - Year, month, day, and seconds within the day.
-
iChannel0sampler2D - Texture bound to channel 0.
-
iChannel1sampler2D - Texture bound to channel 1.
-
iChannel2sampler2D - Texture bound to channel 2.
-
iChannel3sampler2D - Texture bound to channel 3.
-
iChannelResolutionvec3[4] - Pixel size of each bound channel texture.
-
iChannelTimefloat[4] - Playback time of each channel, in seconds.
-
iSampleRatefloat - Audio sample rate, always 44100.
Main Image6
uniform float uSeed; // @param 0.0..10.0 = 2.5 "Seed"
uniform int uGridScale; // @param 1..40 = 14 "Grid scale"
float hash21(vec2 cell) {
return fract(sin(dot(cell, vec2(12.9898, 78.233)) + uSeed) * 43758.5453);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = fragCoord / iResolution.xy;
uv.x *= iResolution.x / iResolution.y;
vec2 grid = uv * float(uGridScale);
vec2 cell = floor(grid);
vec2 local = fract(grid) - 0.5;
float value = hash21(cell);
float radius = 0.10 + 0.32 * hash21(cell + vec2(37.7, 19.3));
float dotMask = 1.0 - smoothstep(radius, radius + 0.03, length(local));
vec3 base = mix(vec3(0.03, 0.04, 0.08), vec3(0.20, 0.26, 0.38), value);
vec3 color = mix(base, vec3(0.98, 0.82, 0.36), dotMask * value);
fragColor = vec4(color, 1.0);
}
Learn from this shader
How it works
The hash projects a cell coordinate onto a fixed direction with a dot product, feeds the result through sine, multiplies by a large constant, and keeps only the fractional part. Sine is not here to make a wave; it scrambles neighbouring inputs so that similar coordinates produce dissimilar outputs. Because the result depends only on position, it is stable across frames. Two independent values per cell come from a second call with a large offset, so brightness and radius vary without appearing linked.
Try changing
Raise Grid scale and watch the field stay coherent instead of flickering, which is the practical proof that the value is a function of position. Sweep Seed to shuffle the whole field. In a fork, offset the second call by a very small amount and watch the two values become visibly correlated, then compare this sine based hash against an integer bit mixing hash on mobile hardware where float precision is lower.
Using it in a game
This is the base layer for scattering, variation, and jitter: per tile texture choice, per instance colour, dithering, sampling offsets, and every noise function built on top. Precision differs between GPUs, so the exact pattern is not guaranteed to match everywhere; never use it for gameplay logic that has to agree across clients.
Explore the techniques
Continue with curated explanations and progressively related examples.
Discussion