2
0

LensFlareComponent.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. #region File Description
  2. //-----------------------------------------------------------------------------
  3. // LensFlareComponent.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.Graphics;
  13. #endregion
  14. namespace LensFlare
  15. {
  16. /// <summary>
  17. /// Reusable component for drawing a lensflare effect over the top of a 3D scene.
  18. /// </summary>
  19. public class LensFlareComponent : DrawableGameComponent
  20. {
  21. #region Constants
  22. // How big is the circular glow effect?
  23. const float glowSize = 400;
  24. // How big a rectangle should we examine when issuing our occlusion queries?
  25. // Increasing this makes the flares fade out more gradually when the sun goes
  26. // behind scenery, while smaller query areas cause sudden on/off transitions.
  27. const float querySize = 100;
  28. #endregion
  29. #region Fields
  30. // These are set by the main game to tell us the position of the camera and sun.
  31. public Matrix View;
  32. public Matrix Projection;
  33. public Vector3 LightDirection = Vector3.Normalize(new Vector3(-1, -0.1f, 0.3f));
  34. // Computed by UpdateOcclusion, which projects LightDirection into screenspace.
  35. Vector2 lightPosition;
  36. bool lightBehindCamera;
  37. // Graphics objects.
  38. Texture2D glowSprite;
  39. SpriteBatch spriteBatch;
  40. BasicEffect basicEffect;
  41. VertexPositionColor[] queryVertices;
  42. // Custom blend state so the occlusion query polygons do not show up on screen.
  43. static readonly BlendState ColorWriteDisable = new BlendState
  44. {
  45. ColorWriteChannels = ColorWriteChannels.None
  46. };
  47. // An occlusion query is used to detect when the sun is hidden behind scenery.
  48. OcclusionQuery occlusionQuery;
  49. bool occlusionQueryActive;
  50. float occlusionAlpha;
  51. // The lensflare effect is made up from several individual flare graphics,
  52. // which move across the screen depending on the position of the sun. This
  53. // helper class keeps track of the position, size, and color for each flare.
  54. class Flare
  55. {
  56. public Flare(float position, float scale, Color color, string textureName)
  57. {
  58. Position = position;
  59. Scale = scale;
  60. Color = color;
  61. TextureName = textureName;
  62. }
  63. public float Position;
  64. public float Scale;
  65. public Color Color;
  66. public string TextureName;
  67. public Texture2D Texture;
  68. }
  69. // Array describes the position, size, color, and texture for each individual
  70. // flare graphic. The position value lies on a line between the sun and the
  71. // center of the screen. Zero places a flare directly over the top of the sun,
  72. // one is exactly in the middle of the screen, fractional positions lie in
  73. // between these two points, while negative values or positions greater than
  74. // one will move the flares outward toward the edge of the screen. Changing
  75. // the number of flares, or tweaking their positions and colors, can produce
  76. // a wide range of different lensflare effects without altering any other code.
  77. Flare[] flares =
  78. {
  79. new Flare(-0.5f, 0.7f, new Color( 50, 25, 50), "flare1"),
  80. new Flare( 0.3f, 0.4f, new Color(100, 255, 200), "flare1"),
  81. new Flare( 1.2f, 1.0f, new Color(100, 50, 50), "flare1"),
  82. new Flare( 1.5f, 1.5f, new Color( 50, 100, 50), "flare1"),
  83. new Flare(-0.3f, 0.7f, new Color(200, 50, 50), "flare2"),
  84. new Flare( 0.6f, 0.9f, new Color( 50, 100, 50), "flare2"),
  85. new Flare( 0.7f, 0.4f, new Color( 50, 200, 200), "flare2"),
  86. new Flare(-0.7f, 0.7f, new Color( 50, 100, 25), "flare3"),
  87. new Flare( 0.0f, 0.6f, new Color( 25, 25, 25), "flare3"),
  88. new Flare( 2.0f, 1.4f, new Color( 25, 50, 100), "flare3"),
  89. };
  90. #endregion
  91. #region Initialization
  92. /// <summary>
  93. /// Constructs a new lensflare component.
  94. /// </summary>
  95. public LensFlareComponent(Game game)
  96. : base(game)
  97. {
  98. }
  99. /// <summary>
  100. /// Loads the content used by the lensflare component.
  101. /// </summary>
  102. protected override void LoadContent()
  103. {
  104. // Create a SpriteBatch for drawing the glow and flare sprites.
  105. spriteBatch = new SpriteBatch(GraphicsDevice);
  106. // Load the glow and flare textures.
  107. glowSprite = Game.Content.Load<Texture2D>("glow");
  108. foreach (Flare flare in flares)
  109. {
  110. flare.Texture = Game.Content.Load<Texture2D>(flare.TextureName);
  111. }
  112. // Effect for drawing occlusion query polygons.
  113. basicEffect = new BasicEffect(GraphicsDevice);
  114. basicEffect.View = Matrix.Identity;
  115. basicEffect.VertexColorEnabled = true;
  116. // Create vertex data for the occlusion query polygons.
  117. queryVertices = new VertexPositionColor[4];
  118. queryVertices[0].Position = new Vector3(-querySize / 2, -querySize / 2, -1);
  119. queryVertices[1].Position = new Vector3( querySize / 2, -querySize / 2, -1);
  120. queryVertices[2].Position = new Vector3(-querySize / 2, querySize / 2, -1);
  121. queryVertices[3].Position = new Vector3( querySize / 2, querySize / 2, -1);
  122. // Create the occlusion query object.
  123. occlusionQuery = new OcclusionQuery(GraphicsDevice);
  124. }
  125. #endregion
  126. #region Draw
  127. /// <summary>
  128. /// Draws the lensflare component.
  129. /// </summary>
  130. public override void Draw(GameTime gameTime)
  131. {
  132. // Check whether the light is hidden behind the scenery.
  133. UpdateOcclusion();
  134. // Draw the flare effect.
  135. DrawGlow();
  136. DrawFlares();
  137. RestoreRenderStates();
  138. }
  139. /// <summary>
  140. /// Mesures how much of the sun is visible, by drawing a small rectangle,
  141. /// centered on the sun, but with the depth set to as far away as possible,
  142. /// and using an occlusion query to measure how many of these very-far-away
  143. /// pixels are not hidden behind the terrain.
  144. ///
  145. /// The problem with occlusion queries is that the graphics card runs in
  146. /// parallel with the CPU. When you issue drawing commands, they are just
  147. /// stored in a buffer, and the graphics card can be as much as a frame delayed
  148. /// in getting around to processing the commands from that buffer. This means
  149. /// that even after we issue our occlusion query, the occlusion results will
  150. /// not be available until later, after the graphics card finishes processing
  151. /// these commands.
  152. ///
  153. /// It would slow our game down too much if we waited for the graphics card,
  154. /// so instead we delay our occlusion processing by one frame. Each time
  155. /// around the game loop, we read back the occlusion results from the previous
  156. /// frame, then issue a new occlusion query ready for the next frame to read
  157. /// its result. This keeps the data flowing smoothly between the CPU and GPU,
  158. /// but also causes our data to be a frame out of date: we are deciding
  159. /// whether or not to draw our lensflare effect based on whether it was
  160. /// visible in the previous frame, as opposed to the current one! Fortunately,
  161. /// the camera tends to move slowly, and the lensflare fades in and out
  162. /// smoothly as it goes behind the scenery, so this out-by-one-frame error
  163. /// is not too noticeable in practice.
  164. /// </summary>
  165. public void UpdateOcclusion()
  166. {
  167. // The sun is infinitely distant, so it should not be affected by the
  168. // position of the camera. Floating point math doesn't support infinitely
  169. // distant vectors, but we can get the same result by making a copy of our
  170. // view matrix, then resetting the view translation to zero. Pretending the
  171. // camera has not moved position gives the same result as if the camera
  172. // was moving, but the light was infinitely far away. If our flares came
  173. // from a local object rather than the sun, we would use the original view
  174. // matrix here.
  175. Matrix infiniteView = View;
  176. infiniteView.Translation = Vector3.Zero;
  177. // Project the light position into 2D screen space.
  178. Viewport viewport = GraphicsDevice.Viewport;
  179. Vector3 projectedPosition = viewport.Project(-LightDirection, Projection,
  180. infiniteView, Matrix.Identity);
  181. // Don't draw any flares if the light is behind the camera.
  182. if ((projectedPosition.Z < 0) || (projectedPosition.Z > 1))
  183. {
  184. lightBehindCamera = true;
  185. return;
  186. }
  187. lightPosition = new Vector2(projectedPosition.X, projectedPosition.Y);
  188. lightBehindCamera = false;
  189. if (occlusionQueryActive)
  190. {
  191. // If the previous query has not yet completed, wait until it does.
  192. if (!occlusionQuery.IsComplete)
  193. return;
  194. // Use the occlusion query pixel count to work
  195. // out what percentage of the sun is visible.
  196. const float queryArea = querySize * querySize;
  197. occlusionAlpha = Math.Min(occlusionQuery.PixelCount / queryArea, 1);
  198. }
  199. // Set renderstates for drawing the occlusion query geometry. We want depth
  200. // tests enabled, but depth writes disabled, and we disable color writes
  201. // to prevent this query polygon actually showing up on the screen.
  202. GraphicsDevice.BlendState = ColorWriteDisable;
  203. GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead;
  204. // Set up our BasicEffect to center on the current 2D light position.
  205. basicEffect.World = Matrix.CreateTranslation(lightPosition.X,
  206. lightPosition.Y, 0);
  207. basicEffect.Projection = Matrix.CreateOrthographicOffCenter(0,
  208. viewport.Width,
  209. viewport.Height,
  210. 0, 0, 1);
  211. basicEffect.CurrentTechnique.Passes[0].Apply();
  212. // Issue the occlusion query.
  213. occlusionQuery.Begin();
  214. GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleStrip, queryVertices, 0, 2);
  215. occlusionQuery.End();
  216. occlusionQueryActive = true;
  217. }
  218. /// <summary>
  219. /// Draws a large circular glow sprite, centered on the sun.
  220. /// </summary>
  221. public void DrawGlow()
  222. {
  223. if (lightBehindCamera || occlusionAlpha <= 0)
  224. return;
  225. Color color = Color.White * occlusionAlpha;
  226. Vector2 origin = new Vector2(glowSprite.Width, glowSprite.Height) / 2;
  227. float scale = glowSize * 2 / glowSprite.Width;
  228. spriteBatch.Begin();
  229. spriteBatch.Draw(glowSprite, lightPosition, null, color, 0,
  230. origin, scale, SpriteEffects.None, 0);
  231. spriteBatch.End();
  232. }
  233. /// <summary>
  234. /// Draws the lensflare sprites, computing the position
  235. /// of each one based on the current angle of the sun.
  236. /// </summary>
  237. public void DrawFlares()
  238. {
  239. if (lightBehindCamera || occlusionAlpha <= 0)
  240. return;
  241. Viewport viewport = GraphicsDevice.Viewport;
  242. // Lensflare sprites are positioned at intervals along a line that
  243. // runs from the 2D light position toward the center of the screen.
  244. Vector2 screenCenter = new Vector2(viewport.Width, viewport.Height) / 2;
  245. Vector2 flareVector = screenCenter - lightPosition;
  246. // Draw the flare sprites using additive blending.
  247. spriteBatch.Begin(0, BlendState.Additive);
  248. foreach (Flare flare in flares)
  249. {
  250. // Compute the position of this flare sprite.
  251. Vector2 flarePosition = lightPosition + flareVector * flare.Position;
  252. // Set the flare alpha based on the previous occlusion query result.
  253. Vector4 flareColor = flare.Color.ToVector4();
  254. flareColor.W *= occlusionAlpha;
  255. // Center the sprite texture.
  256. Vector2 flareOrigin = new Vector2(flare.Texture.Width,
  257. flare.Texture.Height) / 2;
  258. // Draw the flare.
  259. spriteBatch.Draw(flare.Texture, flarePosition, null,
  260. new Color(flareColor), 1, flareOrigin,
  261. flare.Scale, SpriteEffects.None, 0);
  262. }
  263. spriteBatch.End();
  264. }
  265. /// <summary>
  266. /// Sets renderstates back to their default values after we finish drawing
  267. /// the lensflare, to avoid messing up the 3D terrain rendering.
  268. /// </summary>
  269. void RestoreRenderStates()
  270. {
  271. GraphicsDevice.BlendState = BlendState.Opaque;
  272. GraphicsDevice.DepthStencilState = DepthStencilState.Default;
  273. GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
  274. }
  275. #endregion
  276. }
  277. }