Engine – DeferredRenderer.cs
#region Using Statements using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; #endregion namespace ParadigmEngine.DeferredRenderer { /// <summary> /// Deferred renderer /// </summary> public class DeferredRenderer { /// <summary> /// Enum to designate render pass /// </summary> public enum RenderPass { Color, Normal, Depth, } /// <summary> /// The current render pass /// </summary> public static RenderPass CurrentPass { get; set; } /// <summary> /// Render target used for color maps /// </summary> private RenderTarget2D colorTarget; /// <summary> /// Render target used for normal maps /// </summary> private RenderTarget2D normalTarget; /// <summary> /// Render target used for depth /// </summary> private RenderTarget2D depthTarget; /// <summary> /// Render target used for light map /// </summary> private RenderTarget2D lightMapTarget; /// <summary> /// Temporary render target used for combining light maps /// </summary> private RenderTarget2D tempTarget; /// <summary> /// Render target used to store the final drawn scene /// </summary> private RenderTarget2D finalTarget; /// <summary> /// Shader used to calculate light map from normals /// </summary> private Effect normalEffect; /// <summary> /// Shader used to combine light maps /// </summary> private Effect combineLightMapsEffect; /// <summary> /// Shader used to shade initial color map with light map /// </summary> private Effect finalLightingEffect; /// <summary> /// Hook to graphics device /// </summary> private GraphicsDevice graphicsDevice; /// <summary> /// Initialization flag /// </summary> private bool isInitialized; /// <summary> /// List of lights /// </summary> private List<PointLight> lightList; /// <summary> /// Use display mode for RenderTarget resolution instead of backbuffer /// </summary> private bool useDisplayMode; /// <summary> /// Initialization flag /// </summary> public bool IsInitialized { get { return isInitialized; } } /// <summary> /// Deferred Renderer /// </summary> /// <param name="graphicsDevice">Hook to Graphics Device</param> /// <param name="useDisplayMode">If true, RenderTargets will be created based on display mode /// instead of back buffer. Useful for Win32 applications where resizing may occur</param> public DeferredRenderer(GraphicsDevice graphicsDevice, bool useDisplayMode) { this.graphicsDevice = graphicsDevice; isInitialized = false; this.useDisplayMode = useDisplayMode; } /// <summary> /// Initialize the renderer /// </summary> /// <param name="normalEffect">Shader used to calculate light map from normals</param> /// <param name="combineLightMapsEffect">Shader used to shade initial color map with light map</param> /// <param name="finalLightingEffect">Shader used to shade initial color map with light map</param> public void Initialize(Effect normalEffect, Effect combineLightMapsEffect, Effect finalLightingEffect) { this.normalEffect = normalEffect; this.combineLightMapsEffect = combineLightMapsEffect; this.finalLightingEffect = finalLightingEffect; int width = 0; int height = 0; // Decide on render target resolution if (useDisplayMode) { width = graphicsDevice.DisplayMode.Width; height = graphicsDevice.DisplayMode.Height; } else { width = graphicsDevice.PresentationParameters.BackBufferWidth; height = graphicsDevice.PresentationParameters.BackBufferHeight; } colorTarget = new RenderTarget2D( graphicsDevice, width, height, 1, graphicsDevice.PresentationParameters.BackBufferFormat); normalTarget = new RenderTarget2D( graphicsDevice, width, height, 1, graphicsDevice.PresentationParameters.BackBufferFormat); depthTarget = new RenderTarget2D( graphicsDevice, width, height, 1, graphicsDevice.PresentationParameters.BackBufferFormat); lightMapTarget = new RenderTarget2D( graphicsDevice, width, height, 1, graphicsDevice.PresentationParameters.BackBufferFormat); tempTarget = new RenderTarget2D( graphicsDevice, width, height, 1, graphicsDevice.PresentationParameters.BackBufferFormat); finalTarget = new RenderTarget2D( graphicsDevice, width, height, 1, graphicsDevice.PresentationParameters.BackBufferFormat); lightList = new List<PointLight>(); isInitialized = true; } /// <summary> /// Manually update render target resolutions /// </summary> /// <param name="width"></param> /// <param name="height"></param> public void UpdateResolution(GraphicsDevice graphicsDevice) { this.graphicsDevice = graphicsDevice; int width = 0; int height = 0; // Decide on render target resolution if (useDisplayMode) { width = graphicsDevice.DisplayMode.Width; height = graphicsDevice.DisplayMode.Height; } else { width = graphicsDevice.PresentationParameters.BackBufferWidth; height = graphicsDevice.PresentationParameters.BackBufferHeight; } colorTarget = new RenderTarget2D( graphicsDevice, width, height, 1, graphicsDevice.PresentationParameters.BackBufferFormat); normalTarget = new RenderTarget2D( graphicsDevice, width, height, 1, graphicsDevice.PresentationParameters.BackBufferFormat); depthTarget = new RenderTarget2D( graphicsDevice, width, height, 1, graphicsDevice.PresentationParameters.BackBufferFormat); lightMapTarget = new RenderTarget2D( graphicsDevice, width, height, 1, graphicsDevice.PresentationParameters.BackBufferFormat); tempTarget = new RenderTarget2D( graphicsDevice, width, height, 1, graphicsDevice.PresentationParameters.BackBufferFormat); finalTarget = new RenderTarget2D( graphicsDevice, width, height, 1, graphicsDevice.PresentationParameters.BackBufferFormat); } /// <summary> /// Draws the lit scene /// </summary> /// <param name="spriteBatch">SpriteBatch</param> /// <param name="colorMap">Rendered color map</param> /// <param name="normalMap">Rendered normal map</param> /// <param name="depthMap">Rendered depth map</param> /// <param name="lightList">List of scene lights</param> /// <param name="lightParams">World light parameters</param> /// <param name="transformMatrix">Transform matrix used to scale and translate lights</param> /// <returns></returns> public Texture2D Draw(SpriteBatch spriteBatch, Texture2D colorMap, Texture2D normalMap, Texture2D depthMap, List<PointLight> lights, LightParams lightParams, Matrix? transformMatrix) { // TODO: // Perform light coordinate transforms here RenderColorMap(spriteBatch, colorMap); RenderNormalMap(spriteBatch, normalMap); RenderDepthMap(spriteBatch, depthMap); RenderLightMap(spriteBatch, lights); RenderFinal(spriteBatch, lightParams); return finalTarget.GetTexture(); } /// <summary> /// Render the color map /// </summary> private void RenderColorMap(SpriteBatch spriteBatch, Texture2D colorMap) { graphicsDevice.SetRenderTarget(0, colorTarget); graphicsDevice.RenderState.SourceBlend = Blend.One; graphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha; graphicsDevice.RenderState.AlphaBlendEnable = true; graphicsDevice.Clear(Color.TransparentWhite); spriteBatch.Begin(SpriteBlendMode.AlphaBlend); spriteBatch.Draw(colorMap, Vector2.Zero, Color.White); spriteBatch.End(); graphicsDevice.RenderState.AlphaBlendEnable = false; graphicsDevice.SetRenderTarget(0, null); } /// <summary> /// Render the normal map /// </summary> private void RenderNormalMap(SpriteBatch spriteBatch, Texture2D normalMap) { graphicsDevice.SetRenderTarget(0, normalTarget); graphicsDevice.RenderState.SourceBlend = Blend.One; graphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha; graphicsDevice.Clear(Color.TransparentWhite); spriteBatch.Begin(SpriteBlendMode.None); spriteBatch.Draw(normalMap, Vector2.Zero, Color.White); spriteBatch.End(); graphicsDevice.SetRenderTarget(0, null); } /// <summary> /// Render the depth map /// </summary> private void RenderDepthMap(SpriteBatch spriteBatch, Texture2D depthMap) { graphicsDevice.SetRenderTarget(0, depthTarget); graphicsDevice.RenderState.SourceBlend = Blend.One; graphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha; graphicsDevice.Clear(Color.Black); spriteBatch.Begin(SpriteBlendMode.None); spriteBatch.Draw(depthMap, Vector2.Zero, Color.White); spriteBatch.End(); graphicsDevice.SetRenderTarget(0, null); } /// <summary> /// Render the light map /// </summary> private void RenderLightMap(SpriteBatch spriteBatch, List<PointLight> lightList) { // clear light map target graphicsDevice.SetRenderTarget(0, lightMapTarget); graphicsDevice.Clear(Color.Black); graphicsDevice.SetRenderTarget(0, null); foreach (PointLight light in lightList) { //render current light pass shading info to temp target graphicsDevice.SetRenderTarget(0, tempTarget); //graphicsDevice.RenderState.AlphaBlendEnable = true; //graphicsDevice.RenderState.SourceBlend = Blend.One; //graphicsDevice.RenderState.DestinationBlend = Blend.One; graphicsDevice.Clear(Color.TransparentWhite); float brightness = light.Brightness; if (light.Flicker != 0f) { // if there is a flicker setting, 10% of the time we want the light to // actually flicker right out float r = Rand.GetRandomFloat(0f, 1f); if (r < 0.1f) brightness = 0f; else brightness += Rand.GetRandomFloat(-light.Flicker, light.Flicker); } // set light params normalEffect.Parameters["ScreenWidth"].SetValue(graphicsDevice.Viewport.Width); normalEffect.Parameters["ScreenHeight"].SetValue(graphicsDevice.Viewport.Height); normalEffect.Parameters["LightPosition"].SetValue(light.Position); normalEffect.Parameters["LightColor"].SetValue(light.Color); normalEffect.Parameters["Brightness"].SetValue(brightness); normalEffect.Parameters["Radius"].SetValue(light.Radius); normalEffect.Parameters["AttenuateLight"].SetValue(light.AttenuateCone); normalEffect.Parameters["NormalMap"].SetValue(normalTarget.GetTexture()); normalEffect.Parameters["DepthMap"].SetValue(depthTarget.GetTexture()); // render scene using normal shader to get light map info spriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.None); normalEffect.Begin(); foreach (EffectPass pass in normalEffect.CurrentTechnique.Passes) { pass.Begin(); spriteBatch.Draw(colorTarget.GetTexture(), Vector2.Zero, Color.White); pass.End(); } spriteBatch.End(); normalEffect.End(); graphicsDevice.SetRenderTarget(0, null); // we now take the last lighting pass map data and feed it into the g buffer, // combining it will all previous passes // first retrieve old G Buffer data Texture2D oldGBuffer = lightMapTarget.GetTexture(); //render current shading info to temp target graphicsDevice.SetRenderTarget(0, lightMapTarget); //graphicsDevice.RenderState.AlphaBlendEnable = true; //graphicsDevice.RenderState.SourceBlend = Blend.One; //graphicsDevice.RenderState.DestinationBlend = Blend.One; graphicsDevice.Clear(Color.TransparentWhite); Texture2D newLightMap = tempTarget.GetTexture(); combineLightMapsEffect.Parameters["NewLightMap"].SetValue(newLightMap); // render combineLightMapsEffect.Begin(); spriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.None); foreach (EffectPass pass in combineLightMapsEffect.CurrentTechnique.Passes) { pass.Begin(); spriteBatch.Draw(oldGBuffer, Vector2.Zero, Color.White); pass.End(); } spriteBatch.End(); combineLightMapsEffect.End(); graphicsDevice.SetRenderTarget(0, null); } graphicsDevice.SetRenderTarget(0, null); } /// <summary> /// Render the final scene /// </summary> private void RenderFinal(SpriteBatch spriteBatch, LightParams lightParams) { graphicsDevice.SetRenderTarget(0, finalTarget); graphicsDevice.Clear(Color.TransparentWhite); finalLightingEffect.Parameters["AmbientColor"].SetValue(lightParams.AmbientColor); finalLightingEffect.Parameters["AmbientBrightness"].SetValue(lightParams.AmbientBrightness); // This variable is used to boost to output of the light sources when they are combined finalLightingEffect.Parameters["Boost"].SetValue(lightParams.Boost); finalLightingEffect.Parameters["LightMap"].SetValue(lightMapTarget.GetTexture()); spriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.None); finalLightingEffect.Begin(); foreach (var pass in finalLightingEffect.CurrentTechnique.Passes) { pass.Begin(); spriteBatch.Draw(colorTarget.GetTexture(), Vector2.Zero, Color.White); pass.End(); } finalLightingEffect.End(); spriteBatch.End(); graphicsDevice.SetRenderTarget(0, null); } } }