UnityShader_屏幕后处理之高斯模糊

475 阅读3分钟

本文已参与[新人创作礼]活动,一起开启掘金创作之路
原理: 高斯方程公式如下 G(x,y)=12πσ2ex2+y22σ2G(x,y) =\frac{1}{2πσ^2}e^{-\frac{x^2+y^2}{2σ^2}} 其中σ(标准差)一般取值为1;x、y为当前位置到卷积中心的距离,将上述公式带入到5X5的高斯核中,可以得到下面的高斯核m(1~25)

 0.00290.01310.02150.01310.0029  0.01310.05850.09650.05850.0131  0.02150.09650.15920.09650.0215  0.01310.05850.09650.05850.0131  0.00290.01310.02150.01310.0029 \begin{array}{|rrrrrrrr|} \hline \verb+ + & & & & & \\ \verb+0.0029+ & 0.0131 & 0.0215 &0.0131 &0.0029& \\ \verb+ + & & & & & \\ \hline \verb+ + & & & & & \\ \verb+0.0131+ & 0.0585& 0.0965&0.0585&0.0131 & \\ \verb+ + & & & & & \\ \hline \verb+ + & & & & & \\ \verb+0.0215+ & 0.0965& 0.1592&0.0965&0.0215& \\ \verb+ + & & & & & \\ \hline \verb+ + & & & & & \\ \verb+0.0131+ & 0.0585& 0.0965&0.0585&0.0131 & \\ \verb+ + & & & & & \\ \hline \verb+ + & & & & & \\ \verb+0.0029+ & 0.0131 & 0.0215 &0.0131 &0.0029& \\ \verb+ + & & & & & \\ \hline \end{array}

i=125m(i)=0.9816 \sum_{i=1}^{25} m(i)=0.9816 归一化(将每个元素除以上面的和)得到下图高斯核 在这里插入图片描述 因此我们只需要利用上面的高斯核计算出包括远点在内的25个点的RBGA的和就是该点新的RBGA。 但是上面需要计算25个点,计算量十分大,我们可以利用高斯方程的降维性质,即 G(x,y)=G(x)×G(y)使原本的渲染可以变成先进行水平方向的(x)渲染,再将渲染过后的进行垂直方向(y)的渲染来降低计算的复杂度 推导过程如下: G(x,y)=12πσ2ex2+y22σ2=12πσ2ex22σ2×12πσ2ey22σ2=G(x)×G(y) G(x,y) =\frac{1}{2πσ^2}e^{-\frac{x^2+y^2}{2σ^2}} =\frac{1}{2πσ^2}e^{-\frac{x^2}{2σ^2}}× \frac{1}{2πσ^2}e^{-\frac{y^2}{2σ^2}} =G(x)×G(y) 因此高斯核可以转化为如下图(同样是利用公式求出对应值,然后再进行归一化) 在这里插入图片描述 通过观察可以发现,上述的权重值实际上只有3个(后面代码中会提到) 上代码(代码出自《UnityShader入门精要》)

//Shader部分的代码
Shader "Unity Shaders Book/Chapter 12/Gaussian Blur" {
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_BlurSize ("Blur Size", Float) = 1.0 //采样间隔
	}
	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;//0
		o.uv[1] = uv + float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;//右1
		o.uv[2] = uv - float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;//左1
		o.uv[3] = uv + float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;//右2
		o.uv[4] = uv - float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;//左2
					
		return o;
	}
	
	v2f vertBlurHorizontal(appdata_img v) {
		v2f o;
		o.pos = UnityObjectToClipPos(v.vertex);
		
		half2 uv = v.texcoord;
		
		o.uv[0] = uv;//0
		o.uv[1] = uv + float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;//右1
		o.uv[2] = uv - float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;//左1
		o.uv[3] = uv + float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;//右2
		o.uv[4] = uv - float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;//左2
					
		return o;
	}
	
	//取周围及自身共5个点的颜色采样值累加
	fixed4 fragBlur(v2f i) : SV_Target {
		float weight[3] = {0.4026, 0.2442, 0.0545};//权重列表(对称的,实际上是5个)
		
		fixed3 sum = tex2D(_MainTex, i.uv[0]).rgb * weight[0];// 0
		
		for (int it = 1; it < 3; it++) {
			sum += tex2D(_MainTex, i.uv[it*2-1]).rgb * weight[it];//右1、右2
			sum += tex2D(_MainTex, i.uv[it*2]).rgb * weight[it];//左1、左2
		}
		
		return fixed4(sum, 1.0);
	}
		
	ENDCG
	
	SubShader {	
		ZTest Always Cull Off ZWrite Off
		
		Pass {
			//命名之后可以重复利用该Pass
			NAME "GAUSSIAN_BLUR_VERTICAL"
			
			CGPROGRAM
			  
			#pragma vertex vertBlurVertical  
			#pragma fragment fragBlur
			  
			ENDCG  
		}
		
		Pass {  
			NAME "GAUSSIAN_BLUR_HORIZONTAL"
			
			CGPROGRAM  
			
			#pragma vertex vertBlurHorizontal  
			#pragma fragment fragBlur
			
			ENDCG
		}
	} 
	FallBack "Diffuse"
}
//C#部分的关键代码
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);

				// 使用第一个pass(垂直模糊)渲染
				Graphics.Blit(buffer0, buffer1, material, 0);

				RenderTexture.ReleaseTemporary(buffer0);
				buffer0 = buffer1;
				//存储水平渲染后的图片
				buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);

				// 使用第二个pass(水平模糊)渲染
				Graphics.Blit(buffer0, buffer1, material, 1);

				RenderTexture.ReleaseTemporary(buffer0);
				buffer0 = buffer1;
			}

			Graphics.Blit(buffer0, dest);
			RenderTexture.ReleaseTemporary(buffer0);
		} else {
			Graphics.Blit(src, dest);
		}
	}

C#部分简单讲解一下: 1、这里的Shader必须搭配C#脚本使用 2、C#脚本中控制模糊的主要有三个变量,分别是**downSample(采样缩小尺寸)、iterations(迭代次数)、blurSpread(模糊扩散),**这三个变量都是值越大,得到的图案就越模糊。 核心: 进行 iterations 的迭代渲染,即 循环{进行shader中的垂直模糊,保存模糊后图片,将模糊后的图片再次进行shader中的水平模糊,保存模糊后图片} ,在模糊后的图片基础上进行多次模糊,并且通过设置 blurSpread ,以达到每次模糊的范围不断扩大的效果

material.SetFloat("_BlurSize", 1.0f + i * blurSpread);

还有一个值是 downSample ,采样一个图片的时候后,我们采样的点越少,图片就越模糊

3张gif分别是 1、downSample 由0不断变大 2、iterations 由0不断变大 3、在 iterations=5的情况下,blurSpread 由0不断变大(如果迭代次数≤1,这个值变化是没有用的,原因可以看看C#代码的blurSpread 那条代码 ) 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 完整代码的可以去找《UnityShader入门精要》 冯乐乐版本