Painting river cobbles and gravel bars by The cobbles student

6. The palette is the light

← pockets, not outlines · front page · next: material marks →

When: always. Paint steps (front page), then choose a ramp per rock, hour and wetness at render time.

same map, five hours

One index canvas rendered through five palettes. Same stones, same steps, same pixels; only ramp(rock, hour) changes. Made by code/figs.py.

Note the limit: the index map fixes the direction of the light (it decided which pixels are lit). The palette changes the light's colour and key. To move the sun, repaint (sheet D, page 10). This is the same as Ferrari's colour-cycling scenes, where morning and afternoon share one index map.

One ramp: nine steps, a hue walk in Lab (palette.ramp)

Each ramp is built in CIE Lab, from four ingredients:

ROCK = {'granite':   dict(L_fl=72, L_sh=30, tint=(45, 4)),      # pink-grey
        'greywacke': dict(L_fl=62, L_sh=25, tint=(165, 4)),     # green-grey
        'basalt':    dict(L_fl=46, L_sh=16, tint=(260, 5)),     # blue-black
        'gravel':    dict(L_fl=60, L_sh=25, tint=(120, 4))}     # the matrix: the rocks' average

HOUR = {'noon':     dict(sun=(80, 6), sky=(265, 9), gap=1.0, top=1.0),
        'morning':  dict(sun=(70, 12), sky=(255, 12), gap=1.05, top=1.0),
        'golden':   dict(sun=(60, 26), sky=(290, 14), gap=1.2, top=1.05),
        'dusk':     dict(sun=(20, 18), sky=(305, 16), gap=0.8, top=0.85),
        'overcast': dict(sun=(230, 3), sky=(235, 6), gap=0.55, top=0.9)}

def ramp(rock='granite', hour='noon', wet=0.0):
    r, h = ROCK[rock], HOUR[hour]
    Lfl, Lsh = r['L_fl'], r['L_sh']
    g = h['gap']
    Lsh_scale = Lsh * (1.0 if g >= 1 else 1 + (1 - g) * 0.9)     # overcast: shadows lift
    Ls = [Lsh_scale * 0.35, Lsh_scale * 0.62, Lsh_scale * 0.85, Lsh_scale * 1.08]
    span = Lfl - Ls[-1]
    Ll = [Ls[-1] + span * f for f in (0.28, 0.55, 0.78, 1.0)] + [min(97, Lfl + 12 * h['top'])]
    Ls[-1] = min(Ls[-1], Ll[0] - 5)                                # keep the terminator gap
    Lv = np.array(Ls + Ll)
    if wet > 0:
        Lv[:8] = Lv[:8] * (1 - 0.3 * wet)                          # wet: darker ...
    env = np.array([0.25, 0.45, 0.7, 0.9, 0.85, 1.0, 0.9, 0.7, 0.35]) * (1 + 0.25 * wet)   # ... a bit richer
    ab = []
    for i in range(9):
        own = hue(*r['tint']) * (1.2 if i in (4, 5, 6) else 0.8)
        light = hue(*h['sun']) if i >= 4 else hue(*h['sky'])
        if i == 3:
            light = 0.6 * hue(*h['sky']) + 0.4 * hue(*h['sun'])    # reflected: bounce off lit gravel
        ab.append((own + light) * env[i])
    rgb = lab_to_rgb(np.concatenate([Lv[:, None], np.array(ab)], 1))
    if wet > 0:
        rgb[8] = lab_to_rgb(np.array([min(98, Lfl + 24), *(hue(*h['sun']) * 0.4)]))   # the glint
    return [tuple(int(c) for c in x) for x in rgb]

ramps

The ramps: four materials (rows) under five hours (columns), steps 0–8 from left to right.

Why these particular choices

hours per rock

Stage 3: a single cobble of each rock under each hour.

compare

A granite cobble in sun (Kuban) | my granite on the morning ramp | Ferrari's V19 ramp.

Traps