UnityShader实例14:屏幕特效之高斯模糊(Gaussian Blur)

高斯模糊(Gaussian Blur)




概述


高斯模糊(Gaussian Blur),也叫高斯平滑,在photoshop中也有高斯模糊滤镜,通常用它来减少图像噪声以及降低细节层次。从数学的角度来看,图像的高斯模糊过程就是图像与正态分布做卷积,由于正态分布又叫做高斯分布,所以这项技术又叫做高斯模糊。



原理



高斯模糊和均值模糊一样,也是取每个像素以及周边像素的平均值,只不过高斯模糊在取值是离原像素越远的像素权重越低,而均值模糊则所有像素的权重相等,因此从计算量上来说,采用相同阶数的高斯模糊计算量要比均值模糊要大,但是模糊效果要更好,由于没有明显的边界,不会出现均值模糊会出现的方块化效果。高斯模糊的权重计算时根据正态分布来计算的,它在N维空间的正态分布方程为:






这个函数省略了位置参数\mu,因为处理像素都以原像素为中心点,\mu默认为0了,通常我们只用到正态分布函数的一维函数G(x)





或者二维函数G(x,y),其中r是模糊半径(r²=x²+y²)




其对应的图像分别为:






从图像我们也可以看出,正态分布是一种很不错的权重分布方式;计算平均值的时候,我们只需将中心点作为原点,其他点按照其在正态曲线上的位置,分配权重,就可以得到一个加权平均值。下面还有当σ有取不同值时的曲线图像,从图可知,当σ越小时,曲线越高越尖,当σ越大时,曲线就越低越平缓,对应的结果就是图像越模糊。


在实际应用中,如果只是针对图像进行预处理(如photoshop的高斯滤镜),可以比较精确的使用高斯函数(虽然也有优化如把二维计算转化成一维计算),权重都会随着模糊半径变化而变化,因此对应的结果也是最精确的,效果也比较好,而在游戏开发中,因为考虑到实时处理,效率优先一般会采用近似的高斯模板,优化效率。



游戏中对高斯模糊的优化



在游戏中里的模糊通常都是近似高斯模糊,只要保证权重采样是一条类似高斯模糊的钟形曲线就行,即从中心到边缘是平滑渐变的。一般来说对高斯模糊优化有几点:

  • 降低阶数:降低采样的阶数,比如5阶的滤波器就比7阶的滤波器效率高,实际上一般不超过7阶。
  • 迭代计算:采用低阶采样的同时,可以将进行迭代计算,就是把用上一次的模糊结果,再进行同样的采样模糊,以达到更好的效果。在上一篇均值模糊的文章里也采用了这种方式。
  • 固定权重:权重预先计算好,并且归一化固定下来,不在游戏过程中实时计算。
  • 降维计算:因为高斯模糊在二维图像上是线性可分的,可以二维计算拆分正两次一维计算。具体就是先在水平方向做一维高斯矩阵变换后,将其结果再进行垂直方向的一维高斯矩阵变换。从下图来看对于一个9阶的二维运算来说,需要9X9=81次采样,但是如果拆分成一维运算的话,只需要9+9=18次采样,只不过需要缓存第一次计算的结果,以空间换时间还是划算的,也可以得出阶数越高,效率相差也越大的结论。





shader代码实现


在本例中将和上篇均值模糊一样,只开放一个参数给外部,本例使用了一个7阶的权重矩阵,由于考虑到函数的对称型我们只需要定义一个包含四个权重的数组,权重已经提前计算好并做了归一化处理【(0.0205+0.0855+ 0.232)*2+ 0.324=1】,因此不会出现图像变暗的问题:

		uniform half4 _MainTex_TexelSize;
		uniform float _blurSize;

		static const half curve[4] = { 0.0205, 0.0855, 0.232, 0.324};  
		static const half4 coordOffs = half4(1.0h,1.0h,-1.0h,-1.0h);

定义一个顶点结构体,包含一个四维数数组用来存储采样坐标,这样可以存储6个采样坐标再加上一个二维数 uv共七个:


		struct v2f_withBlurCoordsSGX 
		{
			float4 pos : SV_POSITION;
			half2 uv : TEXCOORD0;
			half4 offs[3] : TEXCOORD1;
		};

