Truchet tiles
Purpose
A single simple tile, placed in a grid and rotated randomly in each cell, produces a richly connected global pattern that looks nothing like its parts. Randomness at the cell level, structure across the whole.
Key insight
Earlier noise lessons generated complexity from continuous randomness — smooth, flowing noise fields. Truchet is the opposite: randomness in discrete choices, one per tile. Take one tile with a simple motif (commonly two quarter-arcs connecting opposite corners). Lay it in a grid. For each cell, hash the cell's integer coordinates into one of two rotations. Draw the motif rotated by that choice.
The arcs line up at tile edges regardless of which rotation was chosen, so every junction connects — producing a single unbroken network of arcs winding through the whole grid. Almost all the visual interest comes not from any one tile but from the accidental connections between them. Turn the randomness off and the pattern collapses into something trivial; turn it on and the same tile suddenly reads as a maze, a circuit, an Islamic lattice.
Break it
1. Press re-seed a dozen times in rapid succession. Each press snaps to a completely different maze while the tile, grid, and cell-size never change. Teaches: Truchet is a generator, not a texture. What you are authoring is the tile and the rule, not any specific image.
2. Watch what happens when the re-seed lands on a "boring" looking pattern, then press again. You can't un-press it — there's no undo. Teaches: every seed is one sample from the distribution. The pattern space is huge; the viewer is browsing it.
Direct Claude
uniform float u_cellSize; // coordinate: size of one tile in canvas units
uniform float u_seed; // coordinate: which sample from the distribution
uniform float u_stroke; // signal: arc line thickness
float hash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7)) + u_seed) * 43758.5453);
}
void main() {
// Aspect-correct UV in canvas units
vec2 uv = gl_FragCoord.xy / u_resolution.y;
// COORDINATE LAYER — tile the plane; i is the cell ID, f is the local [0,1] coord
vec2 scaled = uv / u_cellSize;
vec2 i = floor(scaled);
vec2 f = fract(scaled);
// Per-cell hash → binary choice of orientation.
// Flip the tile's X inside the cell for half the cells.
if (hash(i) > 0.5) f.x = 1.0 - f.x;
// SIGNAL LAYER — distance to two quarter-arcs meeting the corners (0,0) and (1,1)
float arc1 = abs(length(f - vec2(0.0, 0.0)) - 0.5);
float arc2 = abs(length(f - vec2(1.0, 1.0)) - 0.5);
float d = min(arc1, arc2);
// Ink width — controlled by stroke slider
float w = u_stroke;
float ink = 1.0 - smoothstep(w, w + 0.02, d);
// COLOR LAYER — ink on paper
vec3 paper = vec3(0.95, 0.92, 0.86);
vec3 inkC = vec3(0.14, 0.12, 0.18);
vec3 col = mix(paper, inkC, ink);
gl_FragColor = vec4(col, 1.0);
}