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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值