topical media & game development

talk show tell print

game-xna-intro-XnaShooter-Shaders-ShadowMapShader.cs / cs



  // Project: XnaShooter, File: ShadowMapShader.cs
  // Namespace: XnaShooter.Shaders, Class: ShadowMapShader
  // Path: C:\code\XnaShooter\Shaders, Author: Abi
  // Code lines: 655, Size of file: 19,49 KB
  // Creation date: 07.10.2006 10:23
  // Last modified: 14.10.2006 10:10
  // Generated with Commenter by abi.exDream.com
  
  #region Using directives
  using Microsoft.Xna.Framework;
  using Microsoft.Xna.Framework.Graphics;
  using Microsoft.Xna.Framework.Input;
  using System;
  using System.IO;
  using XnaShooter.Game;
  using XnaShooter.Graphics;
  using XnaShooter.GameScreens;
  using Model = XnaShooter.Graphics.Model;
  using Texture = XnaShooter.Graphics.Texture;
  using XnaShooter.Helpers;
  #endregion
  
  namespace XnaShooter.Shaders
  {
  
<summary> Shadow map shader </summary> public class ShadowMapShader : ShaderEffect { #region Variables <summary> Shadow mapping shader filename </summary> const string ShaderFilename = "ShadowMap.fx";

  
<summary> Shadow map texture we render to. </summary> internal RenderToTexture shadowMapTexture = null;

  
<summary> Restrict near and far plane for much better depth resolution! </summary> const float //good for outdoor: //orthographicWidth = 70, //orthographicHeight = 100, shadowNearPlane = Mission.ViewDistance * 6.5f, shadowFarPlane = Mission.ViewDistance * 9.0f;

  
<summary> Virtual point light parameters for directional shadow map lighting. Used to create a point light position for the directional light. </summary> const float virtualLightDistance = 8.0f * Mission.ViewDistance, virtualVisibleRange = 1.25f * Mission.ViewDistance;

  
<summary> Shadow distance </summary> <returns>Float</returns> public float ShadowDistance { get { return virtualLightDistance; } // get } // ShadowDistance

                  private Vector3 shadowLightPos = Vector3.Zero;
  
  
<summary> Shadow light position </summary> <returns>Vector 3</returns> public Vector3 ShadowLightPos { get { return shadowLightPos; } // get } // ShadowLightPos

                  /*see RenderToTexture!
  
<summary> Shadow map width and height, by default 1024x1024, but 2048x2048 or better would also be great! 4096x4096 would be nice, but I guess that is slow. Used and set in RenderToTexture </summary> internal static int shadowMapWidth = 2048,//1024, shadowMapHeight = 2048;//1024;

  
  
  
<summary> Texel width and height and offset for texScaleBiasMatrix, this way we can directly access the middle of each texel. </summary> float texelWidth = 1.0f / 1024.0f, texelHeight = 1.0f / 1024.0f, texOffsetX = 0.5f, texOffsetY = 0.5f;

  
<summary> Factor for shadow map generation filter, makes shadow map more soft. </summary> const float TexelFilterFactor = 4.0f;//3.0f;//2.5f;

  
<summary> Compare depth bias </summary> internal float compareDepthBias = 0.00025f; //0.0025f; //0.0001f;//0.001f;//0.005f;//0.025f;//0.015f;

  
<summary> Tex extra scale </summary> <returns>1.0f</returns> internal float texExtraScale = 1.0f;//1.0015f;//1.0075f;

  
<summary> Shadow map depth bias value </summary> <returns>+</returns> internal float shadowMapDepthBiasValue = 0.00025f; //0.0005f; //0.00025f;//+0.005f;

  
<summary> The matrix to convert proj screen coordinates in the -1..1 range to the shadow depth map texture coordinates. </summary> Matrix texScaleBiasMatrix;

  
<summary> Used matrices for the light casting the shadows. </summary> internal Matrix lightProjectionMatrix, lightViewMatrix;

  
<summary> Additional xnaEffect handles </summary> private EffectParameter shadowTexTransform, worldViewProjLight, nearPlane, farPlane, depthBias, shadowMapDepthBias, shadowMap, shadowMapTexelSize, shadowDistanceFadeoutTexture;

  
<summary> Shadow map blur post screen shader, used in RenderShadows to blur the shadow results. </summary> internal ShadowMapBlur shadowMapBlur = null; #endregion

                  #region Calc shadow map bias matrix
  
<summary> Calculate the texScaleBiasMatrix for converting proj screen coordinates in the -1..1 range to the shadow depth map texture coordinates. </summary> internal void CalcShadowMapBiasMatrix() { texelWidth = TexelFilterFactor * (1.0f / (float)shadowMapTexture.Width); texelHeight = TexelFilterFactor * (1.0f / (float)shadowMapTexture.Height); texOffsetX = 0.5f + (0.5f / (float)shadowMapTexture.Width); texOffsetY = 0.5f + (0.5f / (float)shadowMapTexture.Height);

                          texScaleBiasMatrix = new Matrix(
                                  0.5f * texExtraScale, 0.0f, 0.0f, 0.0f,
                                  0.0f, -0.5f * texExtraScale, 0.0f, 0.0f,
                                  0.0f, 0.0f, texExtraScale, 0.0f,
                                  texOffsetX, texOffsetY, 0.0f, 1.0f);
                  } // CalcShadowMapBiasMatrix()
                  #endregion
  
                  #region Properties
  
<summary> Shadow map texture </summary> internal RenderToTexture ShadowMapTexture { get { return shadowMapTexture; } // get } // ShadowMapTexture (RenderToTexture) #endregion

                  #region Constructors
  
<summary> Shadow map shader </summary> public ShadowMapShader() : base(ShaderFilename) { if (BaseGame.CanUsePS20) // We use R32F, etc. and have a lot of precision compareDepthBias = 0.0001f; else // A8R8G8B8 isn't very precise, increase comparing depth bias compareDepthBias = 0.0075f;// 0.025f;

                          Load();
  
                          CalcShadowMapBiasMatrix();
  
                          shadowMapBlur = new ShadowMapBlur();
                  } // ShadowMapShader(tryToUsePS20, setBackFaceCheck, setShadowMapSize)
                  #endregion
  
                  #region Dispose
  
<summary> Dispose </summary> public override void Dispose() { base.Dispose(); if (shadowMapTexture != null) shadowMapTexture.Dispose(); shadowMapTexture = null; } // Dispose() #endregion

                  #region Load
  
<summary> Load in case the device got lost. </summary> public override void Load() { base.Load(); // Ok, time to create the shadow map render target if (shadowMapTexture == null) shadowMapTexture = new RenderToTexture( RenderToTexture.SizeType.ShadowMap); } // Load() #endregion

                  #region Get parameters
  
<summary> Get parameters </summary> protected override void GetParameters() { // Can't get parameters if loading failed! if (xnaEffect == null) return;

                          base.GetParameters();
  
                          // Get additional parameters
                          shadowTexTransform = xnaEffect.Parameters["shadowTexTransform"];
                          worldViewProjLight = xnaEffect.Parameters["worldViewProjLight"];
                          nearPlane = xnaEffect.Parameters["nearPlane"];
                          farPlane = xnaEffect.Parameters["farPlane"];
                          depthBias = xnaEffect.Parameters["depthBias"];
                          shadowMapDepthBias = xnaEffect.Parameters["shadowMapDepthBias"];
                          shadowMap = xnaEffect.Parameters["shadowMap"];
                          shadowMapTexelSize = xnaEffect.Parameters["shadowMapTexelSize"];
                          shadowDistanceFadeoutTexture =
                                  xnaEffect.Parameters["shadowDistanceFadeoutTexture"];
                          // Load shadowDistanceFadeoutTexture
                          if (shadowDistanceFadeoutTexture != null)
                                  shadowDistanceFadeoutTexture.SetValue(
                                          new Texture("ShadowDistanceFadeoutMap").XnaTexture);
                  } // GetParameters()
                  #endregion
  
                  #region Update parameters
  
<summary> Update parameters </summary> public override void SetParameters(Material setMat) { // Can't set parameters if loading failed! if (xnaEffect == null) return;

                          /*already set, don't change!
                          shadowNearPlane = 1.0f;
                          shadowFarPlane = 28;
                          virtualLightDistance = 24;
                          virtualVisibleRange = 23.5f;
  			 */
                          if (BaseGame.CanUsePS20)
                          {
                                  compareDepthBias = 0.00075f;//0.00025f;
                                  shadowMapDepthBiasValue = 0.00075f;//0.00025f;
                          } // if (BaseGame.CanUsePS20)
                          else
                          {
                                  // Ps 1.1 isn't as percise!
                                  compareDepthBias = 0.0025f;
                                  shadowMapDepthBiasValue = 0.002f;
                          } // else
  
                          base.SetParameters(setMat);
  
                          // Set all extra parameters for this shader
                          depthBias.SetValue(compareDepthBias);
                          shadowMapDepthBias.SetValue(shadowMapDepthBiasValue);
                          shadowMapTexelSize.SetValue(
                                  new Vector2(texelWidth, texelHeight));
                          nearPlane.SetValue(shadowNearPlane);
                          farPlane.SetValue(shadowFarPlane);
                  } // UpdateShadowParameters()
                  #endregion
  
                  #region Create simple directional shadow mapping matrix
  
<summary> Calc simple directional shadow mapping matrix </summary> private void CalcSimpleDirectionalShadowMappingMatrix() { // Put light for directional mode away from origin (create virutal point // light). But adjust field of view to see enough of the visible area. // Note: Using light that is 10 times farther away to appear more ortogonal! float virtualFieldOfView = (float)Math.Atan2( virtualVisibleRange, virtualLightDistance);//*10);

                          // Set projection matrix for light
                          BaseGame.ProjectionMatrix = lightProjectionMatrix =
                                  Matrix.CreatePerspectiveFieldOfView(
                                  virtualFieldOfView,
                                  1.0f,
                                  shadowNearPlane,
                                  shadowFarPlane);// * 12);
                                  /*kills depth.z values
                                  Matrix.CreateOrthographic(
                                  orthographicWidth,
                                  orthographicHeight,
                                  shadowNearPlane,
                                  shadowFarPlane);
                                  /*obs
                                  Matrix.CreatePerspectiveFieldOfView(
                                  // Don't use graphics fov and aspect ratio in directional lighting mode
                                  MathHelper.Pi*0.1f,//virtualFieldOfView,
                                  1.0f,
                                  shadowNearPlane,
                                  shadowFarPlane);
  				 */
  
                          // Calc light look pos, put it a little bit in front of our car
                          Vector3 shadowLookPos = Mission.LookAtPosition + new Vector3(-8, +6, 3);
  
                          // Well, this is how directional lights are done:
                          BaseGame.ViewMatrix = lightViewMatrix = Matrix.CreateLookAt(
                                  // Use our current car position for our light look at origin!
                                  shadowLookPos +
                                  BaseGame.LightDirection * virtualLightDistance,// * 10,//virtualLightDistance,
                                  shadowLookPos,
                                  new Vector3(0, 1, 0));
  
                          // Update light pos
                          Matrix invView = Matrix.Invert(lightViewMatrix);
                          shadowLightPos = new Vector3(invView.M41, invView.M42, invView.M43);
                  } // CalcSimpleDirectionalShadowMappingMatrix()
                  #endregion
  
                  #region Generate shadow
  
<summary> Update shadow world matrix. Calling this function is important to keep the shaders WorldMatrix and WorldViewProjMatrix up to date. </summary> <param name="setWorldMatrix">World matrix</param> internal void UpdateGenerateShadowWorldMatrix(Matrix setWorldMatrix) { Matrix world = setWorldMatrix; WorldMatrix = world; WorldViewProjMatrix = world * lightViewMatrix * lightProjectionMatrix; xnaEffect.CommitChanges(); } // UpdateGenerateShadowWorldMatrix(setWorldMatrix)

  
<summary> Generate shadow </summary> internal void GenerateShadows(BaseGame.RenderDelegate renderObjects) { // Can't generate shadow if loading failed! if (xnaEffect == null) return;

                          // This method sets all required shader variables.
                          this.SetParameters();
                          Matrix remViewMatrix = BaseGame.ViewMatrix;
                          Matrix remProjMatrix = BaseGame.ProjectionMatrix;
                          CalcSimpleDirectionalShadowMappingMatrix();
                          
                          DepthStencilBuffer remBackBufferSurface = null;
                          // Time to generate the shadow texture
                          try
                          {
                                  // Start rendering onto the shadow map
                                  shadowMapTexture.SetRenderTarget();
                                  if (shadowMapTexture.ZBufferSurface != null)
                                  {
                                          remBackBufferSurface = BaseGame.Device.DepthStencilBuffer;
                                          BaseGame.Device.DepthStencilBuffer =
                                                  shadowMapTexture.ZBufferSurface;
                                  } // if (shadowMapTexture.ZBufferSurface)
  
                                  // Make sure depth buffer is on
                                  BaseGame.Device.RenderState.DepthBufferEnable = true;
                                  // Disable alpha
                                  BaseGame.Device.RenderState.AlphaBlendEnable = false;
  
                                  // Clear render target
                                  shadowMapTexture.Clear(Color.White);
  
                                  //tst:
                                  //ShaderEffect.normalMapping.RenderSinglePassShader(renderObjects);
  
                                  if (BaseGame.CanUsePS20)
                                          xnaEffect.CurrentTechnique = xnaEffect.Techniques["GenerateShadowMap20"];
                                  else
                                          xnaEffect.CurrentTechnique = xnaEffect.Techniques["GenerateShadowMap"];
  
                                  // Render shadows with help of the GenerateShadowMap shader
                                  RenderSinglePassShader(renderObjects);
  
                                  /*
                                  xnaEffect.Begin(SaveStateMode.None);
                                  foreach (EffectPass pass in xnaEffect.CurrentTechnique.Passes)
                                  {
                                          pass.Begin();
                                          renderObjects();
                                          pass.End();
                                  } // foreach (pass)
  				 */
                          } // try
                          finally
                          {
                                  //xnaEffect.End();
  
                                  // Resolve the render target to get the texture (required for Xbox)
                                  shadowMapTexture.Resolve();
  
                                  // Set render target back to default
                                  BaseGame.ResetRenderTarget(false);
                                  
                                  if (shadowMapTexture.ZBufferSurface != null)
                                          BaseGame.Device.DepthStencilBuffer = remBackBufferSurface;
  
                                  /*
  if DEBUG
                                  //tst: show light shadow view pos
                                  if (Input.Keyboard.IsKeyDown(Keys.Tab) == false)
  endif
                                  {
  				 */
                                          BaseGame.ProjectionMatrix = remProjMatrix;
                                          BaseGame.ViewMatrix = remViewMatrix;
                                  //} // if
                          } // finally
                  } // GenerateShadows()
                  #endregion
  
                  #region Use shadow
  
<summary> Update calc shadow world matrix, has to be done for each object we want to render in CalcShadows. </summary> <param name="setWorldMatrix">Set world matrix</param> internal void UpdateCalcShadowWorldMatrix(Matrix setWorldMatrix) { this.WorldMatrix = setWorldMatrix; this.WorldViewProjMatrix = setWorldMatrix * BaseGame.ViewMatrix * BaseGame.ProjectionMatrix;

                          // Compute the matrix to transform from view space to light proj:
                          // inverse of view matrix * light view matrix * light proj matrix
                          Matrix lightTransformMatrix =
                                  setWorldMatrix *
                                  lightViewMatrix *
                                  lightProjectionMatrix *
                                  texScaleBiasMatrix;
                          shadowTexTransform.SetValue(lightTransformMatrix);
  
                          Matrix worldViewProjLightMatrix =
                                  setWorldMatrix *
                                  lightViewMatrix *
                                  lightProjectionMatrix;
                          worldViewProjLight.SetValue(worldViewProjLightMatrix);
  
                          xnaEffect.CommitChanges();
  
                          /*tst
                          ShaderEffect.normalMapping.SetParameters();
                          ShaderEffect.normalMapping.WorldMatrix = setWorldMatrix;
                          ShaderEffect.normalMapping.Update();
  			 */
                  } // UpdateCalcShadowWorldMatrix(setWorldMatrix)
  
  
<summary> Calc shadows with help of generated light depth map, all objects have to be rendered again for comparing. We could save a pass when directly rendering here, but this has 2 disadvantages: 1. we can't post screen blur the shadow and 2. we can't use any other shader, especially bump and specular mapping shaders don't have any instructions left with ps_1_1. This way everything is kept simple, we can do as complex shaders as we want, the shadow shaders work seperately. </summary> <param name="renderObjects">Render objects</param> public void RenderShadows(BaseGame.RenderDelegate renderObjects) { // Can't calc shadows if loading failed! if (xnaEffect == null) return;

                          // Make sure z buffer and writing z buffer is on
                          BaseGame.Device.RenderState.DepthBufferEnable = true;
                          BaseGame.Device.RenderState.DepthBufferWriteEnable = true;
  
                          // Render shadows into our shadowMapBlur render target
                          shadowMapBlur.RenderShadows(
                                  delegate
                                  {
                                          if (BaseGame.CanUsePS20)
                                                  xnaEffect.CurrentTechnique = xnaEffect.Techniques["UseShadowMap20"];
                                          else
                                                  xnaEffect.CurrentTechnique = xnaEffect.Techniques["UseShadowMap"];
  
                                          // This method sets all required shader variables.
                                          this.SetParameters();
  
                                          // Use the shadow map texture here which was generated in
                                          // GenerateShadows().
                                          shadowMap.SetValue(shadowMapTexture.XnaTexture);
  
                                          //ShaderEffect.normalMapping.RenderSinglePassShader(renderObjects);
  
                                          // Render shadows with help of the UseShadowMap shader
                                          RenderSinglePassShader(renderObjects);
  
                                          /*not required
                                          // Make sure warping is on, stupid xna bug again
                                          BaseGame.Device.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
                                          BaseGame.Device.SamplerStates[0].AddressV = TextureAddressMode.Wrap;
                                          BaseGame.Device.SamplerStates[1].AddressU = TextureAddressMode.Wrap;
                                          BaseGame.Device.SamplerStates[1].AddressV = TextureAddressMode.Wrap;
  					 */
  
                                          /*
                                          // Render everything into the current render target (set before
                                          // or just use the screen).
                                          try
                                          {
                                                  xnaEffect.Begin(SaveStateMode.None);
                                                  foreach (EffectPass pass in xnaEffect.CurrentTechnique.Passes)
                                                  {
                                                          pass.Begin();
                                                          renderObjects();
                                                          pass.End();
                                                  } // foreach (pass)
                                          } // try
                                          finally
                                          {
                                                  xnaEffect.End();
                                          } // finally
  					 */
                                  });
  
                          // Start rendering the shadow map blur (pass 1, which messes up our
                          // background), pass 2 can be done below without any render targets.
                          shadowMapBlur.ShowShadowsPart1();
  
                          // Kill background z buffer
                          XnaShooterGame.Device.Clear(ClearOptions.DepthBuffer, Color.Black, 1, 0);
                  } // RenderShadows(renderObjects)
                  #endregion
  
                  #region ShowShadows
  
<summary> Show Shadows </summary> public void ShowShadows() { shadowMapBlur.ShowShadowsPart2(); } // ShowShadows() #endregion

                  #region Unit Testing
  if DEBUG
  
<summary> Test shadow mapping </summary> public static void TestShadowMapping() { Model testModel = null; Model testGround = null;

                          TestGame.Start("TestShadowMapping",
                                  delegate
                                  {
                                          testModel = new Model("Building");
                                          testGround = new Model("BackgroundGround");
                                  },
                                  delegate
                                  {
                                          if (Input.Keyboard.IsKeyDown(Keys.Tab))
                                                  BaseGame.ViewMatrix = Matrix.CreateLookAt(
                                                          Mission.LookAtPosition +
                                                          BaseGame.LightDirection * Mission.ViewDistance,
                                                          Mission.LookAtPosition,
                                                          new Vector3(0, 1, 0));
  
                                          Matrix renderMatrix = Matrix.CreateScale(15) *
                                                  Matrix.CreateRotationZ(0.85f) *
                                                  Matrix.CreateTranslation(0, 0, -16);
                                          Matrix groundMatrix = Matrix.CreateScale(100);
  
                                          if (Input.Keyboard.IsKeyUp(Keys.LeftAlt) &&
                                                  Input.GamePadXPressed == false)
                                          {
                                                  // Generate shadows
                                                  ShaderEffect.shadowMapping.GenerateShadows(
                                                          delegate
                                                          {
                                                                  testModel.GenerateShadow(renderMatrix);
                                                          });
  
                                                  // Render shadows
                                                  ShaderEffect.shadowMapping.RenderShadows(
                                                          delegate
                                                          {
                                                                  testModel.UseShadow(renderMatrix);
                                                                  testGround.UseShadow(groundMatrix);
                                                          });
                                          } // if
  
                                          testModel.Render(renderMatrix);
                                          testGround.Render(groundMatrix);
                                          BaseGame.MeshRenderManager.Render();
  
                                          if (Input.Keyboard.IsKeyUp(Keys.LeftAlt) &&
                                                  Input.GamePadXPressed == false)
                                          {
                                                  ShaderEffect.shadowMapping.ShowShadows();
                                          } // if
  
                                          if (Input.Keyboard.IsKeyDown(Keys.LeftShift) ||
                                                  Input.GamePadAPressed)
                                          {
                                                  //if (Input.KeyboardRightPressed)// == false)
                                                  ShaderEffect.shadowMapping.shadowMapTexture.RenderOnScreen(
                                                          new Rectangle(10, 10, 256, 256));
                                                  //if (Input.KeyboardLeftPressed)// == false)
                                                  ShaderEffect.shadowMapping.shadowMapBlur.SceneMapTexture.
                                                          RenderOnScreen(
                                                          new Rectangle(10 + 256 + 10, 10, 256, 256));
                                                  // Unused on xbox360
  //#if !XBOX360
                                                  //ShaderEffect.shadowMapping.shadowMapBlur.BlurMapTexture.
                                                  //        RenderOnScreen(
                                                  //        new Rectangle(10 + 256 + 10+256+10, 10, 256, 256));
  //#endif
                                          } // if (Input.Keyboard.IsKeyDown)
  
                                          //tst:
                                          //XnaShooterGame.GlowShader.Show();
  
                                          TextureFont.WriteText(2, 510,
                                                  "Press left Shift or A to show all shadow pass textures.");
                                          TextureFont.WriteText(2, 540,
                                                  "Press Alt or X to skip shadow map rendering.");
                                          TextureFont.WriteText(2, 570,
                                                  "Camera pos="+BaseGame.CameraPos);//+
                                                  //", "+ShaderEffect.shadowMapping.shadowMapTexture.ZBufferSurface);
                                  });
                  } // TestShadowMapping()
  endif
                  #endregion
          } // class ShadowMapShader
  } // namespace Abi.Graphic.ShadowMapping
  


(C) Æliens 20/2/2008

You may not copy or print any of this material without explicit permission of the author or the publisher. In case of other copyright issues, contact the author.