【Unity3D】高斯模糊特效

1 高斯模糊原理

        边缘检测特效中使用了卷积运算进行了边缘检测,本文实现的高斯模糊特效同样使用了卷积运算,关于卷积核和卷积运算的概念,读者可以参考边缘检测特效

        本文完整资源见→Unity3D高斯模糊特效

        我们将用于模糊处理的卷积核称为模糊算子,它一般满足以下条件:

  • 卷积核中权值上下对称、左右对称;
  • 卷积核中每个权值大于或等于 0,小于 1;
  • 卷积核中所有权值之和为 1。

        我们将所有权值都为 1 / n(n 为权值个数)的卷积核称为平均模糊算子;将权值随位置变化且符合高斯分布(或正太分布)的卷积核称为高斯模糊算子(或高斯核),它较好地模拟了邻域中每个像素对当前处理像素的影响程度(距离越近,影响越大)。高斯方程如下:

         σ 是标准差,其值越大,高斯分布函数的图像越矮胖,一般取值为 1,x、y 为当前位置到卷积核中心的整数距离。要构建一个高斯核,我们只需要计算高斯核中各个位置对应的高斯值。为了保证模糊处理后的图像不会变暗,我们需要对高斯核中的元素进行归一化,即将所有元素都除以它们的权值和,从而保证归一化后的权值和为 1。因此,高斯函数中 e 前面的系数不会对高斯核产生任何影响,在计算高斯核的过程中可以省去。

        高斯核的维数越高,模糊程度越大。使用一个 n * n 的卷积核,需要进行 n * n * w * h 次纹理采样(w、h 分别为图像的宽高),为节省性能,我们将二维高斯核拆分为 2 个一维高斯核,采样次数只需要 2 * n * w * h 次。进一步观察到,2 个高斯核中包含了很多重复权重。对于一个大小为 5 的一维高斯核,实际只需记录 3 个权值即可。

2 代码实现

        GaussianBlur.cs

using UnityEngine;

[ExecuteInEditMode] // 编辑态可以查看脚本运行效果
[RequireComponent(typeof(Camera))] // 需要相机组件
public class GaussianBlur : MonoBehaviour {
	[Range(0, 4)]
	public int iterations = 3; // 高斯模糊迭代次数
	[Range(0.2f, 3.0f)]
	public float blurSpread = 0.6f; // 每次迭代纹理坐标偏移的速度
	[Range(1, 8)]
	public int downSample = 2; // 降采样比率
	private Material material = null; // 材质

	private void Start() {
		material = new Material(Shader.Find("MyShader/GaussianBlur"));
		material.hideFlags = HideFlags.DontSave;
	}

	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);
				Graphics.Blit(buffer0, buffer1, material, 0); // 渲染垂直的Pass
				RenderTexture.ReleaseTemporary(buffer0);
				buffer0 = buffer1;
				buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);
				Graphics.Blit(buffer0, buffer1, material, 1); // 渲染水平的Pass
				RenderTexture.ReleaseTemporary(buffer0);
				buffer0 = buffer1;
			}
			Graphics.Blit(buffer0, dest);
			RenderTexture.ReleaseTemporary(buffer0);
		} else {
			Graphics.Blit(src, dest);
		}
	}
}

        GaussianBlur.shader

Shader "MyShader/GaussianBlur" { // 高斯模糊
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {} // 主纹理
		_BlurSize ("Blur Size", Float) = 1.0 // 模糊尺寸(纹理坐标的偏移量)
	}

	SubShader {
		CGINCLUDE
		
		#include "UnityCG.cginc"
		
		sampler2D _MainTex; // 主纹理
		half4 _MainTex_TexelSize; // _MainTex的像素尺寸大小, float4(1/width, 1/height, width, height)
		float _BlurSize; // 模糊尺寸(纹理坐标的偏移量)
		  
		struct v2f {
			float4 pos : SV_POSITION; // 模型空间顶点坐标
			half2 uv[5]: TEXCOORD0; // 5个邻域的纹理坐标
		};
		  
		v2f vertBlurVertical(appdata_img v) { // 垂直模糊顶点着色器
			v2f o;
			o.pos = UnityObjectToClipPos(v.vertex); // 模型空间顶点坐标变换到裁剪空间, 等价于: mul(UNITY_MATRIX_MVP, v.vertex)
			half2 uv = v.texcoord;
			o.uv[0] = uv;
			o.uv[1] = uv + float2(0.0, _MainTex_TexelSize.y * 1.0) * _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); // 模型空间顶点坐标变换到裁剪空间, 等价于: mul(UNITY_MATRIX_MVP, 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}; // 大小为5的一维高斯核,实际只需记录3个权值
			fixed3 sum = tex2D(_MainTex, i.uv[0]).rgb * weight[0];
			for (int j = 1; j < 3; j++) {
				sum += tex2D(_MainTex, i.uv[j * 2 - 1]).rgb * weight[j]; // 中心右侧或下侧的纹理*权值
				sum += tex2D(_MainTex, i.uv[j * 2]).rgb * weight[j]; // 中心左侧或上侧的纹理*权值
			}
			return fixed4(sum, 1.0);
		}
		    
		ENDCG
		
		ZTest Always Cull Off ZWrite Off
		
		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"
}

3 运行效果

        1)原图

        2)模糊处理

        调整模糊迭代次数 iterations 由 0 ~ 4 变化,效果如下:

猜你喜欢

转载自blog.csdn.net/m0_37602827/article/details/131019526