Tutorial - Bloom Effects (using 3.4 right now)


This is based on original XNA version (which used DrawComponent) however we'll make some slight modifications. I personally don't recommend using DrawComponent so we'll just make it a regular class. Also we just make a few slight adjustments to the shaders since shaders are at this time a tiny bit different (just parameters). What we'll make:

bloom_test

Step 1) Start a new MonoGame project. I will call this one BloomTest. (or add to an existing one)
First we'll add a SpriteFont to the content pipeline. If using MonoGame, you'll double click on the following(highlighted) to open the MonoGame content pipeline:

MonoGamePipeline1

Then add a SpriteFont to the Pipeline. I'm calling it hudFont like in the xna example. To do this click edit, add item, and SpriteFont. You can also right-click an item and go to location - then right-click and edit with notepad++ or something to edit the font. (it's possible to use font textures too - need to edit the processing)

pipeline1

Step 2) Add the following variables at the top of your Game1 class:

Code:
public class Game1 : Game
{
    const int SCREENWIDTH = 1024, SCREENHEIGHT = 768;
    const bool FULLSCREEN = false;        
    GraphicsDeviceManager graphics;
    PresentationParameters pp;        
    SpriteBatch spriteBatch;
    SpriteFont spriteFont;
    Texture2D background, shipImage, lazer_images;
    RenderTarget2D renderTarget1, renderTarget2;
    Song music; 

    Bloom bloom; // <--need to make this class still

    int bloomSettingsIndex = 0;              
        
    static public KeyboardState kb, lkb;
    static public GamePadState gp, lgp;
    static public MouseState ms, lms;
    static public int screenW, screenH;
    Rectangle screenRect;
    float BloomSatPulse = 1f, bloomSatDir=0.09f;
    Vector2 mp, pos, vel, aim, background_pos; //mouse pos, ship_pos, ship velocity, background position (scrolling)
    float rotation; // ship rotation
        
    // LAZERS VARS:
    List<Lazer> lazers = new List<Lazer>();
    Rectangle lazer1_rect, glow_rect;
    Vector2 lazer1_orig, glow_orig; 
    int lazer_timer = 0;
    static Random rand = new Random();
    SoundEffect lazerSound;

    // C O N S T R U C T        
    public Game1() {
        graphics = new GraphicsDeviceManager(this) { 
            PreferredBackBufferWidth = SCREENWIDTH, PreferredBackBufferHeight = SCREENHEIGHT, IsFullScreen = FULLSCREEN, PreferredDepthStencilFormat = DepthFormat.Depth16 };            
        Content.RootDirectory = "Content";
    }

(debugging so FullScreen false for now), presentation parameters - hold info about how graphics display (like size /color format for example)... I guess most of these vars are easy to understand - so I'll just explain a few. Bloom is going to be a class that we will add that will hold our bloom stuff (except we won't do it as a drawComponent like in xna example). The lkb for example holds the previous keyboard state (to check for single keypress).
BloomSatPulse, bloomSatDir -- pulse amount and direction of pulse change
We'll add a Lazer class also which will store information about each lazer shot. The rectangles are the sourceRects for the lazer spritesheet (where to copy from).
lazer1_orig, glow_orig -- hold the center of rotation points inside each sourceRect. Important to understand that the origin is not relative to the spritesheet - it's actually relative to the sourceRect. So if the sourceRect was (40,40 - 100,80) and we wanted a center rotation for that we would use and origin of (100-40)/2, (80-40)/2 so origin = (30,20)
(Note: Remember - if you find some things underlined (ie: Song) you can often right-click on it and click resolve and it will show you the using option which if you click it will automatically include the using namespace at the top)


