高斯模糊 【Unity Shader入门精要12.4】

Blit(src,dest,mat,pass)函数的作用,按照Unity官方API的说法是将src这个RT用mat这个材质中的某个pass渲染,然后复制到dest中。如果要给渲染加一些后处理效果(SSAO,HDR,bloom之类的),几乎可以肯定会用到这个函数。根据Unity自带文档中的例子,在OnRenderImage中调用Blit,然后用指定的mat渲染出来。

OnRenderImage(src,dest)是Camera的一个回调(message),他会在camera执行渲染时候被调用,官方给的大部分Image Effect的实现都是用了这个回调。

(记得分配新的RT,用完后手动 Release 掉)

https://blog.csdn.net/yaokang522/article/details/46789913/

高斯模糊 【Unity Shader入门精要12.4】

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

//12.4 高斯模糊
public class GaussianBlur : PostEffectsBase  {
    //声明该效果需要的shader,并据此创建相应的材质
    public Shader gaussianBlurShader;
    private Material gaussianBlurMaterial = null;

    public Material material
    {
        get
        {//gaussianBlurShader是指定的shader,对应了本节所用的shader
            gaussianBlurMaterial = CheckShaderAndCreateMaterial(gaussianBlurShader, gaussianBlurMaterial);
            return gaussianBlurMaterial;
        }
    }
    //在脚本中提供调整高斯模糊的参数
    //【高斯模糊迭代次数】Bulr iterations - Larger number means more blur(越大越模糊)
    [Range(0, 4)]
    public int iterations = 3;
    //【模糊范围】Blur spread for each iteration - larger value means more blur(越大越模糊)
    [Range(0.2f, 3.0f)]
    public float blurSpread = 0.6f;
    //【缩放系数】
    [Range(1, 8)]
    public int downSpread = 2;
    /*blurSpread,downSpread都是出于性能的考虑,在高斯核维数不变的情况下,_BlurSize越大,模糊程度越高,但采样数却不受到影响。
     但过大的_BlurSize值会造成虚影。而downSpread越大,需要处理的像素数越少,同时也能进一步提高模糊程度,但过大的downSpread可能会使图像像素化*/



    //one:最简单的OnRenderImage
    /*
    private void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        if (material !=null)
        {
            int rtW = src.width;
            int rtH = src.height;
            //利用RenderTexture.GetTemporary函数分配一块与屏幕图像大小相同的缓冲区【buffer】
            RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);


        //高斯迷糊需要调用两个Pass,需要使用一块中间缓存来储存第一个Pass执行完毕之后得到的模糊结果
            //Render the vertical pass(渲染垂直通道)
            //使用shader中的第一个pass【0】,对【src】进行处理,并将结果储存在【buffer】中
            Graphics.Blit(src, buffer, material, 0);
            //Render the horizontal pass(渲染水平通道)
           //使用shader中的第二个pass【1】,对【buffer】进行处理,并将结果储存在【dest】中
           Graphics.Blit(buffer, dest, material, 1);

        //释放缓存【buffer】
            RenderTexture.ReleaseTemporary(buffer);
        }else
        {
            Graphics.Blit(src, dest);
        }
    }*/

    //two
    //利用缩放对图像进行采样,从而减少需要处理的像素个数,提高性能
    /* private void OnRenderImage(RenderTexture src, RenderTexture dest)
  {
      if (material !=null ) {
      //与one不同的是在声明缓冲区大小时,用了小雨屏幕分辨率的尺寸
          int rtW = src.width / downSpread;
          int rtH = src.height / downSpread;
          RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);
          //将该临时渲染纹理的滤波模式设置为双线性
          buffer.filterMode = FilterMode.Bilinear;

          //
          Graphics.Blit(src, buffer, material, 0);
          //
          Graphics.Blit(buffer, dest, material, 1);

          RenderTexture.ReleaseTemporary(buffer);
      }else
      {
          Graphics.Blit(src, dest);
      }
  }*/
    //three,考虑了高斯模糊的迭代次数

