Chapter 14: Spotlight Shadow Map

Hi,

I’m really enjoying the book! In chapter 14 (pg. 392/ 393) we create a shadow map using an ortho projection matrix. In the description, the book mentions " If instead you used a spotlight, for example, you would use the perspective projection matrix." I’m trying to accomplish this however I’m having a hard time getting the map to work with perspective projection. Do you or anyone have a working example I could reference? Thanks!

@smehsu we’re happy you’re enjoying the book! :slightly_smiling_face:

unfortunately, I haven’t seen implementations using the perspective matrix… mostly because when we think of shadows they are most of the time caused by a directional light source such as the sun.

I managed to figure out spotlights. I’m still trying to figure out point lights.

let aspect = Float(view.bounds.width) / Float(view.bounds.height)
scene.uniforms.projectionMatrix = float4x4(projectionFov: radians(fromDegrees: 70), near: 0.01, far: 16, aspect: aspect)


let position: float3 = [-sunlight.position.x, -sunlight.position.y, -sunlight.position.z]
let center: float3 = [0, 0, 0]
let lookAt = float4x4(eye: position, center: position - sunlight.coneDirection, up: [0,1,0])


scene.uniforms.viewMatrix = float4x4(translation: [0, 0, 7]) * lookAt
scene.uniforms.shadowMatrix = scene.uniforms.projectionMatrix * scene.uniforms.viewMatrix

Shadow shader vertex

    matrix_float4x4 mvp = uniforms.projectionMatrix * uniforms.viewMatrix * uniforms.modelMatrix;
    float4 position = mvp * vertexIn.position;
    float4 worldPosition = uniforms.modelMatrix * vertexIn.position;

    float3 directionFromLightToFragment = normalize(light.position - worldPosition.xyz);

    if (light.type == Spotlight) {
        float3 tConeDirection = light.coneDirection;
        float3 coneDirection = normalize(-tConeDirection);
        float spotResult = dot(directionFromLightToFragment, coneDirection);
        float coneAngle = cos(light.coneAngle);


        if (spotResult < coneAngle) {
            position = position.xyww;
        }
    }

Main Shader

float2 xy = in.shadowPosition.xy / in.shadowPosition.w;
xy = xy * 0.5 + 0.5;
xy.y = 1 - xy.y;

constexpr sampler s(coord::normalized, filter::linear, address::clamp_to_edge, compare_func:: less);
float shadow_sample = shadowTexture.sample(s, xy);
float current_sample = in.shadowPosition.z / in.shadowPosition.w;

if (current_sample > shadow_sample ) {
    color *= 0.5;
}
1 Like

that’s great! let us know about your progress on it.

Pointlight / omni directional shadow maps were significantly more work. Here are the two resources I used:

https://developer.apple.com/documentation/metal/reflections_with_layer_selection

  • It seems the shadow projection matrix needs to have an aspect ratio of 1 instead of using the views width / height.

@smehsu that’s good to hear. thanks for sharing :slightly_smiling_face: