BloomComponent.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. #region File Description
  2. //-----------------------------------------------------------------------------
  3. // BloomComponent.cs
  4. //
  5. // Microsoft XNA Community Game Platform
  6. // Copyright (C) Microsoft Corporation. All rights reserved.
  7. //-----------------------------------------------------------------------------
  8. #endregion
  9. #region Using Statements
  10. using System;
  11. using Microsoft.Xna.Framework;
  12. using Microsoft.Xna.Framework.Content;
  13. using Microsoft.Xna.Framework.Graphics;
  14. #endregion
  15. namespace NetRumble
  16. {
  17. /// <summary>
  18. /// A DrawableGameComponent that will add bloom post-processing to what the previous
  19. /// components have drawn.
  20. /// </summary>
  21. /// <remarks>
  22. /// This public class is similar to one in the Bloom sample.
  23. /// </remarks>
  24. public class BloomComponent : DrawableGameComponent
  25. {
  26. #region Fields
  27. ContentManager content;
  28. SpriteBatch spriteBatch;
  29. Effect bloomExtractEffect;
  30. Effect bloomCombineEffect;
  31. Effect gaussianBlurEffect;
  32. RenderTarget2D sceneRenderTarget;
  33. RenderTarget2D renderTarget1;
  34. RenderTarget2D renderTarget2;
  35. // Choose what display settings the bloom should use.
  36. public BloomSettings Settings
  37. {
  38. get { return settings; }
  39. set { settings = value; }
  40. }
  41. BloomSettings settings = BloomSettings.PresetSettings[0];
  42. // Optionally displays one of the intermediate buffers used
  43. // by the bloom postprocess, so you can see exactly what is
  44. // being drawn into each rendertarget.
  45. public enum IntermediateBuffer
  46. {
  47. PreBloom,
  48. BlurredHorizontally,
  49. BlurredBothWays,
  50. FinalResult,
  51. }
  52. public IntermediateBuffer ShowBuffer
  53. {
  54. get { return showBuffer; }
  55. set { showBuffer = value; }
  56. }
  57. IntermediateBuffer showBuffer = IntermediateBuffer.FinalResult;
  58. #endregion
  59. #region Initialization
  60. public BloomComponent(Game game)
  61. : base(game)
  62. {
  63. if (game == null)
  64. throw new ArgumentNullException("game");
  65. content = new ContentManager(game.Services, "Content/BloomPostprocess");
  66. }
  67. /// <summary>
  68. /// Load your graphics content.
  69. /// </summary>
  70. protected override void LoadContent()
  71. {
  72. spriteBatch = new SpriteBatch(GraphicsDevice);
  73. bloomExtractEffect = content.Load<Effect>("Effects/BloomExtract");
  74. bloomCombineEffect = content.Load<Effect>("Effects/BloomCombine");
  75. gaussianBlurEffect = content.Load<Effect>("Effects/GaussianBlur");
  76. // Look up the resolution and format of our main backbuffer.
  77. PresentationParameters pp = GraphicsDevice.PresentationParameters;
  78. int width = pp.BackBufferWidth;
  79. int height = pp.BackBufferHeight;
  80. SurfaceFormat format = pp.BackBufferFormat;
  81. // Create a texture for rendering the main scene, prior to applying bloom.
  82. sceneRenderTarget = new RenderTarget2D(GraphicsDevice, width, height, false,
  83. format, pp.DepthStencilFormat, pp.MultiSampleCount,
  84. RenderTargetUsage.DiscardContents);
  85. // Create two rendertargets for the bloom processing. These are half the
  86. // size of the backbuffer, in order to minimize fillrate costs. Reducing
  87. // the resolution in this way doesn't hurt quality, because we are going
  88. // to be blurring the bloom images in any case.
  89. width /= 2;
  90. height /= 2;
  91. renderTarget1 = new RenderTarget2D(GraphicsDevice, width, height, false, format, DepthFormat.None);
  92. renderTarget2 = new RenderTarget2D(GraphicsDevice, width, height, false, format, DepthFormat.None);
  93. }
  94. /// <summary>
  95. /// Unload your graphics content.
  96. /// </summary>
  97. protected override void UnloadContent()
  98. {
  99. content.Unload();
  100. }
  101. #endregion
  102. #region Draw
  103. /// <summary>
  104. /// This should be called at the very start of the scene rendering. The bloom
  105. /// component uses it to redirect drawing into its custom rendertarget, so it
  106. /// can capture the scene image in preparation for applying the bloom filter.
  107. /// </summary>
  108. public void BeginDraw()
  109. {
  110. GraphicsDevice.SetRenderTarget(sceneRenderTarget);
  111. }
  112. /// <summary>
  113. /// This is where it all happens. Grabs a scene that has already been rendered,
  114. /// and uses postprocess magic to add a glowing bloom effect over the top of it.
  115. /// </summary>
  116. public override void Draw(GameTime gameTime)
  117. {
  118. GraphicsDevice.SamplerStates[1] = SamplerState.LinearClamp;
  119. // Pass 1: draw the scene into rendertarget 1, using a
  120. // shader that extracts only the brightest parts of the image.
  121. bloomExtractEffect.Parameters["BloomThreshold"].SetValue(
  122. Settings.BloomThreshold);
  123. DrawFullscreenQuad(sceneRenderTarget, renderTarget1,
  124. bloomExtractEffect,
  125. IntermediateBuffer.PreBloom);
  126. // Pass 2: draw from rendertarget 1 into rendertarget 2,
  127. // using a shader to apply a horizontal gaussian blur filter.
  128. //SetBlurEffectParameters(1.0f / (float)renderTarget1.Width, 0);
  129. DrawFullscreenQuad(renderTarget1, renderTarget2,
  130. gaussianBlurEffect,
  131. IntermediateBuffer.BlurredHorizontally);
  132. // Pass 3: draw from rendertarget 2 back into rendertarget 1,
  133. // using a shader to apply a vertical gaussian blur filter.
  134. //SetBlurEffectParameters(0, 1.0f / (float)renderTarget1.Height);
  135. DrawFullscreenQuad(renderTarget2, renderTarget1,
  136. gaussianBlurEffect,
  137. IntermediateBuffer.BlurredBothWays);
  138. // Pass 4: draw both rendertarget 1 and the original scene
  139. // image back into the main backbuffer, using a shader that
  140. // combines them to produce the final bloomed result.
  141. GraphicsDevice.SetRenderTarget(null);
  142. EffectParameterCollection parameters = bloomCombineEffect.Parameters;
  143. parameters["BloomIntensity"].SetValue(Settings.BloomIntensity);
  144. parameters["BaseIntensity"].SetValue(Settings.BaseIntensity);
  145. parameters["BloomSaturation"].SetValue(Settings.BloomSaturation);
  146. parameters["BaseSaturation"].SetValue(Settings.BaseSaturation);
  147. GraphicsDevice.Textures[1] = sceneRenderTarget;
  148. Viewport viewport = GraphicsDevice.Viewport;
  149. DrawFullscreenQuad(renderTarget1,
  150. viewport.Width, viewport.Height,
  151. bloomCombineEffect,
  152. IntermediateBuffer.FinalResult);
  153. // DrawFullscreenQuad(sceneRenderTarget,
  154. // viewport.Width, viewport.Height,
  155. // bloomCombineEffect,
  156. // IntermediateBuffer.FinalResult);
  157. }
  158. /// <summary>
  159. /// Helper for drawing a texture into a rendertarget, using
  160. /// a custom shader to apply postprocessing effects.
  161. /// </summary>
  162. void DrawFullscreenQuad(Texture2D texture, RenderTarget2D renderTarget,
  163. Effect effect, IntermediateBuffer currentBuffer)
  164. {
  165. GraphicsDevice.SetRenderTarget(renderTarget);
  166. DrawFullscreenQuad(texture,
  167. renderTarget.Width, renderTarget.Height,
  168. effect, currentBuffer);
  169. }
  170. /// <summary>
  171. /// Helper for drawing a texture into the current rendertarget,
  172. /// using a custom shader to apply postprocessing effects.
  173. /// </summary>
  174. void DrawFullscreenQuad(Texture2D texture, int width, int height,
  175. Effect effect, IntermediateBuffer currentBuffer)
  176. {
  177. // If the user has selected one of the show intermediate buffer options,
  178. // we still draw the quad to make sure the image will end up on the screen,
  179. // but might need to skip applying the custom pixel shader.
  180. if (showBuffer < currentBuffer)
  181. {
  182. effect = null;
  183. }
  184. effect = null;
  185. spriteBatch.Begin(0, BlendState.Opaque, null, null, null, effect);
  186. spriteBatch.Draw(texture, new Rectangle(0, 0, width, height), Color.White);
  187. spriteBatch.End();
  188. }
  189. /// <summary>
  190. /// Computes sample weightings and texture coordinate offsets
  191. /// for one pass of a separable gaussian blur filter.
  192. /// </summary>
  193. void SetBlurEffectParameters(float dx, float dy)
  194. {
  195. // Look up the sample weight and offset effect parameters.
  196. EffectParameter weightsParameter, offsetsParameter;
  197. weightsParameter = gaussianBlurEffect.Parameters["SampleWeights"];
  198. offsetsParameter = gaussianBlurEffect.Parameters["SampleOffsets"];
  199. // Look up how many samples our gaussian blur effect supports.
  200. int sampleCount = weightsParameter.Elements.Count;
  201. // Create temporary arrays for computing our filter settings.
  202. float[] sampleWeights = new float[sampleCount];
  203. Vector2[] sampleOffsets = new Vector2[sampleCount];
  204. // The first sample always has a zero offset.
  205. sampleWeights[0] = ComputeGaussian(0);
  206. sampleOffsets[0] = new Vector2(0);
  207. // Maintain a sum of all the weighting values.
  208. float totalWeights = sampleWeights[0];
  209. // Add pairs of additional sample taps, positioned
  210. // along a line in both directions from the center.
  211. for (int i = 0; i < sampleCount / 2; i++)
  212. {
  213. // Store weights for the positive and negative taps.
  214. float weight = ComputeGaussian(i + 1);
  215. sampleWeights[i * 2 + 1] = weight;
  216. sampleWeights[i * 2 + 2] = weight;
  217. totalWeights += weight * 2;
  218. // To get the maximum amount of blurring from a limited number of
  219. // pixel shader samples, we take advantage of the bilinear filtering
  220. // hardware inside the texture fetch unit. If we position our texture
  221. // coordinates exactly halfway between two texels, the filtering unit
  222. // will average them for us, giving two samples for the price of one.
  223. // This allows us to step in units of two texels per sample, rather
  224. // than just one at a time. The 1.5 offset kicks things off by
  225. // positioning us nicely in between two texels.
  226. float sampleOffset = i * 2 + 1.5f;
  227. Vector2 delta = new Vector2(dx, dy) * sampleOffset;
  228. // Store texture coordinate offsets for the positive and negative taps.
  229. sampleOffsets[i * 2 + 1] = delta;
  230. sampleOffsets[i * 2 + 2] = -delta;
  231. }
  232. // Normalize the list of sample weightings, so they will always sum to one.
  233. for (int i = 0; i < sampleWeights.Length; i++)
  234. {
  235. sampleWeights[i] /= totalWeights;
  236. }
  237. // Tell the effect about our new filter settings.
  238. weightsParameter.SetValue(sampleWeights);
  239. offsetsParameter.SetValue(sampleOffsets);
  240. }
  241. /// <summary>
  242. /// Evaluates a single point on the gaussian falloff curve.
  243. /// Used for setting up the blur filter weightings.
  244. /// </summary>
  245. float ComputeGaussian(float n)
  246. {
  247. float theta = Settings.BlurAmount;
  248. return (float)((1.0 / Math.Sqrt(2 * Math.PI * theta)) *
  249. Math.Exp(-(n * n) / (2 * theta * theta)));
  250. }
  251. #endregion
  252. }
  253. }