Strange Behavior with my Shadow Maps

I’ve been working to implement basic shadow mapping to our game, and am currently beating my head against a wall trying to figure out where I’m going wrong. I’m hoping it’s just something small I’m overlooking since I’ve been looking at this code for days now.

I’ve scaled back a huge portion of my own codebase and at this point, I’m more or less using a sample project and using Riemers example with my own assets, but still cannot get this working. It’s in the ballpark, but as you can see in the image there are a few strange behaviors I’m not sure the cause of.


I’m not sure why some objects are shadowing as I’d expect, casting back along the X axis, while others (like the orb) seem to be casting a shadow that seems to be stretching out along the z axis as well.In addition, the “cube” shape in the middle of the level looks like it’s casting a frustum-shaped shadow instead of the hard edges I would expect.

As I mentioned, there is not much custom code left running at this point, it’s primarily the Riemers Shadow Mapping tutorial with a few adjustments here and there.

My XNA code:

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;


namespace XNAseries3
{
	public struct MyOwnVertexFormat
	{
		public Vector3 position;
		private Vector2 texCoord;
		private Vector3 normal;

		public MyOwnVertexFormat( Vector3 position, Vector2 texCoord, Vector3 normal )
		{
			this.position = position;
			this.texCoord = texCoord;
			this.normal = normal;
		}

		public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration
			 (
				 new VertexElement( 0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0 ),
				 new VertexElement( sizeof( float ) * 3, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0 ),
				 new VertexElement( sizeof( float ) * (3 + 2), VertexElementFormat.Vector3, VertexElementUsage.Normal, 0 )
			 );
	}

	public class Game1 : Microsoft.Xna.Framework.Game
	{
		GraphicsDeviceManager graphics;
		GraphicsDevice device;

		SpriteFont _DebugFont;
		Effect effect;
		Matrix viewMatrix;
		Matrix projectionMatrix;
		VertexBuffer vertexBuffer;
		Vector3 cameraPos;
		Texture2D streetTexture;
		//Model lamppostModel;
		//Texture2D[] lamppostTextures;
		//Model carModel;
		//Texture2D[] carTextures;
		Model levelModel;
		Texture2D[] levelTextures;
		Vector3 lightPos;
		float lightPower;
		float ambientPower;
		Matrix lightsViewProjectionMatrix;

		bool _Orbit = false;

		RenderTarget2D renderTarget;
		Texture2D shadowMap;

		public Game1()
		{
			graphics = new GraphicsDeviceManager( this );
			graphics.GraphicsProfile = GraphicsProfile.HiDef;
			Content.RootDirectory = "Content";
		}

		protected override void Initialize()
		{
			graphics.PreferredBackBufferWidth = 1920;
			graphics.PreferredBackBufferHeight = 1280;
			graphics.IsFullScreen = false;
			graphics.ApplyChanges();
			Window.Title = "Riemer's XNA Tutorials -- Series 3";

			base.Initialize();
		}

		protected override void LoadContent()
		{
			device = GraphicsDevice;


			effect = Content.Load<Effect>( "StaticModel" ); SetUpVertices();
			SetUpCamera();

			//levelModel = LoadModel( "lighttest", out levelTextures );
			levelModel = LoadModel( "defaultexportlighttest", out levelTextures );

			streetTexture = Content.Load<Texture2D>( "grass" );
			//carModel = LoadModel( "car", out carTextures );
			//lamppostModel = LoadModel( "lamppost", out lamppostTextures );
			//lamppostTextures[0] = streetTexture;

			PresentationParameters pp = device.PresentationParameters;
			//renderTarget = new RenderTarget2D( device, pp.BackBufferWidth, pp.BackBufferHeight, false, device.DisplayMode.Format, DepthFormat.Depth24 );
			renderTarget = new RenderTarget2D( device, 2048, 2048, false, device.DisplayMode.Format, DepthFormat.Depth24 );
			//renderTarget = new RenderTarget2D( device, 2048, 2048, false, SurfaceFormat.Single, DepthFormat.Depth24 );

			_DebugFont = Content.Load<SpriteFont>( "Fonts/Debug" );
		}

		private Model LoadModel( string assetName, out Texture2D[] textures )
		{

			Model newModel = Content.Load<Model>( assetName );
			textures = new Texture2D[7];
			int i = 0;
			foreach ( ModelMesh mesh in newModel.Meshes )
				foreach ( BasicEffect currentEffect in mesh.Effects )
					textures[i++] = currentEffect.Texture;

			foreach ( ModelMesh mesh in newModel.Meshes )
				foreach ( ModelMeshPart meshPart in mesh.MeshParts )
					meshPart.Effect = effect.Clone();

			return newModel;
		}

