using System; using System.Collections.Generic; using System.IO; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.GamerServices; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Media; namespace StencilCratersTutorial { public class Game1 : Microsoft.Xna.Framework.Game { const int PlanetDataSize = 256; bool firstTime = true; Vector2 craterPosition; Vector2 planetPosition; Texture2D drawingTexture; KeyboardState keyboardState; KeyboardState previousKeyboardState; GraphicsDeviceManager graphics; SpriteBatch spriteBatch; Random random; Texture2D planetTexture; Texture2D craterTexture; RenderTarget2D renderTargetA; RenderTarget2D renderTargetB; RenderTarget2D activeRenderTarget; RenderTarget2D textureRenderTarget; AlphaTestEffect alphaTestEffect; DepthStencilState stencilAlways; DepthStencilState stencilKeepIfZero; public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; random = new Random(); } protected override void Initialize() { base.Initialize(); // initialize keyboard state keyboardState = Keyboard.GetState(); previousKeyboardState = keyboardState; } protected override void LoadContent() { spriteBatch = new SpriteBatch(GraphicsDevice); // load assets planetTexture = Content.Load(@"planet"); craterTexture = Content.Load(@"crater"); // set up alpha test effect Matrix projection = Matrix.CreateOrthographicOffCenter(0, PlanetDataSize, PlanetDataSize, 0, 0, 1); Matrix halfPixelOffset = Matrix.CreateTranslation(-0.5f, -0.5f, 0); alphaTestEffect = new AlphaTestEffect(GraphicsDevice); alphaTestEffect.VertexColorEnabled = true; alphaTestEffect.DiffuseColor = Color.White.ToVector3(); alphaTestEffect.AlphaFunction = CompareFunction.Equal; alphaTestEffect.ReferenceAlpha = 0; alphaTestEffect.World = Matrix.Identity; alphaTestEffect.View = Matrix.Identity; alphaTestEffect.Projection = halfPixelOffset * projection; // set up stencil state to always replace stencil buffer with 1 stencilAlways = new DepthStencilState(); stencilAlways.StencilEnable = true; stencilAlways.StencilFunction = CompareFunction.Always; stencilAlways.StencilPass = StencilOperation.Replace; stencilAlways.ReferenceStencil = 1; stencilAlways.DepthBufferEnable = false; // set up stencil state to pass if the stencil value is 0 stencilKeepIfZero = new DepthStencilState(); stencilKeepIfZero.StencilEnable = true; stencilKeepIfZero.StencilFunction = CompareFunction.Equal; stencilKeepIfZero.StencilPass = StencilOperation.Keep; stencilKeepIfZero.ReferenceStencil = 0; stencilKeepIfZero.DepthBufferEnable = false; // create render targets renderTargetA = new RenderTarget2D(GraphicsDevice, PlanetDataSize, PlanetDataSize, false, SurfaceFormat.Color, DepthFormat.Depth24Stencil8, 0, RenderTargetUsage.DiscardContents); renderTargetB = new RenderTarget2D(GraphicsDevice, PlanetDataSize, PlanetDataSize, false, SurfaceFormat.Color, DepthFormat.Depth24Stencil8, 0, RenderTargetUsage.DiscardContents); // set up the ping-pong texture pointers activeRenderTarget = renderTargetA; textureRenderTarget = renderTargetB; drawingTexture = planetTexture; } protected override void UnloadContent() { } protected override void Update(GameTime gameTime) { // check for exit if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) this.Exit(); // update keyboard state previousKeyboardState = keyboardState; keyboardState = Keyboard.GetState(); // calculate planet position, centered in screen planetPosition = new Vector2(GraphicsDevice.PresentationParameters.BackBufferWidth * 0.5f - PlanetDataSize * 0.4f, GraphicsDevice.PresentationParameters.BackBufferHeight * 0.5f - PlanetDataSize * 0.4f); // add a random crater if space bar is pressed if ((keyboardState.IsKeyDown(Keys.Space) && previousKeyboardState.IsKeyUp(Keys.Space))) { craterPosition = new Vector2(random.Next(PlanetDataSize), random.Next(PlanetDataSize)); } base.Update(gameTime); } public void AddCrater(Vector2 position) { // set up rendering to the active render target GraphicsDevice.SetRenderTarget(activeRenderTarget); // clear the render target to opaque black, // and initialize the stencil buffer with all zeroes GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.Stencil, new Color(0, 0, 0, 1), 0, 0); // draw the new craters into the stencil buffer // stencilAlways makes sure we'll always write a 1 // to the stencil buffer wherever we draw the alphaTestEffect // is set up to only write a pixel if the alpha value // of the source texture is zero spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque, null, stencilAlways, null, alphaTestEffect); Vector2 origin = new Vector2(craterTexture.Width * 0.5f, craterTexture.Height * 0.5f); float rotation = (float)random.NextDouble() * MathHelper.TwoPi; Rectangle r = new Rectangle((int)position.X, (int)position.Y, 50, 50); spriteBatch.Draw(craterTexture, r, null, Color.White, rotation, origin, SpriteEffects.None, 0); spriteBatch.End(); // now draw the latest planet texture, excluding the stencil // buffer, resulting in the new craters being excluded from // the drawing the first time through we don't have a latest // planet texture, so draw from the original texture spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque, null, stencilKeepIfZero, null, null); if (firstTime) { spriteBatch.Draw(planetTexture, Vector2.Zero, Color.White); firstTime = false; } else spriteBatch.Draw(textureRenderTarget, Vector2.Zero, Color.White); spriteBatch.End(); // restore main render target - this lets us get at the render target we just drew and use it as a texture GraphicsDevice.SetRenderTarget(null); //// save image for testing //using (FileStream f = new FileStream("planet.png", FileMode.Create)) //{ // activeRenderTarget.SaveAsPng(f, 256, 256); //} // swap render targets, so the next time our source texture is the render target we just drew, // and the one we'll be drawing on is the one we just used as our source texture this time RenderTarget2D t = activeRenderTarget; activeRenderTarget = textureRenderTarget; textureRenderTarget = t; drawingTexture = textureRenderTarget; } protected override void Draw(GameTime gameTime) { // we have to draw render targets first if (craterPosition != Vector2.Zero) { AddCrater(craterPosition); craterPosition = Vector2.Zero; } // draw the cratered planet texture GraphicsDevice.Clear(Color.CornflowerBlue); spriteBatch.Begin(); spriteBatch.Draw(drawingTexture, planetPosition, Color.White); spriteBatch.End(); base.Draw(gameTime); } } }