#region File Description //----------------------------------------------------------------------------- // Game.cs // // Microsoft XNA Community Game Platform // Copyright (C) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- #endregion #region Using Statements using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; #endregion namespace NonPhotoRealistic { /// /// Sample showing how to implement non-photorealistic rendering techniques, /// providing a cartoon shader, edge detection, and pencil sketch rendering effect. /// public class NonPhotoRealisticGame : Microsoft.Xna.Framework.Game { #region Fields GraphicsDeviceManager graphics; SpriteBatch spriteBatch; SpriteFont spriteFont; Model model; Random random = new Random(); // Effect used to apply the edge detection and pencil sketch postprocessing. Effect postprocessEffect; // Overlay texture containing the pencil sketch stroke pattern. Texture2D sketchTexture; // Randomly offsets the sketch pattern to create a hand-drawn animation effect. Vector2 sketchJitter; TimeSpan timeToNextJitter; // Custom rendertargets. RenderTarget2D sceneRenderTarget; RenderTarget2D normalDepthRenderTarget; // Choose what display settings to use. NonPhotoRealisticSettings Settings { get { return NonPhotoRealisticSettings.PresetSettings[settingsIndex]; } } int settingsIndex = 0; // Current and previous input states. KeyboardState lastKeyboardState; GamePadState lastGamePadState; KeyboardState currentKeyboardState; GamePadState currentGamePadState; #endregion #region Initialization public NonPhotoRealisticGame() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; } /// /// Load your graphics content. /// protected override void LoadContent() { spriteBatch = new SpriteBatch(graphics.GraphicsDevice); spriteFont = Content.Load("hudFont"); model = Content.Load("Ship"); postprocessEffect = Content.Load("PostprocessEffect"); sketchTexture = Content.Load("SketchTexture"); // Change the model to use our custom cartoon shading effect. Effect cartoonEffect = Content.Load("CartoonEffect"); ChangeEffectUsedByModel(model, cartoonEffect); // Create two custom rendertargets. PresentationParameters pp = graphics.GraphicsDevice.PresentationParameters; sceneRenderTarget = new RenderTarget2D(graphics.GraphicsDevice, pp.BackBufferWidth, pp.BackBufferHeight, false, pp.BackBufferFormat, pp.DepthStencilFormat); normalDepthRenderTarget = new RenderTarget2D(graphics.GraphicsDevice, pp.BackBufferWidth, pp.BackBufferHeight, false, pp.BackBufferFormat, pp.DepthStencilFormat); } /// /// Unload your graphics content. /// protected override void UnloadContent() { if (sceneRenderTarget != null) { sceneRenderTarget.Dispose(); sceneRenderTarget = null; } if (normalDepthRenderTarget != null) { normalDepthRenderTarget.Dispose(); normalDepthRenderTarget = null; } } /// /// Alters a model so it will draw using a custom effect, while preserving /// whatever textures were set on it as part of the original effects. /// static void ChangeEffectUsedByModel(Model model, Effect replacementEffect) { // Table mapping the original effects to our replacement versions. Dictionary effectMapping = new Dictionary(); foreach (ModelMesh mesh in model.Meshes) { // Scan over all the effects currently on the mesh. foreach (BasicEffect oldEffect in mesh.Effects) { // If we haven't already seen this effect... if (!effectMapping.ContainsKey(oldEffect)) { // Make a clone of our replacement effect. We can't just use // it directly, because the same effect might need to be // applied several times to different parts of the model using // a different texture each time, so we need a fresh copy each // time we want to set a different texture into it. Effect newEffect = replacementEffect.Clone(); // Copy across the texture from the original effect. newEffect.Parameters["Texture"].SetValue(oldEffect.Texture); newEffect.Parameters["TextureEnabled"].SetValue(oldEffect.TextureEnabled); effectMapping.Add(oldEffect, newEffect); } } // Now that we've found all the effects in use on this mesh, // update it to use our new replacement versions. foreach (ModelMeshPart meshPart in mesh.MeshParts) { meshPart.Effect = effectMapping[meshPart.Effect]; } } } #endregion #region Update and Draw /// /// Allows the game to run logic. /// protected override void Update(GameTime gameTime) { HandleInput(); // Update the sketch overlay texture jitter animation. if (Settings.SketchJitterSpeed > 0) { timeToNextJitter -= gameTime.ElapsedGameTime; if (timeToNextJitter <= TimeSpan.Zero) { sketchJitter.X = (float)random.NextDouble(); sketchJitter.Y = (float)random.NextDouble(); timeToNextJitter += TimeSpan.FromSeconds(Settings.SketchJitterSpeed); } } base.Update(gameTime); } /// /// This is called when the game should draw itself. /// protected override void Draw(GameTime gameTime) { GraphicsDevice device = graphics.GraphicsDevice; // Calculate the camera matrices. float time = (float)gameTime.TotalGameTime.TotalSeconds; Matrix rotation = Matrix.CreateRotationY(time * 0.5f); Matrix view = Matrix.CreateLookAt(new Vector3(3000, 1500, 0), Vector3.Zero, Vector3.Up); Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.Viewport.AspectRatio, 1000, 10000); // If we are doing edge detection, first off we need to render the // normals and depth of our model into a special rendertarget. if (Settings.EnableEdgeDetect) { device.SetRenderTarget(normalDepthRenderTarget); device.Clear(Color.Black); DrawModel(rotation, view, projection, "NormalDepth"); } // If we are doing edge detection and/or pencil sketch processing, we // need to draw the model into a special rendertarget which can then be // fed into the postprocessing shader. Otherwise can just draw it // directly onto the backbuffer. if (Settings.EnableEdgeDetect || Settings.EnableSketch) device.SetRenderTarget(sceneRenderTarget); else device.SetRenderTarget(null); device.Clear(Color.CornflowerBlue); // Draw the model, using either the cartoon or lambert shading technique. string effectTechniqueName; if (Settings.EnableToonShading) effectTechniqueName = "Toon"; else effectTechniqueName = "Lambert"; DrawModel(rotation, view, projection, effectTechniqueName); // Run the postprocessing filter over the scene that we just rendered. if (Settings.EnableEdgeDetect || Settings.EnableSketch) { device.SetRenderTarget(null); ApplyPostprocess(); } // Display some text over the top. Note how we draw this after the // postprocessing, because we don't want the text to be affected by it. DrawOverlayText(); base.Draw(gameTime); } /// /// Helper for drawing the spinning model using the specified effect technique. /// void DrawModel(Matrix world, Matrix view, Matrix projection, string effectTechniqueName) { // Set suitable renderstates for drawing a 3D model. GraphicsDevice.BlendState = BlendState.Opaque; GraphicsDevice.DepthStencilState = DepthStencilState.Default; // Look up the bone transform matrices. Matrix[] transforms = new Matrix[model.Bones.Count]; model.CopyAbsoluteBoneTransformsTo(transforms); // Draw the model. foreach (ModelMesh mesh in model.Meshes) { foreach (Effect effect in mesh.Effects) { // Specify which effect technique to use. effect.CurrentTechnique = effect.Techniques[effectTechniqueName]; Matrix localWorld = transforms[mesh.ParentBone.Index] * world; effect.Parameters["World"].SetValue(localWorld); effect.Parameters["View"].SetValue(view); effect.Parameters["Projection"].SetValue(projection); } mesh.Draw(); } } /// /// Helper applies the edge detection and pencil sketch postprocess effect. /// void ApplyPostprocess() { EffectParameterCollection parameters = postprocessEffect.Parameters; string effectTechniqueName; // Set effect parameters controlling the pencil sketch effect. if (Settings.EnableSketch) { parameters["SketchThreshold"].SetValue(Settings.SketchThreshold); parameters["SketchBrightness"].SetValue(Settings.SketchBrightness); parameters["SketchJitter"].SetValue(sketchJitter); parameters["SketchTexture"].SetValue(sketchTexture); } // Set effect parameters controlling the edge detection effect. if (Settings.EnableEdgeDetect) { Vector2 resolution = new Vector2(sceneRenderTarget.Width, sceneRenderTarget.Height); Texture2D normalDepthTexture = normalDepthRenderTarget; parameters["EdgeWidth"].SetValue(Settings.EdgeWidth); parameters["EdgeIntensity"].SetValue(Settings.EdgeIntensity); parameters["ScreenResolution"].SetValue(resolution); parameters["NormalDepthTexture"].SetValue(normalDepthTexture); // Choose which effect technique to use. if (Settings.EnableSketch) { if (Settings.SketchInColor) effectTechniqueName = "EdgeDetectColorSketch"; else effectTechniqueName = "EdgeDetectMonoSketch"; } else effectTechniqueName = "EdgeDetect"; } else { // If edge detection is off, just pick one of the sketch techniques. if (Settings.SketchInColor) effectTechniqueName = "ColorSketch"; else effectTechniqueName = "MonoSketch"; } // Activate the appropriate effect technique. postprocessEffect.CurrentTechnique = postprocessEffect.Techniques[effectTechniqueName]; // Draw a fullscreen sprite to apply the postprocessing effect. spriteBatch.Begin(0, BlendState.Opaque, null, null, null, postprocessEffect); spriteBatch.Draw(sceneRenderTarget, Vector2.Zero, Color.White); spriteBatch.End(); } /// /// Displays an overlay showing what the controls are, /// and which settings are currently selected. /// void DrawOverlayText() { string text = "A = settings (" + Settings.Name + ")"; spriteBatch.Begin(); // Draw the string twice to create a drop shadow, first colored black // and offset one pixel to the bottom right, then again in white at the // intended position. This makes text easier to read over the background. spriteBatch.DrawString(spriteFont, text, new Vector2(65, 65), Color.Black); spriteBatch.DrawString(spriteFont, text, new Vector2(64, 64), Color.White); spriteBatch.End(); } #endregion #region Handle Input /// /// Handles input for quitting or changing the display settings. /// void HandleInput() { lastKeyboardState = currentKeyboardState; lastGamePadState = currentGamePadState; currentKeyboardState = Keyboard.GetState(); currentGamePadState = GamePad.GetState(PlayerIndex.One); // Check for exit. if (currentKeyboardState.IsKeyDown(Keys.Escape) || currentGamePadState.Buttons.Back == ButtonState.Pressed) { Exit(); } // Switch to the next settings preset? if ((currentGamePadState.Buttons.A == ButtonState.Pressed && lastGamePadState.Buttons.A != ButtonState.Pressed) || (currentKeyboardState.IsKeyDown(Keys.A) && lastKeyboardState.IsKeyUp(Keys.A))) { settingsIndex = (settingsIndex + 1) % NonPhotoRealisticSettings.PresetSettings.Length; } } #endregion } #region Entry Point /// /// The main entry point for the application. /// static class Program { static void Main() { using (NonPhotoRealisticGame game = new NonPhotoRealisticGame()) { game.Run(); } } } #endregion }