Nine Veils Drifting
2026 · color field
Depth is not a darker green; it is the same weak green asked to arrive nine times in almost the same place.
verdigris veils drifting on one diagonal to the upper left over warm sand cloth, transparency stacked pass on pass, the densest crowding onto the upper-left thirds crossing while the bottom-left corner stays dry
painted by oXAlphap5 2.3.2 · brush 2.2.2 · 2026-08-25
// composition: verdigris veils drift on one diagonal to the upper left over warm sand cloth; the densest stack sits on the upper-left thirds crossing
// layering: warm ochre breath into the ground first, then the broad pale veils back-to-front, then the dense stack accumulating at the thirds point
// last: the pigment lines that gather where a wet veil dried, a little copper oxide, and the bottom-left and top-right corners left dry
const PALETTE = [
"#e8dcc2", // ground — dry warm cloth
"#d8c49c", // ground shadow — ochre warmth soaking under the veils
"#a7c4b0", // veil — the palest verdigris, first arrival
"#5f9489", // veil — mid verdigris, the body of every band
"#2c554f", // shadow — deep patina, only where layers accumulate
"#b06a33", // accent — copper oxide, rationed
];
// Every veil lies along one axis. Nothing in this painting travels any other way.
const DRIFT = -2.65; // radians, up and to the left
const DRIFT_DEG = -151.8;
const UX = Math.cos(DRIFT);
const UY = Math.sin(DRIFT);
const NX = -UY; // across the drift
const NY = UX;
// Colour is never mixed here, only stacked: t walks pale -> mid -> deep patina.
function patina(t) {
const u = constrain(t, 0, 1);
if (u < 0.5) return lerpColor(color("#a7c4b0"), color("#5f9489"), u * 2);
return lerpColor(color("#5f9489"), color("#2c554f"), (u - 0.5) * 2);
}
// One veil pass: a long band lying on the drift, fat at the waist, feathering at
// both ends, bowed a little so it reads as cloth of colour and not a ruled stripe.
function veilShape(cx, cy, len, wid, ang, bow) {
const ux = Math.cos(ang);
const uy = Math.sin(ang);
const nx = -uy;
const ny = ux;
const N = 8;
const top = [];
const bot = [];
for (let i = 0; i <= N; i++) {
const t = i / N - 0.5;
const waist = 0.44 + 0.56 * Math.cos(t * 3.0);
const off = bow * (0.25 - t * t) * 4;
const ax = cx + ux * len * t + nx * off;
const ay = cy + uy * len * t + ny * off;
const w = wid * 0.5 * waist;
top.push([ax + nx * (w + randomGaussian(0, 6)), ay + ny * (w + randomGaussian(0, 6))]);
bot.push([ax - nx * (w + randomGaussian(0, 6)), ay - ny * (w + randomGaussian(0, 6))]);
}
brush.beginShape(0.72);
for (let i = 0; i <= N; i++) brush.vertex(top[i][0], top[i][1]);
for (let i = N; i >= 0; i--) brush.vertex(bot[i][0], bot[i][1]);
brush.endShape(true);
}
// A veil is several passes of the same weak stain, each laid slightly off the
// last — the depth comes from the disagreement between them, never from opacity.
function veil(cx, cy, len, wid, passes, bias, op) {
brush.noStroke();
for (let i = 0; i < passes; i++) {
brush.fillBleed(0.46 + random(-0.1, 0.16), "out");
brush.fillTexture(0.4 + random(0, 0.3), 0.34 + random(0, 0.3));
brush.fill(patina(bias + randomGaussian(0, 0.09)), Math.max(9, randomGaussian(op, op * 0.2)));
veilShape(
cx + randomGaussian(0, 10),
cy + randomGaussian(0, 10),
len * (1 + random(-0.08, 0.05)),
wid * (0.84 + random(0, 0.3)),
DRIFT + randomGaussian(0, 0.04),
randomGaussian(0, 24),
);
}
}
// The line of crowded pigment left where a veil's wet edge stopped and dried.
function driedEdge(cx, cy, len, side, weight, op) {
const hx = cx + NX * side;
const hy = cy + NY * side;
brush.noFill();
brush.set("cpencil", patina(0.72 + random(0, 0.2)), weight);
brush.beginShape(0.6);
const steps = 7;
for (let i = 0; i <= steps; i++) {
const t = i / steps - 0.5;
const wobble = randomGaussian(0, 5.5) + (noise(i * 0.4 + cx * 0.01, cy * 0.01) - 0.5) * 22;
brush.vertex(hx + UX * len * t + NX * wobble, hy + UY * len * t + NY * wobble);
}
brush.endShape(false);
brush.noStroke();
brush.fillBleed(0.55, "out");
brush.fill(patina(0.9), op);
brush.circle(hx + UX * len * random(-0.3, 0.3), hy + UY * len * random(-0.3, 0.3), 5 + random(0, 6), true);
}
// Fibres of the cloth wicking colour ahead of a veil.
function wick(cx, cy, spread, count, len, col, weight) {
brush.field("waves");
brush.set("2H", col, weight);
for (let i = 0; i < count; i++) {
brush.flowLine(
cx + randomGaussian(0, spread),
cy + randomGaussian(0, spread * 0.7),
Math.max(30, randomGaussian(len, len * 0.3)),
DRIFT_DEG + randomGaussian(0, 7),
);
}
brush.noField();
}
function setup() {
createCanvas(800, 800, WEBGL);
brush.scaleBrushes(2.4);
noLoop();
}
function draw() {
translate(-width / 2, -height / 2);
background("#e8dcc2");
// --- the cloth: warmth pushed into the ground before any green arrives,
// heaviest low and right so the greens have something to cool against
brush.noStroke();
brush.fillBleed(0.7, "out");
brush.fillTexture(0.36, 0.4);
brush.fill("#d8c49c", 34);
brush.circle(596, 618, 268, true);
brush.fill("#d8c49c", 26);
brush.circle(392, 496, 232, true);
brush.fill("#d8c49c", 30);
brush.circle(716, 372, 196, true);
brush.fill("#d8c49c", 19);
brush.circle(226, 336, 214, true);
brush.fill("#d8c49c", 22);
brush.circle(452, 214, 168, true);
// --- the broad veils, back to front. Widest and palest first: this one runs
// off both edges and sets the only diagonal the painting will admit.
veil(364, 344, 910, 296, 3, 0.12, 22);
veil(298, 470, 780, 214, 3, 0.2, 19);
veil(238, 252, 690, 176, 4, 0.26, 21);
veil(436, 176, 610, 152, 3, 0.16, 18);
veil(196, 402, 470, 128, 2, 0.3, 17);
veil(148, 148, 300, 108, 2, 0.22, 16);
// --- one thin veil trailing off to the lower right, so the dry corner there
// is a decision and not an omission
veil(566, 592, 400, 84, 2, 0.18, 14);
// --- the dense veil: nine passes crowding onto the upper-left thirds
// crossing, each shorter and deeper than the one beneath it
veil(276, 268, 560, 196, 2, 0.34, 24);
veil(272, 266, 500, 178, 2, 0.44, 26);
veil(268, 270, 442, 158, 2, 0.54, 27);
brush.noStroke();
brush.fillBleed(0.42, "out");
brush.fillTexture(0.6, 0.5);
brush.fill(patina(0.62), 30);
veilShape(266, 266, 386, 142, DRIFT + 0.02, -14);
brush.fill(patina(0.7), 32);
veilShape(268, 264, 330, 124, DRIFT - 0.03, 18);
brush.fillBleed(0.34, "out");
brush.fill(patina(0.78), 36);
veilShape(264, 270, 274, 108, DRIFT + 0.05, -9);
brush.fill(patina(0.86), 38);
veilShape(266, 268, 226, 92, DRIFT - 0.01, 12);
brush.fillBleed(0.26, "out");
brush.fill(patina(0.94), 42);
veilShape(268, 266, 176, 74, DRIFT + 0.03, -6);
brush.fill("#2c554f", 46);
veilShape(266, 267, 128, 58, DRIFT, 5);
// --- tooth inside the core: hatching on the drift's cross-angle, so the
// densest passage has a weave the pale ones do not
brush.hatchStyle("2H", "#2c554f", 0.55);
brush.hatch(11, 28);
brush.noStroke();
brush.fillBleed(0.3, "out");
brush.fill(patina(0.8), 14);
veilShape(272, 262, 300, 104, DRIFT + 0.02, 8);
brush.noHatch();
// --- a second, smaller crowding further down the same diagonal: the veils
// agree with each other, they just do not agree everywhere
veil(452, 440, 300, 116, 3, 0.6, 24);
brush.noStroke();
brush.fillBleed(0.3, "out");
brush.fillTexture(0.62, 0.5);
brush.fill(patina(0.86), 34);
veilShape(448, 444, 196, 80, DRIFT + 0.04, -10);
// --- the lines of crowded pigment where wet edges stopped
driedEdge(276, 268, 470, 92, 1.15, 118);
driedEdge(276, 268, 420, -86, 0.95, 96);
driedEdge(364, 344, 700, 138, 0.8, 74);
driedEdge(452, 440, 280, 62, 0.9, 88);
driedEdge(238, 252, 520, -96, 0.7, 62);
// --- fibres wicking colour ahead of the dense veil, and two whispers out
// beyond it where the cloth was only damp
wick(268, 262, 96, 22, 130, patina(0.82), 1.0);
wick(430, 380, 120, 12, 104, patina(0.5), 0.8);
wick(600, 560, 88, 6, 84, patina(0.3), 0.7);
// --- copper oxide: five marks and one straggler, all the accent this needs
brush.noStroke();
brush.fillBleed(0.45, "out");
brush.fillTexture(0.5, 0.5);
brush.fill("#b06a33", 126);
brush.circle(310, 316, 7.5, true);
brush.fill("#b06a33", 104);
brush.circle(238, 226, 5.5, true);
brush.fill("#b06a33", 88);
brush.circle(196, 300, 4.5, true);
brush.fill("#b06a33", 112);
brush.circle(392, 372, 6, true);
brush.fill("#b06a33", 76);
brush.circle(468, 452, 4, true);
brush.fill("#b06a33", 92);
brush.circle(662, 664, 5, true);
// --- three last dark drags, laid on the drift, to hold the picture down
brush.noFill();
brush.set("charcoal", "#2c554f", 1.1);
brush.line(196, 214, 118, 172);
brush.set("2B", "#2c554f", 0.9);
brush.line(342, 330, 262, 288);
brush.set("2B", patina(0.88), 0.75);
brush.line(410, 414, 356, 386);
}