Step 3) Add the following Init code. I should note that you need to wait until after the contructor to attain the presentation parameters (or they may not be correct). If you tried to assign your graphics card to a display resolution it is not compatible with it will use the closest matching display. That is why it is good to extract the current presentation parameters after setting it up and store in screenW, screenH and screenRect (which will come in handy for matching backgrounds to display size).
Here we create our bloom object (we'll make the class later), and 2 render targets which match our backbuffer(swap chain)display format. For some effects that do not require precision you can use half or even quarter resolution to save video ram and blend them into the scene by stretching them with a destination rectangle.

Code:
// I N I T         
protected override void Initialize() {
    spriteBatch = new SpriteBatch(GraphicsDevice);
    pp = GraphicsDevice.PresentationParameters; 
    screenW = pp.BackBufferWidth; screenH = pp.BackBufferHeight;
    screenRect = new Rectangle(0, 0, screenW, screenH);

    bloom = new Bloom(GraphicsDevice, spriteBatch);
    renderTarget1 = new RenderTarget2D(GraphicsDevice, screenW, screenH, false, pp.BackBufferFormat, pp.DepthStencilFormat, pp.MultiSampleCount, RenderTargetUsage.DiscardContents);
    renderTarget2 = new RenderTarget2D(GraphicsDevice, screenW, screenH, false, pp.BackBufferFormat, pp.DepthStencilFormat, pp.MultiSampleCount, RenderTargetUsage.DiscardContents);

    lazer1_rect = new Rectangle(79, 15, 32, 32);   lazer1_orig = new Vector2(16f, 16f);
    glow_rect   = new Rectangle(10, 10, 44, 44);   glow_orig   = new Vector2(22f, 22f);
    base.Initialize();
}


Step 4) Loading content:
(Note - I'm 99.9% sure the dispose of the render targets is not required as it should do this by itself. The original xna version did this - probably so you had the option to do it if in the program you reach a point where you no longer need them -- rarely when programming c# you may find things that are unmanaged which you need to add dispose methods to and call to prevent memory leaks).

Code:
// L O A D 
protected override void LoadContent() {                      
    spriteFont = Content.Load<SpriteFont>("hudFont");            
    background = Content.Load<Texture2D>("background");
    shipImage = Content.Load<Texture2D>("space_ship");
    lazer_images = Content.Load<Texture2D>("lazers");
    bloom.LoadContent(Content, pp);
    lazerSound = Content.Load<SoundEffect>("shootLazer");
    music = Content.Load<Song>("subterain1");                        
    MediaPlayer.Play(music); MediaPlayer.IsRepeating = true;
}        
protected override void UnloadContent() {
    bloom.UnloadContent();
    renderTarget1.Dispose(); renderTarget2.Dispose();
}


We already added hudFont to the content pipeline. You'll want to make a seamless repeating background and add it to your content folder. You can then go into the MonoGame pipeline, edit, add existing item, and add the background.png image (as link). If it is not in your content folder, you also have the option to add it from another location as a copy (it will copy it into content folder).
So you will also need to add space_ship.png and a lazers.png spritesheet (I'll include these in source code).
bloom.LoadContent has not been implemented yet - we'll add that later.
Also you can add shootLazer.wav, subterain1.wma, and others with the pipeline. MediaPlayer controls music (obviously).
add_existing
If you look down in MonoGame pipeline when you select an item you can see its settings. You may sometimes want to edit settings like to target a specific platform or change image processing. For example, you may want to use DXT compression to save video (which is good for backgrounds), or change premultiply alpha -- if you are doing a 3D game you would probably want to generate mipmaps for LOD for example.
I used these for lazer sheet. I will use additive blending and white for center and red (or whatever color you tell it to draw as) for the lazer glow using this image to extract the 2 sourceRects from:
NOTE: That since I don't want to split up bloom layer drawing twice, I cleared the black around the lazers. They have black backgrounds ONLY in this image so you can actually see them (white on transparent):
lazers

Step 5) Just before the update, I'm including enum for controllers. You could also add one for keyboard control so whichever the user prefers to use you can switch control types (potentially you could use all).
It's initially set to mouse, but pressing Start button sets it to gamepad control.
  It first captures the old controller states from the last loop and captures the current keyboard, mouse, and gamepad states.
If the previous state was not pressed but it is pressed now, then it is considered a button hit. (holding it down doesn't count)
BloomSettings will be added in a bloom settings class later (which I'll explain then) - so just trust the mode change code for now.

With mouse velocity, depending on how far the mouse moved in the frame it might cause too much acceleration so it is clamped within a -20 to +20 range for X and Y speeds. Velocity = 20% of position difference between mouse and ship between each frame. This creates a nice easing effect on the velocity that makes the ship velocity adjust smoothly (thus rotation follows more smoothly).
In gamepad mode, it simply multiplies the amount of direction of the thumbstick by 20 (and flips the Y for the velocity).

Code:
// U P D A T E
enum gameControl { mouse, gamepad };
gameControl useController = gameControl.mouse;        
protected override void Update(GameTime gameTime) {
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit();
    lkb = kb; lgp = gp; lms = ms; lgp = gp;
    kb = Keyboard.GetState(); ms = Mouse.GetState(); gp = GamePad.GetState(PlayerIndex.One);            

    // Switch to the next bloom settings preset?
    if ((gp.Buttons.A == ButtonState.Pressed && lgp.Buttons.A != ButtonState.Pressed) || (kb.IsKeyDown(Keys.A) && lkb.IsKeyUp(Keys.A)))  {
        bloomSettingsIndex = (bloomSettingsIndex + 1) % BloomSettings.PresetSettings.Length;
        bloom.Settings = BloomSettings.PresetSettings[bloomSettingsIndex];                
    }  
    // Cycle through the intermediate buffer debug display modes?
    if ((gp.Buttons.X == ButtonState.Pressed && lgp.Buttons.X != ButtonState.Pressed) || (kb.IsKeyDown(Keys.X) && lkb.IsKeyUp(Keys.X)))  {                
        bloom.ShowBuffer++;
        if (bloom.ShowBuffer > Bloom.IntermediateBuffer.FinalResult)   bloom.ShowBuffer = 0;
    }            

    if (gp.Buttons.Start == ButtonState.Pressed) useController = gameControl.gamepad;
    if (ms.MiddleButton == ButtonState.Pressed) useController = gameControl.mouse;

    // SHIP CONTROL / MOVEMENT            
    if (useController == gameControl.mouse)
    {
        mp = new Vector2(ms.X, ms.Y);
        vel = (mp - pos) * 0.2f; // adjust velocity by 20% of the distance movement of mouse each frame (makes smooth movements)   
        vel.X = MathHelper.Clamp(vel.X, -20.0f, 20.0f);
        vel.Y = MathHelper.Clamp(vel.Y, -20.0f, 20.0f);
    }
    else if (useController == gameControl.gamepad) {                 
        vel = gp.ThumbSticks.Left * 20f;
        vel.Y *= -1;                    
    }            
    pos += vel; background_pos += vel;
    Vector2 direction = vel; //vector between mouse point and player position            
    pos.X = MathHelper.Clamp(pos.X, screenW * .4f, screenW - screenW * .4f);
    pos.Y = MathHelper.Clamp(pos.Y, screenH * .4f, screenH - screenH * .4f);
            
    if (direction.LengthSquared() > 0.1f)
    {
        direction.Normalize();                                  //Vector2.Normalize(direction); // <-- DON'T DO THIS
        aim = direction; 
        rotation = (float)Math.Atan2(direction.Y, direction.X);
    }                            
            
    // LAZERS: 
    if (lazer_timer<=0) {                
        if ((gp.Buttons.B == ButtonState.Pressed && lgp.Buttons.B != ButtonState.Pressed) || (kb.IsKeyDown(Keys.Space) && lkb.IsKeyUp(Keys.Space))) {
            Quaternion aimQuat = Quaternion.CreateFromYawPitchRoll(0, 0, rotation); 
            Vector2 offset = Vector2.Transform(new Vector2(25, -8), aimQuat); //rotate to point on gun on ship at angle aimQuat                    
            lazers.Add(new Lazer(pos + offset, vel+aim*10f)); //add bullet at gun1 at correct velocity (ship velocity + lazer velocity)                    
            offset = Vector2.Transform(new Vector2(25, 8), aimQuat);
            lazers.Add(new Lazer(pos + offset, vel+aim * 10f)); //add bullet at gun1 at correct velocity (ship velocity + lazer velocity)                    
            lazerSound.Play(0.2f, RandomFloat(-0.2f, 0.2f), 0);
            lazer_timer=5;
        }
    } else lazer_timer--;
    foreach (var lazer in lazers) lazer.Update(vel); // (vel is provided to adjust positions based on world position change too)
    //remove any expired lazers:            
    lazers.RemoveAll(item => item.expired == true); //Console.WriteLine("lazer count = "+lazers.Count);

    //Just for fun - here I've added a pulse animation by adjusting the bloom saturation:
    BloomSatPulse += bloomSatDir;
    if (BloomSatPulse > 2.5f) bloomSatDir = -0.09f;
    if (BloomSatPulse < 0.1f) bloomSatDir = 0.09f;
    bloom.Settings.BloomSaturation = BloomSatPulse;

    base.Update(gameTime);
}

        
// helper for rand floats:
float RandomFloat(float min, float max) {
    return (float)rand.NextDouble() * (max - min) + min;
}

Position of ship and background are adjusted based on velocity each frame. The background is drawn with UV linear wrapping as the sampler state so no matter where we place the background, it will be some wrapped value causing it to repeat without having to draw 4 copies of the background at different 4 positions. This is much more efficient and something like this can be done for other layers of parallax.
Direction is copied from velocity. We need to remember velocity still and we want to normalize the direction (a direction vector of length 1 ) -- this way we can multiply a direction by a size to get a vector pointing in the same direction of any size we choose.
The position of the ship is clamped within 40% from the edges of the screen.
If the direction of the ship isn't basically zero, we can normalize the direction, remember the original aim direction and calculate the ship rotation using atan from the direction vector.
  if the lazer_timer has reached zero (amount of time before can shoot another lazer), we check for lazer button input. If so then we get a quaternion for z rotation to transform the lazer-gun offsets of the ship based on rotation. We then add a new lazer at the ship position + gun_offset and give it a velocity of ship speed plus 10 in the aiming direction (although you might want to increase this speed higher than 10 so lazers are faster than max ship speed). [ditto for lazer gun 2 except at offset +8 instead of -8]
  We then play the lazer noise with some pitch variation. For enemy ships I would also add stereo panning instead of 0.
Lazer timer is set to 5 to space out the time between lazer shots. (lazer counter counts down until ready for another lazer)
For each lazer, it updates the position based on velocity (in Lazer class which we'll add soon).
Any lazers that have expired (too far away or hit something) are removed from the list (and list is reduced in size).
  Next is animation of the bloom saturation setting to make the bloom pulsate. bloomSatDir is the up down direction of the pulse and BloomSatPulse simply holds the pulse setting which is passed to bloom.Setting.BloomSaturation in the Bloom class (which we'll get to soon).
Also we have a helper method for random floats which we used with the pitch variation in the sounds (we could use for other things too like fire spreading from thrusters).

Step 6) You can set blooming of background to true or false. I wanted to show that bloom and other effects can be applied in layers which is why I don't bloom the background by default (or maybe you want to bloom it with different settings or apply different effects for example).
  First we set the first render target - renderTarget1. Which is any elements in the scene that we want to be drawn with this bloom effect applied. So probably ships, lazers, explosions, lights and other effects.
The bloom class (we've yet to make), will then use the renderTarget1 as a source to make a bloomed version in the destination target: renderTarget2, which will be drawn over top of the background items that we do not want bloom effects for.

Code:
// D R A W
protected override void Draw(GameTime gameTime) {            
    const bool bloom_background = false;

    GraphicsDevice.SetRenderTarget(renderTarget1);
    GraphicsDevice.Clear(Color.TransparentBlack); // <-- need this too
    if (bloom_background) {
        spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, SamplerState.LinearWrap); // <--- using linear WRAP
        spriteBatch.Draw(background, screenRect, new Rectangle((int)background_pos.X, (int)background_pos.Y, background.Width, background.Height), Color.White);
        spriteBatch.End();
    }
    DrawShip();
    DrawLazers();
    bloom.Draw(renderTarget1, renderTarget2);

    GraphicsDevice.SetRenderTarget(null);                        
            
    // draw wrapped background: 
    if (bloom_background == false) {
        spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, SamplerState.LinearWrap); // <--- using linear WRAP
        spriteBatch.Draw(background, screenRect, new Rectangle((int)background_pos.X, (int)background_pos.Y, background.Width, background.Height), Color.White);
        spriteBatch.End();            
    }
        
    // Draw bloomed layer over top: 
    spriteBatch.Begin(0, BlendState.AlphaBlend);
    spriteBatch.Draw(renderTarget2, new Rectangle(0, 0, screenW, screenH), Color.White); // draw all glowing components            
    spriteBatch.End();

    DrawOverlayText();

    base.Draw(gameTime);
}


SetRenderTarget(null) changes rendering to backbuffer which is our regular display (and will be visible when the swap chain/page flips) at the end of the draw call.
So we then draw our wrapped background (by setting the SamplerState to LinearWrap), using opaque (non-see-thru solid) -- layers with transparent pixels would be drawn with nonpremultiplied or alphablend (depending on how the png was made).
Next we draw the bloom layer we already created over top of the scene - stretching it to the screen dimensions(in case it is not already the same).
Finally we call our text drawing method, and then flip the page to present the result(base.Draw).


Step 7) Other Drawing Methods:
Draw ship is simple. It just draws the ship with default settings at it's position, null (to set full image as sourceRect - since it's not a spritesheet), rotation, center of rotation (origin - relative to sourcerect), scale: 1.5
DrawLazers:
Here I use a different Begin style for Additive blending (brightness stacks up and darkness is more transparent) - this is good for lights, lazers, and background fire halos.
All the lazers are drawn with the white center part and additively blending the glow colored red over top (you can try other colors). The text drawing is pretty straight forward (black and white versions with offset so easier to see)

Code:
// D R A W   S H I P 
void DrawShip()
{            
    spriteBatch.Begin();            
    spriteBatch.Draw(shipImage, pos, null, Color.White, rotation, new Vector2(shipImage.Width/2, shipImage.Height/2), 1.5f, SpriteEffects.None, 0f);
    spriteBatch.End();            
}

// D R A W   L A Z E R S
void DrawLazers()
{
    if (lazers.Count < 1) return;
    spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, SamplerState.LinearClamp, DepthStencilState.None, RasterizerState.CullNone);
    int i = 0; 
    do {
        spriteBatch.Draw(lazer_images, lazers[i].pos, lazer1_rect, Color.White, lazers[i].rot, lazer1_orig, lazers[i].scale, SpriteEffects.None, 0f);
        spriteBatch.Draw(lazer_images, lazers[i].pos, glow_rect,   Color.Red, lazers[i].rot,   glow_orig, lazers[i].scale, SpriteEffects.None, 0f);
        i++;
    } while (i < lazers.Count); // could use foreach too (shouldn't impact performance noticably)
    spriteBatch.End();
}

