Jump to content
  • Advertisement
Sign in to follow this  
mickeyren

directional light shadow map

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

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi Would anyone share some code snippet on how to implement directional shadow map? Unfortunately the one I have is from Nvidia's site perspective shadow map example and it could get pretty hairy. Perhaps a little code snippet that uses the D3DXMatrixOrtho**** function to build a projection matrix for the directional light and the computation/shader code necessary? Many thanks.

Share this post


Link to post
Share on other sites
Advertisement
I use the same shaders for spot and directional shadow maps. The only difference is the projection (LookAt for spot, Ortho for directional). There's no need to change anything else unless you want to add specific features to your directional shadow mapping functions.

This is how a build the matrices (Pascal):


case Light.Style of

// Spotlight (field-of-view projection)
z3dlsSpot:
begin
// Build the light view matrix
FPosDir:= z3DFloat3.From(Direction).Normalize.Add(Position);
FViewMatrix.LookAt(Position, FPosDir);
// Build the perspective projection matrix
FProjMatrix.PerspectiveFOV(DegToRad(180-FAngle), 1, 0.01, FRange+10);
FViewProjMatrix:= z3DMatrix.From(FViewMatrix).Multiply(FProjMatrix);
end;

// Directional light (orthogonal projection)
z3dlsDirectional:
begin
// Build the view matrix
FPosDir:= z3DFloat3.From(Direction).Normalize;
Position.From(z3DGlobalEngine.CameraPosition).Add(z3DFloat3.From(FPosDir).Scale(-25));
FViewMatrix.LookAt(Position, FPosDir.Add(Position));
// Build the orthogonal projection matrix
FProjMatrix.Ortho(50, 50, 0.01, 50);
FViewProjMatrix:= z3DMatrix.From(FViewMatrix).Multiply(FProjMatrix);
end;




I use a orthogonal projection with a 50 meter cover area (the camera sees dynamic shadowed objects at a max range of 25m in every direction). For the static shadows, I just use lightmaps and radiosity.

The "LookAt", "PerspectiveFOV" and "Ortho" functions uses the same parameters that the D3DX functions (D3DXMatrixLookAtLH, D3DXMatrixPerspectiveFOVLH, D3DXMatrixOrthoLH).

Hope this helps.

Share this post


Link to post
Share on other sites
You might want to check out this sample from the XNA Creator's Club website. It uses XNA obviously, but you should have no problem at all following along and translating it to C++ and native D3D. And of course, the shader code is directly usable.

Share this post


Link to post
Share on other sites
Hi guys

Thanks for trying to help.

I think I have implemented it correctly:



However, the quality is unacceptable. I couldn't run the xna demo so I'm not sure if its example suffers also from the jagged edges. Also, why does the jagged edges looks so... big?

Perhaps i should go ahead and creating a bounding frustum to limit what the directional light sees and have the light camera always look at point 0,0,0 would this help? Meaning if I want to have a different light angle i should move the light's position but not where it looks at?

Let me know if you guys have some ideas in how to improve the quality of edges, otherwise I'll try some of the filtering techniques found in the net - perhaps this one: http://www.gamedev.net/reference/articles/article2193.asp.

Share this post


Link to post
Share on other sites
That's a common result of using shadow mapping, and there are various ways to soften the edges, though I'm not really conversant with most of them. I'm currently trying to find a clean way to implement Variance Shadow Maps, which have nice edges and few depth-bias artifacts, but have problems of their own. And I cannot find a good implementation of VSM that uses directional lighting either :(

Hopefully someone will chime in with a solution to your problem, and I can give up on VSM and adopt that :)

[EDIT] I was going to suggest PCF, but I now see that's what you linked to. Thanks for the link!

Share this post


Link to post
Share on other sites
As Takuan mentioned, that's a common result of shadow maps. The problem is that your shadow map has a finite resolution, and when the resolution is such that a single shadowmap texel covers an area larger than 1 pixel on the screen you will see visible aliasing. There's two kinds of solutions to this problem:

1. Improve shadowmap resolution. The obvious way to do this is to increase the size of your shadowmap. The more texels you have, the smaller they will be on-screen and the less aliasing will be present. However this will obviously be expensive, since you'll be using more memory, fillrate, and bandwidth. The less obvious way to do this is to optimize how your shadowmap covers the view frustum. There are a few techniques for this, the most notable being perspective shadow maps and Cascaded Shadow Maps (also known as parallel-split shadow maps. Of the two I greatly prefer CSM's, I feel they give better results with less corner cases.

2. Use filtering to reduce aliasing. Unfortunately with shadowmaps we can't just use hardware filtering, since you want avg(shadowMapDepth > surfaceDepth) and not avg(shadowMapDepth) > surfaceDepth. Using the latter produces incorrect results...in fact they tend to look a bit like that screenshot you posted which makes me suspect you're using linear filtering rather than point filtering. The former can be done in shaders using percentage-closer filtering, or PCF. The algorithm is pretty simple...in code it looks like this:

