6
\$\begingroup\$

I'm trying to improve the coverage of a shadow map for a directional light. Currently, it works great if the camera is looking straight down. However, if the camera is close to the ground and looking toward the horizon, I can see where the shadow map ends because it doesn't cover enough of the scene.

I'd like to create a frustum for the directional light so that it entirely encloses the camera's frustum, as this article suggests in figures 14 and 15. (I'm not really worried about the near and far planes for the light; I've picked a number that works well for every test I've tried)

I know the direction of the light, and the position, rotation, and field of view of the camera. I'm using OpenGL.

How do I create a frustum for the light so that it encloses my view frustum?

\$\endgroup\$
9
\$\begingroup\$

The basic process for a directional light (whose rays are parallel) is as follows:

  1. Calculate the 8 corners of the view frustum in world space. This can be done by using the inverse view-projection matrix to transform the 8 corners of the NDC cube (which in OpenGL is [‒1, 1] along each axis).

  2. Transform the frustum corners to a space aligned with the shadow map axes. This would commonly be the directional light object's local space. (In fact, steps 1 and 2 can be done in one step by combining the inverse view-projection matrix of the camera with the inverse world matrix of the light.)

  3. Calculate the bounding box of the transformed frustum corners. This will be the view frustum for the shadow map.

  4. Pass the bounding box's extents to glOrtho or similar to set up the orthographic projection matrix for the shadow map.

There are a couple caveats with this basic approach. First, the Z bounds for the shadow map will be tightly fit around the view frustum, which means that objects outside the view frustum, but between the view frustum and the light, may fall outside the shadow frustum. This could lead to missing shadows. To fix this, depth clamping can be enabled so that objects in front of the shadow frustum will be rendered with clamped Z instead of clipped. Alternatively, the Z-near of the shadow frustum can be pushed out to ensure any possible shadowers are included.

The bigger issue is that this produces a shadow frustum that continuously changes size and position as the camera moves around. This leads to shadows "swimming", which is a very distracting artifact. In order to fix this, it's common to do the following additional two steps:

  1. Fix the overall size of the frustum based on the longest diagonal of the camera frustum. This ensures that the camera frustum can fit into the shadow frustum in any orientation. Don't allow the shadow frustum to change size as the camera rotates.

  2. Discretize the position of the frustum, based on the size of texels in the shadow map. In other words, if the shadow map is 1024×1024, then you only allow the frustum to move around in discrete steps of 1/1024th of the frustum size. (You also need to increase the size of the frustum by a factor of 1024/1023, to give room for the shadow frustum and view frustum to slip against each other.)

If you do these, the shadow will remain rock solid in world space as the camera moves around. (It won't remain solid if the camera's FOV, near or far planes are changed, though.)

As a bonus, if you do all the above, you're well on your way to implementing cascaded shadow maps, which are "just" a set of shadow maps calculated from the view frustum as above, but using different view frustum near and far plane values to place each shadow map.

\$\endgroup\$
  • \$\begingroup\$ What exactly is the inverse world matrix of the light? If I multiply it by a world-space vertex, does that transform that vertex into light space? Or would the regular world matrix of the light do that? \$\endgroup\$ – Justin Apr 28 '14 at 22:22
  • \$\begingroup\$ By "world matrix" I mean the local-to-world matrix. So the inverse of that would go from world space to the light's local space. \$\endgroup\$ – Nathan Reed Apr 29 '14 at 2:32
  • \$\begingroup\$ Could I use gluLookAt(lightDirection, (0,0,0), up) to generate the matrix that transforms from world to light space? \$\endgroup\$ – Justin Apr 29 '14 at 18:53
  • \$\begingroup\$ @Justin Yes, I think that's right. The light's local space should be set up like a camera at the light location, looking toward the scene. \$\endgroup\$ – Nathan Reed Apr 29 '14 at 19:53
  • \$\begingroup\$ You say "then you only allow the frustum to move around in discrete steps of 1/1024th of the frustum size." Do you move it around in light space or in world space? \$\endgroup\$ – Justin May 19 '14 at 22:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.