// D R A W   O V E R L A Y   T E X T 
void DrawOverlayText()
{
    string text = "A = settings (" + bloom.Settings.Name + ")\n" +
                    "X = show buffer (" + bloom.ShowBuffer.ToString() + ")\n" +
                    "GamePad B = shoot or   SpaceBar = shoot \n" +
                    "Start = GamePad   or   Mid Mouse Click = Mouse";

    spriteBatch.Begin();
            
    spriteBatch.DrawString(spriteFont, text, new Vector2(65, 65), Color.Black);
    spriteBatch.DrawString(spriteFont, text, new Vector2(64, 64), Color.White);

    spriteBatch.End();
}


Step 8) Now right click on your project name (ie: BloomTest) and add a class called Bloom.cs
In the constructor of the class, we'll pass in the device to use and the instance of the spriteBatch we already made. (in c++ these would be like pointer versions - which merely keep track of the original in memory).
We'll be using 3 effects:
BloomExtractEffect -- extracts the bright parts
GaussianBlurEffect  -- blurs the image (horizontally, vertically or both)
BloomCombineEffect -- creates the final bloom effect
renderTarget1 and renderTarget2 are for creatting intermediate render target stages of the effect (ie: bloom on 1, then gaussian blur it horizontally to 2, then blur again vertical to 1, then combine to 2)
We will also make a BloomSettings class (haven't made yet) which will hold a table of setting types we might want to use

enum IntermediateBuffer -- is for how far into the intermediate buffer stages we want to show. For exampe we might have it only show the first part, prebloom. Or set it to show the next stage: BlurredHorizontally, etc... In your final version you can remove this stuff and just have all the effects active for final result only.

Code:
public class Bloom
{
    GraphicsDevice device;
    SpriteBatch spriteBatch;
    Effect bloomExtractEffect;
    Effect bloomCombineEffect;
    Effect gaussianBlurEffect;        
    RenderTarget2D renderTarget1;
    RenderTarget2D renderTarget2;
    public BloomSettings Settings  { get { return settings; } set { settings = value; } }
    BloomSettings settings = BloomSettings.PresetSettings[0];

    public enum IntermediateBuffer { PreBloom, BlurredHorizontally, BlurredBothWays, FinalResult, }
    public IntermediateBuffer ShowBuffer {  get { return showBuffer; }  set { showBuffer = value; } }
    IntermediateBuffer showBuffer = IntermediateBuffer.FinalResult;        
                

    // C O N S T R U C T
    public Bloom(GraphicsDevice graphics, SpriteBatch passedSpriteBatch) {
        device = graphics;
        spriteBatch = passedSpriteBatch;
    }


Step 9) Load the effects:
We need access to the content manager so we pass that in and the original presentation params so we get the correct screen dimensions and formats for our rendertargets.
First we load the .fx stuff (we'll add these later)
Next we get the display format and create the 2 rendertargets with half the normal screen size to save video ram and generally be more efficient (since precision isn't so important with this effect - not noticable).

Code:
// L O A D 
public void LoadContent(ContentManager Content, PresentationParameters pp)
{                        
    bloomExtractEffect = Content.Load<Effect>("BloomExtract");
    bloomCombineEffect = Content.Load<Effect>("BloomCombine");
    gaussianBlurEffect = Content.Load<Effect>("GaussianBlur");

    int width = pp.BackBufferWidth, height = pp.BackBufferHeight; 
    SurfaceFormat format = pp.BackBufferFormat;
         
    // Create two rendertargets for the bloom processing. These are half the size of the backbuffer, in order to minimize fillrate costs. Reducing
    // the resolution in this way doesn't hurt quality, because we are going to be blurring the bloom images in any case.
    width /= 2;
    height /= 2;

    renderTarget1 = new RenderTarget2D(device, width, height, false, format, DepthFormat.None);
    renderTarget2 = new RenderTarget2D(device, width, height, false, format, DepthFormat.None);
}        
public void UnloadContent()
{            
    renderTarget1.Dispose();
    renderTarget2.Dispose();
}


Step 10) Draw the Effects
1 -- Use bloom extract effect, setting the current bloom threshold and draw from our scene source rendertarget into renderTarget1 using the extract effect (preBloom) [the last parameter is temporary for this test program - its just used to decide whether an effect will be shown]
2 -- We set the blur effect parameters to be 1.0f divided up into the number of pixels wide the render target is (later you'll see how that method works) and then draw from target 1 to target 2 with horizontal blurring.
3 -- We then do the same sort of thing for vertical blurring
4 -- set the destination render target and shader parameters based on current settings.
IT IS IMPORTANT to note that normally if parameters don't change - we would certainly not do this in the draw or update - but instead change them only in the initialize or load. It is only necessary to change parameters which change each frame.
IT IS IMPORTANT - to note that your second or third or more textures being set should be passed as a parameter into the shader (at this time). Also it is important that your vertex declaration matches your vertex shader and the vs shader output matches the ps shader input. Since SpriteBatch uses SV_Target0, COLOR0, TEXCOORD0, you must use that (not POSITION0 for example) as inputs to the pixel shader (otherwise they'll be set to wrong register).
Finally we draw renderTarget1 to the destination render target (passed from game1) with the combined effect (which I'll show later)

Code:
// D R A W
public void Draw(RenderTarget2D sourceRenderTarget, RenderTarget2D destRenderTarget)
{        
    // Pass 1: draw the scene into rendertarget 1, using a shader that extracts only the brightest parts of the image.
    bloomExtractEffect.Parameters["BloomThreshold"].SetValue(Settings.BloomThreshold);
    DrawFullscreenQuad(sourceRenderTarget, renderTarget1, bloomExtractEffect, IntermediateBuffer.PreBloom);

    // Pass 2: draw from rendertarget 1 into rendertarget 2, using a shader to apply a horizontal gaussian blur filter.
    SetBlurEffectParameters(1.0f / (float)renderTarget1.Width, 0);
    DrawFullscreenQuad(renderTarget1, renderTarget2, gaussianBlurEffect, IntermediateBuffer.BlurredHorizontally); 

    // Pass 3: draw from rendertarget 2 back into rendertarget 1, using a shader to apply a vertical gaussian blur filter.
    SetBlurEffectParameters(0, 1.0f / (float)renderTarget1.Height);
    DrawFullscreenQuad(renderTarget2, renderTarget1, gaussianBlurEffect, IntermediateBuffer.BlurredBothWays);

    // Pass 4: draw both rendertarget 1 and the original scene image back into the main backbuffer, using a shader that
    // combines them to produce the final bloomed result.
    device.SetRenderTarget(destRenderTarget);

    EffectParameterCollection parameters = bloomCombineEffect.Parameters;

    parameters["BloomIntensity"].SetValue(Settings.BloomIntensity);
    parameters["BaseIntensity"].SetValue(Settings.BaseIntensity);
    parameters["BloomSaturation"].SetValue(Settings.BloomSaturation);
    parameters["BaseSaturation"].SetValue(Settings.BaseSaturation);

    bloomCombineEffect.Parameters["BaseTexture"].SetValue(sourceRenderTarget);

    Viewport viewport = device.Viewport;
            
    DrawFullscreenQuad(renderTarget1, viewport.Width, viewport.Height, bloomCombineEffect, IntermediateBuffer.FinalResult);
    //now destRenderTarget has the result rendered into it
}


Step 11)

Version 1 draws from texture (or passed in renderTarget) to renderTarget using the specified effect. It sets the target and calls to the version that draws. The showBuffer<currentBuffer thing is temporary. We could remove it later - it's just to show stages of rendering if we've selected to opt out of later stages for demonstration purposes.
IT IS IMPORTANT - to clear the render target to clear (or transparent black) so that it doesn't have a black solid background when layering the effect over the scene. We draw with AlphaBlend (not opaque) to use see-through pixels using the passed in size.
Code:
// DRAW FULL SCREEN QUAD 1
void DrawFullscreenQuad(Texture2D texture, RenderTarget2D renderTarget, Effect effect, IntermediateBuffer currentBuffer)
{
    device.SetRenderTarget(renderTarget);
    DrawFullscreenQuad(texture, renderTarget.Width, renderTarget.Height, effect, currentBuffer);
}
        
// DRAW FULL SCREEN QUAD 2
void DrawFullscreenQuad(Texture2D texture, int width, int height, Effect effect, IntermediateBuffer currentBuffer)
{
    // If the user has selected one of the show intermediate buffer options, we still draw the quad to make sure the image will end up on the screen,
    // but might need to skip applying the custom pixel shader.
    if (showBuffer < currentBuffer) {
        effect = null;
    }
    device.Clear(Color.TransparentBlack); //<-- MUST do this for each target if using transparent target. 
    spriteBatch.Begin(0, BlendState.AlphaBlend, null, null, null, effect);
    spriteBatch.Draw(texture, new Rectangle(0, 0, width, height), Color.White);
    spriteBatch.End();
}


Step 12) SetBlurEffectParameters - We retrieve the parameters for the effects, get the number of samples and use to create temporary arrays for computing the filter values with.
Compute gaussian takes the offset amount and uses its formula to compute the weight value for that sample (based on blur amount)
We then compute the sample offsets -delta, delta in each loop pass (more info in comments)
We then normalize the weights(base on total weights) and pass the weights and offsets into the shader.

Code:
// SET BLUR EFFECT PARAMETERS
void SetBlurEffectParameters(float dx, float dy)
{
    // Look up the sample weight and offset effect parameters.
    EffectParameter weightsParameter, offsetsParameter;

    weightsParameter = gaussianBlurEffect.Parameters["SampleWeights"];
    offsetsParameter = gaussianBlurEffect.Parameters["SampleOffsets"];

    // Look up how many samples our gaussian blur effect supports.
    int sampleCount = weightsParameter.Elements.Count;

    // Create temporary arrays for computing our filter settings.
    float[] sampleWeights = new float[sampleCount];
    Vector2[] sampleOffsets = new Vector2[sampleCount];

    // The first sample always has a zero offset.
    sampleWeights[0] = ComputeGaussian(0);
    sampleOffsets[0] = new Vector2(0);

    // Maintain a sum of all the weighting values.
    float totalWeights = sampleWeights[0];

    // Add pairs of additional sample taps, positioned along a line in both directions from the center.
    for (int i = 0; i < sampleCount / 2; i++)
    {
        // Store weights for the positive and negative taps.
        float weight = ComputeGaussian(i + 1);

        sampleWeights[i * 2 + 1] = weight;
        sampleWeights[i * 2 + 2] = weight;

        totalWeights += weight * 2;

        // To get the maximum amount of blurring from a limited number of pixel shader samples, we take advantage of the bilinear filtering
        // hardware inside the texture fetch unit. If we position our texture coordinates exactly halfway between two texels, the filtering unit
        // will average them for us, giving two samples for the price of one. This allows us to step in units of two texels per sample, rather
        // than just one at a time. The 1.5 offset kicks things off by positioning us nicely in between two texels.
        float sampleOffset = i * 2 + 1.5f;

        Vector2 delta = new Vector2(dx, dy) * sampleOffset;

        // Store texture coordinate offsets for the positive and negative taps.
        sampleOffsets[i * 2 + 1] = delta;
        sampleOffsets[i * 2 + 2] = -delta;
    }

    // Normalize the list of sample weightings, so they will always sum to one.
    for (int i = 0; i < sampleWeights.Length; i++) {
        sampleWeights[i] /= totalWeights;
    }
            
    weightsParameter.SetValue(sampleWeights);  // Tell the effect about our new filter settings.
    offsetsParameter.SetValue(sampleOffsets);
}


// C O M P U T E   G A U S S I A N 
float ComputeGaussian(float n)
{
    float theta = Settings.BlurAmount;
    return (float)((1.0 / Math.Sqrt(2 * Math.PI * theta)) *
                    Math.Exp(-(n * n) / (2 * theta * theta)));
}


Step 13) Right click on the project and add class: BloomSettings.cs
This will hold Name, BloomThreshold (brightness for bloom), BlurAmount, BloomIntensity(0-1), BaseIntensity(0-1), BloomSaturation (which we animate for pulsation), and BaseSaturation -- these are explained in comments below.
PresetSettings creates a list of preset settings of all these values using the constructor.

Code:
public class BloomSettings
{        
    public string Name;    // Name of a preset bloom setting, for display to the user.

    // Controls how bright a pixel needs to be before it will bloom. Zero makes everything bloom equally, while higher values select
    // only brighter colors. Somewhere between 0.25 and 0.5 is good.
    public float BloomThreshold;

    // Controls how much blurring is applied to the bloom image. The typical range is from 1 up to 10 or so.
    public float BlurAmount;

    // Controls the amount of the bloom and base images that will be mixed into the final scene. Range 0 to 1.
    public float BloomIntensity;
    public float BaseIntensity;

    // Independently control the color saturation of the bloom and base images. Zero is totally desaturated, 1.0 leaves saturation
    // unchanged, while higher values increase the saturation level.
    public float BloomSaturation;
    public float BaseSaturation;


    // CONSTRUCT BLOOM SETTING DESCRIPTOR
    public BloomSettings(string name, float bloomThreshold, float blurAmount,
                            float bloomIntensity, float baseIntensity,
                            float bloomSaturation, float baseSaturation)
    {
        Name = name;
        BloomThreshold = bloomThreshold;
        BlurAmount = blurAmount;
        BloomIntensity = bloomIntensity;
        BaseIntensity = baseIntensity;
        BloomSaturation = bloomSaturation;
        BaseSaturation = baseSaturation;
    }
        
    // TABLE OF BLOOM SETTING PRESETS        
    public static BloomSettings[] PresetSettings =
    {
        //                Name           Thresh  Blur Bloom  Base  BloomSat BaseSat
        new BloomSettings("Default",     0.25f,  4,   1.25f, 1,    1,       1),
        new BloomSettings("Soft",        0,      3,   1,     1,    1,       1),
        new BloomSettings("Desaturated", 0.5f,   8,   2,     1,    0,       1),
        new BloomSettings("Saturated",   0.25f,  4,   2,     1,    2,       0),
        new BloomSettings("Blurry",      0,      2,   1,     0.1f, 1,       1),
        new BloomSettings("Subtle",      0.5f,   2,   1,     1,    1,       1),
    };
}


Step 14) In solution explorer, right click the very very topmost thing (ie: Solution 'BloomTest' (1 project)) and right-click add, new item and find shader and call it prototype.hlsl and add the following template code:

Code:
sampler TextureSampler : register(s0);

struct VSOutput
{
	float4 position		: SV_Position;
	float4 color		: COLOR0;
	float2 texCoord		: TEXCOORD0;
};


float4 PixelShaderFunction(VSOutput input) : COLOR0
{	
	return tex2D(TextureSampler, input.texCoord) * input.color;
}


technique EffectTechniqueName
{
	pass Pass1
	{
		PixelShader = compile ps_4_0_level_9_1 PixelShaderFunction();
	}
}


Notice that the vertex shader output struct is made to match that of the spriteBatch. If you were using another primitive rendering technique with a different vertex declaration, you must make sure it matches the declaration (order and naming -- note: use SV_Position not POSITION0). You could also place these directly in the input parameters in place of VSOutput input - to avoid the struct style. Also note that we use ps_4_0_level_9_1 (9_3 for phone I think) (dx9 compatible [ps_2 instruction count limit]) - but if you are using complex instructions it is best to use 4_0 or 5_0 (dx11+). There are some other shader notes you should take care of which I'll mention later.

Step 14.5) Save As: (go into content folder) "BloomExtract.fx"

