Jump to content
  • Advertisement
Sign in to follow this  

Orthographic projection for shadow mapping?

This topic is 2774 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello. I am working on shadow mapping and it works pretty nice with perspective projection. However, the light source should be directional, so I need orthographic projection. From the papers I read, they mostly just say "use ortho projection" without any example code. My question is: how should I set glOrtho parameters so that the projection is equivalent to this perspective projection?

Share this post

Link to post
Share on other sites
Typically with a directional light (like the sun) you'll come up with a new orthographic projection based on where the camera is and which way it's facing. The idea is that you figure out how wide and how tall your projection needs to be so that the entirety of the view frustum is contained in the area being rendered to the shadow map.

Doing this isn't terribly difficult, but it can be a little tricky. The basic algorithm goes like this:

-Figure out the world-space location of all 8 corners of the view frustum (this tutorial can show you how to do it)

-Find the centroid of the frustum (add all points together, divide by 8)

-Start at the centroid, and move back in the opposite direction of the light by an amount equal to the camera's farClip. This is the temporary working position for the light

-Create a view matrix using a LookAt function. The position is the temporary working position derived in the last step, the lookAt point is the centroid.

-Use this view matrix to find the location of the frustum corners with respect to our temporary working position (multiply the positions with the view matrix)

-Loop through the 8 light-space frustum corners we just calculated, and find the Min and Max X, Y, and Z.

-Create an off-center orthographic projection matrix (using glOrtho) based on the min and max values. Left would be min X, Right would be the max X, Bottom would be min Y, Top would be max Y, near would -maxZ, far would -minZ.

-Use these view and projection matrices to render geometry to the shadow buffer.

Share this post

Link to post
Share on other sites
Actually, there's a catch here. Yes, that algorithm will give you a light-aligned box that fits the camera's view frustum, but it may need to be more than that.

There may be shadow-casters that fall outside the camera's view frustum, yet their shadow can be seen. If you fail to take these into account, they could end up outside the light's frustum, get discarded, don't write into the depth buffer and thus don't cast a shadow.

So you may need to move the near clip plane further back (towards to light) to render all shadow casters. How you determine this depends on what you're doing in your application (i.e., what the shadow casters are).

Share this post

Link to post
Share on other sites
Now when I read MJP's post, it looks like it's exactly what I found on gpwiki:

// loop through all vertices in terrain and find min and max points with respect to screen space
float minX = 999999, maxX = -999999;
float minY = 999999, maxY = -999999;
double X, Y, Z;

// get pointer to terrain vertices
float *vertices = Terrain.vertices()->lock();

for(int i = y0-1; i < y1+1; i++)
if(i < 0) continue;
for(int j = x0-1; j < x1+1; j++)
if(j < 0) continue;
int index = i * Terrain.size() + j;

// get screen coordinates for current vertex
static GLint viewport[4];
static GLdouble modelview[16];
static GLdouble projection[16];
static GLfloat winX, winY, winZ;

glGetDoublev( GL_MODELVIEW_MATRIX, modelview );
glGetDoublev( GL_PROJECTION_MATRIX, projection );
glGetIntegerv( GL_VIEWPORT, viewport );

gluProject(vertices[index*3+0], vertices[index*3+1], vertices[index*3+2], modelview, projection, viewport, &X, &Y, &Z);

if(X < minX) minX = X;
if(X > maxX) maxX = X;
if(Y < minY) minY = Y;
if(Y > maxY) maxY = Y;

they loop through the terrain vertices to make sure that whole terrain fits into shadowmap, I think it's the same method, just a little bit simplified (your is for arbitrary camera, I can assume that all shadow casters lie on the terrain). I'll try this first, if it won't work, I'll try your solutions.

Share this post

Link to post
Share on other sites
Yes, that's a good point Mike and I really should have mentioned it. What I posted is really just a base to start from...getting a good projection may require some tweaking for your scene. It's also very common to use techniques Cascaded Shadow Maps to make better use of shadow resolution, so don't expect fantastic results right off the bat.

BTW if you need any code snippets or anything like that just let me know...unfortunately I don't code that's OpenGL but it should be similar enough for you to get the idea.

Share this post

Link to post
Share on other sites
Here's the relevant code from what I use:

// Find the centroid
Vector3 frustumCentroid = new Vector3(0,0,0);
for (int i = 0; i < 8; i++)
frustumCentroid += frustumCornersWS;
frustumCentroid /= 8;

// Position the shadow-caster camera so that it's looking at the centroid,
// and backed up in the direction of the sunlight
const float nearClipOffset = 50.0f;
float distFromCentroid = farZ + nearClipOffset;
Matrix viewMatrix = Matrix.CreateLookAt(frustumCentroid - (light.Direction * distFromCentroid), frustumCentroid, new Vector3(0,1,0));

// Determine the position of the frustum corners in light space
Vector3.Transform(frustumCornersWS, ref viewMatrix, frustumCornersLS);

// Calculate an orthographic projection by sizing a bounding box
// to the frustum coordinates in light space
Vector3 mins = frustumCornersLS[0];
Vector3 maxes = frustumCornersLS[0];
for (int i = 0; i < 8; i++)
if (frustumCornersLS.X > maxes.X)
maxes.X = frustumCornersLS.X;
else if (frustumCornersLS.X < mins.X)
mins.X = frustumCornersLS.X;
if (frustumCornersLS.Y > maxes.Y)
maxes.Y = frustumCornersLS.Y;
else if (frustumCornersLS.Y < mins.Y)
mins.Y = frustumCornersLS.Y;
if (frustumCornersLS.Z > maxes.Z)
maxes.Z = frustumCornersLS.Z;
else if (frustumCornersLS.Z < mins.Z)
mins.Z = frustumCornersLS.Z;

// Create an orthographic camera for use as a shadow caster
OrthographicCamera newCamera;
newCamera = new OrthographicCamera(mins.X, maxes.X, mins.Y, maxes.Y, -maxes.Z - nearClipOffset, -mins.Z);
newCamera.SetViewMatrix(ref viewMatrix);

This is C# and XNA btw...however it should all translate to GL functions and whatever math library you're using. My OrthographicCamera constructor takes the same parameters as glOrtho.

Share this post

Link to post
Share on other sites
No, because mins.z and maxes.z should both be negative already (negating them makes them postive). With right-handed coordinates, points that in front of the camera have a negative Z value.

Share this post

Link to post
Share on other sites
Sign in to follow this  

  • Advertisement

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!