Sign in to follow this  
xytor

Shadow mapping problem

Recommended Posts

xytor    136
Quote:
Original post by Hodgman
Looks like you're using the wrong view matrix when rendering the shadow, or when projecting the shadow-map.

How do you calculate your matrices?


I calculate the view matrix in c++ and pass it to the shader:


D3DXMatrixLookAtLH(&worldToLight,&(light.pos),&(light.dir),&(Vec3(0,1,0)));

Share this post


Link to post
Share on other sites
smasherprog    568
that is your lookat, but you need to create your projection matrix too and combine them. Usually, you create an orthographic projection. Good luck..

http://www.d3dcoder.net/d3d10.aspx

For some good information and code on how to do shadow mapping, download part III, unzip it, and check the folder chapter 13. It is about shadow mapping.

Share this post


Link to post
Share on other sites
xytor    136
Quote:
Original post by smasherprog
that is your lookat, but you need to create your projection matrix too and combine them. Usually, you create an orthographic projection.


Hmm, yes, I have that, too:


D3DXMatrixPerspectiveFovLH(&lightProjection,
D3DXToRadian(90),
(FLOAT)resX / (FLOAT)resY,
NEAR_PLANE,
FAR_PLANE);



Share this post


Link to post
Share on other sites
MJP    19786
Your view matrix is wrong. The LookAt parameter isn't a direction vector, it's a point at which the camera looks. So you want to do this:

D3DXVECTOR3 lookAt = light.pos + light.dir;
D3DXMatrixLookAtLH(&worldToLight,&(light.pos),&lookAt,&(Vec3(0,1,0)));





Share this post


Link to post
Share on other sites
xytor    136
Quote:
Original post by MJP
Your view matrix is wrong. The LookAt parameter isn't a direction vector, it's a point at which the camera looks. So you want to do this:

D3DXVECTOR3 lookAt = light.pos + light.dir;
D3DXMatrixLookAtLH(&worldToLight,&(light.pos),&lookAt,&(Vec3(0,1,0)));


Wow, good call. But it doesn't change the incorrect shadows...
Is there anything wrong with my shader code?

Here is the code for rendering the shadow map (which looks correct):

/* posView is the pixel in light space, because the view matrix is temporarily using the light as a "camera"*/
return float4(IN.posView.z,IN.posView.z,IN.posView.z,1.0);





Here is where I use the shadow map texture:

float Shadow = 1.0;
if(shadowMapExists)
{
/*convert pixel position in light space to NDC, then to the
range [0,1]*/

float4 posTex = mul(IN.posLight,lightProjection);
posTex += float4(1,1,1,0);
posTex *= 0.5;

/*use converted pixel position to index into the texture*/
/*sampled values should be between the near plane and far plane;
[1.0,10000]*/

float D = tex2D(shadowSampler,float2(posTex.x,posTex.y)).z;
/* if the pixel value is smaller than the sampled value, it
means there is something in front of the pixel, in light space;
the pixel is in shadow*/

if( IN.posLight.z < D )
Shadow -= 1.0;

}



Share this post


Link to post
Share on other sites
MJP    19786
If you're using a perspective projection you need to perform homogeneous divide-by-w before you can use X and Y as a texture coordinate. This brings the coordinates into the [-1, 1] range which you can then convert to [0, 1] range (which you're already doing).

After that, you also need to flip the y coordinate around. After divide-by-w, y is -1 at the bottom and 1 at the top. This is the opposite of texture coordinates, where 0 is the top and -1 is the bottom. Try this:

float4 posTex = mul(IN.posLight,lightProjection);
posTex /= posTex.w;
posTex += float4(1,1,1,0);
posTex *= 0.5;
posTex.y = 1.0f - posTex.y;

Share this post


Link to post
Share on other sites
xytor    136
Quote:
Original post by MJP
If you're using a perspective projection you need to perform homogeneous divide-by-w before you can use X and Y as a texture coordinate. This brings the coordinates into the [-1, 1] range which you can then convert to [0, 1] range (which you're already doing).

After that, you also need to flip the y coordinate around. After divide-by-w, y is -1 at the bottom and 1 at the top. This is the opposite of texture coordinates, where 0 is the top and -1 is the bottom. Try this:

float4 posTex = mul(IN.posLight,lightProjection);
posTex /= posTex.w;
posTex += float4(1,1,1,0);
posTex *= 0.5;
posTex.y = 1.0f - posTex.y;


Cool, I did not know that.

The shadows actually look decent now, but there are a lot of problems with them. The biggest problem are the z-values.

