## Recommended Posts

george7378    1441

Hi everyone!

I'm beginning to get fed up with my simple shadow that I'm getting by flattening my geometry onto a plane, so at the moment I'd love more than anything to get a good shadow map working. I've read enough about HLSL to understand what a pixel/vertex shader does, the syntax used to create them, and the .fx file format used to hold the shaders and compile them as a technique. I think I'd be able to have a good stab at implementing shadow mapping if I could find a nice, clean example of a bare-bones shader. The one in the SDK example (I'm using D3D9, by the way) seems to contain a lot of unnecessary stuff which isn't related to the shadow map itself, and a lot of other examples online also contain a lot of stuff which is related specifically to the scene in the demo program in question.

So, is anyone willing to post a shadow map shader/.fx file in its simplest form for me to have a mess around with? Or failing that, is there a good example or tutorial out there which would allow me to build my own?

Thanks, I'd really like to get good shadows working in my game, and I think I'm getting close if I can make this first step!

##### Share on other sites

I can relate to the frustration of using flattenned shadows, since that's what I've been using few yrs back and then switched to Shadow Mapping and couldn't be happier !

I would definitely recommend the Shadow Mapping example from the XNA Samples, since it's drop-dead simple to use and tweak.

It took me a mere single evening to load up that sample, debug through it and reimplement the technique into my engine and tweak it to a much higher quality.

Due to the similarities between XNA / DX9 I don't suppose you could run into any issues converting that sample.

Note, that understanding just the shaders is one thing. You also need an understanding of the Render Targets and that you practically have 2 sets of shaders - one for just rendering them into the ShadowMap and the other set that actually applies the ShadowMap to your geometry.

So, I'd make sure you understand, conceptually, how Shadow Mapping works first - since there's quite a lot of stuff that can (and will) go wrong.

The XNA Sample for SM : http://xbox.create.msdn.com/en-US/education/catalog/sample/shadow_mapping_1

##### Share on other sites
george7378    1441

Yeah, the flattened shadows work OK for simple stuff - I managed to make them work better by using the stencil buffer, disabling z-writes in certain places and by making them follow the height of my terrain, but when I've seen what shadow mapping can do, it makes me wish I could use it!

It's encouraging that you managed to get it going so quickly - it was sort of the same story with DirectInput - it only took an evening to take a sample and change/optimise some stuff to get fully working controller support in my game.

I'll take a look at the sample and see what I can do!

##### Share on other sites

Frankly, the Stencil stuff is probably even more complex and you get all kinds of terrible artifacts when your mesh is not "closed" and has holes. And you can't do self-shadowing of trees with Shadow Volumes.

Look here for the self-shadowed tree I got using SM : http://cloud-2.steampowered.com/ugc/596979776527790985/066CF9742ECC3539A073337F27E2F3285B3BB2E4/

The best thing - I did not have to do anything ! The damn algorithm just works on everything you throw at it, since it works in Image Space. It does not care if it is a rock, animated character or a tree canopy.

The performance hit I got from using SM is about 7% on an XBOX360 and is practically ~free on PC (assuming a full engine/gameplay load)

It makes me want to cry every single time I think about how much time I spent trying to fix and work around (especially closing the meshes and reexporting them is a nightmare) the principally fundamentally flawed algo like the Shadow Volumes via the Stencil, when we have something as drop-dead simple, beautiful and practically almost free (in terms of a performance hit).

The time when Stencil Shadows were an option is a decade behind us. Just let it die like fixed-function pipeline...

Feel free to ask here, but I found all replies to questions I had during implementation of the SM in my engine directly on the XNA forums. If I remember correctly there were about 20 threads about SM at that time, so it was easy to read through them and find lots of other SM-related info there.

##### Share on other sites
george7378    1441

That's a lovely image - exactly the sort of thing that makes me want to strive for shadow mapping myself! By the way, I went through the tutorial on the website and extracted the info to create this shader:

