UnityShader学习——屏幕后处理效果(高斯模糊、Bloom、运动模糊)

接上篇:UnityShader学习——屏幕后处理效果(亮度等、边缘检测)

3.高斯模糊

模糊的实现有很多方法,例如均值模糊和中值模糊。均值模糊同样使用了卷积操作,它使用的卷积核中的各个元素值都相等,且相加等于1,也就是说,卷积后得到的像素值是其邻域内各个像素值的平均值。而中值模糊则是选择邻域内对所有像素排序后的中值替换掉原颜色。一个更高级的模糊方法是高斯模糊。

高斯模糊同样利用了卷积计算,它使用的卷积核名为高斯核。高斯核是一个正方形大小的滤波核,其中每个元素的计算都是基于下面的高斯方程:
在这里插入图片描述
其中,σ是标准方差(一般取值为1), xy分别对应了当前位置到卷积核中心的整数距离。要构建一个高斯核,我们只需要计算高斯核中各个位置对应的高斯值。为了保证滤波后的图像不会变暗,我们需要对高斯核中的权重进行归一化,即让每个权重除以所有权重的和,这样可以保证所有权重的和为1。因此,高斯函数中e前面的系数实际不会对结果有任何影响。

高斯方程很好地模拟了邻域每个像素对当前处理像素的影响程度——距离越近,影响越大高斯核的维数越高,模糊程度越大。使用一个NxN的高斯核对图像进行卷积滤波,就需要N×N×W×H(W和H分别是图像的宽和高)次纹理采样。当N的大小不断增加时,采样次数会变得非常巨大。幸运的是,我们可以把这个二维高斯函数拆分成两个一维函数。也就是说,我们可以使用两个一维的高斯核先后对图像进行滤波,它们得到的结果和直接使用二维高斯核是一样的,但采样次数只需要2×N×W×H。两个一维高斯核中包含了很多重复的权重。对于一个大小为5的一维高斯核,我们实际只需要记录3个权重值即可。
在这里插入图片描述
我们将先后调用两个Pass,第一个Pass将会使用竖直方向的一维高斯核对图像进行滤波,第二个Pass再使用水平方向的一维高斯核对图像进行滤波,得到最终的目标图像。在实现中,我们还将利用图像缩放来进一步提高性能,并通过调整高斯滤波的应用次数来控制模糊程度(次数越多,图像越模糊)。

摄像机脚本关键代码:

在高斯核维数不变的情况下,_BlurSize越大,模糊程度越高,但采样数却不会受到影响。但过大的_BlurSize值会造成虚影,这可能并不是我们希望的。downSample越大,需要处理的像素数越少,同时也能进一步提高模糊程度,但过大的downSample可能会使图像像素化。

......
public class GaussianBlur : PostEffectsBase {
   
	......	
	[Range(0, 4)]	public int iterations = 3;	//高斯模糊迭代次数	
	[Range(0.2f, 3.0f)]	public float blurSpread = 0.6f;//模糊范围	
	[Range(1, 8)]	public int downSample = 2;//缩放系数	
	
	//【版本1】:just apply blur
	void OnRenderImage(RenderTexture src, RenderTexture dest) {
   
		if (material != null) {
   
			//利用 RenderTexture.GetTemporary函数分配了一块与屏幕图像大小相同的缓冲区
			//高斯模糊需要调用两个Pass,我们需要使用一块中间缓存来存储第一个Pass执行完毕后得到的模糊结果。
			int rtW = src.width;
			int rtH = src.height;			
			RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);
			//使用第一个Pass(即使用竖直方向的一维高斯核进行滤波)对src进行处理,并将结果存储在了buffer中
			Graphics.Blit(src, buffer, material, 0);
			//使用第二个Pass(即使用水平方向的一维高斯核进行滤波)对buffer进行处理,返回最终的屏幕图像
			Graphics.Blit(buffer, dest, material, 1);
			//调用RenderTexture.ReleaseTemporary来释放之前分配的缓存
			RenderTexture.ReleaseTemporary(buffer);
		} else {
   
			Graphics.Blit(src, dest);
		}
	} 

	//【版本2】:scale the render texture
	void OnRenderImage (RenderTexture src, RenderTexture dest) {
   
		if (material != null) {
   
			//声明缓冲区的大小时,使用了小于原屏幕分辨率的尺寸
			//调用第一个Pass时,我们需要处理的像素个数就是原来的几分之一
			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);
		}
	}

	//【版本3】:use iterations for larger blur
	void OnRenderImage (RenderTexture src, RenderTexture dest) {
   
		if (material != null) {
   
			int rtW = src.width/downSample;
			int rtH = src.height/downSample;
			定义了第一个缓存buffer0,并把src中的图像缩放后存储到buffer0中
			RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);
			buffer0.filterMode = FilterMode.Bilinear;			
			Graphics.Blit(src, buffer0);
			//【利用两个临时缓存在迭代之间进行交替】
			for (int i = 0; i < iterations; i++) {
   
				
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值