Step 15) Change it to match the following:

Code:
sampler TextureSampler : register(s0);

float BloomThreshold;

struct VSOutput
{
	float4 position		: SV_Position;
	float4 color		: COLOR0;
	float2 texCoord		: TEXCOORD0;
};


float4 PixelShaderFunction(VSOutput input) : COLOR0
{    
    float4 c = tex2D(TextureSampler, input.texCoord);  // Look up the original image color.    
	
	// Adjust it to keep only values brighter than the specified threshold.
    return saturate((c - BloomThreshold) / (1 - BloomThreshold)); 
}


technique BloomExtract
{
    pass Pass1
    {
		PixelShader = compile ps_4_0_level_9_1 PixelShaderFunction();
    }
}


Change technique to BloomExtract, add BloomThreshold, and change the contents of PixelShaderFunciton. This takes the original pixel(texel) color in c. Then it takes the difference between the color and the threshold value(for each color channel) and divides by the amount of the opposite half of threshold -- saturate clamps the colors to their normal maximum ranges (0f-1f) so that the return color makes sense if it tries to go out of bounds. This has the effect of extracting the brightest parts of the image which will be bloomed.

Step 16) Save this.
Then save it as "GaussianBlur.fx" and change it to match this:

Code:
Code:
// Pixel shader applies a one dimensional gaussian blur filter.
// This is used twice by the bloom postprocess, first to
// blur horizontally, and then again to blur vertically.

