1
\$\begingroup\$

I've got shadow mapping with directional lights working, but I believe I am constructing the view/projection matrices wrong.

Here's how I build them:

Mat4 viewMatrix = LookAt(lighting.mCameraPosition + (directionalLight.mLightDirection * Z_FAR / 2.0f), -directionalLight.mLightDirection, Vec3(0.0f, 1.0f, 0.0f));
Mat4 lightVP = CreateOrthographicMatrix(-Z_FAR, Z_FAR, -Z_FAR, Z_FAR, Z_NEAR, Z_FAR) * viewMatrix;

The problem is the shadows become skewed and change depending on the camera position, which is bad. What is the proper way of doing this?

EDIT:

1) No, that is what I'm not sure about; currently as you said it is the camera position and thus the shadows moves around when I move the camera, which is bad - what should be the actual position, since directional lights have no position?

2) Also using Z_FAR DOES make it pixelated which is my second problem - but must not the boundaries be Z_FAR in all directions, otherwise objects in the view range may not cast shadows?

\$\endgroup\$
  • \$\begingroup\$ I hope lighting.mCameraPosition is the position of your light source, otherwise the position of the directional light will change and it will lead to flickering when the camera is moving. Also, what is the value of z_far? If you make an ortho matrix with those ranges, it could easily result in some kind of precision issue. In fact if ortho matrix covers a large enough range, your entire scene might be contained in a couple of texels of the shadow map. \$\endgroup\$ – TravisG Jan 27 '14 at 0:57
  • \$\begingroup\$ updated question \$\endgroup\$ – KaiserJohaan Jan 27 '14 at 18:47
3
\$\begingroup\$

You have two problems here:

First problem:

Adding some kind of position offset to your lookat matrix (currently your camera position) is essentially supposed to center the "light camera" (whatever your light source sees from its view) around that position. This is done because the shadow map will only cover a limited range, but if your shadow maps in one directions extends to 10 [whatever distance unit you have], but objects are positioned at 12[whatever distance unit you have], you obviously still want those objects to cast shadows. So you apply this position offset to all objects via the shadow caster view transform, so they're essentially "pushed into" the visible range of whatever the camera sees.

The reason this causes artifacts if done in a straight forward way is because due to how rasterization works, the amount of pixels that are rasterized for each object are not the same if you change positions of either the objects or of the visible range that the camera sees. The solution to this is to snap that position offset you apply to multiples of the shadow map texel size. I.e. the offset you apply must be a multiple of the shadow map texel size in each direction. You do this by just taking your normal offset, and snapping it to a multiple (either the nearest, or just floor/ceil, doesn't matter I think) of the texel size.

vec3 camPos = lighting.mCameraPosition;
vec2 texelSize = 1.0f/shadowmapResolution;
vec3 snappedCamPos = floor(camPos / texelSize) * texelSize; //this is what you apply instead of lighting.mCameraPosition, in this case I rounded down.

Second problem:

An orthographic projection matrix covers a certain width and height, and since you want all objects that might somehow influence whatever the user camera sees to cast shadows, the naive solution (what you're doing right now) is to make the projection matrix cover such a large range that all objects will definitely be inside it. The problem, however, is that this projection matrix essentially maps the x/y positions of your objects into the [-1,1] range, and the rasterizer will then use this information to actually fill pixels in the texture you're drawing to. For example, if the rendertarget resolution is 2x2 pixels, then all positions falling in [-1,0] in both x and y coordinates will be rasterized to the same corner of that 2x2 shadow map. This of course results an extremely coarse shadow map where barely any detail is visible. In this case, the culprit is the low shadow map resolution, but while your case suffers from the same low-detail problems, the cause is something different. Since your orthographic projection matrix covers such a large range, a huge range of "world space distance" is mapped down to the [-1,1] range and triangles being huge distances apart in world space might be rasterized to the same pixel.

The solution to this is to make sure that your orthographic projection matrix only covers just a large enough range so it sees all objects that might influence the view frustum of the user camera. For now, you can try to manually adjust the range, by taking out z_far and setting manual values (this depends on how large your objects are, etc.), e.g. start with a shadow map from -10 to 10 in all directions and increase or lower the range until the results look right. But that's only a hack to get it working in the beginning, if you want to do it properly you should adjust the range dynamically. Check this article under "Calculating a Tight Projection"

\$\endgroup\$
  • \$\begingroup\$ Should I be using the offset on the position (directionalLight.mLightDirection * Z_FAR / 2.0f) at all? \$\endgroup\$ – KaiserJohaan Jan 28 '14 at 11:11
  • \$\begingroup\$ @KaiserJohaan Not necessarily. That additional part just seems to push the view of what the shadow casters sees a little but further in the view direction of the caster, but the amount is most probably not optimal. You want to push the view a little bit closer to the camera view frustum because otherwise objects might fall outside of the z_near z_far range during rasterization of the shadowmap and not be rendered. But this is all covered in the article I linked. \$\endgroup\$ – TravisG Jan 28 '14 at 20:40

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.