#region File Description
//-----------------------------------------------------------------------------
// ShadowMapping.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion
#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
#endregion
namespace ShadowMapping
{
///
/// Sample showing how to implement a simple shadow mapping technique where
/// the shadow map always contains the contents of the viewing frustum
///
public class ShadowMappingGame : Microsoft.Xna.Framework.Game
{
#region Constants
// The size of the shadow map
// The larger the size the more detail we will have for our entire scene
const int shadowMapWidthHeight = 2048;
const int windowWidth = 800;
const int windowHeight = 480;
#endregion
#region Fields
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
// Starting position and direction of our camera
Vector3 cameraPosition = new Vector3(0, 70, 100);
Vector3 cameraForward = new Vector3(0, -0.4472136f, -0.8944272f);
BoundingFrustum cameraFrustum = new BoundingFrustum(Matrix.Identity);
// Light direction
Vector3 lightDir = new Vector3(-0.3333333f, 0.6666667f, 0.6666667f);
KeyboardState currentKeyboardState;
GamePadState currentGamePadState;
// Our two models in the scene
Model gridModel;
Model dudeModel;
float rotateDude = 0.0f;
// The shadow map render target
RenderTarget2D shadowRenderTarget;
// Transform matrices
Matrix world;
Matrix view;
Matrix projection;
// ViewProjection matrix from the lights perspective
Matrix lightViewProjection;
#endregion
#region Initialization
public ShadowMappingGame()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.PreferredBackBufferWidth = windowWidth;
graphics.PreferredBackBufferHeight = windowHeight;
float aspectRatio = (float)windowWidth / (float)windowHeight;
projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
aspectRatio,
1.0f, 1000.0f);
}
///
/// LoadContent will be called once per game and is the place to load
/// all of your content.
///
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
// Load the two models we will be using in the sample
gridModel = Content.Load("grid");
dudeModel = Content.Load("dude");
// Create floating point render target
shadowRenderTarget = new RenderTarget2D(graphics.GraphicsDevice,
shadowMapWidthHeight,
shadowMapWidthHeight,
false,
SurfaceFormat.Single,
DepthFormat.Depth24);
}
#endregion
#region Update and Draw
///
/// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input, and playing audio.
///
/// Provides a snapshot of timing values.
protected override void Update(GameTime gameTime)
{
HandleInput(gameTime);
UpdateCamera(gameTime);
base.Update(gameTime);
}
///
/// This is called when the game should draw itself.
///
/// Provides a snapshot of timing values.
protected override void Draw(GameTime gameTime)
{
// Update the lights ViewProjection matrix based on the
// current camera frustum
lightViewProjection = CreateLightViewProjectionMatrix();
GraphicsDevice.BlendState = BlendState.Opaque;
GraphicsDevice.DepthStencilState = DepthStencilState.Default;
// Render the scene to the shadow map
CreateShadowMap();
// Draw the scene using the shadow map
DrawWithShadowMap();
// Display the shadow map to the screen
DrawShadowMapToScreen();
base.Draw(gameTime);
}
#endregion
#region Methods
///
/// Creates the WorldViewProjection matrix from the perspective of the
/// light using the cameras bounding frustum to determine what is visible
/// in the scene.
///
/// The WorldViewProjection for the light
Matrix CreateLightViewProjectionMatrix()
{
// Matrix with that will rotate in points the direction of the light
Matrix lightRotation = Matrix.CreateLookAt(Vector3.Zero,
-lightDir,
Vector3.Up);
// Get the corners of the frustum
Vector3[] frustumCorners = cameraFrustum.GetCorners();
// Transform the positions of the corners into the direction of the light
for (int i = 0; i < frustumCorners.Length; i++)
{
frustumCorners[i] = Vector3.Transform(frustumCorners[i], lightRotation);
}
// Find the smallest box around the points
BoundingBox lightBox = BoundingBox.CreateFromPoints(frustumCorners);
Vector3 boxSize = lightBox.Max - lightBox.Min;
Vector3 halfBoxSize = boxSize * 0.5f;
// The position of the light should be in the center of the back
// pannel of the box.
Vector3 lightPosition = lightBox.Min + halfBoxSize;
lightPosition.Z = lightBox.Min.Z;
// We need the position back in world coordinates so we transform
// the light position by the inverse of the lights rotation
lightPosition = Vector3.Transform(lightPosition,
Matrix.Invert(lightRotation));
// Create the view matrix for the light
Matrix lightView = Matrix.CreateLookAt(lightPosition,
lightPosition - lightDir,
Vector3.Up);
// Create the projection matrix for the light
// The projection is orthographic since we are using a directional light
Matrix lightProjection = Matrix.CreateOrthographic(boxSize.X, boxSize.Y,
-boxSize.Z, boxSize.Z);
return lightView * lightProjection;
}
///
/// Renders the scene to the floating point render target then
/// sets the texture for use when drawing the scene.
///
void CreateShadowMap()
{
// Set our render target to our floating point render target
GraphicsDevice.SetRenderTarget(shadowRenderTarget);
// Clear the render target to white or all 1's
// We set the clear to white since that represents the
// furthest the object could be away
GraphicsDevice.Clear(Color.White);
// Draw any occluders in our case that is just the dude model
// Set the models world matrix so it will rotate
world = Matrix.CreateRotationY(MathHelper.ToRadians(rotateDude));
// Draw the dude model
DrawModel(dudeModel, true);
// Set render target back to the back buffer
GraphicsDevice.SetRenderTarget(null);
}
///
/// Renders the scene using the shadow map to darken the shadow areas
///
void DrawWithShadowMap()
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
GraphicsDevice.SamplerStates[1] = SamplerState.PointClamp;
// Draw the grid
world = Matrix.Identity;
DrawModel(gridModel, false);
// Draw the dude model
world = Matrix.CreateRotationY(MathHelper.ToRadians(rotateDude));
DrawModel(dudeModel, false);
}
///
/// Helper function to draw a model
///
/// The model to draw
/// The technique to use
void DrawModel(Model model, bool createShadowMap)
{
string techniqueName = createShadowMap ? "CreateShadowMap" : "DrawWithShadowMap";
Matrix[] transforms = new Matrix[model.Bones.Count];
model.CopyAbsoluteBoneTransformsTo(transforms);
// Loop over meshs in the model
foreach (ModelMesh mesh in model.Meshes)
{
// Loop over effects in the mesh
foreach (Effect effect in mesh.Effects)
{
// Set the currest values for the effect
effect.CurrentTechnique = effect.Techniques[techniqueName];
effect.Parameters["World"].SetValue(world);
effect.Parameters["View"].SetValue(view);
effect.Parameters["Projection"].SetValue(projection);
effect.Parameters["LightDirection"].SetValue(lightDir);
effect.Parameters["LightViewProj"].SetValue(lightViewProjection);
if (!createShadowMap)
effect.Parameters["ShadowMap"].SetValue(shadowRenderTarget);
}
// Draw the mesh
mesh.Draw();
}
}
///
/// Render the shadow map texture to the screen
///
void DrawShadowMapToScreen()
{
spriteBatch.Begin(0, BlendState.Opaque, SamplerState.PointClamp, null, null);
spriteBatch.Draw(shadowRenderTarget, new Rectangle(0, 0, 128, 128), Color.White);
spriteBatch.End();
GraphicsDevice.Textures[0] = null;
GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
}
#endregion
#region Handle Input
///
/// Handles input for quitting the game.
///
void HandleInput(GameTime gameTime)
{
float time = (float)gameTime.ElapsedGameTime.TotalMilliseconds;
currentKeyboardState = Keyboard.GetState();
currentGamePadState = GamePad.GetState(PlayerIndex.One);
// Rotate the dude model
rotateDude += currentGamePadState.Triggers.Right * time * 0.2f;
rotateDude -= currentGamePadState.Triggers.Left * time * 0.2f;
if (currentKeyboardState.IsKeyDown(Keys.Q))
rotateDude -= time * 0.2f;
if (currentKeyboardState.IsKeyDown(Keys.E))
rotateDude += time * 0.2f;
// Check for exit.
if (currentKeyboardState.IsKeyDown(Keys.Escape) ||
currentGamePadState.Buttons.Back == ButtonState.Pressed)
{
Exit();
}
}
///
/// Handles input for moving the camera.
///
void UpdateCamera(GameTime gameTime)
{
float time = (float)gameTime.ElapsedGameTime.TotalMilliseconds;
// Check for input to rotate the camera.
float pitch = -currentGamePadState.ThumbSticks.Right.Y * time * 0.001f;
float turn = -currentGamePadState.ThumbSticks.Right.X * time * 0.001f;
if (currentKeyboardState.IsKeyDown(Keys.Up))
pitch += time * 0.001f;
if (currentKeyboardState.IsKeyDown(Keys.Down))
pitch -= time * 0.001f;
if (currentKeyboardState.IsKeyDown(Keys.Left))
turn += time * 0.001f;
if (currentKeyboardState.IsKeyDown(Keys.Right))
turn -= time * 0.001f;
Vector3 cameraRight = Vector3.Cross(Vector3.Up, cameraForward);
Vector3 flatFront = Vector3.Cross(cameraRight, Vector3.Up);
Matrix pitchMatrix = Matrix.CreateFromAxisAngle(cameraRight, pitch);
Matrix turnMatrix = Matrix.CreateFromAxisAngle(Vector3.Up, turn);
Vector3 tiltedFront = Vector3.TransformNormal(cameraForward, pitchMatrix *
turnMatrix);
// Check angle so we cant flip over
if (Vector3.Dot(tiltedFront, flatFront) > 0.001f)
{
cameraForward = Vector3.Normalize(tiltedFront);
}
// Check for input to move the camera around.
if (currentKeyboardState.IsKeyDown(Keys.W))
cameraPosition += cameraForward * time * 0.1f;
if (currentKeyboardState.IsKeyDown(Keys.S))
cameraPosition -= cameraForward * time * 0.1f;
if (currentKeyboardState.IsKeyDown(Keys.A))
cameraPosition += cameraRight * time * 0.1f;
if (currentKeyboardState.IsKeyDown(Keys.D))
cameraPosition -= cameraRight * time * 0.1f;
cameraPosition += cameraForward *
currentGamePadState.ThumbSticks.Left.Y * time * 0.1f;
cameraPosition -= cameraRight *
currentGamePadState.ThumbSticks.Left.X * time * 0.1f;
if (currentGamePadState.Buttons.RightStick == ButtonState.Pressed ||
currentKeyboardState.IsKeyDown(Keys.R))
{
cameraPosition = new Vector3(0, 50, 50);
cameraForward = new Vector3(0, 0, -1);
}
cameraForward.Normalize();
// Create the new view matrix
view = Matrix.CreateLookAt(cameraPosition,
cameraPosition + cameraForward,
Vector3.Up);
// Set the new frustum value
cameraFrustum.Matrix = view * projection;
}
#endregion
}
}