01 · Chapter one · Pigment

Chromatic Ink

Ink in water is not coloured light. It is a filter — and this chapter renders it as one, with the arithmetic on the page.

Why this is not a colour picker

The obvious way to draw ink in water is to treat it as light: give each pigment an RGB triple, blend the triples, and let the fluid solver carry the result to screen. It is fast, it is easy, and it is the thing that makes most ink simulations look like glowing gas. Blending RGB is additive — it assumes the pigment adds photons to what is behind it. Ink does the opposite. Ink is a filter standing between the light and your eye, and every pigment it contains is removing light from a specific part of the spectrum.

So this chapter solves pigment instead of colour. Each pigment is a pair of optical constants rather than a colour: K, how strongly it absorbs, and S, how strongly it scatters. That is not a stylistic preference. It is the Kubelka–Munk two-flux model — the standard treatment of a turbid medium — and the consequence of taking it seriously is that the two constants are not independent. They ride together. Diluting a pigment with more water lowers the amount of it, but it does not change what it is: the ratio K/S is intensive, so it survives every dilution unchanged. Add more of the same pigment and you get a thicker film, never a different colour.

What that buys, concretely

Take cyan ink and green ink and lay one over the other. Under an RGB model you interpolate toward a third, brighter, less saturated colour and the overlap glows. Under Kubelka–Munk the two absorption spectra sum. Cyan removes red, green removes red and blue, and where they overlap the film is removing more light than either film alone. The overlap is darker than either pigment on its own. That is the whole claim, and it is a claim you can check by eye in the strip below.

Why concentration darkens

A film of pigment over a substrate of reflectance Rg returns R = Rg + (Rinf − Rg)(1 − E) / (1 − Rg·E), where E = exp(−b·h) is how much light the film has not yet touched, h is its thickness, and Rinf is what the pigment reflects with no substrate behind it at all. The gradient of that expression in h has one sign, and it is set by a single inequality: Rg > Rinf. While the water out-reflects the pigment, more pigment means less light. Break that inequality — make the substrate darker than the pigment sitting in it — and the same equation hands back more light for a thicker film, and the render becomes luminous gas no matter what the caption says.

This is worth stating plainly because it is the failure this chapter actually had. An earlier build ran the water substrate at 0.0205 while the pigments sat at 0.0298 and 0.0300 — the water was darker than the ink, in two channels out of three, in the name of a truer black. The model did exactly what it was told. Brightness peaked at 109–117/255 at low density, and a crease — a thicker film — came back brighter than flat in 30 of 30 rows across all five palettes. The fix was not a shading tweak. It was to light the water properly and let the ink fall away from it: the pool now sits at 0.215, more than 150× the darkest pigment reflectance, and the inequality holds everywhere on screen.

The evidence, on the page

None of the above is worth asking you to take on faith. Below, the same forward model that shades the hero renders a concentration ramp at the same substrate — and the numbers under each swatch are that pixel's measured luminance, computed by the page, not typed in.

The ramp stops where the model stops resolving, and the page prints where that is. An earlier version of this strip swept concentration across three orders of magnitude and quoted the correlation it got: r = −0.144. That number was real and it was meaningless: past about c = 0.016 the film is opaque, so reflectance stops moving, and a correlation taken over one resolvable step followed by two hundred identical points is near zero by construction. Six of the seven swatches painted the same colour. The physics was never the problem. The instrument was pointed at a region where it could not see. The strip now finds that boundary from the model itself, spends its seven swatches inside the range where they are actually distinguishable, and quotes r over the concentrations it shows — about −0.86, which is what the rendered pixels measure too. It is also a self-check: if the model ever inverted, the swatches would stop darkening and the card would say so.

Concentration → reflectance, at the light pool

—

—

What is actually being solved

The velocity field is a standard stable-fluids solver — advection, Jacobi pressure projection, vorticity confinement for the curl that a coarse grid would otherwise damp away. The pigment is carried in a separate half-float field that stores an extensive pair: the sum of each pigment's absorption, and the sum of its loading. The display pass divides them to recover the intensive K/S, which is why two pigments that meet in the water mix to a real third colour instead of one painting over the other.

The four flow regimes differ in the solver and the forcing, never in the palette: Ink folds a plume into itself under high confinement, Drift drops confinement to near zero and damps velocity 43× harder so the field goes laminar and travels in sheets, Marble imposes two opposed oscillating jets and lets the shear layer go Kelvin–Helmholtz, and Rorschach is a bloom mirrored on purpose. The five colourways differ in pigment and in optics, so choosing one changes the rendered hue by changing the physics rather than by tinting a swatch.

Drag the water to stir it. The control rail is fixed, so you can work the ink while reading — 1–5 switches colourway, Q–R the flow, space holds the frame, C rinses the tank and S saves a still. The ramp above tracks whatever colourway is selected, so you can watch the numbers move as you switch.

Chromatic Ink
compiling the solver

← Lab