////////////////////

//Global variables//

////////////////////

float4x4 xWorldViewProjection;

float4x4 xLightsWorldViewProjection;

float4x4 xWorld;

float3 xLightPos;

float xLightPower;

float xAmbient;

Texture xTexture;

//////////////////

//Sampler states//

//////////////////

sampler TextureSampler = sampler_state

{

texture = <xTexture>;

magfilter = LINEAR;

minfilter = LINEAR;

mipfilter= LINEAR;

};

{

magfilter = LINEAR;

minfilter = LINEAR;

mipfilter= LINEAR;

};

//////////////////

//I/O structures//

//////////////////

struct VertexToPixel

{

float4 Position     : POSITION;

float2 TexCoords    : TEXCOORD0;

float3 Normal       : TEXCOORD1;

float3 Position3D   : TEXCOORD2;

};

struct PixelToFrame

{

float4 Color        : COLOR0;

};

struct SMapVertexToPixel

{

float4 Position     : POSITION;

float4 Position2D   : TEXCOORD0;

};

struct SMapPixelToFrame

{

float4 Color : COLOR0;

};

struct SSceneVertexToPixel

{

float4 Position             : POSITION;

float4 Pos2DAsSeenByLight   : TEXCOORD0;

float2 TexCoords            : TEXCOORD1;

float3 Normal               : TEXCOORD2;

float4 Position3D           : TEXCOORD3;

};

struct SScenePixelToFrame

{

float4 Color : COLOR0;

};

////////////////////

//Helper functions//

////////////////////

float DotProduct(float3 lightPos, float3 pos3D, float3 normal)

{

float3 lightDir = normalize(pos3D - lightPos);

return dot(-lightDir, normal);

}

///////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////

{

SMapVertexToPixel Output = (SMapVertexToPixel)0;

Output.Position = mul(inPos, xLightsWorldViewProjection);

Output.Position2D = Output.Position;

return Output;

}

{

SMapPixelToFrame Output = (SMapPixelToFrame)0;

Output.Color = PSIn.Position2D.z/PSIn.Position2D.w;

return Output;

}

{

pass Pass0

{

}

}

//////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////

SSceneVertexToPixel ShadowedSceneVertexShader(float4 inPos : POSITION, float2 inTexCoords : TEXCOORD0, float3 inNormal : NORMAL)

{

SSceneVertexToPixel Output = (SSceneVertexToPixel)0;

Output.Position = mul(inPos, xWorldViewProjection);

Output.Pos2DAsSeenByLight = mul(inPos, xLightsWorldViewProjection);

Output.Normal = normalize(mul(inNormal, (float3x3)xWorld));

Output.Position3D = mul(inPos, xWorld);

Output.TexCoords = inTexCoords;

return Output;

}

{

SScenePixelToFrame Output = (SScenePixelToFrame)0;

float2 ProjectedTexCoords;

ProjectedTexCoords[0] = PSIn.Pos2DAsSeenByLight.x/PSIn.Pos2DAsSeenByLight.w/2.0f + 0.5f;

ProjectedTexCoords[1] = -PSIn.Pos2DAsSeenByLight.y/PSIn.Pos2DAsSeenByLight.w/2.0f + 0.5f;

float diffuseLightingFactor = 0;

if ((saturate(ProjectedTexCoords).x == ProjectedTexCoords.x) && (saturate(ProjectedTexCoords).y == ProjectedTexCoords.y))

{

float realDistance = PSIn.Pos2DAsSeenByLight.z/PSIn.Pos2DAsSeenByLight.w;

if ((realDistance - 1.0f/100.0f) <= depthStoredInShadowMap)

{

diffuseLightingFactor = DotProduct(xLightPos, PSIn.Position3D, PSIn.Normal);

diffuseLightingFactor = saturate(diffuseLightingFactor);

diffuseLightingFactor *= xLightPower;

}

}

float4 baseColor = tex2D(TextureSampler, PSIn.TexCoords);

Output.Color = baseColor*(diffuseLightingFactor + xAmbient);

return Output;

}

