Adding a Simple Bloom Effect in MonoGame with RenderTarget2D
Learn how to use MonoGame's RenderTarget2D to create a simple off‑screen bloom effect, with setup, shader passes, performance notes, and verification steps.
01 Sept 2025, 13:16 UTC

Problem: Making bright parts of a scene glow
When you want a subtle bloom—where the brightest pixels bleed light into their surroundings—you need to render the scene to an off‑screen texture, blur that texture, and then add the blurred result back onto the original image. In MonoGame the off‑screen texture is a RenderTarget2D. This post shows how to set it up, draw the scene, apply a simple Gaussian blur, and combine the results, while noting the performance trade‑offs and how to verify the effect.
Setup: Creating a matching render target
First, create a render target that matches the back buffer’s size and format. Do this once in LoadContent (or whenever the window/resolution changes).
// Fields in your Game class
private RenderTarget2D bloomTarget;
private Effect bloomEffect; // holds the blur + combine shader
protected override void LoadContent()
{
// ... other loading
bloomTarget = new RenderTarget2D(
GraphicsDevice,
GraphicsDevice.PresentationParameters.BackBufferWidth,
GraphicsDevice.PresentationParameters.BackBufferHeight,
false,
SurfaceFormat.Color,
DepthFormat.Depth24);
// Load a custom effect file that contains the blur and combine passes
bloomEffect = Content.Load("Shaders/Bloom");
}
protected override void UnloadContent()
{
// Dispose GPU resources to avoid leaks
bloomTarget?.Dispose();
bloomEffect?.Dispose();
}
SurfaceFormat.Color gives us RGBA8 color; DepthFormat.Depth24 provides a depth buffer so 3D geometry can still be rendered correctly.
Rendering pass: Draw the scene to the render target
In Draw we switch the GPU’s render target, clear it, draw the scene as usual, then resolve back to the back buffer.
protected override void Draw(GameTime gameTime)
{
// 1️⃣ Render scene to bloomTarget
GraphicsDevice.SetRenderTarget(bloomTarget);
GraphicsDevice.Clear(Color.CornflowerBlue); // or your scene’s clear color
// Draw your 3D/2D content here
DrawScene();
// 2️⃣ Resolve render target so we can read it as a texture
GraphicsDevice.SetRenderTarget(null);
// 3️⃣ Apply bloom post‑process
ApplyBloom();
base.Draw(gameTime);
}
private void DrawScene()
{
// Example: draw a sprite or model
spriteBatch.Begin();
spriteBatch.Draw(brightSprite, Vector2.Zero, Color.White);
spriteBatch.End();
}
The call to SetRenderTarget(null) tells MonoGame to render directly to the window’s back buffer again.
Post‑process: Blur and combine
A simple two‑pass Gaussian blur works well for a demo. The effect file contains two techniques: BlurHorizontal and BlurVertical, followed by a final additive combine.
private void ApplyBloom()
{
// Temporary target for the blurred image (same size as bloomTarget)
RenderTarget2D blurTarget = new RenderTarget2D(
GraphicsDevice,
bloomTarget.Width,
bloomTarget.Height,
false,
SurfaceFormat.Color,
DepthFormat.None);
// ---- Horizontal blur ----
GraphicsDevice.SetRenderTarget(blurTarget);
GraphicsDevice.Clear(Color.TransparentBlack);
bloomEffect.CurrentTechnique = bloomEffect.Techniques["BlurHorizontal"];
bloomEffect.Parameters["SceneTexture"].SetValue(bloomTarget);
bloomEffect.Parameters["PixelSize"].SetValue(new Vector2(1.0f / bloomTarget.Width, 0));
DrawFullscreenQuad();
// ---- Vertical blur ----
GraphicsDevice.SetRenderTarget(bloomTarget); // reuse bloomTarget as second buffer
GraphicsDevice.Clear(Color.TransparentBlack);
bloomEffect.CurrentTechnique = bloomEffect.Techniques["BlurVertical"];
bloomEffect.Parameters["SceneTexture"].SetValue(blurTarget);
bloomEffect.Parameters["PixelSize"].SetValue(new Vector2(0, 1.0f / bloomTarget.Height));
DrawFullscreenQuad();
// ---- Additive combine ----
GraphicsDevice.SetRenderTarget(null); // back to back buffer
bloomEffect.CurrentTechnique = bloomEffect.Techniques["BloomCombine"];
bloomEffect.Parameters["SceneTexture"].SetValue(bloomTarget);
bloomEffect.Parameters["BlurTexture"].SetValue(blurTarget);
DrawFullscreenQuad();
blurTarget.Dispose();
}
private void DrawFullscreenQuad()
{
// Draw a quad that covers the whole viewport using a basic vertex buffer
// (omitted for brevity – many tutorials provide a ready‑made helper)
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque, null, null, null, bloomEffect);
spriteBatch.Draw(bloomTarget, Vector2.Zero, Color.White);
spriteBatch.End();
}
The shader samples the source texture multiple times offset by PixelSize and averages the results. The combine pass does:
float4 scene = tex2D(SceneTexture, texCoord);
float4 blur = tex2D(BlurTexture, texCoord);
return scene + blur * BloomIntensity;
Adjust BloomIntensity to control glow strength.
Trade‑offs and limitations
- GPU state changes: Each
SetRenderTargetcall stalls the pipeline. On low‑end hardware you can reduce passes by downsampling the render target (e.g., half width/height) before blurring. - Memory: A full‑size
RenderTarget2Dconsumes width × height × 4 bytes. For 1080p that’s ~8 MB; acceptable on desktop but may be tight on mobile. - Quality: A simple two‑pass blur yields a soft bloom but lacks the spread of more advanced techniques (e.g., multiple‑size blurs or HDR thresholds).
To check whether the trade‑off is acceptable, profile the draw calls with GraphicsDevice.GetRenderTargets() (should return one bound target during scene draw and null during the final present) and, if available, a GPU profiler to verify the extra passes.
Actionable closing
Add the code above to your MonoGame project, replace DrawScene() with your own rendering, and tweak the blur intensity until bright areas exhibit a pleasant glow. Remember to recreate bloomTarget whenever the back‑buffer size changes (e.g., toggling fullscreen) to avoid stretched output or crashes. Dispose the target and effect when the game exits, and you’ll have a functional bloom effect ready for further refinement.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.