【Unity渲染——屏幕后处理】Cg代码实现多种模糊算法(持续更新)

常见模糊算法

        图像模糊算法在后处理中有着重要的地位,许多产品级后处理表现都会直接或间接使用模糊算法。在以下项目中将使用Cg代码实现部分模糊算法(均值模糊、高斯模糊、Kawase模糊、双向均值/高斯模糊)。

        参考网站:        一、高品质后处理:十种图像模糊算法的总结与实现

                                  二、Kawase模糊参考网站

                                  三、AI工具——豆包

均值模糊

        图像模糊原理,是使用低通滤波器,过滤图像的高频信息保留低频信息。对图像进行模糊处理。一种常见的方法就是使用卷积滤波器。

        算法原理

        将数字图像视作一个矩阵,每一个像素都是矩阵上的一点。现在我们使用一个矩阵,获取图像上每一个像素附近的像素,这一个矩阵就称之为卷积核。将卷积核中的像素的颜色值进行加权平均,就能得到一个颜色相对平均的图像,从而达到模糊处理的效果。根据卷积核的不同,可以将均值模糊分为3x3的模糊与2x2的模糊。

         2x2卷积核

       使用当前像素作为中心的九宫格区域,取左上、左下、右上、右下四格像素的数值进行加权平均,就是2x2的均值模糊的原理。

