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