//----------------------------------------------------------------------------- // ShadowMapShader.cs // // Microsoft XNA Community Game Platform // Copyright (C) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using System; using System.IO; using RacingGame.GameLogic; using RacingGame.Graphics; using RacingGame.GameScreens; using Model = RacingGame.Graphics.Model; using Texture = RacingGame.Graphics.Texture; namespace RacingGame.Shaders { /// /// Shadow map shader /// public class ShadowMapShader : ShaderEffect { /// /// Shadow mapping shader filename /// const string ShaderFilename = "ShadowMap.fx"; /// /// Shadow map texture we render to. /// internal RenderToTexture shadowMapTexture = null; /// /// Restrict near and far plane for much better depth resolution! /// internal float shadowNearPlane = 1.0f, shadowFarPlane = 1.0f * 28; /// /// Virtual point light parameters for directional shadow map lighting. /// Used to create a point light position for the directional light. /// internal float virtualLightDistance = 24, virtualVisibleRange = 23.5f; /// /// Shadow distance /// /// Float public float ShadowDistance { get { return virtualLightDistance; } } private Vector3 shadowLightPos = Vector3.Zero; /// /// Shadow light position /// /// Vector 3 public Vector3 ShadowLightPos { get { return shadowLightPos; } } /// /// Texel width and height and offset for texScaleBiasMatrix, /// this way we can directly access the middle of each texel. /// float texelWidth = 1.0f / 1024.0f, texelHeight = 1.0f / 1024.0f, texOffsetX = 0.5f, texOffsetY = 0.5f; /// /// Compare depth bias /// internal float compareDepthBias = 0.00025f; /// /// Tex extra scale /// /// 1.0f internal float texExtraScale = 1.0f; /// /// Shadow map depth bias value /// /// + internal float shadowMapDepthBiasValue = 0.00025f; /// /// The matrix to convert proj screen coordinates in the -1..1 range /// to the shadow depth map texture coordinates. /// Matrix texScaleBiasMatrix; /// /// Used matrices for the light casting the shadows. /// internal Matrix lightProjectionMatrix, lightViewMatrix; /// /// Additional effect handles /// private EffectParameter shadowTexTransform, worldViewProjLight, nearPlane, farPlane, depthBias, shadowMapDepthBias, shadowMap, shadowMapTexelSize, shadowDistanceFadeoutTexture; /// /// Shadow map blur post screen shader, used in RenderShadows /// to blur the shadow results. /// internal ShadowMapBlur shadowMapBlur = null; /// /// Calculate the texScaleBiasMatrix for converting proj screen /// coordinates in the -1..1 range to the shadow depth map /// texture coordinates. /// internal void CalcShadowMapBiasMatrix() { texelWidth = 1.0f / (float)shadowMapTexture.Width; texelHeight = 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); } /// /// Shadow map shader /// public ShadowMapShader() : base(ShaderFilename) { // We use R32F, etc. and have a lot of precision compareDepthBias = 0.0001f; // Ok, time to create the shadow map render target shadowMapTexture = new RenderToTexture( RenderToTexture.SizeType.ShadowMap); CalcShadowMapBiasMatrix(); shadowMapBlur = new ShadowMapBlur(); } protected override void SetParameterDefaultValues() { // FilterTaps is hardcoded inside PS_UseShadowMap20 to avoid GLSL std140 // float2 array padding (16 bytes/element on GPU vs 8 bytes on CPU), // which caused a BlockCopy overrun on DesktopGL. } /// /// Get parameters /// protected override void GetParameters() { // Can't get parameters if loading failed! if (effect == null) return; base.GetParameters(); // Get additional parameters shadowTexTransform = effect.Parameters["shadowTexTransform"]; worldViewProjLight = effect.Parameters["worldViewProjLight"]; nearPlane = effect.Parameters["nearPlane"]; farPlane = effect.Parameters["farPlane"]; depthBias = effect.Parameters["depthBias"]; shadowMapDepthBias = effect.Parameters["shadowMapDepthBias"]; shadowMap = effect.Parameters["ShadowMap"]; shadowMapTexelSize = effect.Parameters["shadowMapTexelSize"]; shadowDistanceFadeoutTexture = effect.Parameters["shadowDistanceFadeoutTexture"]; // Load shadowDistanceFadeoutTexture if (shadowDistanceFadeoutTexture != null) shadowDistanceFadeoutTexture.SetValue( new Texture("ShadowDistanceFadeoutMap").XnaTexture); } /// /// Update parameters /// public override void SetParameters(Material setMat) { // Can't set parameters if loading failed! if (effect == null) return; shadowNearPlane = 1.0f; shadowFarPlane = 6.25f * 28 * 1.25f; virtualLightDistance = 5.5f * 24 * 1.3f; virtualVisibleRange = 5.5f * 23.5f; compareDepthBias = 0.00065f; shadowMapDepthBiasValue = 0.00065f; base.SetParameters(setMat); // Set all extra parameters for this shader depthBias.SetValue(compareDepthBias); shadowMapDepthBias.SetValue(shadowMapDepthBiasValue); shadowMapTexelSize.SetValue( new Vector2(texelWidth, texelHeight)); if (nearPlane != null) nearPlane.SetValue(shadowNearPlane); farPlane.SetValue(shadowFarPlane); } /// /// Calc simple directional shadow mapping matrix /// 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. float virtualFieldOfView = (float)Math.Atan2( virtualVisibleRange, virtualLightDistance); // Set projection matrix for light lightProjectionMatrix = Matrix.CreatePerspective( // Don't use graphics fov and aspect ratio in directional lighting mode virtualFieldOfView, 1.0f, shadowNearPlane, shadowFarPlane); // Calc light look pos, put it a little bit in front of our car Vector3 lightLookPos = RacingGameManager.InMenu ? RacingGameManager.Player.CarPosition : RacingGameManager.Player.CarPosition + RacingGameManager.Player.CarDirection * virtualVisibleRange / 6; // Well, this is how directional lights are done: lightViewMatrix = Matrix.CreateLookAt( // Use our current car position for our light look at origin! lightLookPos + BaseGame.LightDirection * virtualVisibleRange,//virtualLightDistance, lightLookPos, new Vector3(0, 0, 1)); // Update light pos Matrix invView = Matrix.Invert(lightViewMatrix); shadowLightPos = new Vector3(invView.M41, invView.M42, invView.M43); } /// /// Update shadow world matrix. /// Calling this function is important to keep the shaders /// WorldMatrix and WorldViewProjMatrix up to date. /// /// World matrix internal void UpdateGenerateShadowWorldMatrix(Matrix setWorldMatrix) { Matrix world = setWorldMatrix; WorldMatrix = world; WorldViewProjMatrix = world * lightViewMatrix * lightProjectionMatrix; effect.CurrentTechnique.Passes[0].Apply(); } /// /// Generate shadow /// internal void GenerateShadows(BaseGame.RenderHandler renderObjects) { // Can't generate shadow if loading failed! if (effect == null) return; // This method sets all required shader variables. this.SetParameters(null); Matrix remViewMatrix = BaseGame.ViewMatrix; Matrix remProjMatrix = BaseGame.ProjectionMatrix; CalcSimpleDirectionalShadowMappingMatrix(); // Time to generate the shadow texture // Start rendering onto the shadow map shadowMapTexture.SetRenderTarget(); // Make sure depth buffer is on BaseGame.Device.DepthStencilState = DepthStencilState.Default; // Disable alpha BaseGame.Device.BlendState = BlendState.Opaque; // Clear render target shadowMapTexture.Clear(Color.White); effect.CurrentTechnique = effect.Techniques["GenerateShadowMap20"]; // Render shadows with help of the GenerateShadowMap shader RenderSinglePassShader(renderObjects); // Resolve the render target to get the texture (required for Xbox) shadowMapTexture.Resolve(); // Set render target back to default BaseGame.ResetRenderTarget(false); BaseGame.ViewMatrix = remViewMatrix; BaseGame.ProjectionMatrix = remProjMatrix; } /// /// Update calc shadow world matrix, has to be done for each object /// we want to render in CalcShadows. /// /// Set world matrix 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); effect.CurrentTechnique.Passes[0].Apply(); } /// /// 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. /// /// Render objects public void RenderShadows(BaseGame.RenderHandler renderObjects) { // Can't calc shadows if loading failed! if (effect == null) return; // Make sure z buffer and writing z buffer is on BaseGame.Device.DepthStencilState = DepthStencilState.Default; // Render shadows into our shadowMapBlur render target shadowMapBlur.RenderShadows( delegate { effect.CurrentTechnique = effect.Techniques["UseShadowMap20"]; // This method sets all required shader variables. this.SetParameters(null); // Use the shadow map texture here which was generated in // GenerateShadows(). shadowMap.SetValue(shadowMapTexture.XnaTexture); // Render shadows with help of the UseShadowMap shader RenderSinglePassShader(renderObjects); }); // Start rendering the shadow map blur (pass 1, which messes up our // background), pass 2 can be done below without any render targets. shadowMapBlur.RenderShadows(); // Kill background z buffer (else glass will not be rendered correctly) RacingGameManager.Device.Clear(ClearOptions.DepthBuffer, Color.Black, 1, 0); } /// /// Generates and renders shadows for all game objects /// public static void PrepareGameShadows() { if (BaseGame.AllowShadowMapping) { // Generate shadows ShaderEffect.shadowMapping.GenerateShadows( delegate { RacingGameManager.Landscape.GenerateShadow(); RacingGameManager.CarModel.GenerateShadow( RacingGameManager.Player.CarRenderMatrix); }); // Render shadows ShaderEffect.shadowMapping.RenderShadows( delegate { RacingGameManager.Landscape.UseShadow(); RacingGameManager.CarModel.UseShadow( RacingGameManager.Player.CarRenderMatrix); }); } } /// /// Show Shadows /// public void ShowShadows() { shadowMapBlur.ShowShadows(); } } }