[D3D9] Shadow Map Artifact Problem (with Pictures!)

Started by
10 comments, last by d h k 12 years, 4 months ago
I'm still having problems implementing shadow maps for point lights! I'm at a point where I don't think I can help myself any more - that's why I'm hoping to get some help here.

My problem is that not a single pixel ends up lit. If I turn the logic that determines if a pixel should get lit (dist1 < dist2) around (dist1 > dist2), everything is lit, not a single pixel lies in the shadow.

The shadow map is a cube map that I create with the following properties:


D3DXCreateCubeTexture ( window->GetDevice ( ), 1024, 1, D3DUSAGE_RENDERTARGET, D3DFMT_R32F, D3DPOOL_DEFAULT, &shadow_map );


This is what one of the six faces of my shadow map looks like:

defrend_shadow2.png
Screenshot taken with the help of NVidia's PerfHUD tool...

I simply take the z-component of the pixel in screen-space in the pixel shader and divide it by the far plane (which is the light's radius) in order to create my shadow map. I'm aware of the fact that most implementations don't store linear depth like that for precision reasons, but I'm not quite there yet. That looks correct to me, the other faces of the shadow map look okay like that as well.

Now, this is what I do in the shader I use when I render a sphere as a point light (which works 100% fine for diffuse and specular lighting - just the shadows won't work):

[source lang="cpp"]
texture tex_shadow;
samplerCUBE smp_shadow = sampler_state
{
Texture = <tex_shadow>;
AddressU = CLAMP;
AddressV = CLAMP;
MinFilter = LINEAR;
MagFilter = LINEAR;
MipFilter = LINEAR;
};

// ...

// in the pixel shader:

// both world_position and light_position are float3s and in world space, they are both used to calculate diffuse and
// specular lighting before these lines so I know they are fine and they are not altered in any way throughout the shader
float3 light_to_pixel = world_position - light_position;

float shadow_map_depth = texCUBE ( smp_shadow, normalize ( light_to_pixel ) ).r;
float shadow_factor = length ( light_to_pixel ) < ( shadow_map_depth + 0.05f );
[/source]

I got that code snippet from a post by MJP around here somewhere I believe. I understand the method behind it.

This is how I send the shadow (cube) map to the point light shader:


shader_point_light->effect->SetTexture ( "tex_shadow", shadow_map );


Note 1: 0.05f is a random shadow bias guess, -0.05f as well as different values do not make a difference. But, if I add a very high value to shadow_map_depth such as 50.0f, I get this result:

defrend_shadow3.png

This leads me to believe shadow_map_depth (ie. the call to texCUBE) might always return 0 for all pixels maybe?

NOTE 2: Unfortunately, PIX (which I would usually use to debug shaders) is crashing on me every time I click "Debug Pixel Shader" in a pixel's history. It used to work fine for me [s]until I took an arrow to the knee [/s]but now for whatever reason it doesn't anymore. I am not aware of any other means of debugging shaders. I am running the d3d9 debug libraries on maximum level and I don't have any functions failing or any other relevant reports!

Any ideas what could be going wrong here? I have read and followed several papers on the matter but have not found a solution to my problem so far. What am I missing? If there's something more you need or want to see, let me know and I'll post it! Thanks ahead of time as always!
Advertisement
Just some quick things I noticed: You create the cubemap without mip levels, so you should set Mipfilter = None.
Since you do your shadow calculation in world space, you should store the world space distance in the cubemap length(vertposworld.xyz - lightposworld.xyz);
Also you say you divide the distance by light radius, but I see no back conversion...
Wow, that was a stupid mistake on my part, haha. I've been stuck on this for days - sometimes you just need a second pair of eyes it seems. You were right, I didn't actually bother bringing the 0..1 range depth back into the 0..light_radius range. I have fixed that and followed your other advice and I'm getting correct shadow mapping now - except for HEAVY artifacts that I don't know how to get rid of. Take a look at this:

defrend_shadow4.png

I followed the GPU Gems advice and done the following:


// in the pixel shader that fills my shadow map
float light_length = length ( input.world_position - light_position );
output.color.rgba = dot ( light_length, light_length );

// in the pixel shader that draws the point light
float3 light_to_pixel = world_position - light_position;
float light_length = length ( light_to_pixel );

float fDistSquared = dot ( light_length, light_length );
float fDepthBias = 0.00005f;
float fDepth = fDistSquared - fDepthBias;
float3 vShadowSample = texCUBE ( smp_shadow, normalize ( light_to_pixel ) );
float shadow_factor = ( fDepth - vShadowSample.x < 0.0f ) ? 1.0f : 0.0f;


That, however, looks exactly the same compared to when I was just simply storing the distance straight-up. Any insight on how to best remove those nasty artifacts?
Holy shadow acne :) Unfortunately I don't know the ultimate solution for it (maybe someone else does?) From the GPU Gems article:

(fDistSquared * DepthBias) - vShadowSample.x—this method works best in practice.[/quote]

There also are a bunch of other optimizations possible:


// in the pixel shader that fills my shadow map
float3 lightToPos = input.world_position - light_position;
output.color.rgba = dot(lightToPos, lightToPos) / lightRadSq;




// in the pixel shader that draws the point light
float3 light_to_pixel = world_position - light_position;
float fDistSquared = dot(light_to_pixel, light_to_pixel);
float fDepth = fDistSquared / lightRadSq;
float fDepthBias = 0.99f;
float vShadowSample = texCUBE(smp_shadow, light_to_pixel).x; // normalization not needed AFAIK
float shadow_factor = vShadowSample > fDepth * fDepthBias;


You should play with the bias value to get the desired results.
Hi!

Indeed, you can improve a little your depth bias.
Finding a good bias is a matter of setting the near and far plane right during capturing of the shadow map (that's a precision problem) and for the most part the artifacts arise due to quantization errors (and are actually slope-dependent).
A couple of days ago, I wrote about exactly that problem right here. I linked to some presentations that might give you some insights and ideas how to get rid of that problem.

Hope this helps!
Render the back faces during the shadow-creation process, not the front faces.
This will largely eliminate your problem, but a bias is still necessary for some edge cases.


L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid


Render the back faces during the shadow-creation process, not the front faces.
This will largely eliminate your problem, but a bias is still necessary for some edge cases.

Yes, this often helps hiding the problem.
Although you simply move the artifact to the backface… But on the backface it is usually darker, right? :)
So, it is probably a little less noticeable there.
You could also try to use the the distance to the closest cubemap face(2nd row) instead to the center of the light source(1st row):
omniSM_reducedAcne.png
Using froop's code I managed to get rid of a lot of the artifacts.

Switching the culling to render only back faces did also solve the artifacts problem but gave wrong shadows for some areas of the map (the floor which is a single quad in the sponza atrium scene for example).

I'm not sure how to apply the code snippet from the GPU Gems article:


(fDistSquared * DepthBias) - vShadowSample.x—this method works best in practice.


@Bummel: I'm afraid I don't understand your suggestion, could you elaborate?

Oh and one more question:

Is there any way to make shadow maps be less of a performance killer? With one shadow-casting light I don't yet notice any actual performance problems but each light requires me to draw the scene SIX more times, that's quite heavy isn't it? With 4 lights that would be a total of rendering the scene 25 times, 4*6 to fill each shadow map and then once to the back buffer from the camera's point of view. That can't be good for the frame rate.

I'm not sure how to apply the code snippet from the GPU Gems article:


I had already applied it in my code :)

(fDistSquared * DepthBias) - vShadowSample.x < 0.0f

is the same as

vShadowSample.x > (fDistSquared * DepthBias)


is there any way to make shadow maps be less of a performance killer?


Well, if they're static, render them only once :) Dynamic shadowed lights are always pretty expensive. Do you use frustum culling while rendering from the lights view? You can try rendering a lower poly version of the model into the shadow maps. Sponza atrium is a very big mesh :) There also are techniques like dual paraboloid shadow mapping where you render less shadow maps.

This topic is closed to new replies.

Advertisement