Windows Phone 7 Bloom Effect

After spending several weeks trying to get a Bloom Effect put together I finally managed to get it all in one piece.  It was kind of all over the place and I didn’t really spend much time organizing it into something I can easily return to and edit if needed.

Tonight a developer was asking for assistance on the App Hub forums on this exact thing, so I figured it would be a good time to re-write the effect and post it on my blog.

First, I want to discuss the concept of a Render Target.  Render Targets allow developers to render contents to a 2D Texture instead of into the back buffer (which gets presented to the screen).  Render Targets can be useful for capturing screen shots, rendering multiple camera angles onto the screen at the same time and also be useful for creating some fake bloom effects.

With the help of the XNA community, I was able to use a Render Target and capture the screen once my objects were rendered.  It was captured to four different Render Targets.  One was the same size as the screen, the 2nd was half the size of the screen, the 3rd was 1/4 the size and the last one was 1/8 the size.  During the Draw() method, all 4 of these render targets were rendered to.  Once they were rendered, they were presented to the back buffer and rendered to the screen at the screen resolution.

To give a visual look at what we are doing, you can see the following picture.  Each Render Target contains the contents of the screen and is rendered to the next smallest size.  It is finally rendered onto the last one (#4) which is stretched to fill the screen.

RenderTargets

The end product is a fake bloom effect due to the small (1/2, 1/4 and 1/8 size screen captures) being stretched to full screen.  The stretch effect caused the rendered image to have a blur to it that worked like bloom.

This approach worked great for objects moving horizontally or vertically, but any rotation would cause oddities during rendering, which I wasn’t to sure how to fix.

You will need a Sprite Font for this example.  If you don’t know how to load fonts into XNA refer to this tutorial.

So I created a quick example that you can follow along with.  What we do in the example is apply the blur effect to a rendered box and then we rendered a string of text outside of the effect.  This demonstrates how you can selectively apply the effect to only certain components of your game.

public class Game1 : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
 
    //We will need 4 render targets to render the screenshots to.
    //We will use an array rather than declare each one individually.
    RenderTarget2D[] targets;
        
    //Our SpriteFont used to render the message to the screen.
    SpriteFont MyFont;
    Texture2D Material;
    Rectangle Box;
 
    public Game1()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
        targets = new RenderTarget2D[4];
 
        // Frame rate is 30 fps by default for Windows Phone.
        TargetElapsedTime = TimeSpan.FromTicks(333333);
 
        // Extend battery life under lock.
        InactiveSleepTime = TimeSpan.FromSeconds(1);
    }
 
    /// <summary>
    /// Allows the game to perform any initialization it needs to before starting to run.
    /// This is where it can query for any required services and load any non-graphic
    /// related content.  Calling base.Initialize will enumerate through any components
    /// and initialize them as well.
    /// </summary>
    protected override void Initialize()
    {
        // TODO: Add your initialization logic here
 
        base.Initialize();
 
        //Instance our first Render Target to the same size as the back buffer.
        targets[0] = new RenderTarget2D(this.GraphicsDevice, 
            this.GraphicsDevice.PresentationParameters.BackBufferWidth, 
            this.GraphicsDevice.PresentationParameters.BackBufferHeight);
 
        //instance the remaining Render Targets at 1/2 of the previous target.
        // 1/2 of Render Target 1
        targets[1] = new RenderTarget2D(this.GraphicsDevice, 
            targets[0].Width / 2, targets[0].Height / 2);
        // 1/4 of Render Target 1 (1/2 of Render Target 2)
        targets[2] = new RenderTarget2D(this.GraphicsDevice, 
            targets[1].Width / 2, targets[1].Height / 2);
        //1/8 of Render Target 1 (1/2 of Render Target 3)
        targets[3] = new RenderTarget2D(this.GraphicsDevice, 
            targets[2].Width / 2, targets[1].Height / 2); 
 
        //Setup our Rectangle for rendering the Material.
        Box = new Rectangle(15, 15, 200, 75);
    }
 
    /// <summary>
    /// LoadContent will be called once per game and is the place to load
    /// all of your content.
    /// </summary>
    protected override void LoadContent()
    {
        // Create a new SpriteBatch, which can be used to draw textures.
        spriteBatch = new SpriteBatch(GraphicsDevice);
 
        MyFont = Content.Load<SpriteFont>("MyFont");
            
        //Create our Texture Color.
        Material = new Texture2D(GraphicsDevice, 1, 1);
        Material.SetData(new Color[] { Color.Yellow });
    }
 
    /// <summary>
    /// UnloadContent will be called once per game and is the place to unload
    /// all content.
    /// </summary>
    protected override void UnloadContent()
    {
        // TODO: Unload any non ContentManager content here
    }
 
    /// <summary>
    /// Allows the game to run logic such as updating the world,
    /// checking for collisions, gathering input, and playing audio.
    /// </summary>
    /// <param name="gameTime">Provides a snapshot of timing values.</param>
    protected override void Update(GameTime gameTime)
    {
        // Allows the game to exit
        if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
            this.Exit();
 
        // TODO: Add your update logic here
 
        base.Update(gameTime);
    }
 
    /// <summary>
    /// This is called when the game should draw itself.
    /// </summary>
    /// <param name="gameTime">Provides a snapshot of timing values.</param>
    protected override void Draw(GameTime gameTime)
    {
        //Now, we want to render our Box with a blur effect
        //but we want to keep our text regular with no effects.
        //So the first thing we do is render our box to our small
        //render targets.
 
        //Set the Graphics Device to render to our Render Target #1
        //instead of the back buffer.
        GraphicsDevice.SetRenderTarget(targets[0]);
        GraphicsDevice.Clear(Color.Black);
 
        //Render our String to the Render Target.
        spriteBatch.Begin();
        //Render our Box.
        spriteBatch.Draw(Material, Box, Color.White);
        spriteBatch.End();
 
        //Re-Render our first Render Target onto a 2nd one at half the size.
        GraphicsDevice.SetRenderTarget(targets[1]);
        spriteBatch.Begin();
        spriteBatch.Draw(targets[0], GraphicsDevice.Viewport.Bounds, Color.White);
        spriteBatch.End();
 
        //Render our 2nd Render Target onto our 3rd at 1/4 the viewport size.
        GraphicsDevice.SetRenderTarget(targets[2]);
        spriteBatch.Begin();
        spriteBatch.Draw(targets[1], GraphicsDevice.Viewport.Bounds, Color.White);
        spriteBatch.End();
 
        //Render our 3rd Render Target onto our 4th at 1/8 the viewport size.
        GraphicsDevice.SetRenderTarget(targets[3]);
        spriteBatch.Begin();
        spriteBatch.Draw(targets[2], GraphicsDevice.Viewport.Bounds, Color.White);
        spriteBatch.End();
 
        //Reset the Graphics Device to render to the back buffer.
        GraphicsDevice.SetRenderTarget(null);
 
        //Render our scene to the back buffer by rendering the
        //first Render Target which was stored at full screen resolution.
        spriteBatch.Begin();
        spriteBatch.Draw(targets[0], GraphicsDevice.Viewport.Bounds, Color.White);
        spriteBatch.End();
 
        //Lastly, render our 4th Render Target which contains all of our previous
        //rendered targets scaled down.  We will draw all of them now scaled up
        //to full resolution which will cause the blur.
        spriteBatch.Begin();
        spriteBatch.Draw(targets[3], GraphicsDevice.Viewport.Bounds, Color.White);
 
        //Render our Text.  Since this takes place after all of our effect renderings
        //to the Render Targets, the effects won't be applied.
        spriteBatch.DrawString(MyFont, "Hello World!",
            new Vector2(250, 250), Color.White);
        spriteBatch.End();
 
        base.Draw(gameTime);
    }
}

The sample can be downloaded as well.  Below is a screenshot of what the final product looks like with the bloom effect applied only to the box.

RenderTargets2

Happy coding!

2 thoughts on “Windows Phone 7 Bloom Effect

  1. Pingback: Windows Phone 7 Bloom Effect

  2. Pingback: #wp7dev in the last month | Windows Phone User Group

You must log in to post a comment.