StencilCraters.cs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using Microsoft.Xna.Framework;
  5. using Microsoft.Xna.Framework.Audio;
  6. using Microsoft.Xna.Framework.Content;
  7. // using Microsoft.Xna.Framework.GamerServices; // Not available in MonoGame 3.8.*
  8. using Microsoft.Xna.Framework.Graphics;
  9. using Microsoft.Xna.Framework.Input;
  10. using Microsoft.Xna.Framework.Media;
  11. namespace StencilCraters
  12. {
  13. public class StencilCratersGame : Game
  14. {
  15. const int PlanetDataSize = 256;
  16. bool firstTime = true;
  17. Vector2 craterPosition;
  18. Vector2 planetPosition;
  19. Texture2D drawingTexture;
  20. KeyboardState currentKeyboardState;
  21. KeyboardState previousKeyboardState;
  22. GraphicsDeviceManager graphics;
  23. SpriteBatch spriteBatch;
  24. private SpriteFont instructionFont;
  25. Random random;
  26. Texture2D planetTexture;
  27. Texture2D craterTexture;
  28. RenderTarget2D renderTargetA;
  29. RenderTarget2D renderTargetB;
  30. RenderTarget2D activeRenderTarget;
  31. RenderTarget2D textureRenderTarget;
  32. AlphaTestEffect alphaTestEffect;
  33. DepthStencilState stencilAlways;
  34. DepthStencilState stencilKeepIfZero;
  35. public StencilCratersGame()
  36. {
  37. graphics = new GraphicsDeviceManager(this);
  38. Content.RootDirectory = "Content";
  39. IsMouseVisible = true;
  40. random = new Random();
  41. }
  42. protected override void Initialize()
  43. {
  44. base.Initialize();
  45. // initialize keyboard state
  46. currentKeyboardState = Keyboard.GetState();
  47. previousKeyboardState = currentKeyboardState;
  48. }
  49. protected override void LoadContent()
  50. {
  51. spriteBatch = new SpriteBatch(GraphicsDevice);
  52. // Load the SpriteFont
  53. instructionFont = Content.Load<SpriteFont>("font");
  54. // load assets
  55. planetTexture = Content.Load<Texture2D>("planet");
  56. craterTexture = Content.Load<Texture2D>("crater");
  57. // set up alpha test effect
  58. Matrix projection = Matrix.CreateOrthographicOffCenter(0, PlanetDataSize, PlanetDataSize, 0, 0, 1);
  59. Matrix halfPixelOffset = Matrix.CreateTranslation(-0.5f, -0.5f, 0);
  60. alphaTestEffect = new AlphaTestEffect(GraphicsDevice);
  61. alphaTestEffect.VertexColorEnabled = true;
  62. alphaTestEffect.DiffuseColor = Color.White.ToVector3();
  63. alphaTestEffect.AlphaFunction = CompareFunction.Equal;
  64. alphaTestEffect.ReferenceAlpha = 0;
  65. alphaTestEffect.World = Matrix.Identity;
  66. alphaTestEffect.View = Matrix.Identity;
  67. alphaTestEffect.Projection = halfPixelOffset * projection;
  68. // set up stencil state to always replace stencil buffer with 1
  69. stencilAlways = new DepthStencilState();
  70. stencilAlways.StencilEnable = true;
  71. stencilAlways.StencilFunction = CompareFunction.Always;
  72. stencilAlways.StencilPass = StencilOperation.Replace;
  73. stencilAlways.ReferenceStencil = 1;
  74. stencilAlways.DepthBufferEnable = false;
  75. // set up stencil state to pass if the stencil value is 0
  76. stencilKeepIfZero = new DepthStencilState();
  77. stencilKeepIfZero.StencilEnable = true;
  78. stencilKeepIfZero.StencilFunction = CompareFunction.Equal;
  79. stencilKeepIfZero.StencilPass = StencilOperation.Keep;
  80. stencilKeepIfZero.ReferenceStencil = 0;
  81. stencilKeepIfZero.DepthBufferEnable = false;
  82. // create render targets
  83. renderTargetA = new RenderTarget2D(GraphicsDevice, PlanetDataSize, PlanetDataSize,
  84. false, SurfaceFormat.Color, DepthFormat.Depth24Stencil8,
  85. 0, RenderTargetUsage.DiscardContents);
  86. renderTargetB = new RenderTarget2D(GraphicsDevice, PlanetDataSize, PlanetDataSize,
  87. false, SurfaceFormat.Color, DepthFormat.Depth24Stencil8,
  88. 0, RenderTargetUsage.DiscardContents);
  89. // set up the ping-pong texture pointers
  90. activeRenderTarget = renderTargetA;
  91. textureRenderTarget = renderTargetB;
  92. drawingTexture = planetTexture;
  93. }
  94. protected override void UnloadContent()
  95. {
  96. }
  97. protected override void Update(GameTime gameTime)
  98. {
  99. // check for exit
  100. if (currentKeyboardState.IsKeyDown(Keys.Escape)
  101. || GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
  102. this.Exit();
  103. // update keyboard state
  104. previousKeyboardState = currentKeyboardState;
  105. currentKeyboardState = Keyboard.GetState();
  106. // calculate planet position, centered in screen
  107. planetPosition = new Vector2(GraphicsDevice.PresentationParameters.BackBufferWidth * 0.5f - PlanetDataSize * 0.4f,
  108. GraphicsDevice.PresentationParameters.BackBufferHeight * 0.5f - PlanetDataSize * 0.4f);
  109. // add a random crater if space bar is pressed
  110. if ((currentKeyboardState.IsKeyDown(Keys.Space) && previousKeyboardState.IsKeyUp(Keys.Space)))
  111. {
  112. craterPosition = new Vector2(random.Next(PlanetDataSize), random.Next(PlanetDataSize));
  113. }
  114. base.Update(gameTime);
  115. }
  116. public void AddCrater(Vector2 position)
  117. {
  118. // set up rendering to the active render target
  119. GraphicsDevice.SetRenderTarget(activeRenderTarget);
  120. // clear the render target to opaque black,
  121. // and initialize the stencil buffer with all zeroes
  122. GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.Stencil,
  123. new Color(0, 0, 0, 1), 0, 0);
  124. // draw the new craters into the stencil buffer
  125. // stencilAlways makes sure we'll always write a 1
  126. // to the stencil buffer wherever we draw the alphaTestEffect
  127. // is set up to only write a pixel if the alpha value
  128. // of the source texture is zero
  129. spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque,
  130. null, stencilAlways, null, alphaTestEffect);
  131. Vector2 origin = new Vector2(craterTexture.Width * 0.5f,
  132. craterTexture.Height * 0.5f);
  133. float rotation = (float)random.NextDouble() * MathHelper.TwoPi;
  134. Rectangle r = new Rectangle((int)position.X, (int)position.Y, 50, 50);
  135. spriteBatch.Draw(craterTexture, r, null, Color.White, rotation,
  136. origin, SpriteEffects.None, 0);
  137. spriteBatch.End();
  138. // now draw the latest planet texture, excluding the stencil
  139. // buffer, resulting in the new craters being excluded from
  140. // the drawing the first time through we don't have a latest
  141. // planet texture, so draw from the original texture
  142. spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque,
  143. null, stencilKeepIfZero, null, null);
  144. if (firstTime)
  145. {
  146. spriteBatch.Draw(planetTexture, Vector2.Zero, Color.White);
  147. firstTime = false;
  148. }
  149. else
  150. spriteBatch.Draw(textureRenderTarget, Vector2.Zero, Color.White);
  151. spriteBatch.End();
  152. // restore main render target - this lets us get at the render target we just drew and use it as a texture
  153. GraphicsDevice.SetRenderTarget(null);
  154. //// save image for testing
  155. //using (FileStream f = new FileStream("planet.png", FileMode.Create))
  156. //{
  157. // activeRenderTarget.SaveAsPng(f, 256, 256);
  158. //}
  159. // swap render targets, so the next time our source texture is the render target we just drew,
  160. // and the one we'll be drawing on is the one we just used as our source texture this time
  161. RenderTarget2D t = activeRenderTarget;
  162. activeRenderTarget = textureRenderTarget;
  163. textureRenderTarget = t;
  164. drawingTexture = textureRenderTarget;
  165. }
  166. protected override void Draw(GameTime gameTime)
  167. {
  168. // we have to draw render targets first
  169. if (craterPosition != Vector2.Zero)
  170. {
  171. AddCrater(craterPosition);
  172. craterPosition = Vector2.Zero;
  173. }
  174. // draw the cratered planet texture
  175. GraphicsDevice.Clear(Color.MonoGameOrange);
  176. spriteBatch.Begin();
  177. spriteBatch.Draw(drawingTexture, planetPosition, Color.White);
  178. spriteBatch.DrawString(
  179. instructionFont,
  180. $"Press Space to add a crater",
  181. new Vector2(20, 105),
  182. Color.Yellow
  183. );
  184. spriteBatch.End();
  185. base.Draw(gameTime);
  186. }
  187. }
  188. }