接下来需要定义两个vert函数,分别计算并存储在水平和垂直方向上的位移坐标:


v2f_withBlurCoordsSGX vertBlurHorizontalSGX (appdata_img v)
		{
			v2f_withBlurCoordsSGX o;
			o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
			
			o.uv = v.texcoord.xy;
			half2 netFilterWidth = _MainTex_TexelSize.xy * half2(1.0, 0.0) * _blurSize; 
			half4 coords = -netFilterWidth.xyxy * 3.0;
			
			o.offs[0] = v.texcoord.xyxy + coords * coordOffs;
			coords += netFilterWidth.xyxy;
			o.offs[1] = v.texcoord.xyxy + coords * coordOffs;
			coords += netFilterWidth.xyxy;
			o.offs[2] = v.texcoord.xyxy + coords * coordOffs;

			return o; 
		}		
		
		v2f_withBlurCoordsSGX vertBlurVerticalSGX (appdata_img v)
		{
			v2f_withBlurCoordsSGX o;
			o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
			
			o.uv = v.texcoord.xy;
			half2 netFilterWidth = _MainTex_TexelSize.xy * half2(0.0, 1.0) * _blurSize;
			half4 coords = -netFilterWidth.xyxy * 3.0;
			
			o.offs[0] = v.texcoord.xyxy + coords * coordOffs;
			coords += netFilterWidth.xyxy;
			o.offs[1] = v.texcoord.xyxy + coords * coordOffs;
			coords += netFilterWidth.xyxy;
			o.offs[2] = v.texcoord.xyxy + coords * coordOffs;

			return o; 
		}


Frag函数比较简单只需要用顶点函数计算的坐标采样图片乘以权重进行累加:

		half4 fragBlurSGX ( v2f_withBlurCoordsSGX i ) : SV_Target
		{
			half2 uv = i.uv;
			
			half4 color = tex2D(_MainTex, i.uv) * curve[3];
			
  			for( int l = 0; l < 3; l++ )  
  			{   
				half4 tapA = tex2D(_MainTex, i.offs[l].xy);
				half4 tapB = tex2D(_MainTex, i.offs[l].zw); 
				color += (tapA + tapB) * curve[l];
  			}

			return color;


需要注意的是,该shader里面需要定义两个pass,分别是水平方向和垂直方向的模糊计算,方便C#脚本调用。

shader完整代码


Shader "PengLu/ImageEffect/Unlit/GaussianBlur" {
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}

	}
	
	CGINCLUDE

		#include "UnityCG.cginc"

		sampler2D _MainTex;
				
		uniform half4 _MainTex_TexelSize;
		uniform float _blurSize;
	
		// weight curves

		static const half curve[4] = { 0.0205, 0.0855, 0.232, 0.324};  
		static const half4 coordOffs = half4(1.0h,1.0h,-1.0h,-1.0h);

		struct v2f_withBlurCoordsSGX 
		{
			float4 pos : SV_POSITION;
			half2 uv : TEXCOORD0;
			half4 offs[3] : TEXCOORD1;
		};


		v2f_withBlurCoordsSGX vertBlurHorizontalSGX (appdata_img v)
		{
			v2f_withBlurCoordsSGX o;
			o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
			
			o.uv = v.texcoord.xy;
			half2 netFilterWidth = _MainTex_TexelSize.xy * half2(1.0, 0.0) * _blurSize; 
			half4 coords = -netFilterWidth.xyxy * 3.0;
			
			o.offs[0] = v.texcoord.xyxy + coords * coordOffs;
			coords += netFilterWidth.xyxy;
			o.offs[1] = v.texcoord.xyxy + coords * coordOffs;
			coords += netFilterWidth.xyxy;
			o.offs[2] = v.texcoord.xyxy + coords * coordOffs;

			return o; 
		}		
		
		v2f_withBlurCoordsSGX vertBlurVerticalSGX (appdata_img v)
		{
			v2f_withBlurCoordsSGX o;
			o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
			
			o.uv = v.texcoord.xy;
			half2 netFilterWidth = _MainTex_TexelSize.xy * half2(0.0, 1.0) * _blurSize;
			half4 coords = -netFilterWidth.xyxy * 3.0;
			
			o.offs[0] = v.texcoord.xyxy + coords * coordOffs;
			coords += netFilterWidth.xyxy;
			o.offs[1] = v.texcoord.xyxy + coords * coordOffs;
			coords += netFilterWidth.xyxy;
			o.offs[2] = v.texcoord.xyxy + coords * coordOffs;

			return o; 
		}	

		half4 fragBlurSGX ( v2f_withBlurCoordsSGX i ) : SV_Target
		{
			half2 uv = i.uv;
			
			half4 color = tex2D(_MainTex, i.uv) * curve[3];
			
  			for( int l = 0; l < 3; l++ )  
  			{   
				half4 tapA = tex2D(_MainTex, i.offs[l].xy);
				half4 tapB = tex2D(_MainTex, i.offs[l].zw); 
				color += (tapA + tapB) * curve[l];
  			}

			return color;

		}	
					
	ENDCG
	
	SubShader {
	  ZTest Off  ZWrite Off Blend Off



	Pass {
		ZTest Always
		
		
		CGPROGRAM 
		
		#pragma vertex vertBlurVerticalSGX
		#pragma fragment fragBlurSGX
		
		ENDCG
		}	
		

	Pass {		
		ZTest Always
		
				
		CGPROGRAM
		
		#pragma vertex vertBlurHorizontalSGX
		#pragma fragment fragBlurSGX
		
		ENDCG
		}	
	}	

	FallBack Off
}



