Bug in Microsoft DirectX shadowmap sample?

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

Recommended Posts

In the shadow map sample provided by Microsoft I've noticed an issue where shadows are not properly projected when thin geometry is projected at high angles, see here the shadows being projected, notice the poles from the lights are not projected: http://imgur.com/QwOBa.png And in this screenshot we see things from the lights perspective, not ethe poles are clearly visible: http://imgur.com/k2woZ.png So two questions really, is this an actual bug or a limitation with shadow mapping and if it's a bug how can I fix it? The source is directly from the Microsoft DirectX Sample Browser 'ShadowMap' sample from July 2004, the sample browser is the latest August 2009 one.

Share on other sites
Looks like a bug

To fix it you'll have to figure out what's wrong ;)

Most likely the poles aren't being drawn into the shadowmap. You can verify this by looking at the actual shadow map depth buffer. Check and see that the draw calls for the poles exist - if they don't, then for some reason they're being culled. If they do exist, figure out why they're not writing depth - bad render state (depth write off?), wrong shader? or what

Do they *ever* draw? Say, if you move the light?

Share on other sites
Yes they do actually draw when the light is not being shown at them from a given distance), the screenshot I showed is a bit dramatic.

In this one you see it is drawing fine: http://imgur.com/R2wEC.png

In this one the only thing that has changed is the distance of the light from the cones cylinders, notice the shadows are no longer touching the end of their respective cylinders: http://i.imgur.com/1bfSh.png

And the further the light gets from the objects it's rendering the less of them we end up seeing, this is independent of the projection matrices near/far points, it always has this effect which has led me to beleive it's happening somewhere in the shadowmap calculations:

//-----------------------------------------------------------------------------// Pixel Shader: PixScene// Desc: Process pixel (do per-pixel lighting) for enabled scene//-----------------------------------------------------------------------------float4 PixScene( float2 Tex : TEXCOORD0,                 float4 vPos : TEXCOORD1,                 float3 vNormal : TEXCOORD2,                 float4 vPosLight : TEXCOORD3 ) : COLOR{    float4 Diffuse;    // vLight is the unit vector from the light to this pixel    float3 vLight = normalize( float3( vPos - g_vLightPos ) );    // Compute diffuse from the light    if( dot( vLight, g_vLightDir ) > g_fCosTheta ) // Light must face the pixel (within Theta)    {        // Pixel is in lit area. Find out if it's        // in shadow using 2x2 percentage closest filtering        //transform from RT space to texture space.        float2 ShadowTexC = 0.5 * vPosLight.xy / vPosLight.w + float2( 0.5, 0.5 );        ShadowTexC.y = 1.0f - ShadowTexC.y;        // transform to texel space        float2 texelpos = SMAP_SIZE * ShadowTexC;                // Determine the lerp amounts                   float2 lerps = frac( texelpos );        //read in bilerp stamp, doing the shadow checks        float sourcevals[4];        sourcevals[0] = (tex2D( g_samShadow, ShadowTexC ) + SHADOW_EPSILON < vPosLight.z / vPosLight.w)? 0.0f: 1.0f;          sourcevals[1] = (tex2D( g_samShadow, ShadowTexC + float2(1.0/SMAP_SIZE, 0) ) + SHADOW_EPSILON < vPosLight.z / vPosLight.w)? 0.0f: 1.0f;          sourcevals[2] = (tex2D( g_samShadow, ShadowTexC + float2(0, 1.0/SMAP_SIZE) ) + SHADOW_EPSILON < vPosLight.z / vPosLight.w)? 0.0f: 1.0f;          sourcevals[3] = (tex2D( g_samShadow, ShadowTexC + float2(1.0/SMAP_SIZE, 1.0/SMAP_SIZE) ) + SHADOW_EPSILON < vPosLight.z / vPosLight.w)? 0.0f: 1.0f;                  // lerp between the shadow values to calculate our light amount        float LightAmount = lerp( lerp( sourcevals[0], sourcevals[1], lerps.x ),                                  lerp( sourcevals[2], sourcevals[3], lerps.x ),                                  lerps.y );        // Light it        Diffuse = ( saturate( dot( -vLight, normalize( vNormal ) ) ) * LightAmount * ( 1 - g_vLightAmbient ) + g_vLightAmbient )                  * g_vMaterial;    } else    {        Diffuse = g_vLightAmbient * g_vMaterial;    }    return tex2D( g_samScene, Tex ) * Diffuse;}

I'm not very well versed in shadow maps so I'm not sure what's going wrong here, SHADOW_EPISLON is defined as 0.000005f

I've tweeked the code a bit and found if SHADOW_EPSILON is given a smaller value the issue becomes less prevalent (i.e. the light source can be moved farther away from the geometry its casting shadows against without the shadow detaching). However if I make SHADOW_EPSILON 0 or very close to 0 (like 0.000001f) I end up with artifacts on the screen: http://imgur.com/N3zAN.png

Share on other sites
Oh weird. So this is some pretty serious imprecision. What is the bit depth of the shadow map? 16bit? 24? 32? One option to help this would be to increase the bit depth

The epsilon there is to offset the depth checked in the shadow map to avoid numerical error, which results in inconsistent self-shadowing, or "surface acne", which is exactly the artifact you show in your last screenshot

What are the near and far planes set to for the shadow camera (not the main camera)? It's possible the depth range is way too big, or the near plane is way too close.

You can also try writing a linearized depth in the shadow map to help make the distribution of precision more uniform, and make the epsilon behave better when the light is far away

Share on other sites
Imprecision was my first thought too, however the depth map is rendering to a D3DFMT_R32F texture which as far as I know is the standard texture format to use when rendering a depth map,

// Create the shadow map texture    V_RETURN( pd3dDevice->CreateTexture( ShadowMap_SIZE, ShadowMap_SIZE,                                         1, D3DUSAGE_RENDERTARGET,                                         D3DFMT_R32F,                                         D3DPOOL_DEFAULT,                                         &g_pShadowMap,                                         NULL ) );

Where ShadowMap_SIZE is 512, it is also 512 in the shader as SMAP_SIZE

The sample also creates a depth stencil..

V_RETURN( pd3dDevice->CreateDepthStencilSurface( ShadowMap_SIZE,                                                     ShadowMap_SIZE,                                                     d3dSettings.d3d9.pp.AutoDepthStencilFormat,                                                     D3DMULTISAMPLE_NONE,                                                     0,                                                     TRUE,                                                     &g_pDSShadow,                                                     NULL ) );

I'm not sure if this is releveant to anything specifically.

The projection matrix is created as follows: D3DXMatrixPerspectiveFovLH( &g_mShadowProj, g_fLightFov, 1, 0.01f, 100.0f );

I found that if I did make the near plane larger, say 1, the issue went away however the surface acne (as seen here: http://imgur.com/N3zAN.png) appeared. If I made it something like 0.05f it minimized the occurence of the issue but it still happens, just at slightly larger distances.//

Share on other sites
Yep, it's a big juggling act to deal with the precision. Welcome to shadows ;)

Share on other sites
This is a typical biasing issue. Adding a bias avoids shadow acne, but setting it to high results in the issue that you're seeing. The common remedies are:

1. Use some sort of dynamic bias based on the slope of the receiving triangle
2. Render backfaces into the shadow map (only works if your geometry has backfaces)
3. Tweak the bias based on the scene

Getting good results may require a combination of all 3.