4
\$\begingroup\$

I'm in need of some assistance, as I've been at this for 2 days now and have gotten almost nowhere.

I'm trying to use the XNA Bloom sample's component to apply bloom only to my UI text. (The only way I know of to achieve a nice glow around the text)

(No need to look up the sample, I'll post all the relevant code for reference)

I'm trying to use a Rendertarget to store my UI. Currently it works great, except that the background of the rendertarget is always either A.) Black or B.) The purple screen of doom.

My SurfaceFormat is currently set to Color, so that's not the issue.

Here's how I'm drawing my UI:

 GraphicsDevice.Clear(Color.Transparent); //clear the device  
 spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied); //start my spritebatch  

 BloomComponent.BeginDraw(); //Call the method in the component to start rendering to the rendertarget  

 // Draw all of my UI stuff   

 spriteBatch.End();  // end the batch 
 GraphicsDevice.SetRenderTarget(null);  //clear the rendertarget 
 GraphicsDevice.Clear(new Color(0, 0, 0, 0));  //clear the device

Then, later in my code, I call this:

 BloomComponent.DrawBloomedScreen(); 

Which draws my bloomed objects to the screen. This all works fine, except, inexplicably, the rendertarget has an opaque background.

Here is my BloomComponent, in case the problem is in there:

 namespace MyGame   
 {   
 public class BloomComponent : DrawableGameComponent   
 {  
    #region Fields   

    SpriteBatch spriteBatch;   

    Effect bloomExtractEffect;   
    Effect bloomCombineEffect;   
    Effect gaussianBlurEffect;   

    RenderTarget2D sceneRenderTarget;   
    RenderTarget2D renderTarget1;   
    RenderTarget2D renderTarget2;   


    // Choose what display settings the bloom should use.   
    public BloomSettings Settings   
    {   
        get { return settings; }   
        set { settings = value; }   
    }   

    BloomSettings settings = BloomSettings.PresetSettings[0];   


    // Optionally displays one of the intermediate buffers used   
    // by the bloom postprocess, so you can see exactly what is   
    // being drawn into each rendertarget.   
    public enum IntermediateBuffer   
    {   
        PreBloom,   
        BlurredHorizontally,   
        BlurredBothWays,   
        FinalResult,   
    }   

    public IntermediateBuffer ShowBuffer   
    {   
        get { return showBuffer; }   
        set { showBuffer = value; }   
    }   

    IntermediateBuffer showBuffer = IntermediateBuffer.FinalResult;  


    #endregion   

    #region Initialization   


    public BloomComponent(Game1 game)   
        : base(game)   
    {   
        if (game == null)   
            throw new ArgumentNullException("game");   
    }   


    /// <summary>   
    /// Load your graphics content.   
    /// </summary>   
    protected override void LoadContent()   
    {   
        spriteBatch = new SpriteBatch(GraphicsDevice);   

        bloomExtractEffect = Game.Content.Load<Effect>("Effects\\BloomExtract");   
        bloomCombineEffect = Game.Content.Load<Effect>("Effects\\BloomCombine");   
        gaussianBlurEffect = Game.Content.Load<Effect>("Effects\\GaussianBlur");   

        // Create a texture for rendering the main scene, prior to applying bloom.   
        sceneRenderTarget = new RenderTarget2D(GraphicsDevice, 1280, 720, false, SurfaceFormat.Color, DepthFormat.None); // init render target for full viewport size;       

        // Can 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.              

        renderTarget1 = new RenderTarget2D(GraphicsDevice, 1280, 720, false, SurfaceFormat.Color, DepthFormat.None); // init render target for full viewport size;       
        renderTarget2 = new RenderTarget2D(GraphicsDevice, 1280, 720, false, SurfaceFormat.Color, DepthFormat.None); // init render target for full viewport size;       
    }   


    /// <summary>   
    /// Unload your graphics content.   
    /// </summary>   
    protected override void UnloadContent()   
    {   
        sceneRenderTarget.Dispose();   
        renderTarget1.Dispose();   
        renderTarget2.Dispose();   
    }  


    #endregion   

    #region Draw   


    /// <summary>   
    /// This should be called at the very start of the scene rendering. The bloom   
    /// component uses it to redirect drawing into its custom rendertarget, so it   
    /// can capture the scene image in preparation for applying the bloom filter.   
    /// </summary>   
    public void BeginDraw()   
    {   
        if (Visible)   
        {   
            GraphicsDevice.SetRenderTarget(sceneRenderTarget);   
        }   
    }   


    /// <summary>   
    /// This is where it all happens. Grabs a scene that has already been rendered,   
    /// and uses postprocess magic to add a glowing bloom effect over the top of it.   
    /// </summary>   
    public void DrawBloomedScreen()   
    {   
        GraphicsDevice.SamplerStates[1] = SamplerState.LinearClamp;   

        // 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(sceneRenderTarget, 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.   
        GraphicsDevice.SetRenderTarget(null);   

        EffectParameterCollection parameters = bloomCombineEffect.Parameters;   

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

        GraphicsDevice.Textures[11] = sceneRenderTarget;   

        Viewport viewport = GraphicsDevice.Viewport;   

        DrawFullscreenQuad(renderTarget1,   
                           viewport.Width, viewport.Height,   
                           bloomCombineEffect,   
                           IntermediateBuffer.FinalResult);   
    }   


    /// <summary>   
    /// Helper for drawing a texture into a rendertarget, using   
    /// a custom shader to apply postprocessing effects.   
    /// </summary>   
    void DrawFullscreenQuad(Texture2D texture, RenderTarget2D renderTarget,   
                            Effect effect, IntermediateBuffer currentBuffer)   
    {   
        GraphicsDevice.SetRenderTarget(renderTarget);   

        DrawFullscreenQuad(texture,   
                           renderTarget.Width, renderTarget.Height,   
                           effect, currentBuffer);   
    }   


    /// <summary>   
    /// Helper for drawing a texture into the current rendertarget,   
    /// using a custom shader to apply postprocessing effects.   
    /// </summary>   
    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;   
        }   

        spriteBatch.Begin(0, BlendState.Opaque, null, null, null, effect);   
        spriteBatch.Draw(texture, new Rectangle(0, 0, width, height), Color.White);   
        spriteBatch.End();   
    }   


    /// <summary>   
    /// Computes sample weightings and texture coordinate offsets   
    /// for one pass of a separable gaussian blur filter.   
    /// </summary>   
    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;   
        }   

        // Tell the effect about our new filter settings.   
        weightsParameter.SetValue(sampleWeights);   
        offsetsParameter.SetValue(sampleOffsets);   
    }   


    /// <summary>   
    /// Evaluates a single point on the gaussian falloff curve.   
    /// Used for setting up the blur filter weightings.   
    /// </summary>   
    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)));   
    }  


    #endregion   
}   

}

