0
\$\begingroup\$

I'm trying to implement a directional light in my C++ / OpenGL engine, but I struggle to calculate the shadow bounding box (i.e the shadow frustum). The bounding box has to encompass the main frustum and be aligned with the light rays, resulting in an orhographic frustum.

The problem is that as the light have no position, I don't know how to create the light view matrix, which is necessary to calculate the aligned bounding box.

Here's my current attempt:

  • calculation of the main frustum 8 coordinates in world space
  • conversion of these 8 coordinates in light view space, using the lightView matrix
  • calculation of a bounding box around the main frustum, aligned with light rays since I'm in light view space
  • calculation of the bounding box center, in light view space
  • conversion of the bounding box center in world space (using the inverse of the lightView matrix)
  • calculation of the bounding box projection matrix with glm::ortho
  • calculation of the bounding box view matrix (the lightView matrix) with glm::lookAt. For this I need the bounding box center, which I calculated... using the lightView matrix.

This doesn't work, because I need the lightView matrix to calculate the bounding box center. But I need the bounding box center to calculate the lightView matrix.

I can't manage to get it right. What am I missing?

Here is the code:

void Shadows::updateFrustumBoundingBox()
{

  // Here we convert main camera frustum coordinates in light view space
  std::array<glm::vec3,8> points = {
    // Near plane points
    lightView * glm::vec4(cameraPtr->ntl, 1.0),
    lightView * glm::vec4(cameraPtr->ntr, 1.0),
    lightView * glm::vec4(cameraPtr->nbl, 1.0),
    lightView * glm::vec4(cameraPtr->nbr, 1.0),
    // Far plane points
    lightView * glm::vec4(cameraPtr->ftl, 1.0),
    lightView * glm::vec4(cameraPtr->ftr, 1.0),
    lightView * glm::vec4(cameraPtr->fbl, 1.0),
    lightView * glm::vec4(cameraPtr->fbr, 1.0)};

  // Here we find the shadow bounding box dimensions
  bool first = true;
  for (int i=0; i<7; ++i)
  {
    glm::vec3* point = &points[i];

    if (first)
    {
            minX = point->x;
            maxX = point->x;
            minY = point->y;
            maxY = point->y;
            minZ = point->z;
            maxZ = point->z;
      first = false;
    }

        if (point->x > maxX)
            maxX = point->x;
    else if (point->x < minX)
            minX = point->x;

        if (point->y > maxY)
            maxY = point->y;
    else if (point->y < minY)
            minY = point->y;

        if (point->z > maxZ)
            maxZ = point->z;
    else if (point->z < minZ)
            minZ = point->z;
  }

  frustumWidth = maxX - minX;
  frustumHeight = maxY - minY;
  frustumLength = maxZ - minZ;

  // Here we find the bounding box center, in light view space
  float x = (minX + maxX) / 2.0f;
  float y = (minY + maxY) / 2.0f;
  float z = (minZ + maxZ) / 2.0f;
  glm::vec4 frustumCenter = glm::vec4(x, y, z, 1.0f);

  // Here we convert the bounding box center in world space
  glm::mat4 invertedLight = glm::mat4(1.0f);
  invertedLight = glm::inverse(lightView);
  frustumCenter = invertedLight * frustumCenter;

  // Here we define the light projection matrix (shadow frustum dimensions)
  lightProjection = glm::ortho(
    -frustumWidth/2.0f, // left
    frustumWidth/2.0f, // right
    -frustumHeight/2.0f, // down
    frustumHeight/2.0f, // top
    0.01f, // near
    SHADOW_DISTANCE); // far


  // Here we define the light view matrix (shadow frustum position and orientation)
  lightDirection = glm::normalize(lightDirection);
  target = glm::vec3(frustumCenter) + lightDirection;

  lightView = glm::lookAt(
                  // Shadow box center
                  glm::vec3(frustumCenter),

                  // Light orientation
                  target,

                  // Up vector
                  glm::vec3( 0.0f, 1.0f,  0.0f));

  // Final matrix calculation
  lightSpaceMatrix = lightProjection * lightView;
}
\$\endgroup\$
2
  • \$\begingroup\$ The only part of the LightView matrix that the bounding box center should affect is its translation, which doesn't change anything about the light. So couldn't you use an arbitrary translation (say, the translation from the previous frame, or to the player, or to the center of the view frustum) for your first step, then use the result to calculate a revised matrix with a new translation for the later step? \$\endgroup\$
    – DMGregory
    Aug 28 '21 at 14:04
  • \$\begingroup\$ Thank you a lot for your help DMGregory. It helped me to find the solution, which I posted below. Have a good day. \$\endgroup\$
    – Hermann
    Aug 29 '21 at 10:11
2
\$\begingroup\$

Well, probably not the best way but this works pretty well.

Before to process the shadow bounding box, calculate a lightView matrix based on the main frustum center, with the light view set to a position relative to this center.

  lightView = glm::lookAt(
            // Eye
            cameraPtr->adaptativeFrustumCenter + lightPosition,
            // Target
            cameraPtr->adaptativeFrustumCenter,
            // Up vector
            glm::vec3( 0.0f, 1.0f,  0.0f)); 

When the shadow bounding box is processed, we have access to its center in world space. So we can update the lightView matrix. This updated view matrix will be then used to render the depth map.

lightView = glm::lookAt(
            // Eye
            glm::vec3(shadowFrustumCenter) + lightPosition,
            // Target
            glm::vec3(shadowFrustumCenter),
            // Up vector
            glm::vec3( 0.0f, 1.0f,  0.0f)); 
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .