Skip to main content
GameDev.net gamedev.net

Voronoi Cells

by GameDev.net · GLSL ES 3.00 (WebGL2) · 25 Aug 2026

Use in your engine

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.

Source Revision 1

Author notes are linked to specific lines.

Shader inputs

void mainImage(out vec4 fragColor, in vec2 fragCoord)

Called once per pixel. Write the colour to fragColor.

iResolution vec3
Viewport size in pixels (z is the pixel aspect ratio).
iTime float
Seconds since the shader started.
iTimeDelta float
Seconds since the previous frame.
iFrameRate float
Frames per second, smoothed.
iFrame int
Frames rendered since the start.
iMouse vec4
Mouse position: xy while held, zw of the last click.
iDate vec4
Year, month, day, and seconds within the day.
iChannel0 sampler2D
Texture bound to channel 0.
iChannel1 sampler2D
Texture bound to channel 1.
iChannel2 sampler2D
Texture bound to channel 2.
iChannel3 sampler2D
Texture bound to channel 3.
iChannelResolution vec3[4]
Pixel size of each bound channel texture.
iChannelTime float[4]
Playback time of each channel, in seconds.
iSampleRate float
Audio sample rate, always 44100.
Main Image7
1 uniform float uCellScale; // @param 1.0..20.0 = 7.0 "Cell scale"
2 uniform float uEdgeWidth; // @param 0.0..0.25 = 0.05 "Edge width"
3 uniform bool uAnimate; // @param = 1 "Drift the points"
4
5 vec2 hash22(vec2 cell) {
6 float a = fract(sin(dot(cell, vec2(127.1, 311.7))) * 43758.5453);
7 float b = fract(sin(dot(cell, vec2(269.5, 183.3))) * 43758.5453);
8 return vec2(a, b);
9 }
10
11 void mainImage(out vec4 fragColor, in vec2 fragCoord) {
12 vec2 uv = fragCoord / iResolution.xy;
13 uv.x *= iResolution.x / iResolution.y;
14 vec2 p = uv * uCellScale;
15 vec2 baseCell = floor(p);
16 vec2 local = fract(p);
17 float nearest = 8.0;
18 float runnerUp = 8.0;
19 vec2 nearestCell = baseCell;
20 for (int y = -1; y <= 1; y++) {
21 for (int x = -1; x <= 1; x++) {
22 vec2 offset = vec2(float(x), float(y));
23 vec2 point = hash22(baseCell + offset);
24 if (uAnimate) {
25 point = 0.5 + 0.45 * sin(iTime * 0.6 + 6.2831853 * point);
26 }
27 float dist = length(offset + point - local);
28 if (dist < nearest) {
29 runnerUp = nearest;
30 nearest = dist;
31 nearestCell = baseCell + offset;
32 } else if (dist < runnerUp) {
33 runnerUp = dist;
34 }
35 }
36 }
37 float border = smoothstep(0.0, uEdgeWidth + 0.004, runnerUp - nearest);
38 float tone = fract(sin(dot(nearestCell, vec2(12.9898, 78.233))) * 43758.5453);
39 vec3 fill = mix(vec3(0.06, 0.12, 0.22), vec3(0.34, 0.64, 0.80), tone);
40 vec3 color = mix(vec3(0.96, 0.97, 0.99), fill, border);
41 fragColor = vec4(color, 1.0);
42 }
43

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.

LicenseMIT
Views0
Forks0

Discussion

Loading comments...