1. 程式人生 > >高斯模糊 【Unity Shader入門精要12.4】

高斯模糊 【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 掉)

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