		private void SetUpVertices()
		{
			MyOwnVertexFormat[] vertices = new MyOwnVertexFormat[18];

			vertices[0] = new MyOwnVertexFormat( new Vector3( -20, 0, 10 ), new Vector2( -0.25f, 25.0f ), new Vector3( 0, 1, 0 ) );
			vertices[1] = new MyOwnVertexFormat( new Vector3( -20, 0, -100 ), new Vector2( -0.25f, 0.0f ), new Vector3( 0, 1, 0 ) );
			vertices[2] = new MyOwnVertexFormat( new Vector3( 2, 0, 10 ), new Vector2( 0.25f, 25.0f ), new Vector3( 0, 1, 0 ) );
			vertices[3] = new MyOwnVertexFormat( new Vector3( 2, 0, -100 ), new Vector2( 0.25f, 0.0f ), new Vector3( 0, 1, 0 ) );
			vertices[4] = new MyOwnVertexFormat( new Vector3( 2, 0, 10 ), new Vector2( 0.25f, 25.0f ), new Vector3( -1, 0, 0 ) );
			vertices[5] = new MyOwnVertexFormat( new Vector3( 2, 0, -100 ), new Vector2( 0.25f, 0.0f ), new Vector3( -1, 0, 0 ) );
			vertices[6] = new MyOwnVertexFormat( new Vector3( 2, 1, 10 ), new Vector2( 0.375f, 25.0f ), new Vector3( -1, 0, 0 ) );
			vertices[7] = new MyOwnVertexFormat( new Vector3( 2, 1, -100 ), new Vector2( 0.375f, 0.0f ), new Vector3( -1, 0, 0 ) );
			vertices[8] = new MyOwnVertexFormat( new Vector3( 2, 1, 10 ), new Vector2( 0.375f, 25.0f ), new Vector3( 0, 1, 0 ) );
			vertices[9] = new MyOwnVertexFormat( new Vector3( 2, 1, -100 ), new Vector2( 0.375f, 0.0f ), new Vector3( 0, 1, 0 ) );
			vertices[10] = new MyOwnVertexFormat( new Vector3( 3, 1, 10 ), new Vector2( 0.5f, 25.0f ), new Vector3( 0, 1, 0 ) );
			vertices[11] = new MyOwnVertexFormat( new Vector3( 3, 1, -100 ), new Vector2( 0.5f, 0.0f ), new Vector3( 0, 1, 0 ) );
			vertices[12] = new MyOwnVertexFormat( new Vector3( 13, 1, 10 ), new Vector2( 0.75f, 25.0f ), new Vector3( 0, 1, 0 ) );
			vertices[13] = new MyOwnVertexFormat( new Vector3( 13, 1, -100 ), new Vector2( 0.75f, 0.0f ), new Vector3( 0, 1, 0 ) );
			vertices[14] = new MyOwnVertexFormat( new Vector3( 13, 1, 10 ), new Vector2( 0.75f, 25.0f ), new Vector3( -1, 0, 0 ) );
			vertices[15] = new MyOwnVertexFormat( new Vector3( 13, 1, -100 ), new Vector2( 0.75f, 0.0f ), new Vector3( -1, 0, 0 ) );
			vertices[16] = new MyOwnVertexFormat( new Vector3( 13, 21, 10 ), new Vector2( 1.25f, 25.0f ), new Vector3( -1, 0, 0 ) );
			vertices[17] = new MyOwnVertexFormat( new Vector3( 13, 21, -100 ), new Vector2( 1.25f, 0.0f ), new Vector3( -1, 0, 0 ) );

			vertexBuffer = new VertexBuffer( device, MyOwnVertexFormat.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly );
			vertexBuffer.SetData( vertices );
		}

		private void SetUpCamera()
		{
			cameraPos = new Vector3( -25, 13, 18 );
			viewMatrix = Matrix.CreateLookAt( cameraPos, new Vector3( 0, 0, 0 ), new Vector3( 0, 1, 0 ) );
			projectionMatrix = Matrix.CreatePerspectiveFieldOfView( MathHelper.PiOver4, device.Viewport.AspectRatio, 1.0f, 200.0f );
		}

		protected override void UnloadContent()
		{
		}

		protected override void Update( GameTime gameTime )
		{
			if ( GamePad.GetState( PlayerIndex.One ).Buttons.Back == ButtonState.Pressed )
				this.Exit();

			if ( Keyboard.GetState().IsKeyDown( Keys.Space ) )
			{
				_Orbit = !_Orbit;
			}

			if ( _Orbit )
			{
				Matrix rotationMatrix = Matrix.CreateRotationY( MathHelper.ToRadians( 1f ) );
				cameraPos = Vector3.Transform( cameraPos, rotationMatrix );
				viewMatrix = Matrix.CreateLookAt( cameraPos, Vector3.Zero, Vector3.Up );
			}
			

			UpdateLightData();

			base.Update( gameTime );
		}


