Gaussian Blurring

Gaussian Blurring

Color filtering只是使用post-processing生成的众多effects中的一种。另一种常用的technique是对渲染texture进行模糊(blur)处理。有多种方法可以实现模糊的效果,在本书上主要使用Gaussian blurring(高斯模糊),这种方法的名称来自于高斯方程(也称为正态分布),主要用于图像模糊处理。

图像模糊处理是指,每一个pixel的颜色值通过采样与该pixel相邻的pixels计算得到。主要解决的问题是如何计算采样pixels的加权平均值作为最终的pixel颜色值。可以使用如下的高斯方程计算权重值:


其中,σ是高斯分布的标准差。更具体地说,σ的值越小,高斯分布的曲线越陡,因此越接近中心(当前要计算的pixel)的pixels对最终的颜色值影响越大。而σ值越大,相邻pixel的权重就越大,最终产生的图像就越模糊。在接下来的实现中,σ表示模糊系数。
虽然我们要使用上面的高斯方程对一个二维的texture进行模糊处理,但是该函数本身只表示一维的。高斯模糊是可以分离的,意味一个二维的模糊处理可以使用两个独立的一维运算。在实际操作中,就是对图像先执行水平维度的模糊再进行垂直模糊处理。
绘制一个高斯模糊的effect可以分为以下三个步骤:
1、把场景绘制到一个off-screen render target中。
2、对场景texture执行水平模糊,并保存到off-screen render target中。
3、对上一步经过水平模糊的texture再执行垂直模糊,并把最终的texture渲染到屏幕上。

A Gaussian Blurring Shader

列表18.8列出了GaussianBlur.fx effect的代码。其中vertex inputs和vertex shader部分与ColorFilter.fx effect中的完全一样,不同的是新增了SampleOffsets和SampleWeights数组。其中SampleOffsets数组存储要采样的与当前计算的pixel相邻的pixels的位置值。而SampleWeights数组中存储了每一个采样的pixels的权重系数。

列表18.8 The GaussianBlur.fx Effect

/************* Resources *************/

#define SAMPLE_COUNT 9

cbuffer CBufferPerFrame
{
    float2 SampleOffsets[SAMPLE_COUNT];
    float SampleWeights[SAMPLE_COUNT];
}

Texture2D ColorTexture;

SamplerState TrilinearSampler
{
    Filter = MIN_MAG_MIP_LINEAR;
    AddressU = WRAP;
    AddressV = WRAP;
};

/************* Data Structures *************/

struct VS_INPUT
{
    float4 ObjectPosition : POSITION;
    float2 TextureCoordinate : TEXCOORD;
};

struct VS_OUTPUT
{
    float4 Position : SV_Position;
    float2 TextureCoordinate : TEXCOORD;  
};

/************* Vertex Shader *************/

VS_OUTPUT vertex_shader(VS_INPUT IN)
{
    VS_OUTPUT OUT = (VS_OUTPUT)0;
    
    OUT.Position = IN.ObjectPosition;
    OUT.TextureCoordinate = IN.TextureCoordinate;
    
    return OUT;
}

/************* Pixel Shaders *************/

float4 blur_pixel_shader(VS_OUTPUT IN) : SV_Target
{
    float4 color = (float4)0;

    for (int i = 0; i < SAMPLE_COUNT; i++)
    {
        color += ColorTexture.Sample(TrilinearSampler, IN.TextureCoordinate + SampleOffsets[i]) * SampleWeights[i];
    }
    
    return color;
}

float4 no_blur_pixel_shader(VS_OUTPUT IN) : SV_Target
{
    return ColorTexture.Sample(TrilinearSampler, IN.TextureCoordinate);
}

/************* Techniques *************/

technique11 blur
{
    pass p0
    {
        SetVertexShader(CompileShader(vs_5_0, vertex_shader()));
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_5_0, blur_pixel_shader()));
    }
}

technique11 no_blur
{
    pass p0
    {
        SetVertexShader(CompileShader(vs_5_0, vertex_shader()));
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_5_0, no_blur_pixel_shader()));
    }
}


在这个effect中,采样的数量被硬编码为9,但是你可以根据需要修改effec以支持其他的采样数量值,或者包含多种technique,每一种对应一个通用的采样数量传递给pixel shader。一般情况下,都是使用一个相邻pixels组成的grid包围要计算的pixel,因此采样数量都使用一个奇数值。但是,需要知道的是采样数量越多,pixel shader所要执行的运算量越大。
在pixel shader中,主要使用SampleOffsets数组的偏移值遍历采样相邻的pixels。

A Gaussian Blurring Component

要在C++渲染引擎中集成blur shader,首先创建一个GaussianBlur类,该类的声明代码如列表18.9所示。

列表18.9 Declaration of the GaussianBlur Class

#pragma once

#include "Common.h"
#include "DrawableGameComponent.h"

namespace Library
{
	class Effect;
	class GaussianBlurMaterial;
	class FullScreenRenderTarget;
	class FullScreenQuad;

	class GaussianBlur : public DrawableGameComponent
	{
		RTTI_DECLARATIONS(GaussianBlur, DrawableGameComponent)

