Billboard.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. #region File Description
  2. //-----------------------------------------------------------------------------
  3. // Billboard.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. using Microsoft.Xna.Framework.Input;
  15. #endregion
  16. namespace Billboard
  17. {
  18. /// <summary>
  19. /// Sample showing how to efficiently render billboard sprites.
  20. /// </summary>
  21. public class BillboardGame : Microsoft.Xna.Framework.Game
  22. {
  23. #region Fields
  24. GraphicsDeviceManager graphics;
  25. KeyboardState currentKeyboardState = new KeyboardState();
  26. GamePadState currentGamePadState = new GamePadState();
  27. Vector3 cameraPosition = new Vector3(0, 50, 50);
  28. Vector3 cameraFront = new Vector3(0, 0, -1);
  29. Model landscape;
  30. #endregion
  31. #region Initialization
  32. public BillboardGame()
  33. {
  34. graphics = new GraphicsDeviceManager(this);
  35. Content.RootDirectory = "Content";
  36. }
  37. /// <summary>
  38. /// Load your graphics content.
  39. /// </summary>
  40. protected override void LoadContent()
  41. {
  42. landscape = Content.Load<Model>("landscape");
  43. }
  44. #endregion
  45. #region Update and Draw
  46. /// <summary>
  47. /// Allows the game to run logic.
  48. /// </summary>
  49. protected override void Update(GameTime gameTime)
  50. {
  51. HandleInput();
  52. UpdateCamera(gameTime);
  53. base.Update(gameTime);
  54. }
  55. /// <summary>
  56. /// This is called when the game should draw itself.
  57. /// </summary>
  58. protected override void Draw(GameTime gameTime)
  59. {
  60. GraphicsDevice device = graphics.GraphicsDevice;
  61. device.Clear(Color.CornflowerBlue);
  62. // Compute camera matrices.
  63. Matrix view = Matrix.CreateLookAt(cameraPosition,
  64. cameraPosition + cameraFront,
  65. Vector3.Up);
  66. Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
  67. device.Viewport.AspectRatio,
  68. 1, 10000);
  69. Vector3 lightDirection = Vector3.Normalize(new Vector3(3, -1, 1));
  70. Vector3 lightColor = new Vector3(0.3f, 0.4f, 0.2f);
  71. // Time is scaled down to make things wave in the wind more slowly.
  72. float time = (float)gameTime.TotalGameTime.TotalSeconds * 0.333f;
  73. // First we draw the ground geometry using BasicEffect.
  74. foreach (ModelMesh mesh in landscape.Meshes)
  75. {
  76. if (mesh.Name != "Billboards")
  77. {
  78. foreach (BasicEffect effect in mesh.Effects)
  79. {
  80. effect.View = view;
  81. effect.Projection = projection;
  82. effect.LightingEnabled = true;
  83. effect.DirectionalLight0.Enabled = true;
  84. effect.DirectionalLight0.Direction = lightDirection;
  85. effect.DirectionalLight0.DiffuseColor = lightColor;
  86. effect.AmbientLightColor = new Vector3(0.1f, 0.2f, 0.1f);
  87. }
  88. device.BlendState = BlendState.Opaque;
  89. device.DepthStencilState = DepthStencilState.Default;
  90. device.RasterizerState = RasterizerState.CullCounterClockwise;
  91. mesh.Draw();
  92. }
  93. }
  94. // Then we use a two-pass technique to render alpha blended billboards with
  95. // almost-correct depth sorting. The only way to make blending truly proper for
  96. // alpha objects is to draw everything in sorted order, but manually sorting all
  97. // our billboards would be very expensive. Instead, we draw in two passes.
  98. //
  99. // The first pass has alpha blending turned off, alpha testing set to only accept
  100. // ~95% or more opaque pixels, and the depth buffer turned on. Because this is only
  101. // rendering the solid parts of each billboard, the depth buffer works as
  102. // normal to give correct sorting, but obviously only part of each billboard will
  103. // be rendered.
  104. //
  105. // Then in the second pass we enable alpha blending, set alpha test to only accept
  106. // pixels with fractional alpha values, and set the depth buffer to test against
  107. // the existing data but not to write new depth values. This means the translucent
  108. // areas of each billboard will be sorted correctly against the depth buffer
  109. // information that was previously written while drawing the opaque parts, although
  110. // there can still be sorting errors between the translucent areas of different
  111. // billboards.
  112. //
  113. // In practice, sorting errors between translucent pixels tend not to be too
  114. // noticable as long as the opaque pixels are sorted correctly, so this technique
  115. // often looks ok, and is much faster than trying to sort everything 100%
  116. // correctly. It is particularly effective for organic textures like grass and
  117. // trees.
  118. foreach (ModelMesh mesh in landscape.Meshes)
  119. {
  120. if (mesh.Name == "Billboards")
  121. {
  122. // First pass renders opaque pixels.
  123. foreach (Effect effect in mesh.Effects)
  124. {
  125. effect.Parameters["View"].SetValue(view);
  126. effect.Parameters["Projection"].SetValue(projection);
  127. effect.Parameters["LightDirection"].SetValue(lightDirection);
  128. effect.Parameters["WindTime"].SetValue(time);
  129. effect.Parameters["AlphaTestDirection"].SetValue(1f);
  130. }
  131. device.BlendState = BlendState.Opaque;
  132. device.DepthStencilState = DepthStencilState.Default;
  133. device.RasterizerState = RasterizerState.CullNone;
  134. device.SamplerStates[0] = SamplerState.LinearClamp;
  135. mesh.Draw();
  136. // Second pass renders the alpha blended fringe pixels.
  137. foreach (Effect effect in mesh.Effects)
  138. {
  139. effect.Parameters["AlphaTestDirection"].SetValue(-1f);
  140. }
  141. device.BlendState = BlendState.NonPremultiplied;
  142. device.DepthStencilState = DepthStencilState.DepthRead;
  143. mesh.Draw();
  144. }
  145. }
  146. base.Draw(gameTime);
  147. }
  148. #endregion
  149. #region Handle Input
  150. /// <summary>
  151. /// Handles input for quitting the game.
  152. /// </summary>
  153. private void HandleInput()
  154. {
  155. currentKeyboardState = Keyboard.GetState();
  156. currentGamePadState = GamePad.GetState(PlayerIndex.One);
  157. // Check for exit.
  158. if (currentKeyboardState.IsKeyDown(Keys.Escape) ||
  159. currentGamePadState.Buttons.Back == ButtonState.Pressed)
  160. {
  161. Exit();
  162. }
  163. }
  164. /// <summary>
  165. /// Handles camera input.
  166. /// </summary>
  167. private void UpdateCamera(GameTime gameTime)
  168. {
  169. float time = (float)gameTime.ElapsedGameTime.TotalMilliseconds;
  170. // Check for input to rotate the camera.
  171. float pitch = -currentGamePadState.ThumbSticks.Right.Y * time * 0.001f;
  172. float turn = -currentGamePadState.ThumbSticks.Right.X * time * 0.001f;
  173. if (currentKeyboardState.IsKeyDown(Keys.Up))
  174. pitch += time * 0.001f;
  175. if (currentKeyboardState.IsKeyDown(Keys.Down))
  176. pitch -= time * 0.001f;
  177. if (currentKeyboardState.IsKeyDown(Keys.Left))
  178. turn += time * 0.001f;
  179. if (currentKeyboardState.IsKeyDown(Keys.Right))
  180. turn -= time * 0.001f;
  181. Vector3 cameraRight = Vector3.Cross(Vector3.Up, cameraFront);
  182. Vector3 flatFront = Vector3.Cross(cameraRight, Vector3.Up);
  183. Matrix pitchMatrix = Matrix.CreateFromAxisAngle(cameraRight, pitch);
  184. Matrix turnMatrix = Matrix.CreateFromAxisAngle(Vector3.Up, turn);
  185. Vector3 tiltedFront = Vector3.TransformNormal(cameraFront, pitchMatrix *
  186. turnMatrix);
  187. // Check angle so we cant flip over
  188. if (Vector3.Dot(tiltedFront, flatFront) > 0.001f)
  189. {
  190. cameraFront = Vector3.Normalize(tiltedFront);
  191. }
  192. // Check for input to move the camera around.
  193. if (currentKeyboardState.IsKeyDown(Keys.W))
  194. cameraPosition += cameraFront * time * 0.1f;
  195. if (currentKeyboardState.IsKeyDown(Keys.S))
  196. cameraPosition -= cameraFront * time * 0.1f;
  197. if (currentKeyboardState.IsKeyDown(Keys.A))
  198. cameraPosition += cameraRight * time * 0.1f;
  199. if (currentKeyboardState.IsKeyDown(Keys.D))
  200. cameraPosition -= cameraRight * time * 0.1f;
  201. cameraPosition += cameraFront *
  202. currentGamePadState.ThumbSticks.Left.Y * time * 0.1f;
  203. cameraPosition -= cameraRight *
  204. currentGamePadState.ThumbSticks.Left.X * time * 0.1f;
  205. if (currentGamePadState.Buttons.RightStick == ButtonState.Pressed ||
  206. currentKeyboardState.IsKeyDown(Keys.R))
  207. {
  208. cameraPosition = new Vector3(0, 50, 50);
  209. cameraFront = new Vector3(0, 0, -1);
  210. }
  211. }
  212. #endregion
  213. }
  214. #region Entry Point
  215. /// <summary>
  216. /// The main entry point for the application.
  217. /// </summary>
  218. static class Program
  219. {
  220. static void Main()
  221. {
  222. using (BillboardGame game = new BillboardGame())
  223. {
  224. game.Run();
  225. }
  226. }
  227. }
  228. #endregion
  229. }