source: 2013/30/MiskaK/MW2(My Warfare 2)/Krypton/KryptonEngine.cs @ 4507

Revision 4507, 15.9 KB checked in by anlakane, 8 years ago (diff)

Talletus.

Line 
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using Microsoft.Xna.Framework;
5using Microsoft.Xna.Framework.Audio;
6using Microsoft.Xna.Framework.Content;
7using Microsoft.Xna.Framework.GamerServices;
8using Microsoft.Xna.Framework.Graphics;
9using Microsoft.Xna.Framework.Input;
10using Microsoft.Xna.Framework.Media;
11
12using Krypton.Lights;
13using Krypton.Common;
14
15
16namespace Krypton
17{
18    public enum LightMapSize
19    {
20        Full = 1,
21        Fourth = 2,
22        Eighth = 4,
23    }
24
25    /// <summary>
26    /// A GPU-based 2D lighting engine
27    /// </summary>
28    public class KryptonEngine : DrawableGameComponent
29    {
30        // The Krypton Effect
31        private string mEffectAssetName;
32        private Effect mEffect;
33        private CullMode mCullMode = CullMode.CullCounterClockwiseFace;
34
35        // The goods
36        private List<ShadowHull> mHulls = new List<ShadowHull>();
37        private List<ILight2D> mLights = new List<ILight2D>();
38
39        // World View Projection matrix, and it's min and max view bounds
40        private Matrix mWVP = Matrix.Identity;
41        private bool mSpriteBatchCompatabilityEnabled = false;
42        private BoundingRect mBounds = BoundingRect.MinMax;
43
44        // Blur
45        private float mBluriness = 0.25f;
46        private RenderTarget2D mMapBlur;
47
48        // Light maps
49        private RenderTarget2D mMap;
50        private Color mAmbientColor = new Color(35,35,35);
51        private LightMapSize mLightMapSize = LightMapSize.Full;
52
53        /// <summary>
54        /// Krypton's render helper. It helps render. It also needs to be re-written.
55        /// </summary>
56        public KryptonRenderHelper RenderHelper { get; private set; }
57
58        /// <summary>
59        /// Gets or sets a value indicating how Krypton should cull geometry. The default value is CullMode.CounterClockwise
60        /// </summary>
61        public CullMode CullMode
62        {
63            get { return this.mCullMode; }
64            set { this.mCullMode = value; }
65        }
66
67        /// <summary>
68        /// The collection of lights krypton uses to render shadows
69        /// </summary>
70        public List<ILight2D> Lights { get { return this.mLights; } }
71
72        /// <summary>
73        /// The collection of hulls krypton uses to render shadows
74        /// </summary>
75        public List<ShadowHull> Hulls { get { return this.mHulls; } }
76
77        /// <summary>
78        /// Gets or sets the matrix used to draw the light map. This should match your scene's matrix.
79        /// </summary>
80        public Matrix Matrix
81        {
82            get { return this.mWVP; }
83            set
84            {
85                if (this.mWVP != value)
86                {
87                    this.mWVP = value;
88
89                    // This is totally ghetto, but it works for now. :)
90                    // Compute the world-space bounds of the given matrix
91                    var inverse = Matrix.Invert(value);
92
93                    var v1 = Vector2.Transform(new Vector2(1, 1), inverse);
94                    var v2 = Vector2.Transform(new Vector2(1, -1), inverse);
95                    var v3 = Vector2.Transform(new Vector2(-1, -1), inverse);
96                    var v4 = Vector2.Transform(new Vector2(-1, 1), inverse);
97
98                    this.mBounds.Min = v1;
99                    this.mBounds.Min = Vector2.Min(this.mBounds.Min, v2);
100                    this.mBounds.Min = Vector2.Min(this.mBounds.Min, v3);
101                    this.mBounds.Min = Vector2.Min(this.mBounds.Min, v4);
102
103                    this.mBounds.Max = v1;
104                    this.mBounds.Max = Vector2.Max(this.mBounds.Max, v2);
105                    this.mBounds.Max = Vector2.Max(this.mBounds.Max, v3);
106                    this.mBounds.Max = Vector2.Max(this.mBounds.Max, v4);
107
108                    this.mBounds = BoundingRect.MinMax;
109                }
110            }
111        }
112
113        /// <summary>
114        /// Gets or sets a value indicating weither or not to use SpriteBatch's matrix when drawing lightmaps
115        /// </summary>
116        public bool SpriteBatchCompatablityEnabled
117        {
118            get { return this.mSpriteBatchCompatabilityEnabled; }
119            set { this.mSpriteBatchCompatabilityEnabled = value; }
120        }
121
122        /// <summary>
123        /// Ambient color of the light map. Lights + AmbientColor = Final
124        /// </summary>
125        public Color AmbientColor
126        {
127            get { return this.mAmbientColor; }
128            set { this.mAmbientColor = value; }
129        }
130
131        /// <summary>
132        /// Gets or sets the value used to determine light map size
133        /// </summary>
134        public LightMapSize LightMapSize
135        {
136            get { return this.mLightMapSize; }
137            set
138            {
139                if (this.mLightMapSize != value)
140                {
141                    this.mLightMapSize = value;
142                    this.DisposeRenderTargets();
143                    this.CreateRenderTargets();
144                }
145            }
146        }
147
148        /// <summary>
149        /// Gets or sets a value indicating how much to blur the final light map. If the value is zero, the lightmap will not be blurred
150        /// </summary>
151        public float Bluriness
152        {
153            get { return this.mBluriness; }
154            set { this.mBluriness = Math.Max(0, value); }
155        }
156
157        /// <summary>
158        /// Constructs a new instance of krypton
159        /// </summary>
160        /// <param name="game">Your game object</param>
161        /// <param name="effectAssetName">The asset name of Krypton's effect file, which must be included in your content project</param>
162        public KryptonEngine(Game game, string effectAssetName)
163            : base(game)
164        {
165            this.mEffectAssetName = effectAssetName;
166        }
167
168        /// <summary>
169        /// Initializes Krpyton, and hooks itself to the graphics device
170        /// </summary>
171        public override void Initialize()
172        {
173            base.Initialize();
174
175            this.GraphicsDevice.DeviceReset += new EventHandler<EventArgs>(GraphicsDevice_DeviceReset);
176        }
177
178        /// <summary>
179        /// Resets kryptons graphics device resources
180        /// </summary>
181        private void GraphicsDevice_DeviceReset(object sender, EventArgs e)
182        {
183            this.DisposeRenderTargets();
184            this.CreateRenderTargets();
185        }
186
187        /// <summary>
188        /// Load's the graphics related content required to draw light maps
189        /// </summary>
190        protected override void LoadContent()
191        {
192            // This needs to better handle content loading...
193            // if the window is resized, Krypton needs to notice.
194            this.mEffect = this.Game.Content.Load<Effect>(this.mEffectAssetName);
195            this.RenderHelper = new KryptonRenderHelper(this.GraphicsDevice, this.mEffect);
196
197            this.CreateRenderTargets();
198        }
199
200        /// <summary>
201        /// Unload's the graphics content required to draw light maps
202        /// </summary>
203        protected override void UnloadContent()
204        {
205            this.DisposeRenderTargets();
206        }
207
208        /// <summary>
209        /// Creates render targets
210        /// </summary>
211        private void CreateRenderTargets()
212        {
213            var targetWidth = GraphicsDevice.Viewport.Width / (int)(this.mLightMapSize);
214            var targetHeight = GraphicsDevice.Viewport.Height / (int)(this.mLightMapSize);
215
216            this.mMap = new RenderTarget2D(GraphicsDevice, targetWidth, targetHeight, false, SurfaceFormat.Color, DepthFormat.Depth24Stencil8, 0, RenderTargetUsage.PlatformContents);
217            this.mMapBlur = new RenderTarget2D(GraphicsDevice, targetWidth, targetHeight, false, SurfaceFormat.Color, DepthFormat.Depth24Stencil8, 0, RenderTargetUsage.PlatformContents);
218        }
219
220        /// <summary>
221        /// Disposes of render targets
222        /// </summary>
223        private void DisposeRenderTargets()
224        {
225            KryptonEngine.TryDispose(this.mMap);
226            KryptonEngine.TryDispose(this.mMapBlur);
227        }
228
229        /// <summary>
230        /// Attempts to dispose of disposable objects, and assigns them a null value afterward
231        /// </summary>
232        /// <param name="obj"></param>
233        private static void TryDispose(IDisposable obj)
234        {
235            if (obj != null)
236            {
237                obj.Dispose();
238                obj = null;
239            }
240        }
241
242        /// <summary>
243        /// Draws the light map to the current render target
244        /// </summary>
245        /// <param name="gameTime">N/A - Required</param>
246        public override void Draw(GameTime gameTime)
247        {
248            this.LightMapPresent();
249        }
250
251        /// <summary>
252        /// Prepares the light map to be drawn (pre-render)
253        /// </summary>
254        public void LightMapPrepare()
255        {
256            // Prepare and set the matrix
257            var viewWidth = this.GraphicsDevice.ScissorRectangle.Width;
258            var viewHeight = this.GraphicsDevice.ScissorRectangle.Height;
259
260            // Prepare the matrix with optional settings and assign it to an effect parameter
261            Matrix lightMapMatrix = this.LightmapMatrixGet();
262            this.mEffect.Parameters["Matrix"].SetValue(lightMapMatrix);
263
264            // Obtain the original rendering states
265            var originalRenderTargets = this.GraphicsDevice.GetRenderTargets();
266
267            // Set and clear the target
268            this.GraphicsDevice.SetRenderTarget(this.mMap);
269            this.GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.Stencil, this.mAmbientColor, 0, 1);
270
271            // Make sure we're culling the right way!
272            this.GraphicsDevice.RasterizerState = KryptonEngine.RasterizerStateGetFromCullMode(this.mCullMode);
273
274            // put the render target's size into a more friendly format
275            var targetSize = new Vector2(this.mMap.Width, this.mMap.Height);
276
277            // Render Light Maps
278            foreach (var light in this.mLights)
279            {
280                // Loop through each light within the view frustum
281                if (light.Bounds.Intersects(this.mBounds))
282                {
283                    // Clear the stencil and set the scissor rect (because we're stretching geometry past the light's reach)
284                    this.GraphicsDevice.Clear(ClearOptions.Stencil, Color.Black, 0, 1);
285                    this.GraphicsDevice.ScissorRectangle = KryptonEngine.ScissorRectCreateForLight(light, lightMapMatrix, targetSize);
286                   
287                    // Draw the light!
288                    light.Draw(this.RenderHelper, this.mHulls);
289                }
290            }
291
292            if (this.mBluriness > 0)
293            {
294                // Blur the shadow map horizontally to the blur target
295                this.GraphicsDevice.SetRenderTarget(this.mMapBlur);
296                this.RenderHelper.BlurTextureToTarget(this.mMap, LightMapSize.Full, BlurTechnique.Horizontal, this.mBluriness);
297
298                // Blur the shadow map vertically back to the final map
299                this.GraphicsDevice.SetRenderTarget(this.mMap);
300                this.RenderHelper.BlurTextureToTarget(this.mMapBlur, LightMapSize.Full, BlurTechnique.Vertical, this.mBluriness);
301            }
302
303            // Reset to the original rendering states
304            this.GraphicsDevice.SetRenderTargets(originalRenderTargets);
305        }
306
307        /// <summary>
308        /// Returns the final, modified matrix used to render the lightmap.
309        /// </summary>
310        /// <returns></returns>
311        private Matrix LightmapMatrixGet()
312        {
313            if (this.mSpriteBatchCompatabilityEnabled)
314            {
315                float xScale = (this.GraphicsDevice.Viewport.Width > 0) ? (1f / this.GraphicsDevice.Viewport.Width) : 0f;
316                float yScale = (this.GraphicsDevice.Viewport.Height > 0) ? (-1f / this.GraphicsDevice.Viewport.Height) : 0f;
317
318                // This is the default matrix used to render sprites via spritebatch
319                var matrixSpriteBatch = new Matrix()
320                {
321                    M11 = xScale * 2f,
322                    M22 = yScale * 2f,
323                    M33 = 1f,
324                    M44 = 1f,
325                    M41 = -1f - xScale,
326                    M42 = 1f - yScale,
327                };
328
329                // Return krypton's matrix, compensated for use with SpriteBatch
330                return this.mWVP * matrixSpriteBatch;
331            }
332            else
333            {
334                // Return krypton's matrix
335                return this.mWVP;
336            }
337        }
338
339        /// <summary>
340        /// Gets a pixel-space rectangle which contains the light passed in
341        /// </summary>
342        /// <param name="light">The light used to create the rectangle</param>
343        /// <param name="matrix">the WorldViewProjection matrix being used to render</param>
344        /// <param name="targetSize">The rendertarget's size</param>
345        /// <returns></returns>
346        private static Rectangle ScissorRectCreateForLight(ILight2D light, Microsoft.Xna.Framework.Matrix matrix, Vector2 targetSize)
347        {
348            // This needs refining, but it works as is (I believe)
349            var lightBounds = light.Bounds;
350
351            var min = KryptonEngine.VectorToPixel(lightBounds.Min, matrix, targetSize);
352            var max = KryptonEngine.VectorToPixel(lightBounds.Max, matrix, targetSize);
353
354            var min2 = Vector2.Min(min, max);
355            var max2 = Vector2.Max(min, max);
356
357            min = Vector2.Clamp(min2, Vector2.Zero, targetSize);
358            max = Vector2.Clamp(max2, Vector2.Zero, targetSize);
359
360            return new Rectangle((int)(min.X), (int)(min.Y), (int)(max.X - min.X), (int)(max.Y - min.Y));
361        }
362
363        /// <summary>
364        /// Takes a screen-space vector and puts it in to pixel space
365        /// </summary>
366        /// <param name="v"></param>
367        /// <param name="matrix"></param>
368        /// <param name="targetSize"></param>
369        /// <returns></returns>
370        private static Vector2 VectorToPixel(Vector2 v, Matrix matrix, Vector2 targetSize)
371        {
372            Vector2.Transform(ref v, ref matrix, out v);
373
374            v.X = (1 + v.X) * (targetSize.X / 2f);
375            v.Y = (1 - v.Y) * (targetSize.Y / 2f);
376
377            return v;
378        }
379
380        /// <summary>
381        /// Takes a screen-space size vector and converts it to a pixel-space size vector
382        /// </summary>
383        /// <param name="v"></param>
384        /// <param name="matrix"></param>
385        /// <param name="targetSize"></param>
386        /// <returns></returns>
387        private static Vector2 ScaleToPixel(Vector2 v, Matrix matrix, Vector2 targetSize)
388        {
389            v.X *= matrix.M11 * (targetSize.X / 2f);
390            v.Y *= matrix.M22 * (targetSize.Y / 2f);
391
392            return v;
393        }
394
395        /// <summary>
396        /// Retrieves a rasterize state by using the cull mode as a lookup
397        /// </summary>
398        /// <param name="cullMode">The cullmode used to lookup the rasterize state</param>
399        /// <returns></returns>
400        private static RasterizerState RasterizerStateGetFromCullMode(CullMode cullMode)
401        {
402            switch (cullMode)
403            {
404                case (CullMode.CullCounterClockwiseFace):
405                    return RasterizerState.CullCounterClockwise;
406
407                case (CullMode.CullClockwiseFace):
408                    return RasterizerState.CullClockwise;
409
410                default:
411                    return RasterizerState.CullNone;
412            }
413        }
414
415        /// <summary>
416        /// Presents the light map to the current render target
417        /// </summary>
418        private void LightMapPresent()
419        {
420            RenderHelper.DrawTextureToTarget(this.mMap, this.mLightMapSize, BlendTechnique.Multiply);
421        }
422    }
423}
Note: See TracBrowser for help on using the repository browser.