    private void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        if  (material !=null)
        {
            int rtW = src.width / downSpread;
            int rtH = src.height / downSpread;

            //定义第一个缓存【buffer0】
            RenderTexture buffer0 = RenderTexture.GetTemporary(rtW, rtH, 0);
            //将该临时渲染纹理的滤波模式设置为双线性
            buffer0.filterMode = FilterMode.Bilinear;

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

            for (int i = 0; i < iterations; i++)
            {
                material.SetFloat("_BlurSize", 1.0f + i * blurSpread);

                //定义第二个缓存【buffer1】
                RenderTexture buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);

                //使用shader中的第一个pass【0】,对【buffer0】进行处理,并将结果储存在【buffer1】中
                Graphics.Blit(buffer0, buffer1, material, 0);

                //释放【buffer0】
                RenderTexture.ReleaseTemporary(buffer0);
                //把【buffer1】储存到【buffer0】中
                buffer0 = buffer1;
                //重新分配【buffer1】
                buffer1 = RenderTexture.GetTemporary(rtW, rtH, 0);

                //调用第二个pass【1】重复。。。对【buffer0】进行处理,并将结果储存在【buffer1】中
                Graphics.Blit(buffer0, buffer1, material, 1);

                //释放【buffer0】
                RenderTexture.ReleaseTemporary(buffer0);
                //把【buffer1】储存到【buffer0】中
                buffer0 = buffer1;
                //【buffer0】将存储最终的图像

            }
            //把结果显示到屏幕上
            Graphics.Blit(buffer0, dest);
            //释放【buffer0】
            RenderTexture.ReleaseTemporary(buffer0);
        }
        else
        {
            Graphics.Blit(src, dest);
        }
    }
}
//12.4 高斯模糊

Shader "Unlit/Chapter12-GaussianBlur"
{
	Properties
	{
		_MainTex ("Base(RGB)", 2D) = "white" {}
		_BlurSize("Blur Size",Float)=1.0
	}
	SubShader
		{
		/*   CGINCLUDE
		 。。。。。。。。。
		      ENDCG

		 包含的代码不需要包含在任何Pass语义块中,使用的时候只需要在Pass中直接指定需要使用的顶点着色器和片元着色器函数名即可
		 CGINCLUDE类似C++头文件的功能.
		 由于高斯模糊需要定义两个Pass,但他们使用的片元着色器代码是完全相同的,使用CGINCIUDE可以避免编写两个完全一样的frag.
		 */
				CGINCLUDE

	#include "UnityCG.cginc"
				//定义属性对应变量
				sampler2D _MainTex;
		//由于要用到相邻像素的纹理坐标,使用unity提供的_MainTex_TexelSize变量,以计算相邻像素的纹理坐标偏移量
				half4 _MainTex_TexelSize;
				float _BlurSize;

				//分别定义两个Pass将要使用的顶点着色器
				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;

					//数组的第一个坐标储存了当前的采样,而剩余的四个坐标则是高斯模糊中对邻域采样使用的纹理坐标,属性_BlurSize控制采样距离
					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);

					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;
				}

				//定义李定义两个Pass共用的片元着色器
				fixed4 fragBlur(v2f i) : SV_Target{
					//一个5*5的二维高斯核可以拆分成两个大小为5的一维高斯核,并且由于对称性,只需要记录3个高斯权重,也就是代码的Weight变量
					float weight[3] = { 0.4026, 0.2442, 0.0545 };

				//将sum初始化为当前的像素值乘以它的权重值
				fixed3 sum = tex2D(_MainTex, i.uv[0]).rgb * weight[0];

				//根据对称性,我们进行两次迭代,每次迭代包含两次纹理采样,并把像素值和权重相乘后的结果叠加到sum中
				for (int it = 1; it < 3; it++) {
					sum += tex2D(_MainTex, i.uv[it ]).rgb * weight[it];
					sum += tex2D(_MainTex, i.uv[it +1]).rgb * weight[it];

					/*
					//书上原来是这个,做了一点改动,
					sum += tex2D(_MainTex, i.uv[it*2-1]).rgb * weight[it];
					sum += tex2D(_MainTex, i.uv[it*2]).rgb * weight[it];

					改动后的对应关系如下:
					|权重|weight[2]     | weight[1]    |weight[0]    |weight[1]       |weight[2]
					| uv | uv[3]        |   uv[1]      | uv[0]       |  uv[2]         |  uv[4] 
					| Y  |uv+(0,2)      |  uv+(0,1)    | uv+(0,0)    |uv+(0,-1)       |uv+(0,-2)
					| X  |uv+(2,0)      |  uv+(1,0)    | uv+(0,0)    |uv+(-1,0)       |uv+(-2,0)
					
					                                2【uv 3】
					                                1【uv 1】
					        -2【uv 4】  -1【uv 2】  0【uv 0】   1【uv 1】   2【uv 3】
							                       -1【uv 2】
								                   -2【uv 4】
					*/
				}
				//最后函数返回滤波结果sum
				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"
}