{

pass Pass0

{

}

}


I think this may be the thing I'm after - I see what you mean now about the two sets of shaders - the first one renders a depth image and the second one calculates the scene pixel colours based on the depth comparison test.

Now all that's needed is to put it into my game, I guess! I'll see how that goes...

Up until now, I've been able to use stencil shadows without a problem. I knew the limitations, but I didn't need anything more. Now though, I am getting to the stage where I can make scenes which are complex enough to need something like shadow mapping.

I did look at shadow volumes, but it always seemed like an ugly way of doing it - extruding the edges of the mesh? It didn't sound very pretty. Also, I saw that it has problems like, for example, when you want to use it on a low-res sphere, since it works per-edge, you get some horrible artefacts on the seams. Shadow mapping seems to be a per-pixel algorithm by default, which is exactly what we need, because that's how shadows work in real life (sort of!). No extruding edges or flattening geometry - just a case of whether or not the light can see the 'pixel' or not.

I'll see how it goes!

##### Share on other sites
phil_t    8084

I'd agree that shadow mapping is a much more general purpose algorithm than shadow volumes, but I wouldn't call shadow volumes "principally fundamentally flawed". I released my Tank Negotiator game on the Xbox with a shadow volume implementation. It both performed faster and looked better than the shadow map implementation I had. You don't have to deal with aliasing issues, so you get nice sharp edges (which is good for a space game, which mine was). And, on the Xbox at least, texture sampling bandwidth is a frequent bottleneck, which can make good shadow map performance difficult.

Shadow maps are plagued with "jaggies", and the techniques used to fix that problem generally make shadow maps a significant performance hit. On the other hand, they are flexible enough that it is fairly easy to trade performance for quality. That flexibility, and the fact that shadow volumes are difficult to work with for many reasons (mainly to do with restrictions on geometry), usually makes SM the better choice.

##### Share on other sites
george7378    1441
I guess with shadow volumes you always get the same resolution because your geometry doesn't change no matter how far away from your light it gets. But yeah, messing around with that geometry seems like a lot of work!
I guess the larger an area your shadow map covers, the more pixellated the edges become. Same goes for the size of the map texture.

I've been wondering too - since my light is a directional light (out at infinity), how do I modify the algorithm to handle a directional light instead of a positional one? Will it still work?

##### Share on other sites
phil_t    8084

This difference is that point lights use a perspective projection matrix and directional lights use an orthographic projection matrix.

##### Share on other sites
george7378    1441

Yeah - I thought you'd have to do a projection with the light at infinity. What do you do about the light position though for rendering the shadow map?

##### Share on other sites
george7378    1441

OK, I just got it to the point where I can use the first technique in the shader to render the depth as seen from the light:

I'm using one of my old scenes which I made when I first started - a rotating pyramid over a plane. I tried drawing the same geometry immediately afterwards using the second technique along with render targets, but it didn't work - the whole scene was just showing ambient light. I'm not all too sure on exactly what I should be doing with my render targets though (how to select the right one, draw to it, etc...), but I'll do that tomorrow. Here's the depth buffer of the pyramid scene:

##### Share on other sites
george7378    1441

...aaandddd... here it is! A semi-working effect-file shadow mapping implementation!

As you can see it's not perfect yet (for some reason, the pyramid doesn't have any diffuse lighting even though it's facing the light) but wow - I'm so pleased! I am really looking foward to seeing how this performs in my main game project at the moment!

EDIT: I figured out the diffuse problem - I was passing the wrong light location into the shader.

Edited by george7378

##### Share on other sites
antmck2013    113

