Bloom Effect On Windows Phone 7


In this post iam going to explain a dirty way to simulate the bloom effect under Windows Phone 7. Unfortunatelly, we dont have custom shaders in this plataform yet, so we need to use some combination of alpha blending to simulate the threshold filter and the Blur filter.

In a simple way, the classic boom post effect consists of:

  • A first pass that exctracts the parts of the image that are more “bright” (we get the pixels with rgb colors bigger than something like 0.7 and discard the others — this is the threshold filter)
  • The second pass apply a gaussian blur (or another blur algorithm) to the previous generated image
  • We make an additive blending between the original image and the previous generate image

For PC and Consoles, we normally use Custom shaders for all the passes, but in WP7 we cant. The alternative is to use Subtrative alpha blending for the threshold pass, draw the images with offset for the blur and use additive blending for the last pass.

The results i got are pretty cool (for a phone =P). Next i will show my implementation of this idea:

 Creating the Blend States

The following piece of code shows the blending states creation:

            ///Additive blending
            BlendState additiveBlend = new BlendState();
            additiveBlend.AlphaBlendFunction = BlendFunction.Add;
            additiveBlend.AlphaSourceBlend = Blend.One;
            additiveBlend.AlphaDestinationBlend = Blend.One;
            additiveBlend.ColorBlendFunction = BlendFunction.Add;
            additiveBlend.ColorSourceBlend = Blend.One;
            additiveBlend.ColorDestinationBlend = Blend.One;

            ///Subtractive blending
            BlendState subBlend = new BlendState();
            subBlend.AlphaBlendFunction = BlendFunction.Subtract;
            subBlend.AlphaSourceBlend = Blend.One;
            subBlend.AlphaDestinationBlend = Blend.One;
            subBlend.ColorBlendFunction = BlendFunction.Subtract;
            subBlend.ColorSourceBlend = Blend.One;
            subBlend.ColorDestinationBlend = Blend.One;

We also need two Render Targets to hold our intermediate images. (letst call SCENERT (used to hold the scene) and BLOOMRT (used to hold the bloom passes) )

Executing the Bloom Pass

The following code shows the complete Bloom Pass. (the Render class is our wrapper to the xna graphic device)

            ///set the scene render target on device
            render.PushRenderTarget(SCENERT);
            ////RENDER THE SCENE HERE !!!! EVERYTHING ..
            ///set the bloom render target on device
            render.PopRenderTarget();
            render.PushRenderTarget(BLOOMRT);
            render.Clear(Color.FromNonPremultiplied(bloomThreshold, bloomThreshold, bloomThreshold, 255));
            ///Render the Scene Texture using the SubBlend (the spritebatch can do it easily =P, i used the render object cause this is part of our engine)
            render.RenderTextureComplete(SCENERT, Color.White, GraphicInfo.FullScreenRectangle, Matrix.Identity, null, true, SpriteSortMode.Deferred, SamplerState.LinearClamp, subBlend);
            ///set the frame buffer render target (now we are going to render to the MONITOR)
            render.PushRenderTarget(null);

            render.Clear(Color.Black);
            ///Render the original texture (without blending)
            render.RenderTextureComplete(SCENERT, Color.White, GraphicInfo.FullScreenRectangle, Matrix.Identity);

            ///FAKE BLUR KKKKK (mais KKKK)
            ///Render the BLOOM render target four times on the top of the original images with offset and Additive blending
            ///We also use a brightNess variable to control how brigh the final image will be. (use something like 60)
            render.RenderTextureComplete(BLOOMRT, Color.FromNonPremultiplied(255, 255, 255, brightNess), GraphicInfo.FullScreenRectangle, Matrix.CreateTranslation(2, 2, 0), null, true, SpriteSortMode.Deferred, SamplerState.AnisotropicClamp, additiveBlend);
            render.RenderTextureComplete(BLOOMRT, Color.FromNonPremultiplied(255, 255, 255, brightNess), GraphicInfo.FullScreenRectangle, Matrix.CreateTranslation(-2, -2, 0), null, true, SpriteSortMode.Deferred, SamplerState.AnisotropicClamp, additiveBlend);
            render.RenderTextureComplete(BLOOMRT, Color.FromNonPremultiplied(255, 255, 255, brightNess), GraphicInfo.FullScreenRectangle, Matrix.CreateTranslation(2, -2, 0), null, true, SpriteSortMode.Deferred, SamplerState.AnisotropicClamp, additiveBlend);
            render.RenderTextureComplete(BLOOMRT, Color.FromNonPremultiplied(255, 255, 255, brightNess), GraphicInfo.FullScreenRectangle, Matrix.CreateTranslation(-2, 2, 0), null, true, SpriteSortMode.Deferred, SamplerState.AnisotropicClamp, additiveBlend);

The helper Code i Used:

public void RenderTextureComplete(Texture2D texture, Color color, Rectangle destination, Matrix transform, Rectangle? source = null, SpriteSortMode SpriteSortMode = SpriteSortMode.Deferred, SamplerState samplerState = null, BlendState blenderState = null, RasterizerState rasterizerState = null, DepthStencilState depthState = null, Effect effect = null)
        {
            spriteBatch.Begin(SpriteSortMode, blenderState, samplerState, depthState, rasterizerState, effect, transform);
            spriteBatch.Draw(texture, destination,source, color);
            spriteBatch.End();
        }

///actually is a bit more than this ... but this is enough for this effect
public void PushRenderTarget(params RenderTarget2D[] renderTarget)
        {
            if (renderTarget == null)
            {
                device.SetRenderTargets(null);
            }
            else
            {
                RenderTargetBinding[] bindings = new RenderTargetBinding[renderTarget.Count()];
                for (int i = 0; i < renderTarget.Count(); i++)
                {
                    bindings[i] = renderTarget[i];
                }

                device.SetRenderTargets(bindings);
            }
        }

public void Clear(Color color, ClearOptions options = ClearOptions.Target | ClearOptions.DepthBuffer, float depth = 1, int stencil = 0)
        {
            device.Clear(options, color, depth, stencil);
        }

You can use this idea to simulate others post effects like Blur.
Enjoy =P