half4 frag (v2f i) : SV_Target
{
    half col = 0;
    //uv坐标采样加上像素偏移值。分别向左上、右上、左下、右下方向进行偏移。
    //定义变量_BlurOffset控制偏移大小
    col += tex2D(_MainTex, i.uv + _MainTex_TexelSize.xy * half2(1,1) * _BlurOffset;
    col += tex2D(_MainTex, i.uv + _MainTex_TexelSize.xy * half2(-1,1) * _BlurOffset;
    col += tex2D(_MainTex, i.uv + _MainTex_TexelSize.xy * half2(1,-1) * _BlurOffset;
    col += tex2D(_MainTex, i.uv + _MainTex_TexelSize.xy * half2(-1,-1) * _BlurOffset;
    //平均
    col *=0.25;
    return col;
}
         3x3卷积核

        原理与2x2卷积核类似,使用3x3大小的卷积核进行卷积操作即可得出。

half4 frag_testTap9 (v2f_img i) : SV_Target 
{
    
    half4 col = 0;
    //uv坐标采样加上像素偏移值。分别向左上、右上、左下、右下方向进行偏移。
    //定义变量_BlurOffset控制偏移大小
    col += tex2D(_MainTex, i.uv);
    col += tex2D(_MainTex, i.uv + _MainTex_TexelSize.xy * half2(1,1) * _BlurOffset);
    col += tex2D(_MainTex, i.uv + _MainTex_TexelSize.xy * half2(-1,1) * _BlurOffset);
    col += tex2D(_MainTex, i.uv + _MainTex_TexelSize.xy * half2(1,-1) * _BlurOffset);
    col += tex2D(_MainTex, i.uv + _MainTex_TexelSize.xy * half2(-1,-1) * _BlurOffset);
 
    //分别向右、左、上、下进行偏移
    col += tex2D(_MainTex, i.uv + _MainTex_TexelSize.xy * half2(1,0) * _BlurOffset);
    col += tex2D(_MainTex, i.uv + _MainTex_TexelSize.xy * half2(-1,0) * _BlurOffset);
    col += tex2D(_MainTex, i.uv + _MainTex_TexelSize.xy * half2(0,-1) * _BlurOffset);
    col += tex2D(_MainTex, i.uv + _MainTex_TexelSize.xy * half2(0,1) * _BlurOffset);
    //平均
    col = col/9.0;
    return col;
}

        代码详解

        将上述2x2、3x3的模糊算法片段着色器分别放在不同的pass中,绑定材质和脚本文件。通过OnRenderImage()函数,使用Graphics.Blit()分别调用以上着色器实现不同的算法。

public enum PassEnum
{
    //定义枚举类用于执行不同的模糊算法
    //实现的滤波器
    Tap4BoxFilter,
    Tap9BoxFilter
}
public Material material;
[Range(1, 10)]    
public int _Iteration = 4;    //定义迭代次数
[Range(0, 7)]
public float blurOffset = 1.0f;    //定义偏移值
public float downSample = 1.0f;    //定义图像降采样值
public PassEnum thePass;    //定义选用的模糊算法

.....


private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        int width = (int)(source.width / downSample);
        int height = (int)(source.height / downSample);
        //设置shader的参数
        material.SetFloat("_BlurOffset", blurOffset);

        RenderTexture rt1 = RenderTexture.GetTemporary(width, height);
        RenderTexture rt2 = RenderTexture.GetTemporary(width, height);

        Graphics.Blit(source, rt1);
        
        //定义枚举类PassEnum,判断使用的模糊算法
        if (thePass == PassEnum.Tap4BoxFilter)
        {
            //迭代
            for (int i = 0; i < _Iteration; i++)
            {
                Graphics.Blit(rt1, rt2, material, 0);
                Graphics.Blit(rt2, rt1, material, 0);
            }
        }
        if (thePass == PassEnum.Tap9BoxFilter)
        {
            for (int i = 0; i < _Iteration; i++)
            {
                Graphics.Blit(rt1, rt2, material, 1);
                Graphics.Blit(rt2, rt1, material, 1);
            }
        }
        //将最后结果传递到destination
        Graphics.Blit(rt1, destination);
        //释放资源
        RenderTexture.ReleaseTemporary(rt1);
        RenderTexture.ReleaseTemporary(rt2);
    }

高斯模糊

        高斯模糊是最经典的模糊算法之一。与均值模糊类似,高斯模糊同样使用卷积核对图像进行卷积处理。只是卷积核换成了高斯卷积核。

        算法原理

        高斯模糊实际上是使用了正态分布对图像进行卷积。因为正态分布别称高斯分布,故称高斯模糊。高斯模糊使用5x5的矩阵,模拟正态分布进行加权卷积计算。得出的模糊效果比均值模糊更平滑。有关高斯模糊原理,详细请见高斯模糊原理

\frac{1}{256}\begin{bmatrix} 1 & 4 & 6 & 4 & 1\\ 4 & 16 & 24 & 16 &4 \\ 6 & 24 & 36 & 24 & 6\\ 4 & 16 & 24 & 16 & 4\\ 1 & 4 & 6 & 4 & 1 \end{bmatrix}

一个典型的高斯核↑

        5x5大小的卷积核太大,大量消耗算力,可以将高斯卷积核进行线性分解,拆解成{[1, 4, 6, 4, 1]}^{T} 和  [1, 4, 6, 4, 1]两个条状卷积核,纵向、横向各进行一次卷积处理即可达成高斯卷积核的效果。

        代码详解

half4 frag_HorizontalBlur(v2f i) : SV_Target
{
    half2 uv1 = i.uv + _MainTex_TexelSize.xy * _BlurOffset * half2(1, 0) * -2.0;
    half2 uv2 = i.uv + _MainTex_TexelSize.xy * _BlurOffset * half2(1, 0) * -1.0;
    half2 uv3 = i.uv;
    half2 uv4 = i.uv + _MainTex_TexelSize.xy * _BlurOffset * half2(1, 0) * 1.0;
    half2 uv5 = i.uv + _MainTex_TexelSize.xy * _BlurOffset * half2(1, 0) * 2.0;

  	half4 col = 0;
	col += tex2D(_MainTex, uv1) * 0.05;
    col += tex2D(_MainTex, uv2) * 0.25;
    col += tex2D(_MainTex, uv3) * 0.40;
	col += tex2D(_MainTex, uv4) * 0.25;
	col += tex2D(_MainTex, uv5) * 0.05;
	return col;
}

half4 frag_VerticalBlur(v2f i) : SV_Target
{
	half2 uv1 = i.uv + _MainTex_TexelSize.xy * _BlurOffset * half2(0, 1) * -2.0;
	half2 uv2 = i.uv + _MainTex_TexelSize.xy * _BlurOffset * half2(0, 1) * -1.0;
	half2 uv3 = i.uv;
	half2 uv4 = i.uv + _MainTex_TexelSize.xy * _BlurOffset * half2(0, 1) * 1.0;
	half2 uv5 = i.uv + _MainTex_TexelSize.xy * _BlurOffset * half2(0, 1) * 2.0;

	half4 col = 0;
	col += tex2D(_MainTex, uv1) * 0.05;
	col += tex2D(_MainTex, uv2) * 0.25;
	col += tex2D(_MainTex, uv3) * 0.40;
	col += tex2D(_MainTex, uv4) * 0.25;
	col += tex2D(_MainTex, uv5) * 0.05;
	return col;
}

private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
    ......

    if (thePass == PassEnum.GaussainFilter)
    {
        for (int i = 0; i < _Iteration; i++)
        {
            //分别调用横向、竖向的两个卷积核进行渲染
            Graphics.Blit(rt1, rt2, material, 2);
            Graphics.Blit(rt2, rt1, material, 3);
        }
    }
}

Kawase模糊

        相较于高斯模糊、均值模糊等的“恒定卷积核”,使用“动态卷积核”来提升效率是一种很自然朴素的想法。所以,Kawase模糊被提了出来。

        Kawase Blur的思路是对距离当前像素越来越远的地方对四个角进行采样,且在两个大小相等的纹理之间进行乒乓式的blit。创新点在于,采用了随迭代次数移动的blur kernel,而不是类似高斯模糊,或box blur一样从头到尾固定的blur kernel。

        直接上代码

half4 frag_KawaseBlur(v2f i) : SV_Target
{
    half4 col = 0;

    col += tex2D(_MainTex, i.uv);
    col += tex2D(_MainTex, i.uv + half2(1, 1) * _KawaseRange * _MainTex_TexelSize);
    col += tex2D(_MainTex, i.uv + half2(1, -1) * _KawaseRange * _MainTex_TexelSize);
    col += tex2D(_MainTex, i.uv + half2(-1, 1) * _KawaseRange * _MainTex_TexelSize);
    col += tex2D(_MainTex, i.uv + half2(-1, -1) * _KawaseRange * _MainTex_TexelSize);

    col = col*0.2;
    return col;
}
if (thePass == PassEnum.KawaseFilter)
{
    material.SetFloat("_KawaseRange", 0);
    for(int i = 0; i < _Iteration; i++)
    {
        //设置KawaseRange用于在迭代过程中动态递进采样点
        material.SetFloat("_KawaseRange", (i + 1) * kawaseRange);
        Graphics.Blit(rt1, rt2, material, 3);
        //置换rt1与rt2
        RenderTexture temp = RenderTexture.GetTemporary(width, height);
        temp = rt2;
        rt2 = rt1;
        rt1 = temp;
        RenderTexture.ReleaseTemporary(temp);

    }
    
}

        我们可以看到,相比高斯模糊与均值模糊,Kawase模糊的每一次迭代只需调用一次Blit()函数。通过Unity自带的Frame Debugger进行检查,可以发现Kawase模糊调用Pass的次数更少的情况下,就能达到高斯模糊、均值模糊的模糊效果。也就是说,Kawase模糊计算性能更好。使用Kawase模糊,在一定程度上起到了性能优化的效果。

双重模糊

        算法原理

        Dual Kawase Blur,简称Dual Blur,是SIGGRAPH 2015上ARM团队提出的一种衍生自Kawase Blur的模糊算法。

        相较于Kawase Blur在两个大小相等的纹理之间进行乒乓blit的的思路,Dual Kawase Blur的核心思路在于blit过程中进行降采样和升采样,即对RT进行了降采样以及升采样。

        代码详解

        还是直接上代码。以下是双重高斯模糊

        if (thePass == PassEnum.DoubleGaussain)
        {
            //降采样
            for (int i = 0; i < _Iteration; i++)
            {
                RenderTexture.ReleaseTemporary(rt2);
                width = width / 2;
                height = height / 2;
                rt2 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(rt1, rt2, material, 2);

                RenderTexture.ReleaseTemporary(rt1);
                width = width / 2;
                height = height / 2;
                rt1 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(rt2, rt1, material, 3);
            }

            //升采样
            for (int i = 0; i < _Iteration; i++)
            {
                RenderTexture.ReleaseTemporary(rt2);
                width = width * 2;
                height = height * 2;
                rt2 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(rt1, rt2, material, 2);

                RenderTexture.ReleaseTemporary(rt1);
                width = width * 2;
                height = height * 2;
                rt1 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(rt2, rt1, material, 3);
            }
        }

        双重模糊只是一个模板,他可以是双重高斯模糊、双重均值模糊、双重Kawase模糊等。只需调整Blit()中调用的片段着色器即可。 

项目源码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[ExecuteInEditMode()]
public class BlurEffect : MonoBehaviour
{
    public Material material;
    [Range(1, 10)]
    public int _Iteration = 4;
    [Range(0, 7)]
    public float blurOffset = 1.0f;
    [Range(1, 15)]
    public float downSample = 5.0f;
    public PassEnum thePass;
    [Range(0, 5)]
    public float kawaseRange = 1.0f;
    [Range(0, 7)]
    public float test = 1.5f;
    
    // Start is called before the first frame update
    void Start()
    {
        if(material == null 
           || material.shader == null || material.shader.isSupported == false)
        {
            enabled = false;
            return;
        }

    }

    // Update is called once per frame
    void Update()
    {
        
    }
    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        //use the Screen width + height.
        int width = (int)(source.width / downSample);
        int height = (int)(source.height / downSample);
        //Performance Optimization.set the Vertex Shader caculating(GPU) to C#             caculating(CPU)
        //性能优化:将在GPU(顶点着色器)中计算的内容放在(CPU)C#脚本上进行计算提升性能
        material.SetVector("_BlurOffset", new Vector4(blurOffset / width, blurOffset / height, 0, 0));
        material.SetFloat("_TestOffset", test);

        RenderTexture rt1 = RenderTexture.GetTemporary(width, height);
        RenderTexture rt2 = RenderTexture.GetTemporary(width, height);

        Graphics.Blit(source, rt1);

        if (thePass == PassEnum.Tap4BoxFilter)
        {
            for (int i = 0; i < _Iteration; i++)
            {
                Graphics.Blit(rt1, rt2, material, 0);
                Graphics.Blit(rt2, rt1, material, 0);
            }
        }
        if (thePass == PassEnum.Tap9BoxFilter)
        {
            for (int i = 0; i < _Iteration; i++)
            {
                Graphics.Blit(rt1, rt2, material, 1);
                Graphics.Blit(rt2, rt1, material, 1);
            }
        }
        if (thePass == PassEnum.GaussainFilter)
        {
            for (int i = 0; i < _Iteration; i++)
            {
                Graphics.Blit(rt1, rt2, material, 2);
                Graphics.Blit(rt2, rt1, material, 3);
            }
        }
        if (thePass == PassEnum.KawaseFilter)
        {
            material.SetFloat("_KawaseRange", 0);
            for(int i = 0; i < _Iteration; i++)
            {
                material.SetFloat("_KawaseRange", (i + 1) * kawaseRange);
                Graphics.Blit(rt1, rt2, material, 4);

                RenderTexture temp = RenderTexture.GetTemporary(width, height);

                temp = rt2;
                rt2 = rt1;
                rt1 = temp;
                RenderTexture.ReleaseTemporary(temp);
                //Graphics.Blit(rt2, rt1, material, 4);
            }
            
        }
        if (thePass == PassEnum.DoubleTap4)
        {
            for (int i = 0; i < _Iteration; i++)
            {
                RenderTexture.ReleaseTemporary(rt2);
                width = width / 2;
                height = height / 2;
                rt2 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(rt1, rt2, material, 0);

                RenderTexture.ReleaseTemporary(rt1);
                width = width / 2;
                height = height / 2;
                rt1 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(rt2, rt1, material, 0);
            }

            for (int i = 0; i < _Iteration; i++)
            {
                RenderTexture.ReleaseTemporary(rt2);
                width = width * 2;
                height = height * 2;
                rt2 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(rt1, rt2, material, 0);

                RenderTexture.ReleaseTemporary(rt1);
                width = width * 2;
                height = height * 2;
                rt1 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(rt2, rt1, material, 0);
            }

        }
        if (thePass == PassEnum.DoubleTap9)
        {
            for (int i = 0; i < _Iteration; i++)
            {
                RenderTexture.ReleaseTemporary(rt2);
                width = width / 2;
                height = height / 2;
                rt2 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(rt1, rt2, material, 1);

                RenderTexture.ReleaseTemporary(rt1);
                width = width / 2;
                height = height / 2;
                rt1 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(rt2, rt1, material, 1);
            }

            for (int i = 0; i < _Iteration; i++)
            {
                RenderTexture.ReleaseTemporary(rt2);
                width = width * 2;
                height = height * 2;
                rt2 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(rt1, rt2, material, 1);

                RenderTexture.ReleaseTemporary(rt1);
                width = width * 2;
                height = height * 2;
                rt1 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(rt2, rt1, material, 1);
            }

        }
        if (thePass == PassEnum.DoubleGaussain)
        {
            //降采样
            for (int i = 0; i < _Iteration; i++)
            {
                RenderTexture.ReleaseTemporary(rt2);
                width = width / 2;
                height = height / 2;
                rt2 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(rt1, rt2, material, 2);

                RenderTexture.ReleaseTemporary(rt1);
                width = width / 2;
                height = height / 2;
                rt1 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(rt2, rt1, material, 3);
            }

            //升采样
            for (int i = 0; i < _Iteration; i++)
            {
                RenderTexture.ReleaseTemporary(rt2);
                width = width * 2;
                height = height * 2;
                rt2 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(rt1, rt2, material, 2);

                RenderTexture.ReleaseTemporary(rt1);
                width = width * 2;
                height = height * 2;
                rt1 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(rt2, rt1, material, 3);
            }
        }
        
        Graphics.Blit(rt1, destination);
        RenderTexture.ReleaseTemporary(rt1);
        RenderTexture.ReleaseTemporary(rt2);





    }


}
Shader "Hidden/BlurShader"
{

    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _KawaseRange("KawaseRange", Float) = 1 
        _BlurOffset("BlurOffset", Float) = 1
    }
        //使用CGINCLUDE,将cg语言放在前面,方便管理
        CGINCLUDE
        #include "UnityCG.cginc"

        sampler2D _MainTex;
        float4 _BlurOffset;
        float _KawaseRange;
        float4 _MainTex_TexelSize;


        half4 frag_BoxFilter_4Tap (v2f_img i) : SV_Target
        {
            half4 d = _BlurOffset.xyxy * half4(1,1,-1,-1);
            half4 s = 0;
            //采样进行像素偏移,偏移值与_BlurOffset挂钩
            //计算完毕后做平均
            s += tex2D(_MainTex, i.uv + d.xy);
            s += tex2D(_MainTex, i.uv + d.zy);
            s += tex2D(_MainTex, i.uv + d.xw);
            s += tex2D(_MainTex, i.uv + d.zw);
            s = s*0.25;
            return s;
        }

        half4 frag_testTap9 (v2f_img i) : SV_Target 
        {
            
            half4 col = 0;
            //uv坐标采样加上像素偏移值。分别向左上、右上、左下、右下方向进行偏移。
            //定义变量_BlurOffset控制偏移大小
            col += tex2D(_MainTex, i.uv);
            col += tex2D(_MainTex, i.uv + _MainTex_TexelSize.xy * half2(1,1) * _TestOffset);
            col += tex2D(_MainTex, i.uv + _MainTex_TexelSize.xy * half2(-1,1) * _TestOffset);
            col += tex2D(_MainTex, i.uv + _MainTex_TexelSize.xy * half2(1,-1) * _TestOffset);
            col += tex2D(_MainTex, i.uv + _MainTex_TexelSize.xy * half2(-1,-1) * _TestOffset);
 
            //分别向右、左、上、下进行偏移
            col += tex2D(_MainTex, i.uv + _MainTex_TexelSize.xy * half2(1,0) * _TestOffset);
            col += tex2D(_MainTex, i.uv + _MainTex_TexelSize.xy * half2(-1,0) * _TestOffset);
            col += tex2D(_MainTex, i.uv + _MainTex_TexelSize.xy * half2(0,-1) * _TestOffset);
            col += tex2D(_MainTex, i.uv + _MainTex_TexelSize.xy * half2(0,1) * _TestOffset);
            //平均
            col = col/9.0;
            return col;
        }

        half4 frag_BoxFilter_9Tap (v2f_img i) : SV_Target
        {
            
            //half4 d = _BlurOffset.xyxy * half4(1,-1,0,0);
            half4 d = _BlurOffset.xyxy * half4(-1, -1, 1, 1);
            half4 s = 0;
            s = tex2D(_MainTex, i.uv);
            
            s += tex2D(_MainTex, i.uv + d.xy);
            s += tex2D(_MainTex, i.uv + d.zy);
            s += tex2D(_MainTex, i.uv + d.xw);
            s += tex2D(_MainTex, i.uv + d.zw);

            s += tex2D(_MainTex, i.uv + half2(0.0, d.w));
            s += tex2D(_MainTex, i.uv + half2(0.0, d.y));
            s += tex2D(_MainTex, i.uv + half2(d.z, 0.0));
            s += tex2D(_MainTex, i.uv + half2(d.x, 0.0));
            s = s/9.0;
            return s;
        }

        half4 frag_HorizontalBlur(v2f_img i) : SV_Target
	    {

		    half2 uv1 = i.uv + _BlurOffset.xy * half2(1, 0) * -2.0;
		    half2 uv2 = i.uv + _BlurOffset.xy * half2(1, 0) * -1.0;
		    half2 uv3 = i.uv;
		    half2 uv4 = i.uv + _BlurOffset.xy * half2(1, 0) * 1.0;
		    half2 uv5 = i.uv + _BlurOffset.xy * half2(1, 0) * 2.0;

    		half4 s = 0;
	    	s += tex2D(_MainTex, uv1) * 0.05;
		    s += tex2D(_MainTex, uv2) * 0.25;
		    s += tex2D(_MainTex, uv3) * 0.40;
	    	s += tex2D(_MainTex, uv4) * 0.25;
	    	s += tex2D(_MainTex, uv5) * 0.05;
	    	return s;
	    }

	    half4 frag_VerticalBlur(v2f_img i) : SV_Target
	    {
	    	half2 uv1 = i.uv + _BlurOffset.xy * half2(0, 1) * -2.0;
	    	half2 uv2 = i.uv + _BlurOffset.xy * half2(0, 1) * -1.0;
	    	half2 uv3 = i.uv;
	    	half2 uv4 = i.uv + _BlurOffset.xy * half2(0, 1) * 1.0;
	    	half2 uv5 = i.uv + _BlurOffset.xy * half2(0, 1) * 2.0;

	    	half4 s = 0;
	    	s += tex2D(_MainTex, uv1) * 0.05;
	    	s += tex2D(_MainTex, uv2) * 0.25;
	    	s += tex2D(_MainTex, uv3) * 0.40;
	    	s += tex2D(_MainTex, uv4) * 0.25;
	    	s += tex2D(_MainTex, uv5) * 0.05;
	    	return s;
	    }

        half4 frag_KawaseBlur(v2f_img i) : SV_Target
        {
            half4 d = half4(1,1,-1,-1);
            half4 s = 0;

            s += tex2D(_MainTex, i.uv);

            s += tex2D(_MainTex, i.uv + d.xy * _KawaseRange * _MainTex_TexelSize.xy);
            s += tex2D(_MainTex, i.uv + d.zy * _KawaseRange * _MainTex_TexelSize.xy);
            s += tex2D(_MainTex, i.uv + d.xw * _KawaseRange * _MainTex_TexelSize.xy);
            s += tex2D(_MainTex, i.uv + d.zw * _KawaseRange * _MainTex_TexelSize.xy);

            s = s*0.2;
            return s;
        }

    ENDCG 

    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass    //0号Pass
        {
            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag_BoxFilter_4Tap
            ENDCG
        }

        Pass    //1号Pass
        {
            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag_BoxFilter_9Tap
            ENDCG
        }
        pass    //2号Pass
        {
            CGPROGRAM
            #include "UnityCG.cginc"
            #pragma vertex vert_img
            #pragma fragment frag_HorizontalBlur  
            ENDCG
        }

        pass    //3号Pass
        {
            CGPROGRAM

            #include "UnityCG.cginc"
            #pragma vertex vert_img
            #pragma fragment frag_VerticalBlur
            ENDCG
        }

        pass    //4号Pass
        {
            CGPROGRAM
            #include "UnityCG.cginc"
            #pragma vertex vert_img
            #pragma fragment frag_KawaseBlur
            ENDCG
        }

        

    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值