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);
        }
    }
}