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.)
發佈了5 篇原創文章 · 獲贊 27 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章