Unity Shader屏幕后处理高斯模糊

匆匆过客 提交于 2019-12-16 07:22:33

原理

利用卷积计算,使用的卷积核叫高斯核在这里插入图片描述

使用一个N x N的高斯核对图像进行卷积滤波,就需要N x N x W x H次纹理采样,当N的大小不断增加时,采样次数会变得非常巨大,不过我们可以把这个二维高斯函数拆分成两个一维函数.

得到的结果是一样的。

实现

我们会先后调用两个Pass,第一个Pass使用竖直方向的一维高斯核进行滤波,第二个Pass使用水平方向的一维高斯核,得到最终的目标图像.

首先,创建一个脚本:

public class GaussianBlur : PostEffectsBase

声明需要的Shader,并创建相应的材质:

    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;//缩放系数

downSample越大,需要处理的像素数越少,同时也能进一步提高模糊程度,但过大有可能造成图像像素化.
最后定义关键的OnRenderImage函数:

    void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        if (material != null)
        {
            int rtW = src.width / downSample;//利用缩放对图像进行降采样,从而减少需要处理的像素个数提高性能
            int rtH = src.height / downSample;

            //GetTemporary分配一块与屏幕图像大小相同的缓冲区,高斯模糊需要调用两个Pass,我们需要使用一块中间缓存来存储第一个Pass
            //执行完毕后得到的模糊结果
            RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);
            //第一个缓存,并把缩放后的图像存储到里面。
            buffer0.filterMode = FilterMode.Bilinear;//滤波模式设置维双线性

            Graphics.Blit(src, buffer0);//把src的图像缩放后存储到buffer0中


            //执行第一个Pass时,输入是buffer0,输出是buffer1,执行完后buffer0先释放,然后把Buffer1存到buffer0
            //然后buffer1重新分配,然后又调用第二个Pass,执行完毕后buffer0释放,然后把buffer1存到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);//第一次输入是buffer0 输出是buffer1

                RenderTexture.ReleaseTemporary(buffer0);//然后释放buffer0
                buffer0 = buffer1;//把buffer1的结果存储到buffer0
                buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);//然后buffer1重新分配


                Graphics.Blit(buffer0, buffer1, material, 1);//调用第二个Pass通道 重复上述过程

                RenderTexture.ReleaseTemporary(buffer0);
                buffer0 = buffer1;//最后buffer0存储最终的图像
            }

            Graphics.Blit(buffer0, dest);//把结果显示到屏幕上
            RenderTexture.ReleaseTemporary(buffer0);//释放缓存
        }
        else
        {
            Graphics.Blit(src, dest);
        }
    }
}

Shader部分:

Shader "Gaussian Blur"
{
	Properties 
	{
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_BlurSize ("Blur Size", Float) = 1.0
	}
	SubShader 
	{
		CGINCLUDE//高斯模糊需要定义两个Pass,但他们使用的片元着色器代码完全相同,
		//使用CGINCLUDE可以避免编写两个完全一样的frag函数,类似include头文件
		
		#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;//记录了当前的采样纹理
			o.uv[1] = uv + float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;//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);
			
			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};//权重
			
			fixed3 sum = tex2D(_MainTex, i.uv[0]).rgb * weight[0];
			
			for (int it = 1; it < 3; it++) {
				sum += tex2D(_MainTex, i.uv[it*2-1]).rgb * weight[it];
				sum += tex2D(_MainTex, i.uv[it*2]).rgb * weight[it];
			}
			
			return fixed4(sum, 1.0);
		}
		    
		ENDCG
		
		ZTest Always Cull Off ZWrite Off
		
		Pass {
			NAME "GAUSSIAN_BLUR_VERTICAL"//定义名字可以在其他shader中直接通过它们的名字使用该pass
			
			CGPROGRAM
			  
			#pragma vertex vertBlurVertical  
			#pragma fragment fragBlur
			  
			ENDCG  
		}
		
		Pass {  
			NAME "GAUSSIAN_BLUR_HORIZONTAL"
			
			CGPROGRAM  
			
			#pragma vertex vertBlurHorizontal  
			#pragma fragment fragBlur
			
			ENDCG
		}
	} 
	FallBack "Diffuse"
}

效果如下:
在这里插入图片描述

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!