	public:
		GaussianBlur(Game& game, Camera& camera);
		GaussianBlur(Game& game, Camera& camera, float blurAmount);
		~GaussianBlur();
		
		ID3D11ShaderResourceView* SceneTexture();
		void SetSceneTexture(ID3D11ShaderResourceView& sceneTexture);

		ID3D11ShaderResourceView* OutputTexture();

		float BlurAmount() const;
		void SetBlurAmount(float blurAmount);

		virtual void Initialize() override;
		virtual void Draw(const GameTime& gameTime) override;
		void DrawToTexture(const GameTime& gameTime);

	private:
		GaussianBlur();
		GaussianBlur(const GaussianBlur& rhs);
		GaussianBlur& operator=(const GaussianBlur& rhs);

		
		void InitializeSampleOffsets();
		void InitializeSampleWeights();		
		float GetWeight(float x) const;
		void UpdateGaussianMaterialWithHorizontalOffsets();
		void UpdateGaussianMaterialWithVerticalOffsets();
		void UpdateGaussianMaterialNoBlur();

		static const float DefaultBlurAmount;

		Effect* mEffect;
		GaussianBlurMaterial* mMaterial;
		ID3D11ShaderResourceView* mSceneTexture;
		ID3D11ShaderResourceView* mOutputTexture;
		FullScreenRenderTarget* mHorizontalBlurTarget;
		FullScreenRenderTarget* mVerticalBlurTarget;
		FullScreenQuad* mFullScreenQuad;

		std::vector<XMFLOAT2> mHorizontalSampleOffsets;
		std::vector<XMFLOAT2> mVerticalSampleOffsets;
		std::vector<float> mSampleWeights;
		float mBlurAmount;
	};
}


在GaussianBlur类中包含了用于表示Gaussian blurring effect的成员变量,如对应的material,以及用于存储输入的scene texture(要进行模糊处理的texture图像)。还有一个render target用于水平模糊操作,并把输出的texture作为垂直模糊的输入texture。所有的渲染(包括渲染到render target或渲染到屏幕)操作都使用full-screen quad成员变量。
其中成员变量horizontal和vertical sample offsets,以及sample weights分别由函数InitializeSampleOffsets()和InitializeSampleWeights()进行初始化。GetWeight()函数使用高斯方程计算单个权重值。以“UpdateGaussianMaterial”开头的三个函数是full-screen quad的回调函数,根据模糊处理过程的不同阶段调用不同的函数。其中UpdateGaussianMaterialNoBlur()函数用于mBlurAmout为0的情况。在这种情况下,shader中的no_blur technique会被应用到scene texture中,只是简单地把未修改的texture渲染到屏幕上。
通过SetBlurAmount()函数可以在程序运行时修改blur数量值。前面已经讲过,blur amount值表示高斯方程的σ系数,因此每次改变blur amount时,都需要调用InitializeSampleWeight()函数(用于重新计算采样的权重)。列表18.10列出了初始化sample offsets和weights变量的函数代码。

列表18.10 Initializing the Gaussian Blurring Sample Offsets and Weights

void GaussianBlur::InitializeSampleOffsets()
{
	float horizontalPixelSize = 1.0f / mGame->ScreenWidth();
	float verticalPixelSize = 1.0f / mGame->ScreenHeight();

	UINT sampleCount = mMaterial->SampleOffsets().TypeDesc().Elements;

	mHorizontalSampleOffsets.resize(sampleCount);
	mVerticalSampleOffsets.resize(sampleCount);
	mHorizontalSampleOffsets[0] = Vector2Helper::Zero;
	mVerticalSampleOffsets[0] = Vector2Helper::Zero;

	for (UINT i = 0; i < sampleCount / 2; i++)
	{
		float sampleOffset = i * 2 + 1.5f;
		float horizontalOffset = horizontalPixelSize * sampleOffset;
		float verticalOffset = verticalPixelSize * sampleOffset;

		mHorizontalSampleOffsets[i * 2 + 1] = XMFLOAT2(horizontalOffset, 0.0f);
		mHorizontalSampleOffsets[i * 2 + 2] = XMFLOAT2(-horizontalOffset, 0.0f);

		mVerticalSampleOffsets[i * 2 + 1] = XMFLOAT2(0.0f, verticalOffset);
		mVerticalSampleOffsets[i * 2 + 2] = XMFLOAT2(0.0f, -verticalOffset);
	}
}

void GaussianBlur::InitializeSampleWeights()
{
	UINT sampleCount = mMaterial->SampleOffsets().TypeDesc().Elements;

	mSampleWeights.resize(sampleCount);
	mSampleWeights[0] = GetWeight(0);

	float totalWeight = mSampleWeights[0];
	for (UINT i = 0; i < sampleCount / 2; i++)
	{
		float weight = GetWeight((float)i + 1);
		mSampleWeights[i * 2 + 1] = weight;
		mSampleWeights[i * 2 + 2] = weight;
		totalWeight += weight * 2;
	}

	// Normalize the weights so that they sum to one
	for (UINT i = 0; i < mSampleWeights.size(); i++)
	{
		mSampleWeights[i] /= totalWeight;
	}
}


