Link Search Menu Expand Document

7: Shadows


Download code
07-shadows.zip
Live example
examples/07-shadows/index.html

At the end of the last section, our scene looked like this:

image-20220320011118565

We’re going to add support for shadows, so that shapes will cast shadows, and areas that are in shadow won’t be illuminated by the associated light source.

All this happens inside the Shape class - in fact, we only need to modify the getColorAt method.

Here’s shape.js with the modified method in place:

// modules/shape.js

import { THRESHOLD } from './settings.js';
import { Vector } from './vector.js';
import { Color } from './color.js';
import { Ray } from './ray.js';

export class Shape {

    constructor(appearance) {
        this.appearance = appearance;
    }

    intersect = () => { throw ("Classes which extend Shape must implement intersect"); };

    getNormalAt = () => { throw ("Classes which extend Shape must implement getNormalAt"); }

    closestDistanceAlongRay = (ray) => {
        let distances = this.intersect(ray).filter(d => d > THRESHOLD);
        let shortestDistance = Math.min.apply(Math, distances);
        return shortestDistance;
    }

    /** return true if the specified light casts a shadow of this shape at the specified point  */
    castsShadowFor = (point, vector) => {
        let distanceToLight = vector.length;
        let ray = new Ray(point, vector);
        return (this.closestDistanceAlongRay(ray) <= distanceToLight);
    }

    getColorAt = (point, scene) => {
        let normal = this.getNormalAt(point);
        let color = Color.Black;
        scene.lights.forEach(light => {
            let v = Vector.from(point).to(light.position);

            // If this point is in shadow, do not add any illumination for this light source
            if (scene.shapes.some(shape => shape.castsShadowFor(point, v))) return;

            let brightness = normal.dot(v.unit());
            if (brightness <= 0) return;

            let illumination = light.illuminate(this.appearance, point, brightness);
            color = color.add(illumination);
        });
        return color;
    }
}

What we do here is, for each point on the shape’s surface, we trace a ray from that point to the light source, and check whether that ray intersects any other shapes along the way. If it does, then we’re in that shape’s shadow, and so we should skip that light source when calculating how much light falls on this shape.

Compare the result below with the original image above – see how the shadows add depth and definition to the scene?

image-20220320012307359


Download code
07-shadows.zip
Live example
examples/07-shadows/index.html