XNA Game Engine教程系列10:Post Processing
本教程我们将在引擎中添加一个Post Processing。Post Processing是一个只含有pixel shader的Effect(.fx文件)。它获取当前帧缓冲并在其上应用pixel shader,例如模糊、发光等特殊效果,post processor类叫做PostProcessor,拥有获取当前帧缓冲并使用post processor进行绘制的方法。
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; namespace Innovation { // Applies a post processing effect to a texture or the frame buffer public class PostProcessor : Component, I2DComponent { // The post processor fx file public Effect Effect; // Width and height the the render target/frame buffer protected int Width; protected int Height; // Texture that will processed Texture2D input; public Texture2D Input { get { return input; } set { input = value; if (Effect.Parameters["InputTexture"] != null) Effect.Parameters["InputTexture"].SetValue(value); } } // Draw rectangle public Rectangle Rectangle { get { return new Rectangle(0, 0, Width, Height); } set { this.Width = value.Width; this.Height = value.Height; } } public PostProcessor() : base() { Setup(null, Engine.GraphicsDevice.Viewport.Width, Engine.GraphicsDevice.Viewport.Height); } public PostProcessor(Effect Effect, int Width, int Height) : base() { Setup(Effect, Width, Height); } public PostProcessor(Effect Effect, int Width, int Height, GameScreen Parent) : base(Parent) { Setup(Effect, Width, Height); } // Set the effect, width, and height, and inititalize the Input texture void Setup(Effect Effect, int Width, int Height) { this.Effect = Effect; Input = new Texture2D(Engine.GraphicsDevice, 1, 1); Input.SetData<Color>(new Color[] { Color.White }); this.Width = Width; this.Height = Height; } // Gets the current scene texture from the frame buffer public void GetInputFromFrameBuffer() { // Make sure we are working with a resolve texture if (!(Input is ResolveTexture2D)) Input = GraphicsUtil.CreateResolveTexture(); // Then resolve the frame buffer to the Input texture Engine.GraphicsDevice.ResolveBackBuffer((ResolveTexture2D)Input); } public override void Draw() { Engine.GraphicsDevice.Clear(Color.Black); // Begin in a mode that will overrite everything and draw immediately Engine.SpriteBatch.Begin(SpriteBlendMode.None, SpriteSortMode.Immediate, SaveStateMode.None); Effect.Begin(); // For each pass... foreach (EffectPass pass in Effect.CurrentTechnique.Passes) { pass.Begin(); // Draw the input texture with the effect applied Engine.SpriteBatch.Draw(Input, Rectangle, Color.White); pass.End(); } Effect.End(); Engine.SpriteBatch.End(); base.Draw(); } } }
下面我们创建一个使用post processor的例子,高斯模糊。高斯模糊首先模糊水平方向的像素,然后是竖直方向,使用在纹理的宽高基础上生成的weights和offsets。
using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace Innovation { // Post processor that applies a Gaussian blur to an image or the frame buffer public class GaussianBlur : PostProcessor { // Offsets and wieghts for horizontal and vertical blurs Vector2[] sampleOffsetsH = new Vector2[15]; float[] sampleWeightsH = new float[15]; Vector2[] sampleOffsetsV = new Vector2[15]; float[] sampleWeightsV = new float[15]; // Constructors load Effect called GaussianBlur.fx public GaussianBlur(int Width, int Height) : base(Engine.Content.Load<Effect>("Content/GaussianBlur"), Width, Height) { Setup(Width, Height); } public GaussianBlur(int Width, int Height, GameScreen Parent) : base(Engine.Content.Load<Effect>("Content/GaussianBlur"), Width, Height, Parent) { Setup(Width, Height); } // Calculate the weights and offsets based on width and height void Setup(int Width, int Height) { Vector2 texelSize = new Vector2(1f / Width, 1f / Height); SetBlurParameters(texelSize.X, 0, ref sampleOffsetsH, ref sampleWeightsH); SetBlurParameters(0, texelSize.Y, ref sampleOffsetsV, ref sampleWeightsV); } // Borrowed from "Shadow Mapping" by Andrew Joli // http://www.ziggyware.com/readarticle.php?article_id=161 void SetBlurParameters(float dx, float dy, ref Vector2[] vSampleOffsets, ref float[] fSampleWeights) { // The first sample always has a zero offset. fSampleWeights[0] = ComputeGaussian(0); vSampleOffsets[0] = new Vector2(0); // Maintain a sum of all the weighting values. float totalWeights = fSampleWeights[0]; // Add pairs of additional sample taps, positioned // along a line in both directions from the center. for (int i = 0; i < 15 / 2; i++) { // Store weights for the positive and negative taps. float weight = ComputeGaussian(i + 1); fSampleWeights[i * 2 + 1] = weight; fSampleWeights[i * 2 + 2] = weight; totalWeights += weight * 2; // The 1.5 offset kicks things off by // positioning us nicely in between two texels. float sampleOffset = i * 2 + 1.5f; Vector2 delta = new Vector2(dx, dy) * sampleOffset; // Store texture coordinate offsets for the positive and negative taps. vSampleOffsets[i * 2 + 1] = delta; vSampleOffsets[i * 2 + 2] = -delta; } // Normalize the list of sample weightings, so they will always sum to one. for (int i = 0; i < fSampleWeights.Length; i++) fSampleWeights[i] /= totalWeights; } // Borrowed from "Shadow Mapping" by Andrew Joli // http://www.ziggyware.com/readarticle.php?article_id=161 private float ComputeGaussian(float n) { float theta = 2.0f + float.Epsilon; return theta = (float)((1.0 / Math.Sqrt(2 * Math.PI * theta)) * Math.Exp(-(n * n) / (2 * theta * theta))); } // Applies the post processor to the texture in the specified direction public void Draw(GaussianBlurDirection Direction, Texture2D Input) { this.Input = Input; SetParameters(Direction); base.Draw(); } // Applies Gaussian Blur to frame buffer public override void Draw() { GetInputFromFrameBuffer(); // Set Input texture Engine.GraphicsDevice.Clear(Color.Black); SetParameters(GaussianBlurDirection.Horizontal); // Set horizontal parameters base.Draw(); // Apply blur GetInputFromFrameBuffer(); // Set Input texture again Engine.GraphicsDevice.Clear(Color.Black); SetParameters(GaussianBlurDirection.Vertical); // Set vertical parameters base.Draw(); // Apply blur } // Set blur parameters to effect void SetParameters(GaussianBlurDirection Direction) { if (Direction == GaussianBlurDirection.Horizontal) { Effect.Parameters["sampleWeights"].SetValue(sampleWeightsH); Effect.Parameters["sampleOffsets"].SetValue(sampleOffsetsH); } else { Effect.Parameters["sampleWeights"].SetValue(sampleWeightsV); Effect.Parameters["sampleOffsets"].SetValue(sampleOffsetsV); } } } // Direction of Gaussian Blur: Horizontal or Vertical public enum GaussianBlurDirection { Horizontal, Vertical }; }
下面是post processor effect文件GaussianBlur.fx:
float sampleWeights[15]; float2 sampleOffsets[15]; Texture InputTexture; sampler inputTexture = sampler_state { texture = <InputTexture>; magfilter = LINEAR; minfilter = LINEAR; mipfilter = LINEAR; }; struct VS_OUTPUT { float4 Position : POSITION; float2 TexCoords : TEXCOORD0; }; float4 GaussianBlur_PS (VS_OUTPUT Input) : COLOR0 { float4 color = float4(0, 0, 0, 1); for(int i = 0; i < 15; i++ ) color += tex2D(inputTexture, Input.TexCoords + sampleOffsets[i]) * sampleWeights[i]; return color; } technique Blur { pass P0 { PixelShader = compile ps_2_0 GaussianBlur_PS(); } }
现在就可以实现高斯模糊效果了:
// Top of Game1 class: GaussianBlur blur; // End of LoadContent() method: blur = new GaussianBlur(Engine.GraphicsDevice.Viewport.Width, Engine.GraphicsDevice.Viewport.Height); blur.Visible = false; // This will keep the engine from drawing it before we want it to // End of Draw() method: blur.Draw();
现在如果你运行游戏,你将看到所有物体都是模糊的。
发布时间:2009/2/13 上午7:05:30 阅读次数:9836