I have had a lot of trouble getting shadow mapping working with terrain.  I've used code from tutes too.  In fact pretty much the same code as listed further up this page.  I'm using SharpDX which is Directx10/11 based.  Either the entire terrain is in shadow or it's lit.  Looks really odd when the sun moves through a complete cycle.  Still don't have it working.  People tell me to put other objects in the scene and turn off shadows on the terrain, but I only have terrain.  At this point I just want the terrain to be correctly shadowed.  I'd like to do real-time editing and watch the shadows change.

##### Share on other sites
phil_t    8084

What are you using for your depth comparison bias?

##### Share on other sites
antmck2013    113

Actually I've tried several different values - done look right.  Is there a way calculate the ideal value?

##### Share on other sites
george7378    1441

For the offset I just started with a small value and increased it until the nasty effects stopped. For long-range shading on terrain (not counting self shadowing) I can get away with a zero offset. As for the other problems, I guess people can't do anything unless they see some code - I suppose your shader would be a good place to start! I probably can't help much myself because I've only just got it working, but maybe I can spot a mistake that I also made and tell you about it!

##### Share on other sites
antmck2013    113

Here's the complete code for shader.

At the moment I've calculated the normals for each vertex but not using them yet.

As I said before I've tried several bias values.  The shadows just don't look right.

This is self shadows on terrain only - at the moment.

float4x4 WorldMatrix;
float4x4 ViewMatrix;
float4x4 ProjectionMatrix;

float4x4 LightViewMatrix;
float4x4 LightProjectionMatrix;

float maxHeight;           //maximum height of the terrain

Texture2D heightMap;

Texture2D sandTexture;
Texture2D grassTexture;
Texture2D snowTexture;
Texture2D rockTexture;

SamplerState pointSampler;
SamplerState linearSampler;

struct VS_INPUT
{
float4 Position : SV_POSITION;
float2 UV  : TEXCOORD0;
};

struct VS_OUTPUT
{
float4 Position   : SV_POSITION;
float4 Normal   : NORMAL0;
float2 UV    : TEXCOORD0;
float4 TextureWeights : TEXCOORD1; // Weights used for multitexturing
float  Depth            : TEXCOORD2; // Used for texture LOD
float4 LightPosition : TEXCOORD3; // Position in light space

};

{
float4 Position   : SV_POSITION;
float2 UV    : TEXCOORD0;
};

{
float4 Position : SV_POSITION;
float4 Depth    : TEXTURE0;
};

//--------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
VS_OUTPUT VS(VS_INPUT input)
{
float height = heightMap.SampleLevel(pointSampler, input.UV, 0).r;
input.Position.y = height * maxHeight;

float4x4 worldViewProj = mul(mul(WorldMatrix, ViewMatrix), ProjectionMatrix);
float4x4 lightWorldViewProj = mul(mul(WorldMatrix, LightViewMatrix), LightProjectionMatrix);

// Height values of adjacent vertices
float y = input.Position.y;
float N = heightMap.SampleLevel(pointSampler, input.UV, 0, float2(0,-1)).r * maxHeight;
float S = heightMap.SampleLevel(pointSampler, input.UV, 0, float2(0,1)).r * maxHeight;
float W = heightMap.SampleLevel(pointSampler, input.UV, 0, float2(-1,0)).r * maxHeight;
float E = heightMap.SampleLevel(pointSampler, input.UV, 0, float2(1,0)).r * maxHeight;
float NE = heightMap.SampleLevel(pointSampler, input.UV, 0, float2(1,-1)).r * maxHeight;
float SW = heightMap.SampleLevel(pointSampler, input.UV, 0, float2(-1,1)).r * maxHeight;

float3 vectorN = float3(0, N - y, 1);
float3 vectorS = float3(0, S - y, -1);
float3 vectorE = float3(1, E - y, 0);
float3 vectorW = float3(-1, W - y, 0);
float3 vectorNE = float3(1, NE - y, 1);
float3 vectorSW = float3(-1, SW - y, -1);

// Average normal for current vertex
float3 normal = normalize(cross(vectorN, vectorNE) + cross(vectorNE, vectorE) + cross(vectorE, vectorS)
+ cross(vectorS, vectorSW) + cross(vectorSW, vectorW) + cross(vectorW, vectorN));

VS_OUTPUT output = (VS_OUTPUT)0;
output.Position = mul(input.Position, worldViewProj);
output.Normal = mul(float4(normal, 1), WorldMatrix);

float4 texWeights = 0;

texWeights.x = saturate(1.0f - height * 3.0f);
texWeights.y = saturate(1.0f - abs(height - 0.4f) * 3.0f);
texWeights.z = saturate(1.0f - abs(height - 0.9f) * 3.0f);
texWeights.w = 1;

float totalWeight = texWeights.x + texWeights.y + texWeights.z;
texWeights.xyz /= totalWeight;

texWeights.w = 1;

if (normal.y > 0.81f)
{
texWeights.w = 0;
}
else if (normal.y > 0.75)
{
texWeights.w = normal.y;
}

output.TextureWeights = texWeights;
output.UV = input.UV;

output.Depth = output.Position.z/output.Position.w;

output.LightPosition = mul(input.Position, lightWorldViewProj);
//output.LightPosition /= output.LightPosition.w;

return output;
}

