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!