图像模糊处理是指,每一个pixel的颜色值通过采样与该pixel相邻的pixels计算得到。主要解决的问题是如何计算采样pixels的加权平均值作为最终的pixel颜色值。可以使用如下的高斯方程计算权重值:
其中,σ是高斯分布的标准差。更具体地说,σ的值越小,高斯分布的曲线越陡,因此越接近中心(当前要计算的pixel)的pixels对最终的颜色值影响越大。而σ值越大,相邻pixel的权重就越大,最终产生的图像就越模糊。在接下来的实现中,σ表示模糊系数。列表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())); } }
列表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; }; }
列表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; } }
列表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; }
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.)