C#脚本完整代码


C#脚本同样比较简单,和均值模糊的脚本类似,将模糊的迭代的次数固定为2,只开放了模糊半径参数,由于将二维换成了两次一维计算,因此多调用了两次pass,drawcall也比上个例子均值模糊多两个。这里只放出关键代码;




	

	void OnRenderImage (RenderTexture sourceTexture, RenderTexture destTexture)
	{	
		if(BlurSize != 0 && GaussianBlurShader != null){

			int rtW = sourceTexture.width/8;
	        int rtH = sourceTexture.height/8;
	

	        RenderTexture rtTempA = RenderTexture.GetTemporary (rtW, rtH, 0, sourceTexture.format);
            rtTempA.filterMode = FilterMode.Bilinear;
            

            Graphics.Blit (sourceTexture, rtTempA);

	        for(int i = 0; i < 2; i++){

	        	float iteraionOffs = i * 1.0f;
	        	material.SetFloat("_blurSize",BlurSize+iteraionOffs);

	        	//vertical blur
	        	RenderTexture rtTempB = RenderTexture.GetTemporary (rtW, rtH, 0, sourceTexture.format);
            	rtTempB.filterMode = FilterMode.Bilinear;
                Graphics.Blit (rtTempA, rtTempB, material,0);
                RenderTexture.ReleaseTemporary(rtTempA);
                rtTempA = rtTempB;

                //horizontal blur
                rtTempB = RenderTexture.GetTemporary (rtW, rtH, 0, sourceTexture.format);
                rtTempB.filterMode = FilterMode.Bilinear;
                Graphics.Blit (rtTempA, rtTempB, material,1);
                RenderTexture.ReleaseTemporary(rtTempA);
                rtTempA = rtTempB;
                
	        }	  
            Graphics.Blit(rtTempA, destTexture);

            RenderTexture.ReleaseTemporary(rtTempA);
		}

		else{
			Graphics.Blit(sourceTexture, destTexture);
			
		}
		
		
	}
		


这里需要注意的下面这个函数,函数中的最后那个参数0,表示是取shader的第1个pass,依此类推;默认是-1,则表示取shader所有的pass。



Graphics.Blit (rtTempA, rtTempB, material,0);



本例实现效果如下:





总结



从图可以看出本例的效果比之前的均值模糊效果要好太多,之所以有这样的结果,主要是因为本例图像滤波器的采样阶数达到了7阶,相当于每个像素采样了49个顶点(但只花了14次采样,不包括迭代),而上篇只是一个3阶的缩水版的图像滤波器(实际只采样了四次,不包括迭代),因此效果差也成了必然,实际上在移动平台上,如果要求不太高,可以将7阶采样降为5阶就足够了,取消掉迭代,效果也可以达到上篇均值模糊的效果,甚至还要稍微好一些,而且计算量相差无几,,一个14次采样,一个8次(迭代了两次)采样,而drawcall可以降到3次。本例也不是严格的高斯模糊,只能算是近似高斯模糊,实际上,权重曲线我们也可以换成其他的曲线,,只要按照离原像素越远,权重越低的原则即可,为了效率经常会使用一些近似权重矩阵采样计算,如:

          [1 2 1]
