Shadow map depth range seems off

Started by
9 comments, last by MJP 8 years, 2 months ago

I have been stumped by this for some hours now, so I figured I'd just ask here.

I have had cascaded shadow mapping working for directional lights (ie. the sun) for some time now, and I just now implemented spot lights. For shadow mapping I use basically the exact same code as for the directional lights with the obvious exception that I use a perspective projection for the camera used when rendering the depth pass for the spot light.

My problem is that the depth range seems off when rendering the spot lights. If visualizing the shadow map of the sun light and comparing to the shadow map of the spot light, I can only see the objects in the shadow map of the spot lights when extremely close to the objects (ie. when having the spot light extremely close to an object).

For example, my near plane for the spot light camera is 0.1 and the far plane is set to the range of the light (eg. 100). Only when moving the light to within one unit from an object can you actually see any visual change in the shadow map visualization. In comparison, when rendering the directional lights, you can easily make out the objects on the shadow map even when high up in the sky, so to say.

I do get correct shadows with the spotlight but only within a few world units away from the spot light which indeed suggest the depth values are all huddled together really close to the spotlight. Shadows further away from the light starts fading away, which I guess is because of the insufficient resolution of the depth map. Toying with the near/far distances helps a little, but not nearly enough.

I know it's probably difficult for you to say where the problem is, but what would you look at next? Can I somehow scale the depth values into a more suitable range, say between 0.1 and 100 units away from the light source?

Advertisement

.....which indeed suggest the depth values are all huddled together really close to the spotlight.


This is exactly what's happening; the depth buffer is not linear and the distribution of values in it is precisely as you observe.

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls.

^^^ what mhagain said. I would suggest reading through this article for some good background information on the subject.

Also, a common trick for visualizing a perspective depth buffer is to just ignore the range close to the near clip plane and then treat the remaining part as linear. Like this:


float zw = DepthBuffer[pixelPos];  
float visualizedDepth = saturate(zw - 0.9f) * 10.0f;  
You can also compute the original view-space Z value from a depth buffer if you have the original projection matrix used for generating that depth buffer. If you take this and normalize to a [0,1] range use the near and far clip planes, then you get a nice linear value:


float zw = DepthBuffer[pixelPos];
float z = Projection._43 / (zw - Projection._33);
float visualizedDepth = saturate((z - NearClip) / (FarClip - NearClip));

Thanks guys. Yes, I am quite aware that the depth buffer is non-linear (although it may not have been clear from my post). Thanks MJP for those visualizing tips.

The question remains though, what would be the sensible thing to do in a case like this where I seem to be eating up all my depth buffer precision in the first world unit or so in front of the spotlight, and I want to spread that out to say 25 or 100 units?

Cheers!

EDIT: I guess I could try the reverse-Z tip mentioned in the linked nVidia article, but it seems to me that should not be needed and there is something fundamentally wrong with how I am doing things. Suggestions?

What I've done in the past is not render shadow maps to depth at all. Instead using a colour buffer, R16 or R32 is sufficient, and setup the blend equation to max or min, then calculate the distance manually in your shader code and write it as output from your PS. This will effectively emulate a linear depth buffer.

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls.

Interesting, I might look into that. In the meantime I looked at the formats I use for the shadow map. I use the following (and I'll happily admit I took them straight out of Frank Luna's DX11 book):

Texture: DXGI_FORMAT_R24G8_TYPELESS

Depth-stencil-view: DXGI_FORMAT_D24_UNORM_S8_UINT

Shader resource view: DXGI_FORMAT_R24_UNORM_X8_TYPELESS

That would suggest I am using an integer shadow map rather than a floating point one right? (particularily the UINT-part of the DSV). Which means, in my case flipping the depth range would not help. Am I correct?

What happens if you use R32_FLOAT instead?

I always use that for shadowmaps, since stencils aren't needed in shadowmaps it seems like a waste of precision for me smile.png

Also try fiddling with the near plane, what happens if you set it to 0.01 or 0.5?

Last, how do you bias your shadowmap?

.:vinterberg:.

I actually went ahead and tried that, since I wanted to get rid of the stencil part too. So now I have these values:

Texture: DXGI_FORMAT_R32_TYPELESS
Depth-stencil-view: DXGI_FORMAT_D32_FLOAT
Shader resource view: DXGI_FORMAT_R32_FLOAT
These seem more sane to me, but would there be something even more appropriate? Anyway, with these I get the exact same output as before. I then did a quick test to see if I could flip the depth values, but apparently I did not do it right. What I did was.
  • Set the shadow map rendering viewport min depth to 1.0 and max to 0.0
  • Before rendering the shadow map, clear the depth to 0.0
  • Multiply the light camera projection matrix by a "Z reversal matrix" found here: http://dev.theomader.com/depth-precision/
  • Switch the shader to light pixels that have a greater depth rather than a smaller depth value

Did I miss something? Should the above be enough to flip the range and should they work with the new formats I am now using?

As for the biasing, I use the following rasterizer state:

RasterizerState ShadowMapPancakingRS
{
DepthBias = 20000;
DepthBiasClamp = 0.0f;
SlopeScaledDepthBias = 1.0f;
DepthClipEnable = false;
CullMode = None;
};
The DepthClipEnable flag is there for the sunlight shadow maps and makes it so that objects behind the camera are not culled away, ie a way to do "shadow map pancaking".
Finally, like I said before toying with the near plane helps indeed. Currently I have the near plane set to 0.1, but moving it to 1.0 gives me a lot better results with the obvious problem that the near plane is now too far away from the light source.
I appreciate your input, it is nice to bounce these ideas off someone. Thanks!

DepthBiasClamp = 0.0f;

You probably need to set this to some sensible value, or your bias will potentially "oversteer" - at least that's how I've understood it smile.png I think that could be part of your issue!

Those bias settings are a PITA to figure out, so I'm always doing my own biasing in the pixel shader instead (got more control over it, or: at least the illusion of it haha) biggrin.png

Edit: Also, how big is your shadowmap? To me it seems like it'd need to be pretty large to have enough detail for a range of 100.0f??

I'm testing my own project with a 0.1f - 12.0f shadowmap and at 2048x2048 it's somewhat sufficiently detailed..

.:vinterberg:.

Unfortunately adjusting the DepthBiasClamp did not help. You may be right in that a range of 100 units is too large, 25 is probably more like what I actually need. Anyway, for now I think I am just going to expose both the near clip and far clip values for the spot light components and see on a case-by-case basis how to get the best values for each light.

What about the texture formats I mentioned? Those seem ok?

Cheers!

This topic is closed to new replies.

Advertisement