		private void UpdateLightData()
		{
			ambientPower = 0.2f;

			lightPos = new Vector3( -15, 10, 0 );
			lightPower = 1.0f;

			Matrix lightsView = Matrix.CreateLookAt( lightPos, new Vector3( 0, 0, 0 ), new Vector3( 0, 1, 0 ) );
			Matrix lightsProjection = Matrix.CreatePerspectiveFieldOfView( MathHelper.PiOver2, 1f, 5f, 100f );

			//Matrix lightsView = Matrix.CreateLookAt( lightPos, lightPos + new Vector3( 0.5f, -0.5f, 0 ), Vector3.Up );
			//Matrix lightsProjection = Matrix.CreateOrthographic( 1920, 720, 0.1f, 100.0f );

			lightsViewProjectionMatrix = lightsView * lightsProjection;
		}

		protected override void Draw( GameTime gameTime )
		{
			device.SetRenderTarget( renderTarget );
			device.Clear( ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0 );

			DrawScene( "ShadowMap" );

			device.SetRenderTarget( null );
			shadowMap = (Texture2D)renderTarget;

			device.Clear( ClearOptions.Target | ClearOptions.DepthBuffer, Color.DarkSlateBlue, 1.0f, 0 );

			DrawScene( "ShadowedScene" ); 
			//shadowMap = null;

			BlendState prevBlendState = GraphicsDevice.BlendState;
			DepthStencilState prevStencilState = GraphicsDevice.DepthStencilState;

			string lightPosString = string.Format( "Light Pos: {0}, {1}, {2}", lightPos.X, lightPos.Y, lightPos.Z );
			string camPosString = string.Format( "Cam Pos: {0}, {1}, {2}", cameraPos.X, cameraPos.Y, cameraPos.Z );

			using ( SpriteBatch sprite = new SpriteBatch( device ) )
			{
				sprite.Begin();
				sprite.Draw( shadowMap, new Vector2( 0, 0 ), null, Color.White, 0, new Vector2( 0, 0 ), 0.4f, SpriteEffects.None, 1 );
				sprite.End();

				sprite.Begin();
				sprite.DrawString( _DebugFont, lightPosString, new Vector2( 10, 1200 ), Color.Red );
				sprite.DrawString( _DebugFont, camPosString, new Vector2( 10, 1230 ), Color.Red );
				sprite.End();
			}

			GraphicsDevice.BlendState = prevBlendState;
			GraphicsDevice.DepthStencilState = prevStencilState;

			base.Draw( gameTime );
		}

		private void DrawScene( string technique )
		{
			effect.CurrentTechnique = effect.Techniques[technique];
			effect.Parameters["xWorldViewProjection"].SetValue( Matrix.Identity * viewMatrix * projectionMatrix );
			effect.Parameters["xTexture"].SetValue( streetTexture );
			effect.Parameters["xWorld"].SetValue( Matrix.Identity );
			effect.Parameters["xLightPos"].SetValue( lightPos );
			effect.Parameters["xLightPower"].SetValue( lightPower );
			effect.Parameters["xAmbient"].SetValue( ambientPower );
			effect.Parameters["xLightsWorldViewProjection"].SetValue( Matrix.Identity * lightsViewProjectionMatrix );
			effect.Parameters["xShadowMap"].SetValue( shadowMap );

			foreach ( EffectPass pass in effect.CurrentTechnique.Passes )
			{
				pass.Apply();

				device.SetVertexBuffer( vertexBuffer );
				device.DrawPrimitives( PrimitiveType.TriangleStrip, 0, 16 );
			}

			Matrix levelMat = Matrix.CreateRotationX( MathHelper.ToRadians( 90.0f ) ) * Matrix.CreateTranslation( 0, 1, 0 );
			DrawModel( levelModel, levelTextures, levelMat, technique );
		}

		private void DrawModel( Model model, Texture2D[] textures, Matrix wMatrix, string technique )
		{
			Matrix[] modelTransforms = new Matrix[model.Bones.Count];
			model.CopyAbsoluteBoneTransformsTo( modelTransforms );
			int i = 0;
			foreach ( ModelMesh mesh in model.Meshes )
			{
				foreach ( Effect currentEffect in mesh.Effects )
				{
					Matrix worldMatrix = modelTransforms[mesh.ParentBone.Index] * wMatrix;
					currentEffect.CurrentTechnique = currentEffect.Techniques[technique];
					currentEffect.Parameters["xWorldViewProjection"].SetValue( worldMatrix * viewMatrix * projectionMatrix );
					currentEffect.Parameters["xTexture"].SetValue( textures[i++] );
					currentEffect.Parameters["xWorld"].SetValue( worldMatrix );
					currentEffect.Parameters["xLightPos"].SetValue( lightPos );
					currentEffect.Parameters["xLightPower"].SetValue( lightPower );
					currentEffect.Parameters["xAmbient"].SetValue( ambientPower );
					currentEffect.Parameters["xLightsWorldViewProjection"].SetValue( worldMatrix * lightsViewProjectionMatrix );
					currentEffect.Parameters["xShadowMap"].SetValue( shadowMap );
				}
				mesh.Draw();
			}
		}

	}
}

