Chemical Garden
Two chemicals, one shader, and a seed in the URL, so every link grows a different organism.
What it is
A Gray-Scott reaction between two chemicals, running entirely on the GPU. Drag to add more of the second chemical and watch the growth respond.
The seed sits in the URL, so the link you share grows the same thing for whoever opens it. Press N for a new one.
How it works
Two concentrations live in the red and green channels of a texture. Each step is one fragment
shader pass: sample the 3×3 neighbourhood for a Laplacian, then apply
A' = A + (Dₐ∇²A − AB² + f(1−A)) and B' = B + (D_b∇²B + AB² − (k+f)B). Fourteen passes per
frame, so growth happens at a watchable speed.
Two mechanical details are unavoidable. A shader can’t read and write the same texture, so there are two render targets and each step reads one and writes the other, swapping every pass. That’s ping-ponging.
And the textures have to be half-float. At eight bits per channel the concentrations quantise and the whole pattern collapses into flat grey, which looks exactly like a broken shader rather than a precision problem.
The simulation grid is a fixed 512×512 whatever the window size, because grid resolution changes the scale of the pattern, and a shared link that grows something different on a phone than on a laptop isn’t really shared.
What surprised me
Most of the parameter space is dead. I’d assumed a seed could hash straight into a feed rate and a kill rate.
Almost every pair produces nothing at all. The second chemical either dies out completely or floods everything, and you get a blank field either way. The interesting patterns live in a thin, oddly shaped band, and “near a good value” is often already outside it.
So a seed doesn’t choose numbers, it chooses a regime. One of six hand-picked live points, with jitter small enough to stay inside it. That makes every link grow something, which is the same guarantee as the last two days: day 3 built its puzzle so a solution had to exist, day 4 tested shots until one did, and this one just refuses to visit the dead regions.
The other thing worth writing down: three.js already declares uv in every shader it compiles,
so a varying vec2 uv fails with “redefinition”. Cost me a confusing minute for a one-word
fix.
What I would do next
Two chemicals is the classic. Three would give genuinely competing structures.