Question

I'm currently having an issue with directional light shadow maps from a moving (sun-like) light source.

When I initially implemented, the light projection matrix was computed as 3D, and the shadow map appears beautifully. I then learned that for what I'm trying to do, an orthographic projection would work better, but I'm having a hard time substituting the proper projection matrix.

Each tick, the sun moves a certain amount along a circle, as one would expect. I use a homegrown "lookAt" method to determine the proper viewing matrix. So, for instance, daylight occurs from 6AM to 6PM. When the sun is at the 9AM position (45 degrees) it should look at the origin and render the shadow map to the framebuffer. What appears to be happening with the orthographic projection is that it doesn't "tilt down" toward the origin. It simply keeps looking straight down the Z axis instead. Things look fine at 6AM and 6PM, but 12 noon, for instance, show absolutely nothing.

Here's how I'm setting things up:

Original 3D projection matrix:

Matrix4f projectionMatrix = new Matrix4f();
float aspectRatio = (float) width / (float) height;

float y_scale = (float) (1 / cos(toRadians(fov / 2f)));
float x_scale = y_scale / aspectRatio;
float frustum_length = far_z - near_z;

projectionMatrix.m00 = x_scale;
projectionMatrix.m11 = y_scale;
projectionMatrix.m22 = (far_z + near_z) / (near_z - far_z);
projectionMatrix.m23 = -1;
projectionMatrix.m32 = -((2 * near_z * far_z) / frustum_length);

LookAt method:

public Matrix4f lookAt( float x, float y, float z,
                      float center_x, float center_y, float center_z ) {
  Vector3f forward = new Vector3f( center_x - x, center_y - y, center_z - z );
  Vector3f up      = new Vector3f( 0, 1, 0 );

  if ( center_x == x && center_z == z && center_y != y ) {
    up.y = 0;
    up.z = 1;
  }

  Vector3f side = new Vector3f();

  forward.normalise();

  Vector3f.cross(forward, up, side );
  side.normalise();

  Vector3f.cross(side, forward, up);
  up.normalise();

  Matrix4f multMatrix = new Matrix4f();
  multMatrix.m00 = side.x;
  multMatrix.m10 = side.y;
  multMatrix.m20 = side.z;
  multMatrix.m01 = up.x;
  multMatrix.m11 = up.y;
  multMatrix.m21 = up.z;
  multMatrix.m02 = -forward.x;
  multMatrix.m12 = -forward.y;
  multMatrix.m22 = -forward.z;

  Matrix4f translation = new Matrix4f();
  translation.m30 = -x;
  translation.m31 = -y;
  translation.m32 = -z;

  Matrix4f result = new Matrix4f();

  Matrix4f.mul( multMatrix, translation, result );
  return result;
}

Orthographic projection (using width 100, height 75, near 1.0, far 100 ) I've tried this with many many different values:

Matrix4f projectionMatrix = new Matrix4f();

float r = width * 1.0f;
float l = -width;
float t = height * 1.0f;
float b = -height;

projectionMatrix.m00 = 2.0f / ( r - l );
projectionMatrix.m11 = 2.0f / ( t - b );
projectionMatrix.m22 = 2.0f / (far_z - near_z);
projectionMatrix.m30 = - ( r + l ) / ( r - l );
projectionMatrix.m31 = - ( t + b ) / ( t - b );
projectionMatrix.m32 = -(far_z + near_z) / (far_z - near_z);
projectionMatrix.m33 = 1;

Shadow map vertex shader:

#version 150 core

uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;

in vec4 in_Position;

out float pass_Position;

void main(void) {
  gl_Position = projectionMatrix * viewMatrix * modelMatrix * in_Position;
  pass_Position = gl_Position.z;
}

Shadow map fragment shader:

#version 150 core

in vec4 pass_Color;
in float pass_Position;

layout(location=0) out float fragmentdepth;

out vec4 out_Color;

void main(void) {
  fragmentdepth = gl_FragCoord.z;
}

I feel that I'm missing something very simple here. As I said, this works fine with a 3D projection matrix, but I want the shadows constant as the user travels across the world, which makes sense for directional lighting, and thus orthographic projection.

Was it helpful?

Solution

Actually, who told you that using an orthographic projection matrix would be a good idea for shadow maps? This might work for things like the sun, which are effectively infinitely far away, but for local lights perspective is very relevant. You have to be careful with perspective projection and shadow maps though, because the sample frequency varies with distance and you wind up getting a lot of precision at some distances and not enough at others unless you use things like cascading or perspective warping in general; this is probably more than you should be thinking about at the moment though :)

Also, orthographic projection matrices are no more or no less 3D than perspective, insofar as they work by projecting a 3D "image" onto a 2D viewing plane... the only difference between them and perspective is that parallel lines remain parallel. Put another way, (x,y,near) and (x,y,far) ideally project to the same position on screen in an orthographic projection.


Your use of gl_FragCoord.z in the fragment shader is unusual. Since this is the value that is written to the depth buffer, you might as well write NOTHING in your fragment shader and re-use the depth buffer. Unless your implementation does not support a floating-point depth buffer you are wasting memory bandwidth by writing the depth to two places. A depth-only pass with glColorMask (GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE) will usually get you much higher throughput when constructing shadow maps.

If you actually used the value of pass_Position (which is your non-perspective corrected Z coordinate in clip-space), I could see using a separate color attachment to write this, but you're writing the perspective-correct depth-range adjusted depth (gl_FragDepth) currently.



In any case, when the sun is directly overhead and you are using orthographic projection it is to be expected that no shadows are cast. This goes back to the property I explained earlier where parallel lines remain parallel. Since the distance an object is from the sun has no affect on where the object is projected (orthographically), if it is directly overhead you will not see any shadows. Try tracking the sun's position along a sphere instead of a circle to minimize this.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top