Ollin

Relight

a layer read as a height map and lit as an embossed physical material: matte, metal, glass, sand, and liquid (.relight)

Run
swift run --package-path Examples Example-Effects-Relight
Source
Examples/Effects/Relight/Sketch.swift
import Ollin

/// **Relight**: read a layer as a height map (bright = raised) and light it as
/// embossed physical matter. One drifting noise field, shown raw and through the
/// five material finishes: matte clay, metal, wet glass, grainy sand, and liquid.
/// A finish keeps the layer's own color unless `color:` picks the material's.
///
/// Try it: raise `height` for deeper relief, lower `elevation` for a grazing
/// rake of light, or relight a simulation (`Simulation/GrayScott`) instead of
/// noise so the pattern reads as grown matter.
@main
final class Relight_Example: Sketch {
    @Param(0.2...4, icon: "arrow.up.and.down") var relief = 2.4
    @Param(0.15...1.5, icon: "sun.max") var lightElevation = 0.8

    private let labelFont = OutlineFont.system

    override func draw() {
        background(Color(white: 0.06))

        // The height map: a slowly drifting fbm cloud (bright = raised).
        let field = generate(.noise(scale: 3.2,
                                    foreground: Color(hex: 0xB8C4D6),
                                    background: Color(hex: 0x1C2330)))
            .filtered(.perturb(amount: 0.05, scale: 2.4, phase: time * 0.25))

        let tiles: [(String, Filter?)] = [
            ("height field", nil),
            ("matte", .relight(.matte, elevation: lightElevation, height: relief,
                               color: Color(hex: 0xC96F4A))),
            ("metal", .relight(.metal, elevation: lightElevation, height: relief,
                               color: Color(hex: 0xD8A93F))),
            ("glass", .relight(.glass, elevation: lightElevation, height: relief)),
            ("sand", .relight(.sand, elevation: lightElevation, height: relief,
                              color: Color(hex: 0xD9BE8C))),
            ("liquid", .relight(.liquid, elevation: lightElevation, height: relief)),
        ]
        drawRelightSheet(self, field, tiles, font: labelFont)
    }
}

/// Tile the finishes into a labeled grid. (Each family example carries its own
/// copy so the file stays standalone.)
@MainActor
func drawRelightSheet(_ s: Sketch, _ scene: RenderTarget,
                      _ tiles: [(String, Filter?)], font: OutlineFont) {
    let cols = 3
    let rows = (tiles.count + cols - 1) / cols
    let gutter = s.width * 0.012
    let cellW = (s.width - gutter * Double(cols + 1)) / Double(cols)
    let cellH = (s.height - gutter * Double(rows + 1)) / Double(rows)
    for (i, tile) in tiles.enumerated() {
        let r = i / cols, c = i % cols
        let x = gutter + Double(c) * (cellW + gutter)
        let y = gutter + Double(r) * (cellH + gutter)
        let layer = tile.1.map { scene.filtered($0) } ?? scene
        s.drawImage(layer.image, in: Rectangle(x: x, y: y, width: cellW, height: cellH))
        s.withState {
            s.fill(Color(white: 0, alpha: 0.55)); s.noStroke()
            s.drawRect(x, y + cellH - 26, cellW, 26)
            s.fill(.white)
            s.textFont(font); s.textSize(14); s.textAlign(.left, .middle)
            s.drawText(tile.0, x + 8, y + cellH - 13)
        }
    }
}

↑↓ move ↩ open