Currently, I use 1/pixelInLightSpace.z for both the shadow map and testing against the shadow map. This is because the biggest differences are now in the [0,1] range. However, it is still non-linear and causes problems.

Some other problems are:
-Small bands of light between an object and its shadow
-Occasional shadow-light stripes
-Weird behavior when the light is far away from the object

Does anybody know any solutions to these problems?

[Edited by - xytor on October 16, 2010 10:57:42 PM]

Share this post


Link to post
Share on other sites
bushmanpaul    100
1.The small bands of light you are seeing is the inaccuracy in float. This is totally normal.

2.This is also a problem with inaccuracy with float, try adding more bias (might make small bands of light bigger).

http://developer.nvidia.com/object/hwshadowmap_paper.html shows you how to solve this.

3.The further the light is away the higher your shadow Map resolution need to be for you to get better results.If I understand you correctly.

Hope this helps.

Share this post


Link to post
Share on other sites
xytor    136
Quote:
Original post by bushmanpaul
1.The small bands of light you are seeing is the inaccuracy in float. This is totally normal.

2.This is also a problem with inaccuracy with float, try adding more bias (might make small bands of light bigger).

http://developer.nvidia.com/object/hwshadowmap_paper.html shows you how to solve this.

3.The further the light is away the higher your shadow Map resolution need to be for you to get better results.If I understand you correctly.

Hope this helps.


Cool, that helped a lot. But in HLSL, it seems pixel values are clamped to [0,1]. This means that if I store the z-values in a texture, I have to get them in this range. (z-clipNear)/(clipFar-ClipNear) doesn't work because it's non-linear, forcing all the values so close to zero that the shadow map doesnt work. Is there any way (other than 1/z) to render the shadow map?

Share this post


Link to post
Share on other sites
xytor    136
Quote:
Original post by rouncED
you can use a 32 bit floating point texture, use view space z and then thatll work better maybe?


I already use a 32 bit texture: D3DFMT_X8R8G8B8
Also, I already use the view space z. That's why it can be any value between clipNear and clipFar, right?

Share this post


Link to post
Share on other sites
MJP    19786
Quote:
Original post by xytor
Quote:
Original post by rouncED
you can use a 32 bit floating point texture, use view space z and then thatll work better maybe?


I already use a 32 bit texture: D3DFMT_X8R8G8B8
Also, I already use the view space z. That's why it can be any value between clipNear and clipFar, right?


He said 32-bit floating point texture, for example D3DFMT_R32F. 8-bits is not going to have sufficient precision for a shadow map. Plus an integer format is going to be clamped to [0,1], so you can't store view-space z in it.

Share this post


Link to post
Share on other sites
xytor    136
Quote:
Original post by MJP
Quote:
Original post by xytor
Quote:
Original post by rouncED
you can use a 32 bit floating point texture, use view space z and then thatll work better maybe?


I already use a 32 bit texture: D3DFMT_X8R8G8B8
Also, I already use the view space z. That's why it can be any value between clipNear and clipFar, right?


He said 32-bit floating point texture, for example D3DFMT_R32F. 8-bits is not going to have sufficient precision for a shadow map. Plus an integer format is going to be clamped to [0,1], so you can't store view-space z in it.


How does that work? When a pixel shader writes to a texture like that, do I just return a single float instead of a float4? When sampling from it, does the sampler return a single float value instead of float4?

Share this post


Link to post
Share on other sites
MJP    19786
No you still return a float4 from your pixel shader. You can just return 1.0f for the BGA components. When you sample the texture you will still get a float4, but only the first component will be valid. The other 3 will either be 0 or 1, depending on the GPU and driver.

Share this post


Link to post
Share on other sites
xytor    136
Quote:
Original post by MJP
No you still return a float4 from your pixel shader. You can just return 1.0f for the BGA components. When you sample the texture you will still get a float4, but only the first component will be valid. The other 3 will either be 0 or 1, depending on the GPU and driver.


WOA, you guys are amazing, that almost completely solved my shadow mapping problem.
However, there are still a few small problems:

- There is a very thin shadowed line at the base of the spotlight lit area that corresponds to the bottom of the shadow map. Any ideas on how to get rid of that?

- The lighting fails when the light view target is exactly below the light. Why is that?

- Currently, my shadows only are feasible for spot lights. Directional lights fail because the actual shadow map is not large enough (or the fov is not large enough). So, after the borders of the shadow map, the shadows are invalid. Is there any way to make shadow mapping work for directional lights?

Share this post


