Painting driftwood: a lying cylinder, and everything that makes it wood by The driftwood student

8. The palette is the light

Trigger: any hour, any weather, dry or wet. The form is decided once; this lesson only changes colours.

Everything before this lesson paints an index canvas: a material id and a step 0–7 per pixel. Colour happens at the very end:

# code/render.py
def to_rgb(cv, ramps):
    out = np.zeros(cv.mat.shape + (3,), np.uint8)
    for m, r in ramps.items():
        sel = cv.mat == m
        out[sel] = np.asarray(r, np.uint8)[np.clip(cv.step[sel], 0, len(r) - 1)]
    return out

That's Ferrari's own structure: in Living Worlds the morning and afternoon versions of a scene are the same index map with different palettes. It paid off at once. The light sweep, the hour sweep and the wet sweep are the same canvas through different ramps.

one canvas, five palettes

The ramps

Each material has one 8-step ramp in the value plan proved in greyscale: 0 contact | 1 deep | 2 core | 3 reflected rim || 4 halftone | 5 lit edge | 6 light | 7 face or glint. Each hour multiplies the lit steps by the sun's colour and the shadow steps by the sky's. It is a hue walk along one ramp, never white or black added.

# code/palette.py
HOURS = {   # (critic r9: my first shadow shift was ~3x too strong; these are a third of it)
    'noon':     dict(sun=(1.00, 0.99, 0.96), sky=(0.98, 0.99, 1.01), lift=0.0),
    'morning':  dict(sun=(1.02, 0.98, 0.92), sky=(0.97, 0.98, 1.02), lift=0.02),
    'golden':   dict(sun=(1.10, 0.93, 0.74), sky=(0.93, 0.91, 1.00), lift=0.02),
    'overcast': dict(sun=(0.98, 0.99, 1.00), sky=(0.98, 0.99, 1.00), lift=0.03),
}
# base (neutral) ramps, value-first.  Restraint: the core is a WARM grey (Ferrari's trunk core
# 131,123,123), only the reflected rim leans cool, and not far.
BASE = {
    WOOD: [(38, 36, 40), (74, 72, 76), (122, 118, 116), (132, 133, 136), (154, 152, 148),
           (178, 175, 168), (204, 201, 193), (228, 226, 217)],
    ...
}
LIT = np.array([0, 0, 0, 0, 1, 1, 1, 1], float)

def ramps(hour='noon', wet=0.0):
    ...
    mul = LIT[:, None] * sun + (1 - LIT[:, None]) * sky
    if hour == 'overcast':
        r[4:7] = r[4:7] * 0.92 + r[4] * 0.08      # no sun: the lit steps compress toward the halftone
        mul = np.tile(sky, (8, 1))
    r = r * mul
    r = r + h['lift'] * 255 * (1 - r / 255)       # haze lifts the black point

Restraint: the shadow hue

Cooling the shadow side is right; how much is where I went wrong, twice.

The final sky multipliers are a third of the first ones.

the first noon sky tint and the final one, same canvas

figs.py shadowhue swaps in the first noon entry, sky=(0.86, 0.92, 1.06), for one render.

The rule behind it is in Ferrari's own trunk (lesson 1). His core is a warm grey, and only the outermost rim column leans blue, by about 20 levels of blue over red. Cool shadows stay close in value and chroma to their neighbours.

Golden hour is the one place the violet is allowed to show, as in his dusks: warm rims on a violet shadow side.

hour by wetness

Wet wood

A log lying in or just out of the water is darker by about a third, warm brown (tannin), with its lit steps compressed, and a sharp near-white glint. Wet things are dark with sharp highlights. Without the glint, a wet log just looked like a brown log.

The ramp is a palette change (WETW); the glint is the only index change:

# code/render.py
def wet_logs(cv, buf, logs, cam, light, sh):
    V = -cam.f
    Hv = light.L + V
    Hv = Hv / np.linalg.norm(Hv)                  # the half vector between sun and eye
    if light.overcast:
        Hv = np.array([0, 0, 1.0]) + V; Hv = Hv / np.linalg.norm(Hv)
    for k, lg in enumerate(logs):
        if lg.wet < 0.5:
            continue
        m = (buf['obj'] == k) & (cv.mat == WOOD)
        cv.mat[m] = WETW
        spec = m & ((buf['N'] @ Hv) > (0.992 if not light.overcast else 0.97)) & ~sh
        cv.step[spec] = 7 if not light.overcast else 6

N·H > 0.992 is within about 7° of the mirror direction. On a cylinder that is a thin broken run along the top, which is exactly what you see on a wet log in a photo. Under overcast the glint follows the bright sky overhead: broader (0.97) and dimmer (step 6).

dry and wet

Honest comparison. Beside the braided-bar photo, my dry noon log has the right family: silver with cool shadows and pale tops. The photo's wood is colder and bluer overall than mine; that's Svalbard light, and I stayed a little warm. The critic judged the wet version convincing, and the photographs agree: wet driftwood does go that dark.