Sorry chaps, you're right. The light is coming from two different directions because those are two different images. Even though I crudely spliced two screenshots together with reckless abandon, the resulting image was actually a perfectly seamless screenshot.
Here's the modified shader I was using. I stripped out all normal mapping and light projection and kept it simple with just a depth map and base texture.
//--------------
// un-tweaks
//--------------
matrix WorldVP:WorldViewProjection;
matrix World:World;
matrix ViewInv:ViewInverse;
matrix ViewMat={0.5,0,0,0.5,0,-0.5,0,0.5,0,0,0.5,0.5,0,0,0,1};
//--------------
// Tweaks
//--------------
float3 Ambient={0.2f,0.2f, 0.2f};
float4 LightPosition={1000.0f,10.0f,25.0f,1.0f};
float3 LightColor={1.0f,1.0f,1.0f};
float LightRange=100.0f;
matrix LightProjMatrix;
float SpecularPow=16.0f;
float4 FogColor={0.5f,0.5f,0.5f,1.0f};
float FogRange=10000.0f;
float Alpha=1.0f;
//--------------
// Textures
//--------------
texture BaseTX <string Name="";>;
sampler2D Base = sampler_state
{
texture = <BaseTX>;
};
texture DepthMapTX <string Name="";>;
sampler2D DepthMap = sampler_state
{
texture = <DepthMapTX>;
};
//--------------
// structs
//--------------
struct IN_Depth
{
float4 Pos:POSITION;
};
struct OUT_Depth
{
float4 OPos:POSITION;
float Depth:TEXCOORD1;
};
struct IN_NormalMap
{
float4 Pos:POSITION;
float2 UV:TEXCOORD;
float3 Normal:NORMAL;
float3 Tangent:TANGENT;
float3 Binormal:BINORMAL;
};
struct OUT_NormalMap
{
float4 OPos:POSITION;
float2 Tex:TEXCOORD0;
float3 LightVec:TEXCOORD1;
float3 Attenuation:TEXCOORD2;
float3 ViewVec:TEXCOORD3;
float4 Proj:TEXCOORD4;
float Fog:FOG;
};
//--------------
// vertex shaders
//--------------
OUT_Depth VS_Depth(IN_Depth IN)
{
OUT_Depth OUT;
OUT.OPos = mul(IN.Pos,WorldVP);
float4 WPosP = mul(IN.Pos,World);
float4 Proj = mul(WPosP,LightProjMatrix);
OUT.Depth = Proj.z;
return OUT;
}
OUT_NormalMap VS_NormalMap(IN_NormalMap IN)
{
OUT_NormalMap OUT;
OUT.OPos=mul(IN.Pos,WorldVP);
OUT.Tex=IN.UV;
float3x3 TBN={IN.Tangent,IN.Binormal,IN.Normal};
TBN=transpose(mul(TBN,World));
float3 WPos=mul(IN.Pos,World);
float3 LightPos=LightPosition-WPos;
float3 ViewPos=ViewInv[3].xyz-WPos;
OUT.LightVec=mul(LightPos,TBN);
OUT.Attenuation=-(LightPos/LightRange);
OUT.ViewVec=mul(ViewPos,TBN);
float4 Wpos = mul(IN.Pos,World);
float4 Proj = mul(Wpos,LightProjMatrix);
OUT.Proj = mul(ViewMat,Proj);
OUT.Fog=1-saturate(dot(ViewPos/FogRange,ViewPos/FogRange));
return OUT;
}
//--------------
// pixel shaders
//--------------
float4 PS_Depth(OUT_Depth IN) : COLOR
{
return (IN.Depth/LightRange)+0.01f;
}
float4 PS_NormalMap(OUT_NormalMap IN) : COLOR
{
float4 Texture=tex2D(Base,IN.Tex);
float3 LightV=normalize(IN.LightVec);
float3 ViewV=normalize(IN.ViewVec);
float3 Light=LightColor*(1-saturate(dot(IN.Attenuation,IN.Attenuation)));
float Depth=(IN.Proj.z/LightRange);
float shadowmap=1-(tex2Dproj(DepthMap,IN.Proj+float4(-0.04,-0.04,0,0)) < Depth ? 0.111f:0.0f);
shadowmap=shadowmap-(tex2Dproj(DepthMap,IN.Proj+float4(0,-0.04,0,0)) < Depth ? 0.111f:0.0f);
shadowmap=shadowmap-(tex2Dproj(DepthMap,IN.Proj+float4(0.04,-0.04,0,0)) < Depth ? 0.111f:0.0f);
shadowmap=shadowmap-(tex2Dproj(DepthMap,IN.Proj+float4(-0.04,0,0,0)) < Depth ? 0.111f:0.0f);
shadowmap=shadowmap-(tex2Dproj(DepthMap,IN.Proj+float4(0,0,0,0)) < Depth ? 0.111f:0.0f);
shadowmap=shadowmap-(tex2Dproj(DepthMap,IN.Proj+float4(0.04,0,0,0)) < Depth ? 0.111f:0.0f);
shadowmap=shadowmap-(tex2Dproj(DepthMap,IN.Proj+float4(-0.04,0.04,0,0)) < Depth ? 0.111f:0.0f);
shadowmap=shadowmap-(tex2Dproj(DepthMap,IN.Proj+float4(0,0.04,0,0)) < Depth ? 0.111f:0.0f);
shadowmap=shadowmap-(tex2Dproj(DepthMap,IN.Proj+float4(0.04,0.04,0,0)) < Depth ? 0.111f:0.0f);
return float4(Texture*((Light*shadowmap)+Ambient),Texture.w);
}
//--------------
// techniques
//--------------
technique DepthMap
{
pass p1
{
VertexShader = compile vs_2_0 VS_Depth();
PixelShader = compile ps_2_0 PS_Depth();
}
}
technique ShadowMapping
{
pass p1
{
vertexShader = compile vs_2_0 VS_NormalMap();
pixelShader = compile ps_2_0 PS_NormalMap();
//FOGCOLOR=(FogColor);
//FOGENABLE=TRUE;
}
}
Unfortunately I made my decision to stick with lightmapping and stripped out all my test code and have returned it to it's previous state, so I can't provide a depth map or anything else. If it helps though, I reset the camera to the exact location the shadow map camera was at and repositioned the same ramp prefabs as they would've appeared in the depth map. I can also confirm the depth map has a decent blend from light to dark grey, with reasonable good contrast (i.e. I could make out the shapes clearly).
I measure the top of the ramp walls as 8 pixels wide in this image. Also the image is thinner than you'd expect because I've stripped out the GUI that was present in this render. There was no GUI in the actual shadow map.
This was as close as I could possibly get. This cuts out quite a lot of the scenery in the game, so really I'd need to pull the cam back even further to make practical use of the shadow mapping.