sampler TextureSampler : register(s0);

struct VSOutput
{
	float4 position		: SV_Position;
	float4 color		: COLOR0;
	float2 texCoord		: TEXCOORD0;
};


#define SAMPLE_COUNT 15

float2 SampleOffsets[SAMPLE_COUNT];
float SampleWeights[SAMPLE_COUNT];


float4 PixelShaderFunction(VSOutput input) : COLOR0
{
    float4 c = 0;
    
    // Combine a number of weighted image filter taps.
    for (int i = 0; i < SAMPLE_COUNT; i++)
    {
        c += tex2D(TextureSampler, input.texCoord + SampleOffsets[i]) * SampleWeights[i];
    }
    
    return c;
}


technique GaussianBlur
{
    pass Pass1
    {
		PixelShader = compile ps_4_0_level_9_1 PixelShaderFunction();
    }
}


Here we have Vector2 sample offsets and sample weights. (Note if you used cbuffer, sometimes doing both together can cause a glitch so just do one way or the other)
This goes through precalculated offsets to get samples and multiply them by a weighting percentage for the final color average (to gaussian blur it).

Step 17) Save this.
Now save as: "BloomCombine.fx"
NOTICE: that do to a temporary bug, we need to pass in BaseTexture as a parameter using .SetValue and use the following format for textures (when more than 1 [ie: 2 as below]) (don't set like device.texture[1]... )
And use this for the second texture:
sampler BaseSampler : register(s1)
{
  Texture = (BaseTexture);
  Filter = Linear; // Linear clamping
  AddressU = clamp;
  AddressV = clamp;
}

Code:
// Pixel shader combines the bloom image with the original
// scene, using tweakable intensity levels and saturation.
// This is the final step in applying a bloom postprocess.

sampler BloomSampler : register(s0);
sampler BaseSampler : register(s1)
{
	Texture = (BaseTexture); // <--- must use SetValue for BaseTexture
	Filter = Linear;
	AddressU = clamp;
	AddressV = clamp;
};

float BloomIntensity;
float BaseIntensity;
float BloomSaturation;
float BaseSaturation;

struct VSOutput 
{
	float4 position		: SV_Position;
	float4 color		: COLOR0;
	float2 texCoord		: TEXCOORD0;
};


// Helper for modifying the saturation of a color.
float4 AdjustSaturation(float4 color, float saturation)
{
    // The constants 0.3, 0.59, and 0.11 are chosen because the
    // human eye is more sensitive to green light, and less to blue.
    float grey = dot(color, float3(0.3, 0.59, 0.11));
    return lerp(grey, color, saturation);
}


float4 PixelShaderFunction(VSOutput input) : COLOR0
{
    // Look up the bloom and original base image colors.
    float4 bloom = tex2D(BloomSampler, input.texCoord);
	float4 base = tex2D(BaseSampler, input.texCoord);
    
    // Adjust color saturation and intensity.
    bloom = AdjustSaturation(bloom, BloomSaturation) * BloomIntensity;
    base = AdjustSaturation(base, BaseSaturation) * BaseIntensity;
    
    // Darken down the base image in areas where there is a lot of bloom,
    // to prevent things looking excessively burned-out.
    base *= (1 - saturate(bloom));
    
    // Combine the two images.
    return base + bloom;
}


technique BloomCombine
{
    pass Pass1
    {
		PixelShader = compile ps_4_0_level_9_1 PixelShaderFunction();
    }
}

First it looks up the color from the bloom image and also a color from the base at the same pixel location on the other image. Then using an adjust saturation function, we adjust the bloom amount and multiply it by an intensity factor. We use a similar technique for the base.
Then we darken down the base amount to prevent a burned out look and return the combined amounts additively.


Step 18) Right click in solution explorer to add a Lazer.cs class to the project. Add the following code:

