Ollin

PointShadow

A point light as an omnidirectional caster. A bulb at the center throws shadows in every direction, ray-traced on an RT GPU and through a depth cube elsewhere.

Run
swift run --package-path Examples Example-3D-Lighting-PointShadow
Source
Examples/3D/Lighting/PointShadow/Sketch.swift
import Ollin

/// PointShadow — a point light at the center, casting shadows in every direction.
///
/// `castShadows()` casts from the scene's primary caster. With no directional or spot
/// light present, that's the first point light, and a point caster is omnidirectional:
/// the renderer renders the scene's depth into all six faces of a cube map from the
/// light, so a pillar's shadow falls whichever way it points. Here a bulb hangs at the
/// center of a ring of pillars, so every pillar throws its shadow radially outward,
/// the signature look a single direction can't make. The camera orbits.
///
/// (Under the hood, on a ray-tracing GPU each lit pixel traces a few visibility rays
/// straight to the light for an exact, soft contact-hardening shadow; on other GPUs it
/// falls back to an omnidirectional cube depth map sampled by direction.)
@main
final class PointShadow3D: Sketch {

    override func draw() {
        background(Color(hex: 0x0A0B10))

        cameraShowcase(.turntable(period: .tau / 0.16), target: Vector3(0, 1.0, 0), radius: 15,
                    elevation: 0.55, fieldOfView: .pi / 4.6)

        // The bulb hangs high above the center (well over the solids), so each one's
        // shadow fans down and outward onto the lit floor near its base rather than
        // grazing off to the dark edges. A touch of ambient keeps the far sides off
        // black. No directional or spot light, so the point light is the caster.
        ambientLight(Color(white: 0.08))
        let bulb = Vector3(0, 6.0, 0)
        pointLight(Color(hue: 0.10, saturation: 0.15, brightness: 1.0),
                   at: bulb, intensity: 1.7, specular: .white)
        castShadows()

        // The floor that catches the radiating shadows.
        withState {
            fill(Color(white: 0.82))
            specular(0.05)
            drawPlane(width: 30, depth: 30)
        }

        // A ring of pillars around the bulb, each turning a little, so their shadows
        // sweep across the floor as the light reaches past them.
        let count = 9
        for i in 0..<count {
            let a = Double(i) / Double(count) * .tau
            let radius = 3.6
            withState {
                translate(cos(a) * radius, 1.1, sin(a) * radius)
                rotateY(a + time * 0.2)
                fill(Color(hue: Double(i) / Double(count), saturation: 0.55, brightness: 0.95))
                specular(0.3); specularSharpness(40)
                drawBox(width: 1.0, height: 2.2, depth: 1.0)
            }
        }

        // A couple of solids closer in, casting their own outward shadows.
        withState {
            translate(1.5, 0.9, 0.4); rotateY(time * 0.5)
            fill(Color(hue: 0.55, saturation: 0.5, brightness: 0.95)); specular(0.3); specularSharpness(40)
            drawSphere(radius: 0.9)
        }
        withState {
            translate(-1.4, 0.7, -1.1); rotateY(-time * 0.4); rotateX(time * 0.2)
            fill(Color(hue: 0.0, saturation: 0.6, brightness: 0.95)); specular(0.3); specularSharpness(40)
            drawBox(size: 1.3)
        }

        drawCaption("Point shadows — castShadows() from an omnidirectional point light")
    }
}

↑↓ move ↩ open