// Calculates the shadow occlusion using bilinear PCF
float CalcShadowTermPCF(float fLightDepth, float2 vTexCoord)
{
float fShadowTerm = 0.0f;

// transform to texel space
float2 vShadowMapCoord = g_vShadowMapSize * vTexCoord;

// Determine the lerp amounts
float2 vLerps = frac(vShadowMapCoord);

// read in bilerp stamp, doing the shadow checks
float fSamples[4];

fSamples[0] = (tex2D(shadowMapSampler, vTexCoord).x + BIAS < fLightDepth) ? 0.0f: 1.0f;
fSamples[1] = (tex2D(shadowMapSampler, vTexCoord + float2(1.0/g_vShadowMapSize.x, 0)).x + BIAS < fLightDepth) ? 0.0f: 1.0f;
fSamples[2] = (tex2D(shadowMapSampler, vTexCoord + float2(0, 1.0/g_vShadowMapSize.y)).x + BIAS < fLightDepth) ? 0.0f: 1.0f;
fSamples[3] = (tex2D(shadowMapSampler, vTexCoord + float2(1.0/g_vShadowMapSize.x, 1.0/g_vShadowMapSize.y)).x + BIAS < fLightDepth) ? 0.0f: 1.0f;

// lerp between the shadow values to calculate our light amount
fShadowTerm = lerp( lerp( fSamples[0], fSamples[1], vLerps.x ),
lerp( fSamples[2], fSamples[3], vLerps.x ),
vLerps.y );

return fShadowTerm;
}



For that function vTexCoord is the texture coordinate used to sample the shadowmap. PCF can also be extended to more than 4 samples, using a technique known as edge-tap smoothing. The code for that looks like this:

float CalcShadowTermSoftPCF(float fLightDepth, float2 vTexCoord, int iSqrtSamples)
{
float fShadowTerm = 0.0f;

float fRadius = (iSqrtSamples - 1.0f) / 2;
float fWeightAccum = 0.0f;

for (float y = -fRadius; y <= fRadius; y++)
{
for (float x = -fRadius; x <= fRadius; x++)
{
float2 vOffset = 0;
vOffset = float2(x, y);
vOffset /= g_vShadowMapSize;
float2 vSamplePoint = vTexCoord + vOffset;
float fDepth = tex2D(shadowMapSampler, vSamplePoint).x;
float fSample = (fLightDepth <= fDepth + BIAS);

// Edge tap smoothing
float xWeight = 1;
float yWeight = 1;

if (x == -fRadius)
xWeight = 1 - frac(vTexCoord.x * g_vShadowMapSize.x);
else if (x == fRadius)
xWeight = frac(vTexCoord.x * g_vShadowMapSize.x);

if (y == -fRadius)
yWeight = 1 - frac(vTexCoord.y * g_vShadowMapSize.y);
else if (y == fRadius)
yWeight = frac(vTexCoord.y * g_vShadowMapSize.y);

fShadowTerm += fSample * xWeight * yWeight;
fWeightAccum = xWeight * yWeight;
}
}

fShadowTerm /= (iSqrtSamples * iSqrtSamples);

return fShadowTerm;
}



In that function, iSqrtSamples is the square-root of the number of PCF samples to use. So for 25 samples, you'd use iSqrtSamples = 5.

Takuan also mentioned VSM, which is a technique that allows you to filter shadowmaps in light space. This means you can use linear or anisotropic filtering, mip maps, or even pre-blur the shadow maps. The paper for that is found here. I believe there's a few samples floating around that you could find, otherwise I van try to help you out with it (I have it implemented in my engine, but it's a bit dependent on other parts so I'd have to take some time isolating it). Same goes for CSM, which I could also help you out with.

[Edited by - MJP on November 29, 2008 5:16:55 PM]

Share this post


Link to post
Share on other sites
This thread is a little bit funny... we all learn whats its like to implement shadow maps for the first time, and say "Wow its looks awful!". Plain old shadow maps almost never look good.

MJP covered most of what is needed, but I would add one thing: don't overlook hardware shadow maps (both NVIDA cards and ATI have some version), which may be considerably faster than using a floating-point buffer and doing manual filtering in the pixel shader. Nvidia for instance uses depth buffers and does a hardware filter kernel, so the filtering is almost free and the depth rendering is cheaper.

Also, another method that's often ignored is logarithmic shadow maps, which distort the shadow map in the vertex shader without the expense and complication of split frustum methods. No method is ideal however.

Share this post


Link to post
Share on other sites
For example, I'm using a render texture of 1024x1024 for directional shadow maps and I'm rendering an area of 50x50x50 units (meters) using hardware shadow mapping when available. These are my results (ignore the artifacts, it's a screenshot taken on debug mode):



So yes, it's usual to see that kind of rough edges. You may find useful using a PCF filter and implementing hardware shadow mapping when available to improve performance.
I'm using a 16 pass filter on the screenshot (4x4, 4 on hardware (PCF) and 4 on pixel shader (PCF 2x)).

Share this post


Link to post
Share on other sites
Quote:
Original post by Matt Aufderheide
MJP covered most of what is needed, but I would add one thing: don't overlook hardware shadow maps (both NVIDA cards and ATI have some version), which may be considerably faster than using a floating-point buffer and doing manual filtering in the pixel shader.

So-called "hardware shadow maps" are just 2x2 bilinear PCF. Certainly use it when available (all DX10 hardware supports this via SampleCmp on any fp32 texture), but it's just a PCF implementation detail.

Quote:
Original post by Matt Aufderheide
Also, another method that's often ignored is logarithmic shadow maps, which distort the shadow map in the vertex shader without the expense and complication of split frustum methods.

Logarithmic shadow maps require changes to the rasterizer though which is, uhh, "infeasible" on current GPUs :).

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!