After
Before
Class Game1
public class Game1 : Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
private const int BLUR_RADIUS = 7;
private const float BLUR_AMOUNT = 2.0f;
private SpriteFont spriteFont;
private Texture2D texture;
private RenderTarget2D renderTarget1;
private RenderTarget2D renderTarget2;
private Vector2 fontPos;
private KeyboardState currentKeyboardState;
private KeyboardState prevKeyboardState;
private GaussianBlur gaussianBlur;
private int windowWidth;
private int windowHeight;
private int renderTargetWidth;
private int renderTargetHeight;
private int frames;
private int framesPerSecond;
private TimeSpan elapsedTime = TimeSpan.Zero;
private bool displayHelp;
private bool enableGaussianBlur;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
Window.Title = "XNA 4.0 Gaussian Blur";
IsMouseVisible = true;
IsFixedTimeStep = false;
}
protected override void Initialize()
{
// Setup the window to be a quarter the size of the desktop.
windowWidth = GraphicsDevice.DisplayMode.Width / 2;
windowHeight = GraphicsDevice.DisplayMode.Height / 2;
// Setup frame buffer.
graphics.SynchronizeWithVerticalRetrace = false;
graphics.PreferredBackBufferWidth = windowWidth;
graphics.PreferredBackBufferHeight = windowHeight;
graphics.ApplyChanges();
// Position the text.
fontPos = new Vector2(1.0f, 1.0f);
// Setup the initial input states.
currentKeyboardState = Keyboard.GetState();
// Create the Gaussian blur filter kernel.
gaussianBlur = new GaussianBlur(this);
gaussianBlur.ComputeKernel(BLUR_RADIUS, BLUR_AMOUNT);
base.Initialize();
}
private void InitRenderTargets()
{
// Since we're performing a Gaussian blur on a texture image the
// render targets are half the size of the source texture image.
// This will help improve the blurring effect.
renderTargetWidth = texture.Width / 2;
renderTargetHeight = texture.Height / 2;
renderTarget1 = new RenderTarget2D(GraphicsDevice,
renderTargetWidth, renderTargetHeight, false,
GraphicsDevice.PresentationParameters.BackBufferFormat,
DepthFormat.None);
renderTarget2 = new RenderTarget2D(GraphicsDevice,
renderTargetWidth, renderTargetHeight, false,
GraphicsDevice.PresentationParameters.BackBufferFormat,
DepthFormat.None);
// The texture offsets used by the Gaussian blur shader depends
// on the dimensions of the render targets. The offsets need to be
// recalculated whenever the render targets are recreated.
gaussianBlur.ComputeOffsets(renderTargetWidth, renderTargetHeight);
}
private bool KeyJustPressed(Keys key)
{
return currentKeyboardState.IsKeyDown(key) && prevKeyboardState.IsKeyUp(key);
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
spriteFont = Content.Load<SpriteFont>(@"Fonts\DemoFont");
texture = Content.Load<Texture2D>(@"Textures\lena");
InitRenderTargets();
}
private void ProcessKeyboard()
{
prevKeyboardState = currentKeyboardState;
currentKeyboardState = Keyboard.GetState();
if (KeyJustPressed(Keys.Escape))
this.Exit();
if (KeyJustPressed(Keys.Space))
enableGaussianBlur = !enableGaussianBlur;
if (KeyJustPressed(Keys.H))
displayHelp = !displayHelp;
if (currentKeyboardState.IsKeyDown(Keys.LeftAlt) ||
currentKeyboardState.IsKeyDown(Keys.RightAlt))
{
if (KeyJustPressed(Keys.Enter))
ToggleFullScreen();
}
}
private void ToggleFullScreen()
{
int newWidth = 0;
int newHeight = 0;
graphics.IsFullScreen = !graphics.IsFullScreen;
if (graphics.IsFullScreen)
{
newWidth = GraphicsDevice.DisplayMode.Width;
newHeight = GraphicsDevice.DisplayMode.Height;
}
else
{
newWidth = windowWidth;
newHeight = windowHeight;
}
graphics.PreferredBackBufferWidth = newWidth;
graphics.PreferredBackBufferHeight = newHeight;
graphics.ApplyChanges();
}
protected override void UnloadContent()
{
renderTarget1.Dispose();
renderTarget1 = null;
renderTarget2.Dispose();
renderTarget2 = null;
}
protected override void Update(GameTime gameTime)
{
if (!IsActive)
return;
ProcessKeyboard();
UpdateFrameRate(gameTime);
base.Update(gameTime);
}
private void UpdateFrameRate(GameTime gameTime)
{
elapsedTime += gameTime.ElapsedGameTime;
if (elapsedTime > TimeSpan.FromSeconds(1))
{
elapsedTime -= TimeSpan.FromSeconds(1);
framesPerSecond = frames;
frames = 0;
}
}
private void IncrementFrameCounter()
{
++frames;
}
private void DrawText()
{
StringBuilder buffer = new StringBuilder();
if (displayHelp)
{
buffer.AppendLine("Press SPACE to enable/disable Gaussian blur");
buffer.AppendLine("Press ALT and ENTER to toggle full screen");
buffer.AppendLine("Press ESCAPE to exit");
buffer.AppendLine();
buffer.AppendLine("Press H to hide help");
}
else
{
buffer.AppendFormat("FPS: {0}\n", framesPerSecond);
buffer.AppendLine();
buffer.AppendFormat("Radius: {0}\n", gaussianBlur.Radius);
buffer.AppendFormat("Sigma: {0}\n", gaussianBlur.Sigma.ToString("f2"));
buffer.AppendLine();
buffer.AppendLine("Press H to display help");
}
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
spriteBatch.DrawString(spriteFont, buffer.ToString(), fontPos, Color.Yellow);
spriteBatch.End();
}
protected override void Draw(GameTime gameTime)
{
if (!IsActive)
return;
if (enableGaussianBlur)
{
Texture2D result = gaussianBlur.PerformGaussianBlur(texture, renderTarget1, renderTarget2, spriteBatch);
Rectangle rectangle = new Rectangle(0, 0, texture.Width, texture.Height);
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
spriteBatch.Draw(result, rectangle, Color.White);
spriteBatch.End();
}
else
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin();
spriteBatch.Draw(texture, new Rectangle(0, 0, texture.Width, texture.Height), Color.White);
spriteBatch.End();
}
DrawText();
base.Draw(gameTime);
IncrementFrameCounter();
}
Class GaussianBlur
public class GaussianBlur
{
private Game game;
private Effect effect;
private int radius;
private float amount;
private float sigma;
private float[] kernel;
private Vector2[] offsetsHoriz;
private Vector2[] offsetsVert;
/// <summary>
/// Returns the radius of the Gaussian blur filter kernel in pixels.
/// </summary>
public int Radius
{
get { return radius; }
}
/// <summary>
/// Returns the blur amount. This value is used to calculate the
/// Gaussian blur filter kernel's sigma value. Good values for this
/// property are 2 and 3. 2 will give a more blurred result whilst 3
/// will give a less blurred result with sharper details.
/// </summary>
public float Amount
{
get { return amount; }
}
/// <summary>
/// Returns the Gaussian blur filter's standard deviation.
/// </summary>
public float Sigma
{
get { return sigma; }
}
/// <summary>
/// Returns the Gaussian blur filter kernel matrix. Note that the
/// kernel returned is for a 1D Gaussian blur filter kernel matrix
/// intended to be used in a two pass Gaussian blur operation.
/// </summary>
public float[] Kernel
{
get { return kernel; }
}
/// <summary>
/// Returns the texture offsets used for the horizontal Gaussian blur
/// pass.
/// </summary>
public Vector2[] TextureOffsetsX
{
get { return offsetsHoriz; }
}
/// <summary>
/// Returns the texture offsets used for the vertical Gaussian blur
/// pass.
/// </summary>
public Vector2[] TextureOffsetsY
{
get { return offsetsVert; }
}
/// <summary>
/// Default constructor for the GaussianBlur class. This constructor
/// should be called if you don't want the GaussianBlur class to use
/// its GaussianBlur.fx effect file to perform the two pass Gaussian
/// blur operation.
/// </summary>
public GaussianBlur()
{
}
/// <summary>
/// This overloaded constructor instructs the GaussianBlur class to
/// load and use its GaussianBlur.fx effect file that implements the
/// two pass Gaussian blur operation on the GPU. The effect file must
/// be already bound to the asset name: 'Effects\GaussianBlur' or
/// 'GaussianBlur'.
/// </summary>
public GaussianBlur(Game game)
{
this.game = game;
try
{
effect = game.Content.Load<Effect>(@"Effects\GaussianBlur");
}
catch (ContentLoadException)
{
effect = game.Content.Load<Effect>("GaussianBlur");
}
}
/// <summary>
/// Calculates the Gaussian blur filter kernel. This implementation is
/// ported from the original Java code appearing in chapter 16 of
/// "Filthy Rich Clients: Developing Animated and Graphical Effects for
/// Desktop Java".
/// </summary>
/// <param name="blurRadius">The blur radius in pixels.</param>
/// <param name="blurAmount">Used to calculate sigma.</param>
public void ComputeKernel(int blurRadius, float blurAmount)
{
radius = blurRadius;
amount = blurAmount;
kernel = null;
kernel = new float[radius * 2 + 1];
sigma = radius / amount;
float twoSigmaSquare = 2.0f * sigma * sigma;
float sigmaRoot = (float)Math.Sqrt(twoSigmaSquare * Math.PI);
float total = 0.0f;
float distance = 0.0f;
int index = 0;
for (int i = -radius; i <= radius; ++i)
{
distance = i * i;
index = i + radius;
kernel[index] = (float)Math.Exp(-distance / twoSigmaSquare) / sigmaRoot;
total += kernel[index];
}
for (int i = 0; i < kernel.Length; ++i)
kernel[i] /= total;
}
/// <summary>
/// Calculates the texture coordinate offsets corresponding to the
/// calculated Gaussian blur filter kernel. Each of these offset values
/// are added to the current pixel's texture coordinates in order to
/// obtain the neighboring texture coordinates that are affected by the
/// Gaussian blur filter kernel. This implementation has been adapted
/// from chapter 17 of "Filthy Rich Clients: Developing Animated and
/// Graphical Effects for Desktop Java".
/// </summary>
/// <param name="textureWidth">The texture width in pixels.</param>
/// <param name="textureHeight">The texture height in pixels.</param>
public void ComputeOffsets(float textureWidth, float textureHeight)
{
offsetsHoriz = null;
offsetsHoriz = new Vector2[radius * 2 + 1];
offsetsVert = null;
offsetsVert = new Vector2[radius * 2 + 1];
int index = 0;
float xOffset = 1.0f / textureWidth;
float yOffset = 1.0f / textureHeight;
for (int i = -radius; i <= radius; ++i)
{
index = i + radius;
offsetsHoriz[index] = new Vector2(i * xOffset, 0.0f);
offsetsVert[index] = new Vector2(0.0f, i * yOffset);
}
}
/// <summary>
/// Performs the Gaussian blur operation on the source texture image.
/// The Gaussian blur is performed in two passes: a horizontal blur
/// pass followed by a vertical blur pass. The output from the first
/// pass is rendered to renderTarget1. The output from the second pass
/// is rendered to renderTarget2. The dimensions of the blurred texture
/// is therefore equal to the dimensions of renderTarget2.
/// </summary>
/// <param name="srcTexture">The source image to blur.</param>
/// <param name="renderTarget1">Stores the output from the horizontal blur pass.</param>
/// <param name="renderTarget2">Stores the output from the vertical blur pass.</param>
/// <param name="spriteBatch">Used to draw quads for the blur passes.</param>
/// <returns>The resulting Gaussian blurred image.</returns>
public Texture2D PerformGaussianBlur(Texture2D srcTexture,
RenderTarget2D renderTarget1,
RenderTarget2D renderTarget2,
SpriteBatch spriteBatch)
{
if (effect == null)
throw new InvalidOperationException("GaussianBlur.fx effect not loaded.");
Texture2D outputTexture = null;
Rectangle srcRect = new Rectangle(0, 0, srcTexture.Width, srcTexture.Height);
Rectangle destRect1 = new Rectangle(0, 0, renderTarget1.Width, renderTarget1.Height);
Rectangle destRect2 = new Rectangle(0, 0, renderTarget2.Width, renderTarget2.Height);
// Perform horizontal Gaussian blur.
game.GraphicsDevice.SetRenderTarget(renderTarget1);
effect.CurrentTechnique = effect.Techniques["GaussianBlur"];
effect.Parameters["weights"].SetValue(kernel);
effect.Parameters["colorMapTexture"].SetValue(srcTexture);
effect.Parameters["offsets"].SetValue(offsetsHoriz);
spriteBatch.Begin(0, BlendState.Opaque, null, null, null, effect);
spriteBatch.Draw(srcTexture, destRect1, Color.White);
spriteBatch.End();
// Perform vertical Gaussian blur.
game.GraphicsDevice.SetRenderTarget(renderTarget2);
outputTexture = (Texture2D)renderTarget1;
effect.Parameters["colorMapTexture"].SetValue(outputTexture);
effect.Parameters["offsets"].SetValue(offsetsVert);
spriteBatch.Begin(0, BlendState.Opaque, null, null, null, effect);
spriteBatch.Draw(outputTexture, destRect2, Color.White);
spriteBatch.End();
// Return the Gaussian blurred texture.
game.GraphicsDevice.SetRenderTarget(null);
outputTexture = (Texture2D)renderTarget2;
return outputTexture;
}
}
File GaussianBlur.fx
#define RADIUS 7
#define KERNEL_SIZE (RADIUS * 2 + 1)
//-----------------------------------------------------------------------------
// Globals.
//-----------------------------------------------------------------------------
float weights[KERNEL_SIZE];
float2 offsets[KERNEL_SIZE];
//-----------------------------------------------------------------------------
// Textures.
//-----------------------------------------------------------------------------
texture colorMapTexture;
sampler2D colorMap = sampler_state
{
Texture = <colorMapTexture>;
MipFilter = Linear;
MinFilter = Linear;
MagFilter = Linear;
};
//-----------------------------------------------------------------------------
// Pixel Shaders.
//-----------------------------------------------------------------------------
float4 PS_GaussianBlur(float2 texCoord : TEXCOORD) : COLOR0
{
float4 color = float4(0.0f, 0.0f, 0.0f, 0.0f);
for (int i = 0; i < KERNEL_SIZE; ++i)
color += tex2D(colorMap, texCoord + offsets[i]) * weights[i];
return color;
}
//-----------------------------------------------------------------------------
// Techniques.
//-----------------------------------------------------------------------------
technique GaussianBlur
{
pass
{
PixelShader = compile ps_4_0_level_9_1 PS_GaussianBlur();
}
}