Basic shadow mapping shader?

Started by
14 comments, last by antmck2013 11 years ago

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!

Advertisement

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

The SM Tutorial : http://www.riemers.net/eng/Tutorials/XNA/Csharp/Series3/Shadow_map.php

VladR My 3rd person action RPG on GreenLight: http://steamcommunity.com/sharedfiles/filedetails/?id=92951596

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!

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.

VladR My 3rd person action RPG on GreenLight: http://steamcommunity.com/sharedfiles/filedetails/?id=92951596

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;

Texture xShadowMap;





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

//Sampler states//

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

sampler TextureSampler = sampler_state

{

texture = <xTexture>;

magfilter = LINEAR;

minfilter = LINEAR;

mipfilter= LINEAR;

AddressU = mirror;

AddressV = mirror;

};



sampler ShadowMapSampler = sampler_state

{

texture = <xShadowMap>;

magfilter = LINEAR;

minfilter = LINEAR;

mipfilter= LINEAR;

AddressU = clamp;

AddressV = clamp;

};





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

//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);    

}





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

//TECHNIQUE 1: Shaders for building the shadow map itself//

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

SMapVertexToPixel ShadowMapVertexShader( float4 inPos : POSITION)

{

    SMapVertexToPixel Output = (SMapVertexToPixel)0;



    Output.Position = mul(inPos, xLightsWorldViewProjection);

    Output.Position2D = Output.Position;



    return Output;

}



SMapPixelToFrame ShadowMapPixelShader(SMapVertexToPixel PSIn)

{

    SMapPixelToFrame Output = (SMapPixelToFrame)0;            



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



    return Output;

}





technique ShadowMap

{

    pass Pass0

    {

        VertexShader = compile vs_2_0 ShadowMapVertexShader();

        PixelShader = compile ps_2_0 ShadowMapPixelShader();

    }

}





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

//TECHNIQUE 2: Shaders for applying the shadows to the scene//

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

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 ShadowedScenePixelShader(SSceneVertexToPixel PSIn)

 {

     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 depthStoredInShadowMap = tex2D(ShadowMapSampler, ProjectedTexCoords).r;

         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;

 }





technique ShadowedScene

{

    pass Pass0

    {

        VertexShader = compile vs_2_0 ShadowedSceneVertexShader();

        PixelShader = compile ps_2_0 ShadowedScenePixelShader();

    }

}

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!

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.

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?

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

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?

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:

[attachment=14821:depthshader.jpg]

This topic is closed to new replies.

Advertisement