And my shader code:

float4x4 xWorldViewProjection;
float4x4 xLightsWorldViewProjection;
float4x4 xWorld;
float3 xLightPos;
float xLightPower;
float xAmbient;

Texture xTexture;

sampler TextureSampler = sampler_state { texture = <xTexture>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = mirror; AddressV = mirror; }; Texture xShadowMap;

sampler ShadowMapSampler = sampler_state { texture = <xShadowMap>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; AddressU = clamp; AddressV = clamp; };

struct VertexToPixel
{
	float4 Position     : POSITION;
	float2 TexCoords    : TEXCOORD0;
	float3 Normal        : TEXCOORD1;
	float3 Position3D    : TEXCOORD2;
};

struct PixelToFrame
{
	float4 Color        : COLOR0;
};

float DotProduct(float3 lightPos, float3 pos3D, float3 normal)
{
	float3 lightDir = normalize(pos3D - lightPos);
	return dot(-lightDir, normal);
}

VertexToPixel SimplestVertexShader(float4 inPos : POSITION0, float3 inNormal : NORMAL0, float2 inTexCoords : TEXCOORD0)
{
	VertexToPixel Output = (VertexToPixel)0;

	Output.Position = mul(inPos, xWorldViewProjection);
	Output.TexCoords = inTexCoords;
	Output.Normal = normalize(mul(inNormal, (float3x3)xWorld));
	Output.Position3D = mul(inPos, xWorld);

	return Output;
}

PixelToFrame OurFirstPixelShader(VertexToPixel PSIn)
{
	PixelToFrame Output = (PixelToFrame)0;

	float diffuseLightingFactor = DotProduct(xLightPos, PSIn.Position3D, PSIn.Normal);
	diffuseLightingFactor = saturate(diffuseLightingFactor);
	diffuseLightingFactor *= xLightPower;

	PSIn.TexCoords.y--;
	float4 baseColor = tex2D(TextureSampler, PSIn.TexCoords);
	Output.Color = baseColor * (diffuseLightingFactor + xAmbient);

	return Output;
}

technique Simplest
{
	pass Pass0
	{
		VertexShader = compile vs_4_0 SimplestVertexShader();
		PixelShader = compile ps_4_0 OurFirstPixelShader();
	}
}

struct SMapVertexToPixel
{
	float4 Position     : POSITION;
	float4 Position2D    : TEXCOORD0;
};

struct SMapPixelToFrame
{
	float4 Color : COLOR0;
};


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_4_0 ShadowMapVertexShader();
		PixelShader = compile ps_4_0 ShadowMapPixelShader();
	}
}


struct SSceneVertexToPixel
{
	float4 Position             : POSITION;
	float4 Pos2DAsSeenByLight    : TEXCOORD0;

	float2 TexCoords            : TEXCOORD1;
	float3 Normal                : TEXCOORD2;
	float4 Position3D            : TEXCOORD3;

};

struct SScenePixelToFrame
{
	float4 Color : COLOR0;
};


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);
	//Output.Color.a = 1.0f;

	return Output;
}


technique ShadowedScene
{
	pass Pass0
	{
		VertexShader = compile vs_4_0 ShadowedSceneVertexShader();
		PixelShader = compile ps_4_0 ShadowedScenePixelShader();
	}
}

I very much appreciate any help! As I mentioned I’ve been working on this for several days now so it’s definitely possible I’m doing something completely wrong and overlooking it as many times as I’ve reworked the code to attempt to isolate my issues.

It sounds like you are expecting shadows for a directional light. This code is for a point light. There is a light position. It makes sense that objects cast shadows in different directions.

1 Like

The code is actually meant to be a spotlight, with the lightview and lightProjections matrices acting as the frustum of the light cone.

That said, you’re absolutely correct. The direction of the sphere casting is correct since it’s a spot and not directional. While debugging I was switching back and forth between working with a directional and spotlight and mixed myself up. This is a great case for just taking a break and coming back with a clear head instead of trying to keep pushing at the problem.

Really appreciate you pointing this out! This might not be a case of things really being broken but rather me being a little too brain dead to have been working on it at that point. I’ll do a bit more testing but will probably end up deleting this post as it seems to have been an issue with my expectations and not the code.