Shader Lab
29.Reaction-diffusion

Reaction-diffusion (Gray-Scott)

Arc 6 · Simulations · Lesson 29 of 35
coordinate signal color

The signal layer now holds state — two chemical concentrations written each frame into a ping-pong texture and read back the next frame. This is the first arc where a pixel sees its own past.

0.50
18

Purpose

Stripes, spots, coral, fingerprints, mitosis — they are all the same equation. Two diffusing chemicals on a grid, reacting by a tiny local rule. The parameter that decides which one you get is a two-number address in a phase diagram.

Key insight

Alan Turing proposed in 1952 that biology's patterns — a leopard's spots, a zebra's stripes, the whorls on a fingerprint — come from two diffusing chemicals with a feedback reaction. Gray and Scott made it concrete in 1984. Each cell tracks two numbers, U and V. Every step: U diffuses to its neighbors, V diffuses at about half the rate, the reaction U + 2V → 3V converts U into V, a feed rate F drip-drips new U in, and a kill rate k removes V.

Run it for thousands of steps and patterns emerge. The astonishing part: a tiny change to F and k takes you from solid dots to stripes to labyrinths to mitosis (self-replicating blobs). The patterns are not painted — they are computed from local rules. The sim state lives in a texture (U in red, V in green), we render into a second texture each frame, then swap. Ping-pong.

The pattern-mood slider below sweeps a curated path through the F-k phase diagram. Drag it slowly. The universe of emergent organic patterns is not a gallery of algorithms — it is one algorithm at different coordinates in a two-number space.

Break it

1. Let it settle, then nudge pattern-mood slightly. The pattern doesn't redraw — it reorganizes. Dots become striped, stripes branch, branches fuse. Teaches: the image on screen is an equilibrium the rule reaches over time. Change the rule a little and equilibrium shifts a little.

2. Push pattern-mood all the way right (mitosis), then hit reset. A single blob in the center splits. The children split. Within a minute an entire colony has unfolded from one seed. Teaches: the exact phenomenon Turing was pointing at — self-replicating structure from local chemistry, nothing painted, everything grown.

3. Click anywhere on the canvas to drop a fresh seed of V. Watch the pattern rebuild from your click. Teaches: you are not painting the pattern — you are perturbing the field. The simulation does the rest.

Direct Claude

"more dots / cellular" pattern-mood left "more stripes / flowing" pattern-mood mid-left "more labyrinth / maze-like" pattern-mood middle "more coral / branching" pattern-mood mid-right "mitosis / self-replicating blobs" pattern-mood far right "watch it form slowly" iterations-per-frame down "settle faster" iterations-per-frame up
Meta-phrase you gain here: "a Gray-Scott pattern" or "a Turing pattern." This is now a specific brief — distinct from "organic noise texture" (Arc 3 FBM). The difference: emergence from simulation on ping-pong textures versus computed-every-frame-from-scratch.
Combines with: L28 (advect this pattern on top of a Stam velocity field — Karl Sims' classic), Arc 5 displacement (use V as a height map), L31 (coral patterns make great fire-bed shapes).
the simulation + display fragment shaders
// SIM PASS — reads previous-frame state from u_prev, writes new UV
uniform sampler2D u_prev;
uniform vec2 u_texel;   // 1.0 / resolution of the sim texture
uniform float u_F;      // feed rate
uniform float u_k;      // kill rate

void main() {
  vec2 uv = gl_FragCoord.xy * u_texel;
  vec2 c = texture2D(u_prev, uv).rg;

  // 9-point Laplacian — mixes neighbors into self
  vec2 lap = -c;
  lap += texture2D(u_prev, uv + vec2( u_texel.x, 0.0)).rg * 0.2;
  lap += texture2D(u_prev, uv + vec2(-u_texel.x, 0.0)).rg * 0.2;
  lap += texture2D(u_prev, uv + vec2(0.0,  u_texel.y)).rg * 0.2;
  lap += texture2D(u_prev, uv + vec2(0.0, -u_texel.y)).rg * 0.2;
  lap += texture2D(u_prev, uv + u_texel * vec2( 1.0,  1.0)).rg * 0.05;
  lap += texture2D(u_prev, uv + u_texel * vec2(-1.0,  1.0)).rg * 0.05;
  lap += texture2D(u_prev, uv + u_texel * vec2( 1.0, -1.0)).rg * 0.05;
  lap += texture2D(u_prev, uv + u_texel * vec2(-1.0, -1.0)).rg * 0.05;

  float U = c.r, V = c.g;
  float rxn = U * V * V;
  float dU = 1.0   * lap.r - rxn + u_F       * (1.0 - U);
  float dV = 0.5   * lap.g + rxn - (u_F + u_k) *  V;

  gl_FragColor = vec4(clamp(U + dU, 0.0, 1.0),
                      clamp(V + dV, 0.0, 1.0), 0.0, 1.0);
}