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

2006 - 2024,推荐分辨率 1024*768 以上,推荐浏览器 Chrome、Edge 等现代浏览器,截止 2021 年 12 月 5 日的访问次数:1872 万 9823 站长邮箱

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号