Code:
class Lazer
{                
    public Color color = Color.White;
    public Vector2 pos, vel, scale;
    public float rot;
    public float radius; //for collision detect
    public bool expired;        
    public Lazer(Vector2 position, Vector2 velocity) //create a lazer
    {
        pos = position;    vel = velocity;    scale = Vector2.One;
        rot = (float)Math.Atan2(vel.Y, vel.X);
        radius = 8; 
        expired = false;
    }
    public void Update(Vector2 world_vel)
    {
        if (vel.LengthSquared() > 0) rot = (float)Math.Atan2(vel.Y, vel.X); //if is moving, set its' rot angle
        pos += vel; pos -= world_vel;
        // delete lazers that go off-screen            
        if ((pos.X < -32-1000) || (pos.Y < -32-1000) || (pos.X > Game1.screenW+1000) || (pos.Y > Game1.screenH+1000)) {
            expired = true;                
        }
    }

The color can be used to store lazer colors (this could be adapted for explosions or sparks). Position, velocity, and scale are all pretty straight forward (may not need scale - unless for some effect).
Rotation rotates the lazer in its direction(velocity vector).
expired is used to signal to remove it from the list (as in game1 update).
The constructor creates a new lazer at position and speed and sets rotation based on velocity vector using atan2. Default collision radius is 8 (may need to adjust update if scaling).
Update modifies the position based velocity but also updates it based on how fast is world moving by. If the lazer is too far off screen - set it as expired. Lazers could also be given a lifetime or set as expired on impacts too.


Step 19) Just as a precaution - check to make sure

your fx files build - but first make sure the settings for each item aren't messed up. For example fx should look like this:

effect_setup

If you build the program it should now run and should look something like this:

bloom_game

Source Code (project build done with VS 2013 but you can create a solution for VS 2010 too)

Home Page