在InitializeSampleOffset()函数中,根据应用程序当前的分辨率大小,创建围绕中心pixel(索引值为0)的垂直和水平方向的偏移值数组。在InitializeSampleWeights()函数中把权重值赋值数组中对应索引指向的值,并对这些权重值进行规范化以确保权重总和为1。如果不对权重值进行规范化,就会导致提高或降低最终图像的亮度。
列表18.11列出了GaussianBlur::Draw()函数的代码。

列表18.11 Drawing the Gaussian Blurring Component

void GaussianBlur::Draw(const GameTime& gameTime)
{
	mOutputTexture = nullptr;

	if (mBlurAmount > 0.0f)
	{
		// Horizontal blur
		mHorizontalBlurTarget->Begin();
		mGame->Direct3DDeviceContext()->ClearRenderTargetView(mHorizontalBlurTarget->RenderTargetView() , reinterpret_cast<const float*>(&ColorHelper::Purple));
		mGame->Direct3DDeviceContext()->ClearDepthStencilView(mHorizontalBlurTarget->DepthStencilView(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
		mFullScreenQuad->SetActiveTechnique("blur", "p0");
		mFullScreenQuad->SetCustomUpdateMaterial(std::bind(&GaussianBlur::UpdateGaussianMaterialWithHorizontalOffsets, this));
		mFullScreenQuad->Draw(gameTime);
		mHorizontalBlurTarget->End();

		// Vertical blur for the final image
		mFullScreenQuad->SetCustomUpdateMaterial(std::bind(&GaussianBlur::UpdateGaussianMaterialWithVerticalOffsets, this));
		mFullScreenQuad->Draw(gameTime);
	}
	else
	{
		mFullScreenQuad->SetActiveTechnique("no_blur", "p0");
		mFullScreenQuad->SetCustomUpdateMaterial(std::bind(&GaussianBlur::UpdateGaussianMaterialNoBlur, this));
		mFullScreenQuad->Draw(gameTime);
	}
}

void GaussianBlur::UpdateGaussianMaterialWithHorizontalOffsets()
{
	mMaterial->ColorTexture() << mSceneTexture;
	mMaterial->SampleWeights() << mSampleWeights;
	mMaterial->SampleOffsets() << mHorizontalSampleOffsets;
}

void GaussianBlur::UpdateGaussianMaterialWithVerticalOffsets()
{
	mMaterial->ColorTexture() << mHorizontalBlurTarget->OutputTexture();
	mMaterial->SampleWeights() << mSampleWeights;
	mMaterial->SampleOffsets() << mVerticalSampleOffsets;
}

void GaussianBlur::UpdateGaussianMaterialNoBlur()
{
	mMaterial->ColorTexture() << mSceneTexture;
}


在GaussianBlur::Draw()函数中,首先判断变量mBlurAmount是否大于0。如果是就设置full-screen quad变量的technique为blur technique以及p0 pass,并执行模糊处理过程。把水平模糊的render target绑定到管线的output-merger阶段,并clear相关的render target view和depth-stencil view,然后调用回调函数UpdateGaussianMaterialWithHorizontalOffset()函数完成水平模糊的绘制。该回调函数主要用于把sample weights和horizontal sample offsets传递到scene texture中。绘制完成之后,恢复output-merger阶段与back buffer的绑定。
接下来,调用回调函数UpdateGaussianMaterialWithVerticalOffsets()再次绘制四边形。在该函数中,把水平模糊render target的输出,sample weights以及vertical sample offsets传递到scene texture中。由于在output-merger阶段绑定了back buffer,在swap chain完成交换之后就会在屏幕上演示绘制的结果。
要把GaussianBlur component集成到应用程序中(由Game派生),只需要使用以下的代码初始化该component:
mGaussianBlur = new GaussianBlur(*this, *mCamera);
mGaussianBlur->SetSceneTexture(*(mRenderTarget->OutputTexture()));
mGaussianBlur->Initialize();


在该初始化代码中,变量mRenderTarget用于渲染场景。以下是高斯模糊示例程序中GuassianBlurGame::Draw()函数的主要代码。本书的配套网站上提供了完整的示例程序代码。

mRenderTarget->Begin();

mDirect3DDeviceContext->ClearRenderTargetView(mRenderTarget->RenderTargetView() , reinterpret_cast<const float*>(&BackgroundColor));
mDirect3DDeviceContext->ClearDepthStencilView(mRenderTarget->DepthStencilView(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

Game::Draw(gameTime);

mRenderTarget->End();

mDirect3DDeviceContext->ClearRenderTargetView(mRenderTargetView, reinterpret_cast<const float*>(&BackgroundColor));
mDirect3DDeviceContext->ClearDepthStencilView(mDepthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

mGaussianBlur->Draw(gameTime);

mRenderStateHelper->SaveAll();
mFpsComponent->Draw(gameTime);


图18.5显示了高斯模糊示例中blur amount值为3.0时的输出结果。


图18.5 Output of the Gaussian blurring effect. (Skybox texture by Emil Persson. Earth texture from Reto Stöckli, NASA Earth Observatory.)
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值