c++graphicsdirectxshadow

Shadow mapping: How to compute directional lights view and projection matrices


I want to add shadow mapping to my realtime renderer. I start with directional lights first.
The algorithm:

  1. Render the shadow map from the light point of view.
  2. When shading a worldspace point P it is projected to the light space and the shadow map is sampled to see if it is occluded.

I am stuck at (1). I need to compute the light optimal view projection matrix. It is made of the product of the view and projection matrices. To do so I need to make the light frustrum fit the whole scene.
The scene AABB(min,max) is given as well as the light direction.

To summarize the matrices i need are:

a) The light View matrix.
Typically computed by DirectX::XMMatrixLookAtRH or glm::lookAtRH.
What should be the parameters EyePosition, FocusPosition and UpDirection?

b) The light projection matrix. Since it is a directional light this should be an orthographic projection.
Typically computed by DirectX::XMMatrixOrthographicOffCenterRH or glm::orthoRH
What would be the parameters ViewLeft, ViewRight, ViewBottom, ViewTop, NearZ, farZ?

c) The light view projection matrix. Product of (a) and (b)
If too far would get something like this. I lose precision

Far projection

If too close i lose information

Close projection


Solution

  • a) Light View Matrix

    The light's view matrix transforms world space into light space.

    EyePosition

    Pick a position along the light direction such that it can "see" the scene's bounding box. A simple choice is to center it on the scene's AABB center, offset by some distance along the negative light direction.

    glm::vec3 lightDir = glm::normalize(lightDirection);
    glm::vec3 center = (sceneAABBMin + sceneAABBMax) * 0.5f;
    glm::vec3 eyePos = center - lightDir * distance;
    

    The distance should be large enough to encompass the scene.

    FocusPosition

    The center of the scene's AABB

    FocusPosition = center
    

    UpDirection

    You need to pick an arbitrary up vector that isn't parallel to your light direction. Typically (0,1,0) is fine unless your light is exactly vertical.

    if (abs(dot(lightDir, glm::vec3(0,1,0))) > 0.99f)
        up = glm::vec3(0,0,1);
    else
        up = glm::vec3(0,1,0);
        
    

    How to use

    glm::mat4 lightView = glm::lookAtRH(eyePos, center, up);
    

    b) Light Projection Matrix (Orthographic)

    This is where you fit the projection to tightly cover the scene's AABB in light space.

    Code as below.

    // Get scene AABB corners in world space
    std::vector<glm::vec3> corners = {
        {sceneMin.x, sceneMin.y, sceneMin.z},
        {sceneMax.x, sceneMin.y, sceneMin.z},
        {sceneMin.x, sceneMax.y, sceneMin.z},
        {sceneMax.x, sceneMax.y, sceneMin.z},
        {sceneMin.x, sceneMin.y, sceneMax.z},
        {sceneMax.x, sceneMin.y, sceneMax.z},
        {sceneMin.x, sceneMax.y, sceneMax.z},
        {sceneMax.x, sceneMax.y, sceneMax.z},
    };
    
    // Transform to light space
    glm::mat4 lightView = ...;
    std::vector<glm::vec3> lightSpaceCorners;
    for (const auto& c : corners)
        lightSpaceCorners.push_back(glm::vec3(lightView * glm::vec4(c, 1.0)));
    
    // Compute new AABB in light space
    glm::vec3 minLS = lightSpaceCorners[0];
    glm::vec3 maxLS = lightSpaceCorners[0];
    for (const auto& c : lightSpaceCorners)
    {
        minLS = glm::min(minLS, c);
        maxLS = glm::max(maxLS, c);
    }
    
    // Use as ortho bounds
    float left   = minLS.x;
    float right  = maxLS.x;
    float bottom = minLS.y;
    float top    = maxLS.y;
    float nearZ  = minLS.z;
    float farZ   = maxLS.z;
    
    glm::mat4 lightProj = glm::orthoRH(left, right, bottom, top, nearZ, farZ);
    

    c) Light View-Projection Matrix

    Simply glm::mat4 lightViewProj = lightProj * lightView;

    For Precision and Coverage

    To improve shadow stability (avoid shimmering as the camera moves)