1/16* [2 4 2]      
          [1 2 1]

         [1 2 3 2 1]
         [2 3 4 3 2]
1/65*[3 4 5 4 3]
         [2 3 4 3 2]
         [1 2 3 2 1]





参考文章链接












  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: 材质球高斯模糊shader是一种用于渲染图像的特殊技术。高斯模糊Gaussian blur)是一种常用于图像处理中的滤镜效果,能够使图像变得更加模糊和柔和。 对于材质球来说,高斯模糊shader是一种应用于表面的特殊效果,能够使得材质的外观变得模糊。这种效果常用于创建一些特殊的视觉效果,比如表示景深或者模拟柔和的光线。 高斯模糊shader的基本原理是通过对图像进行多次模糊处理,使用一组叫做高斯核(Gaussian kernel)的卷积矩阵来实现。卷积运算会通过对图像中的每个像素点与该核进行相加的方式创建新的模糊像素。多次进行卷积运算,可以得到更加模糊的效果。 在材质球的实现中,高斯模糊shader通常需要设置一些参数,比如模糊的程度和半径。通过调整这些参数,可以达到不同程度的模糊效果。此外,高斯模糊shader还可以与其他shader效果配合使用,从而获得更加复杂的渲染效果。 总的来说,材质球高斯模糊shader是一种非常常用的图像处理技术,可以应用于各种需要模糊效果的场景。它不仅能够增强图像的柔和感,还可以用于创造出更加艺术化的视觉效果。 ### 回答2: 高斯模糊shader是一种常用的图像处理技术,用于在计算机图形中创建模糊效果。它使用高斯函数来对每个像素周围的像素进行加权平均,以模拟出焦点外的图像模糊。 材质球高斯模糊shader是将高斯模糊应用于物体的表面材质,以产生模糊的外观效果。通过将高斯模糊shader应用于物体的材质,可以在渲染过程中实时模糊物体的外观,从而实现一些特殊的视觉效果。 使用材质球高斯模糊shader可以带来一些实际的应用。例如,在游戏中,可以用它来实现物体的动态模糊效果,比如快速移动的车辆或人物,可以通过高斯模糊使其看起来更加流畅,减少运动模糊。 另外,高斯模糊shader还可以在电影特效中使用,例如用于在影片中创建屏幕上的文字模糊效果,使其在画面中更加和谐统一。 通过调整高斯模糊shader的参数,可以实现不同的效果。可以通过改变模糊半径来调整模糊的程度,通过改变方向和强度来调整模糊的方向和强度。 总之,材质球高斯模糊shader在计算机图形和视觉效果中扮演着重要的角色。它通过模拟模糊效果,可以使物体看起来更加真实和流畅,从而提升渲染的质量和观感。 ### 回答3: 材质球高斯模糊shader是一种可用于渲染引擎中的特殊shader程序,用于实现高斯模糊效果。高斯模糊是一种常用的图像处理技术,其原理是对图像中的像素进行加权平均,以降低图像细节并模糊图像。 在材质球高斯模糊shader中,首先需要确定模糊的程度。通过调整高斯函数中的标准差来实现模糊的强度,标准差越大,模糊效果越明显。 然后,shader程序会根据标准差计算出一组权重值,这些权重值代表了每个像素所需的模糊程度。通常使用二维高斯函数来计算权重值,较远的像素权重较小,较近的像素权重较大。 接下来,shader会遍历每个像素,并根据权重值对周围像素进行加权平均。这样,每个像素所得的模糊值就是周围像素的加权平均值。通过循环迭代多次,可以增加模糊效果的强度。 最后,将计算得到的模糊像素值应用到渲染的场景中,以实现高斯模糊效果。 总之,材质球高斯模糊shader是一种能够实现图像模糊效果的特殊shader程序。通过计算周围像素的加权平均值,可以实现不同程度的模糊效果,使渲染的图像看起来更加柔和和模糊
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值