//--------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
float4 PS(VS_OUTPUT input) : SV_TARGET
{
float blendDistance = 0.99f;
float blendWidth = 0.005f;
float blendFactor = clamp((input.Depth - blendDistance) / blendWidth, 0, 1);

float4 weights = input.TextureWeights;

float scale = 4;
float4 rock = rockTexture.Sample(linearSampler, input.UV * scale);
float4 sand  = sandTexture.Sample(linearSampler,  input.UV * scale);
float4 grass = grassTexture.Sample(linearSampler, input.UV * scale);
float4 snow  = snowTexture.Sample(linearSampler,  input.UV * scale);
float4 farColour = rock * weights.w + (1 - weights.w) * (sand * weights.x + grass * weights.y + snow * weights.z);

scale = 16;
rock = rockTexture.Sample(linearSampler, input.UV * scale);
sand  = sandTexture.Sample(linearSampler,  input.UV * scale);
grass = grassTexture.Sample(linearSampler, input.UV * scale);
snow  = snowTexture.Sample(linearSampler,  input.UV * scale);
float4 nearColour = rock * weights.w + (1 - weights.w) * (sand * weights.x + grass * weights.y + snow * weights.z);

float4 colour = lerp(nearColour, farColour, blendFactor);

//--------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------

input.LightPosition.xyz /= input.LightPosition.w;

float2 projectedTexCoords;
projectedTexCoords[0] = input.LightPosition.x / 2.0f + 0.5f;
projectedTexCoords[1] = input.LightPosition.y / -2.0f + 0.5f;

if (shadowMapDepth < input.LightPosition.z - 0.0007f)
{
colour *= 0.25;
}

return colour;
}

//--------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
{
input.Position.w = 1.0f;

// Generate terrain height from heightmap
float height = heightMap.SampleLevel(pointSampler, input.UV, 0).r;
input.Position.y = height * maxHeight;

//float4x4 lightWorldViewProj = mul(mul(WorldMatrix, LightViewMatrix), LightProjectionMatrix);

//output.Position = mul(input.Position, lightWorldViewProj);

output.Position = mul(input.Position,  WorldMatrix);
output.Position = mul(output.Position, LightViewMatrix);
output.Position = mul(output.Position, LightProjectionMatrix);

output.Depth = output.Position;

return output;
}

//--------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
{
float zValue = input.Depth.z / input.Depth.w;

return float4(zValue, zValue, zValue, 1.0f);
}

//--------------------------------------------------------------------------------------
// Techniques
//--------------------------------------------------------------------------------------
{
pass Pass0
{
}
}

technique Terrain
{
pass Pass0
{
}
}

Edited by antmck2013