Procedural 2D Patterns (GLSL) Reference

Overview

Procedural 2D patterns are computed mathematically from UV coordinates — no textures needed. They’re infinitely scalable, tileable, and parameterizable. These patterns serve as building blocks for floors, walls, fabrics, backgrounds, and abstract compositions.

Grid Patterns

Checkerboard

float checkerboard(vec2 uv, float scale) {
  vec2 grid = floor(uv * scale);
  return mod(grid.x + grid.y, 2.0);
}

// Anti-aliased version
float checkerboardAA(vec2 uv, float scale) {
  vec2 p = uv * scale;
  vec2 fw = fwidth(p);
  // Analytical filtering
  vec2 i = 2.0 * (abs(fract(p - 0.25) - 0.5) - abs(fract(p - 0.75) - 0.5));
  return 0.5 - 0.5 * i.x * i.y;
}

Grid Lines

float gridLines(vec2 uv, float scale, float thickness) {
  vec2 grid = abs(fract(uv * scale - 0.5) - 0.5);
  vec2 fw = fwidth(uv * scale);
  vec2 lines = smoothstep(thickness + fw, thickness - fw, grid);
  return max(lines.x, lines.y);
}

Dot Grid

float dotGrid(vec2 uv, float scale, float radius) {
  vec2 cell = fract(uv * scale) - 0.5;
  float d = length(cell);
  return 1.0 - smoothstep(radius - fwidth(d), radius, d);
}

Brick / Masonry

float brickPattern(vec2 uv, vec2 brickSize, float mortarWidth) {
  // Offset every other row
  vec2 p = uv / brickSize;
  float row = floor(p.y);
  p.x += mod(row, 2.0) * 0.5;  // half-brick offset

  vec2 cell = fract(p) - 0.5;
  vec2 fw = fwidth(p);

  // Mortar lines
  float mortarX = smoothstep(0.5 - mortarWidth - fw.x, 0.5 - mortarWidth, abs(cell.x));
  float mortarY = smoothstep(0.5 - mortarWidth - fw.y, 0.5 - mortarWidth, abs(cell.y));
  float mortar = max(mortarX, mortarY);

  return 1.0 - mortar;  // 1 = brick, 0 = mortar
}

// With per-brick color variation
vec3 coloredBricks(vec2 uv, vec2 brickSize) {
  vec2 p = uv / brickSize;
  float row = floor(p.y);
  p.x += mod(row, 2.0) * 0.5;

  vec2 cellId = floor(p);
  float brick = brickPattern(uv, brickSize, 0.05);

  // Per-brick color from hash
  float h = fract(sin(dot(cellId, vec2(127.1, 311.7))) * 43758.5453);
  vec3 color = mix(vec3(0.6, 0.25, 0.15), vec3(0.75, 0.35, 0.2), h);

  return mix(vec3(0.4), color, brick);  // mortar color vs brick color
}

Hexagonal Grid

// Returns: xy = cell-local coords, zw = cell ID
vec4 hexGrid(vec2 uv, float scale) {
  vec2 p = uv * scale;

  // Two candidate hex centers
  vec2 a = mod(p, vec2(1.0, sqrt(3.0))) - vec2(0.5, sqrt(3.0) * 0.5);
  vec2 b = mod(p - vec2(0.5, sqrt(3.0) * 0.5), vec2(1.0, sqrt(3.0))) - vec2(0.5, sqrt(3.0) * 0.5);

  // Choose the closer center
  vec2 g = (length(a) < length(b)) ? a : b;
  vec2 id = p - g;

  return vec4(g, floor(id));
}

// Hex distance (for drawing hex shapes)
float hexDist(vec2 p) {
  p = abs(p);
  return max(dot(p, normalize(vec2(1.0, sqrt(3.0)))), p.x);
}

