Voronoi Cells
by GameDev.net · GLSL ES 3.00 (WebGL2) · 25 Aug 2026
Run the shader to adjust these controls.
What it demonstrates
Noise interpolates between values on a lattice. Voronoi does something different: it scatters one point per cell and asks which point is closest. That single question produces cell structures, cracked surfaces, scales, and stone, and it also produces the borders between cells once you track the runner up distance as well as the winner.
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 Image7
uniform float uCellScale; // @param 1.0..20.0 = 7.0 "Cell scale"
uniform float uEdgeWidth; // @param 0.0..0.25 = 0.05 "Edge width"
uniform bool uAnimate; // @param = 1 "Drift the points"
vec2 hash22(vec2 cell) {
float a = fract(sin(dot(cell, vec2(127.1, 311.7))) * 43758.5453);
float b = fract(sin(dot(cell, vec2(269.5, 183.3))) * 43758.5453);
return vec2(a, b);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = fragCoord / iResolution.xy;
uv.x *= iResolution.x / iResolution.y;
vec2 p = uv * uCellScale;
vec2 baseCell = floor(p);
vec2 local = fract(p);
float nearest = 8.0;
float runnerUp = 8.0;
vec2 nearestCell = baseCell;
for (int y = -1; y <= 1; y++) {
for (int x = -1; x <= 1; x++) {
vec2 offset = vec2(float(x), float(y));
vec2 point = hash22(baseCell + offset);
if (uAnimate) {
point = 0.5 + 0.45 * sin(iTime * 0.6 + 6.2831853 * point);
}
float dist = length(offset + point - local);
if (dist < nearest) {
runnerUp = nearest;
nearest = dist;
nearestCell = baseCell + offset;
} else if (dist < runnerUp) {
runnerUp = dist;
}
}
}
float border = smoothstep(0.0, uEdgeWidth + 0.004, runnerUp - nearest);
float tone = fract(sin(dot(nearestCell, vec2(12.9898, 78.233))) * 43758.5453);
vec3 fill = mix(vec3(0.06, 0.12, 0.22), vec3(0.34, 0.64, 0.80), tone);
vec3 color = mix(vec3(0.96, 0.97, 0.99), fill, border);
fragColor = vec4(color, 1.0);
}
Learn from this shader
How it works
The coordinate is split into a cell and a local position. A hash gives each surrounding cell a point placed somewhere inside it. Only the nine cells in the immediate neighbourhood are examined, because a point in a further cell cannot be closer than one in the ring next to you when every point stays within its own cell. The loop keeps both the smallest distance and the second smallest. The nearest distance identifies the cell, which is what fills each region with its own colour. The gap between the two distances is near zero exactly where two cells meet, so that difference draws the borders without any edge detection pass.
Try changing
Set Edge width to zero for flat cells with no outline, then raise it and watch borders thicken from the seams outward. Turn Drift the points on and off: the pattern reorganises continuously, and cells appear and vanish as points cross. In a fork, output the nearest distance directly for a bubble look, or clamp the drift range and observe borders snapping when a point leaves its cell.
Using it in a game
Voronoi drives cracked ice, dried mud, reptile scales, shattered glass, and cell shaded caustics. The nine cell search is nine hash evaluations per pixel, so animate it sparingly and bake static variants; sampling a third distance for more elaborate patterns raises that cost again.
Explore the techniques
Continue with curated explanations and progressively related examples.
Discussion