Link to post
Share on other sites
MJP    19786
In general for a spotlight the projection for the shadow map should be wide enough that the entire cone-shaped area of the light fits within the borders of the shadow map. So if you're sampling past the border then your projection isn't wide enough. You should make the FOV based on the angular attenuation factor you're using for the spotlight, rather than hard-coding it. Aside from that, also make sure that you have POINT sampling enabled when sampling the shadow map.

The reason it fails when the light is vertical is because you hard-coded the up vector of your view matrix as (0,1,0). So when the light's look direction is parallel to that vector (looking straight up or straight down) the view matrix becomes invalid, since the cross product of parallel vectors is zero. A simple fix is to just test for that case and use a different up vector. Or you can use something like this to always generate an up vector that's perpendicular to your lookAt direction.

Shadows for directional lights are a broad topic, and it's not trivial to implement them properly and avoid aliasing problems. Since a directional light is global you can't just make a view and projection based on a light position and direction like you can with a spot light. Instead you have to fit an orthographic projection to the main camera's view frustum. On top of that you also need to a technique to reduce perspective aliasing caused by the non-rectangular shape of the view frustum. Cascaded shadow maps (CSM) are the most popular for this. If you do some searches on the forum or on Google you should be able to find more information about directional shadow maps in general, and CSM.

Share this post


Link to post
Share on other sites
xytor    136
Quote:
Original post by MJP
In general for a spotlight the projection for the shadow map should be wide enough that the entire cone-shaped area of the light fits within the borders of the shadow map. So if you're sampling past the border then your projection isn't wide enough. You should make the FOV based on the angular attenuation factor you're using for the spotlight, rather than hard-coding it. Aside from that, also make sure that you have POINT sampling enabled when sampling the shadow map.

The reason it fails when the light is vertical is because you hard-coded the up vector of your view matrix as (0,1,0). So when the light's look direction is parallel to that vector (looking straight up or straight down) the view matrix becomes invalid, since the cross product of parallel vectors is zero. A simple fix is to just test for that case and use a different up vector. Or you can use something like this to always generate an up vector that's perpendicular to your lookAt direction.

Shadows for directional lights are a broad topic, and it's not trivial to implement them properly and avoid aliasing problems. Since a directional light is global you can't just make a view and projection based on a light position and direction like you can with a spot light. Instead you have to fit an orthographic projection to the main camera's view frustum. On top of that you also need to a technique to reduce perspective aliasing caused by the non-rectangular shape of the view frustum. Cascaded shadow maps (CSM) are the most popular for this. If you do some searches on the forum or on Google you should be able to find more information about directional shadow maps in general, and CSM.


Ok, cool... I'll save directional shadows for later.
Thanks for the tip about point sampling and up vector, they made the shadows almost perfect!

One more problem occurs due to percentage closer filtering and possibly using back-faces for shadow map:
When two objects are touching (so without shadows, the boundary is imperceptible), there is a thin shadow along the border. What could be causing this?

[Edited by - xytor on October 17, 2010 4:36:32 PM]

Share this post


Link to post
Share on other sites
bushmanpaul    100
The shadow you are seeing is most probably also a inaccuracy with the float. Using D3D10_CULL_BACK to create the depth map from the lights view(this only works if you are using closed meshes) would usually solve most issues with edges of shadows

Can you please include a screen shot of the problem.

Share this post


Link to post
Share on other sites
xytor    136
Quote:
Original post by bushmanpaul
The shadow you are seeing is most probably also a inaccuracy with the float. Using D3D10_CULL_BACK to create the depth map from the lights view(this only works if you are using closed meshes) would usually solve most issues with edges of shadows

Can you please include a screen shot of the problem.


I am actually using directx 9, so that wouldn't work. Also, the reason I'm rendering the backfaces for the shadow map is because there is less worry about float imprecision.

Here is a screenshot of the problem:

http://imgur.com/nD7Q2

As you can see, due to PCF, the boundary has some shadow that's not supposed to be there.

Share this post


Link to post
Share on other sites
xytor    136
Quote:
Original post by rouncED
dont forget to take ambient light into account and make the shadows translucent, they look much better ;)


I do, but my ambient light is extremely low.

Share this post


Link to post
Share on other sites
bushmanpaul    100
I'm not sure what is the cause of the problem. Do they go away when you don't use PCF ? Is this the only place where it happens when two objects overlap?

Try reducing bias perhaps the back faces are to close to the front and they interfere. When sampling points further away from the origin try to sample them as if they were closer to the light (sampling points then form a sort of cone ).Otherwise I am not sure.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this