The only other thing I can think of is that the Bloom shaders are somehow disgarding the alpha values, thus leaving me with an opaque screen. Here's the shaders in case that's the case and I'm not seeing it:

BloomExtract, which is the first step

 // Pixel shader extracts the brighter areas of an image.   
 // This is the first step in applying a bloom postprocess.   

 sampler TextureSampler : register(s0);   

 float BloomThreshold;   


float4 PixelShaderFunction(float2 texCoord : TEXCOORD0) : COLOR0   
{   
     // Look up the original image color.   
     float4 c = tex2D(TextureSampler, texCoord);   

     // Adjust it to keep only values brighter than the specified threshold.   
     return saturate((c - BloomThreshold) / (1 - BloomThreshold));   
}   


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

Then, I apply Gaussian Blur twice, once horizontally, then once vertically. I don't think it's the blur, so I'll leave that shader out for right now.

Then, BloomCombine, which is the final step

 // 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(s11);   

 float BloomIntensity;   
 float BaseIntensity;   

 float BloomSaturation;   
 float BaseSaturation;   


 // 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(float2 texCoord : TEXCOORD0) : COLOR0   
 {   
      // Look up the bloom and original base image colors.   
     float4 bloom = tex2D(BloomSampler, texCoord);   
     float4 base = tex2D(BaseSampler, 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_2_0 PixelShaderFunction();   
    }   
 }  

I've tried everything possible that I could think of, so I'm looking for some advice. Chances are that anything you suggest, I won't know how to do, so brownie points for clear and concise descriptions so I can try to implement it quickly to determine if you were correct.

Thanks in advance for any help rendered! (Pun intended)

\$\endgroup\$
1
  • \$\begingroup\$ Do you think you can post a screenshot of what is happening right now and point out what should happen? I kind of think I understand what you are trying to say, but of course, a screenshot is worth a thousand words. \$\endgroup\$
    – DMan
    Dec 28, 2011 at 21:49

1 Answer 1

7
\$\begingroup\$

I've managed to get it working with a few minor changes to the sample. Here's the steps I took, but let me know if you need additional help.


On the BloomComponent Class

  1. Add a new private RenderTarget2D instance to the BloomComponent class. I called it finalRenderTarget (Line 29).
  2. Add a public getter FinalRenderTarget so that you can retrieve it from outside. (Line 35)
  3. Initialize finalRenderTarget in the LoadContent() method, exactly like the existing sceneRenderTarget. (Line 106)
  4. On the final device.SetRenderTarget() call in the DrawBloomedScreen() method, change null to finalRenderTarget so that the bloomed image is stored in the new render target instead of being rendered to the backbuffer. (Line 188)
  5. At the very end of the DrawBloomedScreen() method add a device.SetRenderTarget(null) to pass control back to the backbuffer. (Line 206)
  6. (Optional) Rename DrawBloomedScreen() to EndDraw(). I think it makes the code more symetric as you'll see below. :-) (Line 156)

Link: Here's what my BloomComponent class looked like after these changes.


On your Drawing Code

Then on your side of the code draw things in this order:

// Step 1 - Render UI with bloom and store it in bloom's internal rendertarget
bloom.BeginDraw();
device.Clear(Color.Transparent);
DrawUi();
bloom.EndDraw();

// Step 2 - Draw your scene
device.Clear(Color.Black);
DrawScene();

// Step 3 - Draw bloomed UI on top of everything by drawing FinalRenderTarget with a spritebatch
spriteBatch.Begin();
spriteBatch.Draw(bloom.FinalRenderTarget, new Rectangle(0, 0, viewport.Width, viewport.Height), Color.White);
spriteBatch.End();

Link: Here's what the Game.Draw method of my sample looked liked.


Results

And this is what I got:

Sample Image

PS: There are other ways (and better) to add an outer glow to your text.

\$\endgroup\$
2
  • \$\begingroup\$ I had run across SpriteFont before, but hadn't thought to use it. Since my UI (I should have called it a HUD) is mostly score, I wrote a fairly simple manual kerning class for it. It looks much better than anything I could have achieved with Bloom. However, I'll need your work later, so you have my thanks! \$\endgroup\$ Dec 29, 2011 at 17:38
  • \$\begingroup\$ Thanks for this! I actually used a slightly modified version of your solution here to do two passes of bloom (one on part of the screen, one on the entire screen) at different settings, without requiring MRT. \$\endgroup\$ Mar 4, 2012 at 22:10

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .