12.4 高斯模糊
高斯滤波
高斯模糊同样利用了卷积计算,它使用的卷积核名为高斯核。高斯核是一个正方形大小的滤波核,其中每个元素的计算都是基于下面的高斯方程:
其中,σ是标准方差(一般取值为1),x和y分别对应了当前位置到卷积核中心的整数距离。要构建一个高斯核,我们只需要计算告诉核内各个位置对应的高斯值。为了保证滤波后的图像不会变暗,我们需要对高斯核中的权重进行归一化,即让每个权重除以所有权重的和,这样可以保证所有权重的和为1。因此,高斯函数中e前面的系数实际不会对结果有任何影响。下图显示了一个标准方差为1的5×5大小的高斯核。
高斯方程很好地模拟了领域每个像素对当前处理像素的影响程度——距离越近,影响越大。高斯核的维数越高,模糊程度越大。使用一个N×N的高斯核对图像进行卷积滤波,就需要N×N×W×H(W和H分别是图像的宽和高)次纹理采样。当N的大小不断增加时。采样次数会变得非常巨大。于是我们可以把这个二维高斯函数拆分成两个一维函数。也就是说,我们可以使用两个一维的高斯核先后对图像进行滤波,这样的采样次数只需要2×N×W×H。进一步观察到,两个一维高斯核中包含了很多重复的权重。对于一个大小为5的一维高斯核,我们实际只需要记录3个权重值。
我们先后调用两个Pass,第一个Pass将会使用竖直方向的一维高斯核对图像进行滤波,第二个Pass再使用水平方向的一维高斯核对图像进行滤波,得到最终的目标图像。在实现中,我们还将利用图像缩放来进一步提高性能,并通过调整高斯滤波的应用次数来控制模糊程度(次数越多,图像越模糊)。
实现流程
(1)新建场景(Scene_12_4)。
(2)把Sakural.jpg拖拽到场景中(纹理类型为Sprite)。
(3)新建一个脚本(GaussianBlur.cs),拖拽到摄像机上。
(4)新建Unity SHader(Chapter12-GaussianBlur)
using UnityEngine;
using System.Collections;
//依旧继承12.1节中创建的基类:
public class GaussianBlur : PostEffectsBase {
//指定的Shader,将会实现的Chapter12-GaussianBlur
public Shader gaussianBlurShader;
private Material gaussianBlurMaterial = null;
public Material material {
get {
gaussianBlurMaterial = CheckShaderAndCreateMaterial(gaussianBlurShader, gaussianBlurMaterial);
return gaussianBlurMaterial;
}
}
// 模糊迭代——更大的数字意味着更多的模糊
[Range(0, 4)]
public int iterations = 3;
// 每个迭代的模糊扩展——更大的值意味着更多的模糊
[Range(0.2f, 3.0f)]
public float blurSpread = 0.6f;
[Range(1, 8)]
public int downSample = 2;
//blurSpread和downSample都是出于性能的考虑。在高斯核维数不变的情况下,_BlurSize越大,模糊程度
//越高,但采样数却不会受到影响。但过大的_BlurSize值会造成虚影,这并不是我们希望的。而downSample越大,
//需要处理的像素越少,同时也能进一步提高模糊程度,但过大的downSample可能会使图像像素化。
/// 第1版:应用模糊
// void OnRenderImage(RenderTexture src, RenderTexture dest) {
// if (material != null) {
// int rtW = src.width;
// int rtH = src.height;
// RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);
//
// // Render the vertical pass
// Graphics.Blit(src, buffer, material, 0);
// // Render the horizontal pass
// Graphics.Blit(buffer, dest, material, 1);
//
// RenderTexture.ReleaseTemporary(buffer);
// } else {
// Graphics.Blit(src, dest);
// }
// }
///与上两节的实现不同,我们这里利用RenderTexture.GetTemporary函数分配了一块与屏幕图像大小
///相同的缓冲区。这是因为,高斯模糊需要调用两个Pass,我们需要使用一块中间缓存来存储第一个Pass执行
///完毕后得到的模糊结果。如代码所示,我们首先调用Graphics.Blit(src, buffer, material, 0);,使用
///Shader中的第一个Pass(即使用竖直方向的一维高斯核进行滤波)对src进行处理,并将结果存储在了
///buffer中。然后再调用Graphics.Blit(buffer, dest, material, 1),使用Shader中的第二个Pass(即
///使用水平方向的一维高斯核进行滤波)对buffer进行处理,返回最终的屏幕图像。最后,我们还需要
///调用RenderTexture.ReleaseTemporary来释放之前分配的缓存。
/// 第二版:缩放渲染纹理
// 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;
//
// // Render the vertical pass
// Graphics.Blit(src, buffer, material, 0);
// // Render the horizontal pass
// Graphics.Blit(buffer, dest, material, 1);
//
// RenderTexture.ReleaseTemporary(buffer);
// } else {
// Graphics.Blit(src, dest);
// }
// }
///将利用缩放对图像进行降采样,从而减少需要处理的像素个数,提高性能。
///与第一个版本代码不同,我们在声明缓冲区的大小时,使用了小于原屏幕分辨率的尺寸,并将该临时
///渲染纹理的滤波模式设置为双线性。这样,在调用第一个Pass时,我们需要处理的像素个数就是原来
///的几分之一。对图像进行降采样不仅可以减少需要处理的像素个数,提高性能,而且适当的降采样往往还可以