#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 } }