// Complete hex tiling with edges
float hexTiling(vec2 uv, float scale, float edgeWidth) {
  vec4 h = hexGrid(uv, scale);
  float d = hexDist(h.xy);
  float fw = fwidth(d);
  return smoothstep(0.5 - edgeWidth - fw, 0.5 - edgeWidth, d);
}

Truchet Tiles (GLSL)

// Quarter-circle Truchet
float truchetPattern(vec2 uv, float scale) {
  vec2 p = uv * scale;
  vec2 cell = floor(p);
  vec2 f = fract(p) - 0.5;

  // Random rotation per cell (0 or 1)
  float flip = step(0.5, fract(sin(dot(cell, vec2(127.1, 311.7))) * 43758.5453));

  // Flip coordinates
  if (flip > 0.5) f = vec2(-f.y, f.x);

  // Quarter circles from two corners
  float d1 = abs(length(f - vec2(-0.5, -0.5)) - 0.5);
  float d2 = abs(length(f - vec2(0.5, 0.5)) - 0.5);
  float d = min(d1, d2);

  float fw = fwidth(d);
  return smoothstep(0.05 + fw, 0.05 - fw, d);
}

// Multi-scale Truchet with line thickness
float truchetLines(vec2 uv, float scale, float lineWidth) {
  vec2 p = uv * scale;
  vec2 cell = floor(p);
  vec2 f = fract(p) - 0.5;

  float h = fract(sin(dot(cell, vec2(127.1, 311.7))) * 43758.5453);
  if (h > 0.5) f.x = -f.x;

  float d1 = length(f - vec2(-0.5)) - 0.5;
  float d2 = length(f - vec2(0.5)) - 0.5;

  float fw = fwidth(uv.x * scale);
  float line1 = smoothstep(lineWidth + fw, lineWidth - fw, abs(d1));
  float line2 = smoothstep(lineWidth + fw, lineWidth - fw, abs(d2));

  return max(line1, line2);
}

Stripes & Waves

// Diagonal stripes
float diagonalStripes(vec2 uv, float scale, float duty) {
  float d = fract((uv.x + uv.y) * scale);
  float fw = fwidth((uv.x + uv.y) * scale);
  return smoothstep(duty - fw, duty + fw, d);
}

// Wavy stripes
float wavyStripes(vec2 uv, float scale, float waveFreq, float waveAmp) {
  float wave = sin(uv.x * waveFreq) * waveAmp;
  float d = fract((uv.y + wave) * scale);
  return smoothstep(0.45, 0.55, d);
}

// Concentric rings
float concentricRings(vec2 uv, vec2 center, float scale) {
  float d = length(uv - center) * scale;
  return smoothstep(0.45, 0.55, fract(d));
}

// Spiral
float spiral(vec2 uv, float arms, float twist) {
  float r = length(uv);
  float theta = atan(uv.y, uv.x);
  float d = fract((theta * arms / 6.28318 + r * twist));
  return smoothstep(0.45, 0.55, d);
}

Polka Dots (Hexagonal Packing)

float polkaDots(vec2 uv, float scale, float radius) {
  // Hexagonal packing (denser than square grid)
  vec2 p = uv * scale;
  vec2 a = mod(p, 2.0) - 1.0;
  vec2 b = mod(p + 1.0, 2.0) - 1.0;
  float da = length(a);
  float db = length(b);
  float d = min(da, db);

  return 1.0 - smoothstep(radius - fwidth(d), radius, d);
}

Noise-Warped Patterns

Combine procedural patterns with noise for organic variations:

vec3 warpedCheckerboard(vec2 uv, float time) {
  // Domain warp with fbm
  vec2 warp = vec2(
    fbm(uv * 3.0 + time * 0.1, 4),
    fbm(uv * 3.0 + vec2(5.0) + time * 0.1, 4)
  );
  float check = checkerboard(uv + warp * 0.1, 8.0);
  return mix(vec3(0.1), vec3(0.9), check);
}

Key References