#region File Description //----------------------------------------------------------------------------- // DistortionComponent.cs // // Microsoft XNA Community Game Platform // Copyright (C) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- #endregion #region Using Statements using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using System.Collections.Generic; #endregion namespace DistortionSample { public class DistortionComponent : DrawableGameComponent { #region Enumerations public enum DistortionTechnique { DisplacementMapped, HeatHaze, PullIn, ZeroDisplacement, } #endregion #region Constant Data private const float blurAmount = 2f; private readonly static string[] distortionTechniqueFriendlyNames = new string[] { "Displacement-Mapped", "Heat-Haze", "Pull-In", "Zero Displacement", }; public static string GetDistortionTechniqueFriendlyName( DistortionTechnique technique) { return distortionTechniqueFriendlyNames[(int)technique]; } #endregion #region Fields SpriteBatch spriteBatch; RenderTarget2D sceneMap; RenderTarget2D distortionMap; Effect distortEffect; EffectTechnique distortTechnique; EffectTechnique distortBlurTechnique; public Matrix View; public Matrix Projection; public Distorter Distorter; public bool ShowDistortionMap = false; #endregion #region Initialization public DistortionComponent(Game game) : base(game) { } /// /// Load your graphics content. /// protected override void LoadContent() { spriteBatch = new SpriteBatch(GraphicsDevice); distortEffect = Game.Content.Load("Distort"); distortTechnique = distortEffect.Techniques["Distort"]; distortBlurTechnique = distortEffect.Techniques["DistortBlur"]; // update the projection matrix Projection = Matrix.CreatePerspectiveFieldOfView(1f, GraphicsDevice.Viewport.AspectRatio, 1f, 10000f); // look up the resolution and format of our main backbuffer PresentationParameters pp = GraphicsDevice.PresentationParameters; int width = pp.BackBufferWidth; int height = pp.BackBufferHeight; SurfaceFormat format = pp.BackBufferFormat; DepthFormat depthFormat = pp.DepthStencilFormat; // create textures for reading back the backbuffer contents sceneMap = new RenderTarget2D(GraphicsDevice, width, height, false, format, depthFormat); distortionMap = new RenderTarget2D(GraphicsDevice, width, height, false, format, depthFormat); // set the blur parameters for the current viewport SetBlurEffectParameters(1f / (float)width, 1f / (float)height); } /// /// Unload your graphics content. /// protected override void UnloadContent() { spriteBatch = null; distortEffect = null; distortTechnique = null; distortBlurTechnique = null; if (sceneMap != null) { sceneMap.Dispose(); sceneMap = null; } if (distortionMap != null) { distortionMap.Dispose(); distortionMap = null; } } #endregion #region Draw /// /// This should be called at the very start of scene rendering. The distortion /// component uses it to redirect drawing into its custom rendertarget, so it /// can capture the scene image ready to apply the distortion postprocess. /// public void BeginDraw() { if (Visible) { GraphicsDevice.SetRenderTarget(sceneMap); } } /// /// Grab a scene that has already been rendered, /// and add a distortion effect over the top of it. /// public override void Draw(GameTime gameTime) { // now draw the distortion map GraphicsDevice.SetRenderTarget(ShowDistortionMap ? null : distortionMap); GraphicsDevice.Clear(Color.Transparent); // draw the distorter if (Distorter != null) { Matrix worldView = Distorter.World * View; Matrix[] transforms = new Matrix[Distorter.Model.Bones.Count]; Distorter.Model.CopyAbsoluteBoneTransformsTo(transforms); // make sure the depth buffering is on, so only parts of the scene // behind the distortion effect are affected GraphicsDevice.DepthStencilState = DepthStencilState.Default; foreach (ModelMesh mesh in Distorter.Model.Meshes) { Matrix meshWorldView = transforms[mesh.ParentBone.Index] * worldView; foreach (Effect effect in mesh.Effects) { effect.CurrentTechnique = effect.Techniques[Distorter.Technique.ToString()]; effect.Parameters["WorldView"].SetValue(meshWorldView); effect.Parameters["WorldViewProjection"].SetValue( meshWorldView * Projection); effect.Parameters["DistortionScale"].SetValue( Distorter.DistortionScale); effect.Parameters["Time"].SetValue( (float)gameTime.TotalGameTime.TotalSeconds); } mesh.Draw(); } } // if we want to show the distortion map, then the backbuffer is done. // if we want to render the scene distorted, then we need to resolve the // backbuffer as the distortion map and use it to distort the scene if (!ShowDistortionMap) { GraphicsDevice.SetRenderTarget(null); // draw the scene image again, distorting it with the distortion map GraphicsDevice.Textures[1] = distortionMap; GraphicsDevice.SamplerStates[1] = SamplerState.PointClamp; Viewport viewport = GraphicsDevice.Viewport; distortEffect.CurrentTechnique = Distorter.DistortionBlur ? distortBlurTechnique : distortTechnique; DrawFullscreenQuad(sceneMap, viewport.Width, viewport.Height, distortEffect); } } /// /// Helper for drawing a texture into the current rendertarget, /// using a custom shader to apply postprocessing effects. /// void DrawFullscreenQuad(Texture2D texture, int width, int height, Effect effect) { spriteBatch.Begin(0, BlendState.Opaque, null, null, null, effect); spriteBatch.Draw(texture, new Rectangle(0, 0, width, height), Color.White); spriteBatch.End(); } #endregion #region Blur Calculation /// /// Computes sample weightings and texture coordinate offsets /// for one pass of a separable gaussian blur filter. /// /// /// This function was originally provided in the BloomComponent class in the /// Bloom Postprocess sample. /// void SetBlurEffectParameters(float dx, float dy) { // Look up the sample weight and offset effect parameters. EffectParameter weightsParameter, offsetsParameter; weightsParameter = distortEffect.Parameters["SampleWeights"]; offsetsParameter = distortEffect.Parameters["SampleOffsets"]; // Look up how many samples our gaussian blur effect supports. int sampleCount = weightsParameter.Elements.Count; // Create temporary arrays for computing our filter settings. float[] sampleWeights = new float[sampleCount]; Vector2[] sampleOffsets = new Vector2[sampleCount]; // The first sample always has a zero offset. sampleWeights[0] = ComputeGaussian(0); sampleOffsets[0] = new Vector2(0); // Maintain a sum of all the weighting values. float totalWeights = sampleWeights[0]; // Add pairs of additional sample taps, positioned // along a line in both directions from the center. for (int i = 0; i < sampleCount / 2; i++) { // Store weights for the positive and negative taps. float weight = ComputeGaussian(i + 1); sampleWeights[i * 2 + 1] = weight; sampleWeights[i * 2 + 2] = weight; totalWeights += weight * 2; // To get the maximum amount of blurring from a limited number of // pixel shader samples, we take advantage of the bilinear filtering // hardware inside the texture fetch unit. If we position our texture // coordinates exactly halfway between two texels, the filtering unit // will average them for us, giving two samples for the price of one. // This allows us to step in units of two texels per sample, rather // than just one at a time. The 1.5 offset kicks things off by // positioning us nicely in between two texels. float sampleOffset = i * 2 + 1.5f; Vector2 delta = new Vector2(dx, dy) * sampleOffset; // Store texture coordinate offsets for the positive and negative taps. sampleOffsets[i * 2 + 1] = delta; sampleOffsets[i * 2 + 2] = -delta; } // Normalize the list of sample weightings, so they will always sum to one. for (int i = 0; i < sampleWeights.Length; i++) { sampleWeights[i] /= totalWeights; } // Tell the effect about our new filter settings. weightsParameter.SetValue(sampleWeights); offsetsParameter.SetValue(sampleOffsets); } /// /// Evaluates a single point on the gaussian falloff curve. /// Used for setting up the blur filter weightings. /// /// /// This function was originally provided in the BloomComponent class in the /// Bloom Postprocess sample. /// static float ComputeGaussian(float n) { return (float)((1.0 / Math.Sqrt(2 * Math.PI * blurAmount)) * Math.Exp(-(n * n) / (2 * blurAmount * blurAmount))); } #endregion } }