Projection View matrix calculation for directional light shadow mapping

2.2k views Asked by At

In order to calculate the projection view matrix for a directional light I take the vertices of the frustum of my active camera, multiply them by the rotation of my directional light and use these rotated vertices to calculate the extends of an orthographic projection matrix for my directional light. Then I create the view matrix using the center of my light's frustum bounding box as the position of the eye, the light's direction for the forward vector and then the Y axis as the up vector.

I calculate the camera frustum vertices by multiplying the 8 corners of a box with 2 as size and centered in the origin.

Everything works fine and the direction light projection view matrix is correct but I've encountered a big issue with this method.

Let's say that my camera is facing forward (0, 0, -1), positioned on the origin and with a zNear value of 1 and zFar of 100. Only objects visible from my camera frustum are rendered into the shadow map, so every object that has a Z position between -1 and -100.

The problem is, if my light has a direction which makes the light come from behind the camera and the is an object, for example, with a Z position of 10 (so behind the camera but still in front of the light) and tall enough to possibly cast a shadow on the scene visible from my camera, this object is not rendered into the shadow map because it's not included into my light frustum, resulting in an error not casting the shadow.

In order to solve this problem I was thinking of using the scene bounding box to calculate the light projection view Matrix, but doing this would be useless because the image rendered into the shadow map cuold be so large that numerous artifacts would be visible (shadow acne, etc...), so I skipped this solution.

How could I overcome this problem?


I've read this post under the section of 'Calculating a tight projection' to create my projection view matrix and, for clarity, this is my code:

Frustum* cameraFrustum = activeCamera->GetFrustum();

Vertex3f direction = GetDirection();                                        // z axis
Vertex3f perpVec1 = (direction ^ Vertex3f(0.0f, 0.0f, 1.0f)).Normalized();  // y axis
Vertex3f perpVec2 = (direction ^ perpVec1).Normalized();                    // x axis

Matrix rotationMatrix;  
rotationMatrix.m[0] = perpVec2.x;   rotationMatrix.m[1] = perpVec1.x;   rotationMatrix.m[2] =   direction.x;
rotationMatrix.m[4] = perpVec2.y;   rotationMatrix.m[5] = perpVec1.y;   rotationMatrix.m[6] =   direction.y;
rotationMatrix.m[8] = perpVec2.z;   rotationMatrix.m[9] = perpVec1.z;   rotationMatrix.m[10] =  direction.z;

Vertex3f frustumVertices[8];
cameraFrustum->GetFrustumVertices(frustumVertices);

for (AInt i = 0; i < 8; i++)
    frustumVertices[i] = rotationMatrix * frustumVertices[i];

Vertex3f minV = frustumVertices[0], maxV = frustumVertices[0];
for (AInt i = 1; i < 8; i++)
{
    minV.x = min(minV.x, frustumVertices[i].x);
    minV.y = min(minV.y, frustumVertices[i].y);
    minV.z = min(minV.z, frustumVertices[i].z);
    maxV.x = max(maxV.x, frustumVertices[i].x);
    maxV.y = max(maxV.y, frustumVertices[i].y);
    maxV.z = max(maxV.z, frustumVertices[i].z);
}

Vertex3f extends = maxV - minV;
extends *= 0.5f;

Matrix viewMatrix = Matrix::MakeLookAt(cameraFrustum->GetBoundingBoxCenter(), direction, perpVec1);
Matrix projectionMatrix = Matrix::MakeOrtho(-extends.x, extends.x, -extends.y, extends.y, -extends.z, extends.z);
Matrix projectionViewMatrix = projectionMatrix * viewMatrix;

SceneObject::SetMatrix("ViewMatrix", viewMatrix);
SceneObject::SetMatrix("ProjectionMatrix", projectionMatrix);
SceneObject::SetMatrix("ProjectionViewMatrix", projectionViewMatrix);

And this is how I calculate the frustum and it's bounding box:

Matrix inverseProjectionViewMatrix = projectionViewMatrix.Inversed();

Vertex3f points[8];

_frustumVertices[0] = inverseProjectionViewMatrix * Vertex3f(-1.0f,  1.0f, -1.0f);  // near top-left
_frustumVertices[1] = inverseProjectionViewMatrix * Vertex3f( 1.0f,  1.0f, -1.0f);  // near top-right
_frustumVertices[2] = inverseProjectionViewMatrix * Vertex3f(-1.0f, -1.0f, -1.0f);  // near bottom-left
_frustumVertices[3] = inverseProjectionViewMatrix * Vertex3f( 1.0f, -1.0f, -1.0f);  // near bottom-right
_frustumVertices[4] = inverseProjectionViewMatrix * Vertex3f(-1.0f,  1.0f,  1.0f);  // far top-left
_frustumVertices[5] = inverseProjectionViewMatrix * Vertex3f( 1.0f,  1.0f,  1.0f);  // far top-right
_frustumVertices[6] = inverseProjectionViewMatrix * Vertex3f(-1.0f, -1.0f,  1.0f);  // far bottom-left
_frustumVertices[7] = inverseProjectionViewMatrix * Vertex3f( 1.0f, -1.0f,  1.0f);  // far bottom-right

_boundingBoxMin = _frustumVertices[0];
_boundingBoxMax = _frustumVertices[0];

for (AInt i = 1; i < 8; i++)
{
    _boundingBoxMin.x = min(_boundingBoxMin.x, _frustumVertices[i].x);
    _boundingBoxMin.y = min(_boundingBoxMin.y, _frustumVertices[i].y);
    _boundingBoxMin.z = min(_boundingBoxMin.z, _frustumVertices[i].z);

    _boundingBoxMax.x = max(_boundingBoxMax.x, _frustumVertices[i].x);
    _boundingBoxMax.y = max(_boundingBoxMax.y, _frustumVertices[i].y);
    _boundingBoxMax.z = max(_boundingBoxMax.z, _frustumVertices[i].z);
}

_boundingBoxCenter = Vertex3f((_boundingBoxMin.x + _boundingBoxMax.x) / 2.0f, (_boundingBoxMin.y + _boundingBoxMax.y) / 2.0f, (_boundingBoxMin.z + _boundingBoxMax.z) / 2.0f);
0

There are 0 answers