Postprocessing effects on WP7, Part III

Previous parts in this mini-series:

In this third and final part of this series of samples related to postprocessing on WP7, we’ll handle two effects: pixelate and radial blur.

We’ll start with the following ‘scene’

Pixelate Image

This one’s a quite easy one, and even with shaders the process would be similar. But when using shaders, this could just be one step towards more interesting effects, like Bloom.

The effect we want to achieve is the have the initial image, and give it a pixelated look, with large blocky pixels. The short of it is: scale the image down into a buffer that is 16 times smaller, and then scale it back up, and draw it using Point filtering.

First step is to create a small randertaget.

smallBuffer = new RenderTarget2D(graphicsDevice, graphicsDevice.Viewport.Width / 4, graphicsDevice.Viewport.Height / 4);

Afterwards, draw the image in the small rendertarget.

graphicsDevice.SetRenderTarget(smallBuffer);
graphicsDevice.Clear(Color.White);
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque);
spriteBatch.Draw(input, new Rectangle(0, 0, smallBuffer.Width, smallBuffer.Height), Color.White);
spriteBatch.End();

And then draw it back into the full rendertarget, by using Point filtering.

// Create this in the effect's constructor
pointSampling = new SamplerState();
pointSampling.AddressU = TextureAddressMode.Clamp;
pointSampling.AddressV = TextureAddressMode.Clamp;
pointSampling.Filter = TextureFilter.Point;

In the Apply method, add this.

// Upscale the image using point filtering, to obtain large pixels
graphicsDevice.SetRenderTarget(buffer);
graphicsDevice.Clear(Color.White);
// Use Immdiate to make sure the sampler state is applied before we draw the image
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque);
oldSamplerState = graphicsDevice.SamplerStates[0];
graphicsDevice.SamplerStates[0] = pointSampling;
spriteBatch.Draw(smallBuffer, new Rectangle(0, 0, buffer.Width, buffer.Height), Color.White);
spriteBatch.End();

The results below.

Radial Blur

The radial blur effect is very nice visually, but in it’s non-shader form, it requires several full-screen drawings, which leads to a large overdraw, which can be quite expensive. Nevertheless, in some circumstances, the additional cost is worth it.

When doing it as a pixel shader, we usually go and read values of pixels extending from the position of the current pixels, along the direction going away from the effect’s center. To simulate this without hacing shaders, you can draw the initial images with different scale values, and blend them together. However, in this case, the pixels of the various image scales will be close to one another at the center, leading to the desired effect, but the difference will increase as we move away from the center, and the technique will show it’s weaknesses.

But playing around with parameters, you can usually achieve somethings that’s good enough for your needs. Let’s get started :)

First, there are two parameters that I chose to expose.

// Center of the radial blur
public Vector2 Center { get; set; }
// desired distance between pixels close to the center
public float Distance { get; set; }

Altering the Distance you can make the ‘blur’ effect stronger or lighter. Altering the Center parameter, you can chose which part of the screen should remain ‘in focus’.

As discussed above, applying the effect simply means drawing the image a few times. Here, you’ll need to balance the need for quality and performance.

  • Drawing the image many times with a small value for Distance will result in a better-looking effect, but will be more expensive
  • Drawing the image fewer times leads to better performance, but requires you to keep the Distance factor small in order to keep the effect looking decent

In the sample, I chose to draw the image 5 times, as this was for use a sweet-spot between performance and quality.

public override Texture2D Apply(Texture2D input, GameTime gameTime)
{
    graphicsDevice.SetRenderTarget(buffer);
    graphicsDevice.Clear(Color.White);
    spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
    float step = Distance / 5;
    // Draw in reverse, starting with the largest
    // This ensures the most proeminent one is the standard sized image, while
    // the far away copies are more and more faded
    for (int i = 4; i >= 0; i--)
    {
        float currentScale = i * step + 1;
        // control how the blur looks using the attenuation
        // a high value will make the far layers more faded-out
        // a small value will make the far layers more visible
        float attenuation = 0.5f;
        spriteBatch.Draw(input, Center, null, Color.White * attenuation, 0, Center, currentScale, SpriteEffects.None, 0);
    }
    spriteBatch.End();

    graphicsDevice.SetRenderTarget(null);
    return buffer;
}

With a Distance of 0.2f, the effects looks like:

In the attached sample, you’ll be able to see how the effect look when varying the Distance between 0 and 0.2f.
Also, moving the Center parameter around the screen can lead to nice results.

The code

You can download the code here: Part3.zip

Running the sample and tapping the screen will take you through each effect.

And with that, the series comes to an end.

As soon as the Windows Phone 8 SDK and devices come out, I plan to make a series of samples that go into shader programming for these devices. I’m very excited about that, and can’t wait to put my hands on a WP8 :)