Shadows shimmer when camera moves
- by Chad Layton
I've implemented shadow maps in my simple block engine as an exercise. I'm using one directional light and using the view volume to create the shadow matrices. I'm experiencing some problems with the shadows shimmering when the camera moves and I'd like to know if it's an issue with my implementation or just an issue with basic/naive shadow mapping itself.
Here's a video:
http://www.youtube.com/watch?v=vyprATt5BBg&feature=youtu.be
Here's the code I use to create the shadow matrices. The commented out code is my original attempt to perfectly fit the view frustum. You can also see my attempt to try clamping movement to texels in the shadow map which didn't seem to make any difference. Then I tried using a bounding sphere instead, also to no apparent effect.
public void CreateViewProjectionTransformsToFit(Camera camera, out Matrix viewTransform, out Matrix projectionTransform, out Vector3 position)
{
BoundingSphere cameraViewFrustumBoundingSphere = BoundingSphere.CreateFromFrustum(camera.ViewFrustum);
float lightNearPlaneDistance = 1.0f;
Vector3 lookAt = cameraViewFrustumBoundingSphere.Center;
float distanceFromLookAt = cameraViewFrustumBoundingSphere.Radius + lightNearPlaneDistance;
Vector3 directionFromLookAt = -Direction * distanceFromLookAt;
position = lookAt + directionFromLookAt;
viewTransform = Matrix.CreateLookAt(position, lookAt, Vector3.Up);
float lightFarPlaneDistance = distanceFromLookAt + cameraViewFrustumBoundingSphere.Radius;
float diameter = cameraViewFrustumBoundingSphere.Radius * 2.0f;
Matrix.CreateOrthographic(diameter, diameter, lightNearPlaneDistance, lightFarPlaneDistance, out projectionTransform);
//Vector3 cameraViewFrustumCentroid = camera.ViewFrustum.GetCentroid();
//position = cameraViewFrustumCentroid - (Direction * (camera.FarPlaneDistance - camera.NearPlaneDistance));
//viewTransform = Matrix.CreateLookAt(position, cameraViewFrustumCentroid, Up);
//Vector3[] cameraViewFrustumCornersWS = camera.ViewFrustum.GetCorners();
//Vector3[] cameraViewFrustumCornersLS = new Vector3[8];
//Vector3.Transform(cameraViewFrustumCornersWS, ref viewTransform, cameraViewFrustumCornersLS);
//Vector3 min = cameraViewFrustumCornersLS[0];
//Vector3 max = cameraViewFrustumCornersLS[0];
//for (int i = 1; i < 8; i++)
//{
// min = Vector3.Min(min, cameraViewFrustumCornersLS[i]);
// max = Vector3.Max(max, cameraViewFrustumCornersLS[i]);
//}
//// Clamp to nearest texel
//float texelSize = 1.0f / Renderer.ShadowMapSize;
//min.X -= min.X % texelSize;
//min.Y -= min.Y % texelSize;
//min.Z -= min.Z % texelSize;
//max.X -= max.X % texelSize;
//max.Y -= max.Y % texelSize;
//max.Z -= max.Z % texelSize;
//// We just use an orthographic projection matrix. The sun is so far away that it's rays are essentially parallel.
//Matrix.CreateOrthographicOffCenter(min.X, max.X, min.Y, max.Y, -max.Z, -min.Z, out projectionTransform);
}
And here's the relevant part of the shader:
if (CastShadows)
{
float4 positionLightCS = mul(float4(position, 1.0f), LightViewProj);
float2 texCoord = clipSpaceToScreen(positionLightCS) + 0.5f / ShadowMapSize;
float shadowMapDepth = tex2D(ShadowMapSampler, texCoord).r;
float distanceToLight = length(LightPosition - position);
float bias = 0.2f;
if (shadowMapDepth < (distanceToLight - bias))
{
return float4(0.0f, 0.0f, 0.0f, 0.0f);
}
}
The shimmer is slightly better if I drastically reduce the view volume but I think that's mostly just because the texels become smaller and it's harder to notice them flickering back and forth.
I'd appreciate any insight, I'd very much like to understand what's going on before I try other techniques.