2
0

Game.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. #region File Description
  2. //-----------------------------------------------------------------------------
  3. // Game.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 System.Collections.Generic;
  12. using Microsoft.Xna.Framework;
  13. using Microsoft.Xna.Framework.Content;
  14. using Microsoft.Xna.Framework.Graphics;
  15. using Microsoft.Xna.Framework.Input;
  16. #endregion
  17. namespace NonPhotoRealistic
  18. {
  19. /// <summary>
  20. /// Sample showing how to implement non-photorealistic rendering techniques,
  21. /// providing a cartoon shader, edge detection, and pencil sketch rendering effect.
  22. /// </summary>
  23. public class NonPhotoRealisticGame : Microsoft.Xna.Framework.Game
  24. {
  25. #region Fields
  26. GraphicsDeviceManager graphics;
  27. SpriteBatch spriteBatch;
  28. SpriteFont spriteFont;
  29. Model model;
  30. Random random = new Random();
  31. // Effect used to apply the edge detection and pencil sketch postprocessing.
  32. Effect postprocessEffect;
  33. // Overlay texture containing the pencil sketch stroke pattern.
  34. Texture2D sketchTexture;
  35. // Randomly offsets the sketch pattern to create a hand-drawn animation effect.
  36. Vector2 sketchJitter;
  37. TimeSpan timeToNextJitter;
  38. // Custom rendertargets.
  39. RenderTarget2D sceneRenderTarget;
  40. RenderTarget2D normalDepthRenderTarget;
  41. // Choose what display settings to use.
  42. NonPhotoRealisticSettings Settings
  43. {
  44. get { return NonPhotoRealisticSettings.PresetSettings[settingsIndex]; }
  45. }
  46. int settingsIndex = 0;
  47. // Current and previous input states.
  48. KeyboardState lastKeyboardState;
  49. GamePadState lastGamePadState;
  50. KeyboardState currentKeyboardState;
  51. GamePadState currentGamePadState;
  52. #endregion
  53. #region Initialization
  54. public NonPhotoRealisticGame()
  55. {
  56. graphics = new GraphicsDeviceManager(this);
  57. Content.RootDirectory = "Content";
  58. }
  59. /// <summary>
  60. /// Load your graphics content.
  61. /// </summary>
  62. protected override void LoadContent()
  63. {
  64. spriteBatch = new SpriteBatch(graphics.GraphicsDevice);
  65. spriteFont = Content.Load<SpriteFont>("hudFont");
  66. model = Content.Load<Model>("Ship");
  67. postprocessEffect = Content.Load<Effect>("PostprocessEffect");
  68. sketchTexture = Content.Load<Texture2D>("SketchTexture");
  69. // Change the model to use our custom cartoon shading effect.
  70. Effect cartoonEffect = Content.Load<Effect>("CartoonEffect");
  71. ChangeEffectUsedByModel(model, cartoonEffect);
  72. // Create two custom rendertargets.
  73. PresentationParameters pp = graphics.GraphicsDevice.PresentationParameters;
  74. sceneRenderTarget = new RenderTarget2D(graphics.GraphicsDevice,
  75. pp.BackBufferWidth, pp.BackBufferHeight, false,
  76. pp.BackBufferFormat, pp.DepthStencilFormat);
  77. normalDepthRenderTarget = new RenderTarget2D(graphics.GraphicsDevice,
  78. pp.BackBufferWidth, pp.BackBufferHeight, false,
  79. pp.BackBufferFormat, pp.DepthStencilFormat);
  80. }
  81. /// <summary>
  82. /// Unload your graphics content.
  83. /// </summary>
  84. protected override void UnloadContent()
  85. {
  86. if (sceneRenderTarget != null)
  87. {
  88. sceneRenderTarget.Dispose();
  89. sceneRenderTarget = null;
  90. }
  91. if (normalDepthRenderTarget != null)
  92. {
  93. normalDepthRenderTarget.Dispose();
  94. normalDepthRenderTarget = null;
  95. }
  96. }
  97. /// <summary>
  98. /// Alters a model so it will draw using a custom effect, while preserving
  99. /// whatever textures were set on it as part of the original effects.
  100. /// </summary>
  101. static void ChangeEffectUsedByModel(Model model, Effect replacementEffect)
  102. {
  103. // Table mapping the original effects to our replacement versions.
  104. Dictionary<Effect, Effect> effectMapping = new Dictionary<Effect, Effect>();
  105. foreach (ModelMesh mesh in model.Meshes)
  106. {
  107. // Scan over all the effects currently on the mesh.
  108. foreach (BasicEffect oldEffect in mesh.Effects)
  109. {
  110. // If we haven't already seen this effect...
  111. if (!effectMapping.ContainsKey(oldEffect))
  112. {
  113. // Make a clone of our replacement effect. We can't just use
  114. // it directly, because the same effect might need to be
  115. // applied several times to different parts of the model using
  116. // a different texture each time, so we need a fresh copy each
  117. // time we want to set a different texture into it.
  118. Effect newEffect = replacementEffect.Clone();
  119. // Copy across the texture from the original effect.
  120. newEffect.Parameters["Texture"].SetValue(oldEffect.Texture);
  121. newEffect.Parameters["TextureEnabled"].SetValue(oldEffect.TextureEnabled);
  122. effectMapping.Add(oldEffect, newEffect);
  123. }
  124. }
  125. // Now that we've found all the effects in use on this mesh,
  126. // update it to use our new replacement versions.
  127. foreach (ModelMeshPart meshPart in mesh.MeshParts)
  128. {
  129. meshPart.Effect = effectMapping[meshPart.Effect];
  130. }
  131. }
  132. }
  133. #endregion
  134. #region Update and Draw
  135. /// <summary>
  136. /// Allows the game to run logic.
  137. /// </summary>
  138. protected override void Update(GameTime gameTime)
  139. {
  140. HandleInput();
  141. // Update the sketch overlay texture jitter animation.
  142. if (Settings.SketchJitterSpeed > 0)
  143. {
  144. timeToNextJitter -= gameTime.ElapsedGameTime;
  145. if (timeToNextJitter <= TimeSpan.Zero)
  146. {
  147. sketchJitter.X = (float)random.NextDouble();
  148. sketchJitter.Y = (float)random.NextDouble();
  149. timeToNextJitter += TimeSpan.FromSeconds(Settings.SketchJitterSpeed);
  150. }
  151. }
  152. base.Update(gameTime);
  153. }
  154. /// <summary>
  155. /// This is called when the game should draw itself.
  156. /// </summary>
  157. protected override void Draw(GameTime gameTime)
  158. {
  159. GraphicsDevice device = graphics.GraphicsDevice;
  160. // Calculate the camera matrices.
  161. float time = (float)gameTime.TotalGameTime.TotalSeconds;
  162. Matrix rotation = Matrix.CreateRotationY(time * 0.5f);
  163. Matrix view = Matrix.CreateLookAt(new Vector3(3000, 1500, 0),
  164. Vector3.Zero,
  165. Vector3.Up);
  166. Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
  167. device.Viewport.AspectRatio,
  168. 1000, 10000);
  169. // If we are doing edge detection, first off we need to render the
  170. // normals and depth of our model into a special rendertarget.
  171. if (Settings.EnableEdgeDetect)
  172. {
  173. device.SetRenderTarget(normalDepthRenderTarget);
  174. device.Clear(Color.Black);
  175. DrawModel(rotation, view, projection, "NormalDepth");
  176. }
  177. // If we are doing edge detection and/or pencil sketch processing, we
  178. // need to draw the model into a special rendertarget which can then be
  179. // fed into the postprocessing shader. Otherwise can just draw it
  180. // directly onto the backbuffer.
  181. if (Settings.EnableEdgeDetect || Settings.EnableSketch)
  182. device.SetRenderTarget(sceneRenderTarget);
  183. else
  184. device.SetRenderTarget(null);
  185. device.Clear(Color.CornflowerBlue);
  186. // Draw the model, using either the cartoon or lambert shading technique.
  187. string effectTechniqueName;
  188. if (Settings.EnableToonShading)
  189. effectTechniqueName = "Toon";
  190. else
  191. effectTechniqueName = "Lambert";
  192. DrawModel(rotation, view, projection, effectTechniqueName);
  193. // Run the postprocessing filter over the scene that we just rendered.
  194. if (Settings.EnableEdgeDetect || Settings.EnableSketch)
  195. {
  196. device.SetRenderTarget(null);
  197. ApplyPostprocess();
  198. }
  199. // Display some text over the top. Note how we draw this after the
  200. // postprocessing, because we don't want the text to be affected by it.
  201. DrawOverlayText();
  202. base.Draw(gameTime);
  203. }
  204. /// <summary>
  205. /// Helper for drawing the spinning model using the specified effect technique.
  206. /// </summary>
  207. void DrawModel(Matrix world, Matrix view, Matrix projection,
  208. string effectTechniqueName)
  209. {
  210. // Set suitable renderstates for drawing a 3D model.
  211. GraphicsDevice.BlendState = BlendState.Opaque;
  212. GraphicsDevice.DepthStencilState = DepthStencilState.Default;
  213. // Look up the bone transform matrices.
  214. Matrix[] transforms = new Matrix[model.Bones.Count];
  215. model.CopyAbsoluteBoneTransformsTo(transforms);
  216. // Draw the model.
  217. foreach (ModelMesh mesh in model.Meshes)
  218. {
  219. foreach (Effect effect in mesh.Effects)
  220. {
  221. // Specify which effect technique to use.
  222. effect.CurrentTechnique = effect.Techniques[effectTechniqueName];
  223. Matrix localWorld = transforms[mesh.ParentBone.Index] * world;
  224. effect.Parameters["World"].SetValue(localWorld);
  225. effect.Parameters["View"].SetValue(view);
  226. effect.Parameters["Projection"].SetValue(projection);
  227. }
  228. mesh.Draw();
  229. }
  230. }
  231. /// <summary>
  232. /// Helper applies the edge detection and pencil sketch postprocess effect.
  233. /// </summary>
  234. void ApplyPostprocess()
  235. {
  236. EffectParameterCollection parameters = postprocessEffect.Parameters;
  237. string effectTechniqueName;
  238. // Set effect parameters controlling the pencil sketch effect.
  239. if (Settings.EnableSketch)
  240. {
  241. parameters["SketchThreshold"].SetValue(Settings.SketchThreshold);
  242. parameters["SketchBrightness"].SetValue(Settings.SketchBrightness);
  243. parameters["SketchJitter"].SetValue(sketchJitter);
  244. parameters["SketchTexture"].SetValue(sketchTexture);
  245. }
  246. // Set effect parameters controlling the edge detection effect.
  247. if (Settings.EnableEdgeDetect)
  248. {
  249. Vector2 resolution = new Vector2(sceneRenderTarget.Width,
  250. sceneRenderTarget.Height);
  251. Texture2D normalDepthTexture = normalDepthRenderTarget;
  252. parameters["EdgeWidth"].SetValue(Settings.EdgeWidth);
  253. parameters["EdgeIntensity"].SetValue(Settings.EdgeIntensity);
  254. parameters["ScreenResolution"].SetValue(resolution);
  255. parameters["NormalDepthTexture"].SetValue(normalDepthTexture);
  256. // Choose which effect technique to use.
  257. if (Settings.EnableSketch)
  258. {
  259. if (Settings.SketchInColor)
  260. effectTechniqueName = "EdgeDetectColorSketch";
  261. else
  262. effectTechniqueName = "EdgeDetectMonoSketch";
  263. }
  264. else
  265. effectTechniqueName = "EdgeDetect";
  266. }
  267. else
  268. {
  269. // If edge detection is off, just pick one of the sketch techniques.
  270. if (Settings.SketchInColor)
  271. effectTechniqueName = "ColorSketch";
  272. else
  273. effectTechniqueName = "MonoSketch";
  274. }
  275. // Activate the appropriate effect technique.
  276. postprocessEffect.CurrentTechnique = postprocessEffect.Techniques[effectTechniqueName];
  277. // Draw a fullscreen sprite to apply the postprocessing effect.
  278. spriteBatch.Begin(0, BlendState.Opaque, null, null, null, postprocessEffect);
  279. spriteBatch.Draw(sceneRenderTarget, Vector2.Zero, Color.White);
  280. spriteBatch.End();
  281. }
  282. /// <summary>
  283. /// Displays an overlay showing what the controls are,
  284. /// and which settings are currently selected.
  285. /// </summary>
  286. void DrawOverlayText()
  287. {
  288. string text = "A = settings (" + Settings.Name + ")";
  289. spriteBatch.Begin();
  290. // Draw the string twice to create a drop shadow, first colored black
  291. // and offset one pixel to the bottom right, then again in white at the
  292. // intended position. This makes text easier to read over the background.
  293. spriteBatch.DrawString(spriteFont, text, new Vector2(65, 65), Color.Black);
  294. spriteBatch.DrawString(spriteFont, text, new Vector2(64, 64), Color.White);
  295. spriteBatch.End();
  296. }
  297. #endregion
  298. #region Handle Input
  299. /// <summary>
  300. /// Handles input for quitting or changing the display settings.
  301. /// </summary>
  302. void HandleInput()
  303. {
  304. lastKeyboardState = currentKeyboardState;
  305. lastGamePadState = currentGamePadState;
  306. currentKeyboardState = Keyboard.GetState();
  307. currentGamePadState = GamePad.GetState(PlayerIndex.One);
  308. // Check for exit.
  309. if (currentKeyboardState.IsKeyDown(Keys.Escape) ||
  310. currentGamePadState.Buttons.Back == ButtonState.Pressed)
  311. {
  312. Exit();
  313. }
  314. // Switch to the next settings preset?
  315. if ((currentGamePadState.Buttons.A == ButtonState.Pressed &&
  316. lastGamePadState.Buttons.A != ButtonState.Pressed) ||
  317. (currentKeyboardState.IsKeyDown(Keys.A) &&
  318. lastKeyboardState.IsKeyUp(Keys.A)))
  319. {
  320. settingsIndex = (settingsIndex + 1) %
  321. NonPhotoRealisticSettings.PresetSettings.Length;
  322. }
  323. }
  324. #endregion
  325. }
  326. #region Entry Point
  327. /// <summary>
  328. /// The main entry point for the application.
  329. /// </summary>
  330. static class Program
  331. {
  332. static void Main()
  333. {
  334. using (NonPhotoRealisticGame game = new NonPhotoRealisticGame())
  335. {
  336. game.Run();
  337. }
  338. }
  339. }
  340. #endregion
  341. }