本节,我们将会使用上述5×5的高斯核对原图像进行高斯模糊。为此,我们需要进行如下准备工作。
(1)新建一个场景。在本书资源中,该场景名为Scene_12_4。在Unity 5.2中,默认情况下场景将包含一个摄像机和一个平行光,并且使用了内置的天空盒子。在Window → Lighting →Skybox中去掉场景中的天空盒子。

(2)把本书资源中的Assets/Textures/Chapter12/Sakura1.jpg拖曳到场景中,并调整的位置使其可以填充整个场景。注意,Sakura1.jpg的纹理类型已被设置为Sprite,因此可以直接拖曳到场景中。
https://github.com/candycat1992/Unity_Shaders_Book/blob/master/Assets/Textures/Chapter12/sakura1.jpg


(3)新建一个脚本。在本书资源中,该脚本名为GaussianBlur.cs。把该脚本拖曳到摄像机上。


(4)新建一个Unity Shader。在本书资源中,该Shader名为Chapter12-GaussianBlur。

我们首先来编写GaussianBlur.cs脚本。打开该脚本,并进行如下修改。
(1)首先,继承12.1节中创建的基类:
(2)声明该效果需要的Shader,并据此创建相应的材质:
(3)在脚本中,我们还提供了调整高斯模糊迭代次数、模糊范围和缩放系数的参数:
(4)最后,我们需要定义关键的OnRenderImage函数。我们首先来看第一个版本,也就是最简单的OnRenderImage的实现:
(5)在理解了上述代码后,我们可以实现第二个版本的OnRenderImage函数。在这个版本中,我们将利用缩放对图像进行降采样,从而减少需要处理的像素个数,提高性能。
(6)最后一个版本的代码还考虑了高斯模糊的迭代次数:
cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GaussianBlur : PostEffectsBase
{
public Shader gaussianBlurShader;
private Material gaussianBlurMaterial = null;
public Material material {
get {
gaussianBlurMaterial = CheckShaderAndCreateMaterial(gaussianBlurShader, gaussianBlurMaterial);
return gaussianBlurMaterial;
}
}
// Blur iterations - larger number means more blur. 高斯模糊迭代次数
[Range(0, 4)]
public int iterations = 3;
// Blur spread for each iteration - larger 模糊范围
[Range(0.2f, 3.0f)]
public float blurSpread = 0.6f;
//缩放系数
[Range(1, 8)]
public int downSample = 2;
//在高斯核维数不变的情况下,_BlurSize越大,模糊程度越高,但采样数却不会受到影响。但过大的_BlurSize值会造成虚影,这可能并不是我们希望的。
//downSample越大,需要处理的像素数越少,同时也能进一步提高模糊程度,但过大的downSample可能会使图像像素化
/* 第一版
void OnRenderImage(RenderTexture src, RenderTexture dest) {
if (material != null) {
int rtW = src.width; //rendertexturewidth
int rtH = src.height; //rendertexturewidth
RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0); //分配了一块与屏幕图像大小相同的缓冲区,高斯模糊需要调用两个Pass,我们需要使用一块中间缓存来存储第一个Pass执行完毕后得到的模糊结果
// Render the vertical pass
Graphics.Blit(src, buffer, material, 0); //使用Shader中的第一个Pass(即使用竖直方向的一维高斯核进行滤波)对src进行处理,并将结果存储在了buffer中
// Render the horizontal pass
Graphics.Blit(buffer, dest, material, 1); //调用Graphics.Blit(buffer, dest, material, 1),使用Shader中的第二个Pass(即使用水平方向的一维高斯核进行滤波)对buffer进行处理,返回最终的屏幕图像
RenderTexture.ReleaseTemporary(buffer); //释放之前分配的缓存
} else {
Graphics.Blit(src, dest);
}
}
*/
/*第二版
void OnRenderImage(RenderTexture src, RenderTexture dest) {
if (material != null) {
int rtW = src.width/downSample;
int rtH = src.height/downSample;
RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);
buffer.filterMode = FilterMode.Bilinear;
//第一个版本代码不同的是,我们在声明缓冲区的大小时,使用了小于原屏幕分辨率的尺寸,并将该临时渲染纹理的滤波模式设置为双线性
//图像进行降采样
Graphics.Blit(src, buffer, material, 0);
Graphics.Blit(buffer, dest, material, 1);
RenderTexture.ReleaseTemporary(buffer);
} else {
Graphics.Blit(src, dest);
}
}
*/
void OnRenderImage(RenderTexture src, RenderTexture dest) {
if (material != null) {
int rtW = src.width/downSample;
int rtH = src.height/downSample;
RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);
buffer0.filterMode = FilterMode.Bilinear;
//第一个版本代码不同的是,我们在声明缓冲区的大小时,使用了小于原屏幕分辨率的尺寸,并将该临时渲染纹理的滤波模式设置为双线性
//图像进行降采样
Graphics.Blit(src, buffer0);
for (int i = 0; i < iterations; i++){
material.SetFloat("_BlurSize", 1.0f + i * blurSpread);
RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
// Render the vertical pass
Graphics.Blit(buffer0, buffer1, material, 0);
RenderTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
// Render the horizontal pass
Graphics.Blit(buffer0, buffer1, material, 1);
RenderTexture.ReleaseTemporary(buffer0);
buffer0 = buffer1;
}
Graphics.Blit(buffer0, dest);
RenderTexture.ReleaseTemporary(buffer0);
} else {
Graphics.Blit(src, dest);
}
//在迭代开始前,我们首先定义了第一个缓存buffer0,并把src中的图像缩放后存储到buffer0中
//在迭代过程中,我们又定义了第二个缓存buffer1
//执行第一个Pass时,输入是buffer0,输出是buffer1,完毕后首先把buffer0释放,再把结果值buffer1存储到buffer0中,重新分配buffer1
//然后再调用第二个Pass,重复上述过程
//迭代完成后,buffer0将存储最终的图像,我们再利用Graphics.Blit(buffer0, dest)把结果显示到屏幕上,并释放缓存。
}
}
下面,我们来实现Shader的部分。打开Chapter12-GaussianBlur,进行如下修改。
(1)我们首先需要声明本例使用的各个属性:
2)在本节中,我们将第一次使用CGINCLUDE来组织代码。我们在SubShader块中利用CGINCLUDE和ENDCG语义来定义一系列代码:
(3)在CG代码块中,定义与属性对应的变量:
(4)分别定义两个Pass使用的顶点着色器。下面是竖直方向的顶点着色器代码:
(5)定义两个Pass共用的片元着色器:
(6)然后,我们定义了高斯模糊使用的两个Pass:
(7)最后,关闭该Shader的Fallback:
完成后返回编辑器,并把Chapter12-GaussianBlur拖曳到摄像机的GaussianBlur.cs脚本中的gaussianBlurShader参数中。当然,我们可以在GaussianBlur.cs的脚本面板中将gaussianBlurShader参数的默认值设置为Chapter12-GaussianBlur,这样就不需要以后使用时每次都手动拖曳了。
cs
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Custom/Chapter12-GaussianBlur"
{
Properties{
_MainTex ("Base (RGB)", 2D) = "white" {}
_BlurSize ("Blur Size", Float) = 1.0
}
SubShader {
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
half4 _MainTex_TexelSize;
float _BlurSize;
struct v2f {
float4 pos : SV_POSITION;
half2 uv[5]: TEXCOORD0;
};
v2f vertBlurVertical(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
o.uv[0] = uv;
o.uv[1] = uv + float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize; //属性_BlurSize相乘来控制采样距离
o.uv[2] = uv - float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
o.uv[3] = uv + float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
o.uv[4] = uv - float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
return o;
}
v2f vertBlurHorizontal(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
o.uv[0] = uv;
o.uv[1] = uv + float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
o.uv[2] = uv - float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
o.uv[3] = uv + float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
o.uv[4] = uv - float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
return o;
}
fixed4 fragBlur(v2f i) : SV_Target {
float weight[3] = {0.4026, 0.2442, 0.0545};
fixed3 sum = tex2D(_MainTex, i.uv[0]).rgb * weight[0];
for (int it = 1; it < 3; it++) {
sum += tex2D(_MainTex, i.uv[it*2-1]).rgb * weight[it];
sum += tex2D(_MainTex, i.uv[it*2]).rgb * weight[it];
}
return fixed4(sum, 1.0);
}
ENDCG
ZTest Always Cull Off ZWrite Off
Pass {
NAME "GAUSSIAN_BLUR_VERTICAL" //为Pass定义名字,可以在其他Shader中直接通过它们的名字来使用该Pass,而不需要再重复编写代码。
CGPROGRAM
#pragma vertex vertBlurVertical
#pragma fragment fragBlur
ENDCG
}
Pass {
NAME "GAUSSIAN_BLUR_HORIZONTAL"
CGPROGRAM
#pragma vertex vertBlurHorizontal
#pragma fragment fragBlur
ENDCG
}
}
FallBack "Diffuse"
}






















