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

## 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 on other sites
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 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 on other sites
Quote:
 Original post by MJPYou might want to check out this sample from the XNA Creator's Club website

I had not realized that sample used a directional light. That could be pretty useful, thanks!

##### 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 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 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 PCFfloat 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 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 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 on other sites
Quote:
 Original post by Matt AufderheideMJP 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 AufderheideAlso, 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 :).

1. 1
2. 2
3. 3
Rutin
15
4. 4
khawk
13
5. 5
frob
12

• 9
• 9
• 11
• 11
• 23
• ### Forum Statistics

• Total Topics
633669
• Total Posts
3013257
×