1. 程式人生 > >Unity5中新的Shader體系簡析

Unity5中新的Shader體系簡析

Unity5和之前的書寫模式有了一定的改變。Unity5時代的Shader Reference官方文件也進一步地變得豐滿。

主要需要了解到的是,在原來的Unity中,若想要新建一個Shader原始檔,不考慮compute shader的話,僅有一種Shader模板供選擇。而自從Unity5.1起(好像是Unity5.1)

想在Unity5.1之後的版本中新建Shader,【右鍵在Project視窗中單擊】->【Create】,會出現如下的四個選項:

而由於暫時不考慮compute shader。所以,新版Unity中有三種基本的Shader模板分別為:

  • Standard Surface Shader標準表面著色器
  • Unlit Shader 無燈光著色器
  • Image Effect Shader 影象特效著色器

二、Unity5中新的Shader模板原始碼解析

下面,對Unity5中三種基本Shader模板進行逐行註釋與思路解析。

可以點選這裡跳轉到Github,檢視詳細註釋好的三種Shader模板的原始碼。

2.1 標準表面著色器(Standard Surface Shader)模板原始碼解析

在Unity中,我們若要實現新的表面著色器時,可以根據這個模板,進行一步新增子著色器和新的引數與特性。

這個Shader模板的脈絡很清晰,先是定義一些屬性,然後在SubShader中設定渲染模式,層次細節LOD的值,然後開啟一個CG程式語言模組,寫一些編譯指令#pragma,宣告一下變數讓屬性值在CG塊中可見,定義輸入結構,然後填充一下表面著色函式即可。注意:專門強調一句,SurfaceShader不能使用Pass,一使用就報錯,我們直接在SubShader中實現和填充程式碼就可以了。

Standard Surface Shader模板詳細註釋的Shader程式碼如下:

Shader "淺墨Shader程式設計/Volume8/Surface Shader模板"  
{  
       //------------------------------------【屬性值】------------------------------------  
       Properties  
       {  
              //主顏色  
              _Color("Color", Color) = (1,1,1,1)  
              //主紋理  
              _MainTex("Albedo (RGB)", 2D) = "white" {}  
              //光澤度  
              _Glossiness("Smoothness", Range(0,1)) = 0.5  
              //金屬度  
              _Metallic("Metallic", Range(0,1)) = 0.0  
       }  
   
       //------------------------------------【唯一的子著色器】------------------------------------  
       SubShader  
       {  
              //【注意:Surface Shader不能使用Pass,直接在SubShader中實現即可】  
   
              //渲染型別設定:不透明  
              Tags{"RenderType" = "Opaque" }  
   
              //細節層次設為:200  
              LOD200  
   
              //===========開啟CG著色器語言編寫模組===========  
              CGPROGRAM  
   
              //編譯指令:告知編譯器表明著色函式的名稱為surf  
              //Standard表示光照模型為Unity標準版光照模型  
              //fullforwardshadows表示在正向渲染路徑中支援所有陰影型別  
              #pragma surface surf Standard fullforwardshadows  
   
              //編譯指令: 指定著色器編譯目標為Shader Model 3.0  
              #pragma target 3.0  
   
              //變數的宣告  
              sampler2D _MainTex;  
   
              //表面輸入結構體  
              struct Input  
              {  
                     float2 uv_MainTex;//紋理座標  
              };  
   
              //變數的宣告  
              half _Glossiness;  
              half _Metallic;  
              fixed4 _Color;  
   
              //--------------------------------【表面著色函式】-----------------------------  
              //輸入:表面輸入結構體  
              //輸出:Unity內建的SurfaceOutputStandard結構體  
              //SurfaceOutputStandard原型如下:  
              /* 
                     struct SurfaceOutputStandard 
                     { 
                            fixed3 Albedo;                  // 漫反射顏色 
                            fixed3 Normal;                  // 切線空間法線 
                            half3 Emission;                 //自發光 
                            half Metallic;                           // 金屬度;取0為非金屬, 取1為金屬 
                            half Smoothness;             // 光澤度;取0為非常粗糙, 取1為非常光滑 
                            half Occlusion;                 // 遮擋(預設值為1) 
                            fixed Alpha;                      // 透明度 
                     }; 
              */  
              //---------------------------------------------------------------------------------  
              void surf(Input IN, inout SurfaceOutputStandard o)  
              {  
                     //【1】漫反射顏色為主紋理對應的紋理座標,並乘以主顏色  
                     fixed4c = tex2D(_MainTex, IN.uv_MainTex) * _Color;  
                     //【2】將準備好的顏色的rgb分量作為漫反射顏色  
                     o.Albedo= c.rgb;  
                     //【3】金屬度取自屬性值  
                     o.Metallic= _Metallic;  
                     //【4】光澤度也取自屬性值  
                     o.Smoothness= _Glossiness;  
                     //【5】將準備好的顏色的alpha分量作為Alpha分量值  
                     o.Alpha= c.a;  
              }  
   
              //===========結束CG著色器語言編寫模組===========  
              ENDCG  
       }  
       //備胎為漫反射  
       FallBack"Diffuse"  
}  

接著來看Unity5的第二種Shader模板,無燈光著色器(Unlit Shader)模板。

2.2 無燈光著色器(Unlit Shader)模板原始碼解析

Unlit Shader,簡單來說,就是直接採用漫反射紋理,不考慮場景中的任何燈光效果。使用無燈光著色器的話,也就不能使用任何鏡面或者法線效果了。Unlit系的Shader基本原理和其他Shader無異,但是計算量更小,更快速,更高效。

在Unity內建的各種著色器中,有如下的四種是Unlit系的:


好的,已經稍微解釋了下什麼是Unlit Shader。下面一起看一下Unity為我們提供的無燈光著色器模板的程式碼:

Shader "淺墨Shader程式設計/Volume8/無燈光著色器(Unlit Shader)模板"  
{  
       //------------------------------------【屬性值】------------------------------------  
       Properties  
       {  
              //主紋理  
              _MainTex("Texture", 2D) = "white" {}  
       }  
   
       //------------------------------------【唯一的子著色器】------------------------------------  
       SubShader  
       {  
              //渲染型別設定:不透明  
              Tags{ "RenderType"="Opaque" }  
   
              //細節層次設為:100  
              LOD 100  
   
              //--------------------------------唯一的通道-------------------------------  
              Pass  
              {  
                     //===========開啟CG著色器語言編寫模組===========  
                     CGPROGRAM  
   
                     //編譯指令:告知編譯器頂點和片段著色函式的名稱  
                     #pragma vertex vert  
                     #pragma fragment frag  
   
                     //著色器變體快捷編譯指令:霧效。編譯出幾個不同的Shader變體來處理不同型別的霧效(關閉/線性/指數/二階指數)  
                     #pragma multi_compile_fog  
   
                     //包含標頭檔案  
                     #include"UnityCG.cginc"  
   
                     //頂點著色器輸入結構  
                     struct appdata  
                     {  
                            float4 vertex : POSITION;//頂點位置  
                            float2 uv : TEXCOORD0;//紋理座標  
                     };  
   
                     //頂點著色器輸出結構  
                     struct v2f  
                     {  
                            float2 uv : TEXCOORD0;//紋理座標  
                            UNITY_FOG_COORDS(1)//霧資料  
                            float4 vertex : SV_POSITION;//畫素位置  
                     };  
   
                     //變數宣告  
                     sampler2D _MainTex;  
                     float4 _MainTex_ST;  
                      
                     //--------------------------------【頂點著色函式】-----------------------------  
                     //輸入:頂點輸入結構體  
                     //輸出:頂點輸出結構體  
                     //---------------------------------------------------------------------------------  
                     v2f vert (appdata v)  
                     {  
                            //【1】例項化一個輸入結構體  
                            v2f o;  
                            //【2】填充此輸出結構  
                            //輸出的頂點位置(畫素位置)為模型檢視投影矩陣乘以頂點位置,也就是將三維空間中的座標投影到了二維視窗  
                            o.vertex= mul(UNITY_MATRIX_MVP, v.vertex);  
                            //【3】用UnityCG.cginc標頭檔案中內建定義的巨集,根據uv座標來計算真正的紋理上對應的位置(按比例進行二維變換)                   
                            o.uv= TRANSFORM_TEX(v.uv, _MainTex);  
                            //【4】用UnityCG.cginc標頭檔案中內建定義的巨集處理霧效,從頂點著色器中輸出霧效資料  
                            UNITY_TRANSFER_FOG(o,o.vertex);  
   
                            //【5】返回此輸出結構物件  
                            return o;  
                     }  
                      
                     //--------------------------------【片段著色函式】-----------------------------  
                     //輸入:頂點輸出結構體  
                     //輸出:float4型的畫素顏色值  
                     //---------------------------------------------------------------------------------  
                     fixed4 frag (v2f i) : SV_Target  
                     {  
                            //【1】取樣主紋理在對應座標下的顏色值  
                            fixed4 col = tex2D(_MainTex, i.uv);  
   
                            //【2】用UnityCG.cginc標頭檔案中內建定義的巨集啟用霧效  
                            UNITY_APPLY_FOG(i.fogCoord,col);             
   
                            //【3】返回最終的顏色值  
                            return col;  
                     }  
   
                     //===========結束CG著色器語言編寫模組===========  
                     ENDCG  
              }  
       }  
}  

不難分析得到,無燈光著色器是一種頂點&片段著色器,這邊模板給出的是單子著色器,單通道的寫法。

並且,無燈光著色器中使用了一些UnityCG.cginc標頭檔案中內建的巨集,比如說TRANSFORM_TEX、UNITY_TRANSFER_FOG、UNITY_APPLY_FOG。接下來分別把這三個巨集簡單解釋一下。

2.2.1 TRANSFORM_TEX巨集

TRANSFORM_TEX巨集的定義為:

  1. #define TRANSFORM_TEX(tex,name) (tex.xy *name##_ST.xy + name##_ST.zw)

其位於UnityCG.cginc(Unity5.2.1版本)的第266行。其可以根據uv座標來計算真正的紋理上對應的位置(按比例進行二維變換),組合上上文中定義的float4 _MainTex_ST,便可以計算真正的紋理上對應的位置。

2.2.2 UNITY_TRANSFER_FOG巨集

UNITY_TRANSFER_FOG巨集的作用是從頂點著色輸出霧資料。在UnityCG.cginc(Unity5.2.1版本)的第772行起,具體定義如下:

  1. #if (SHADER_TARGET < 30) ||defined(SHADER_API_MOBILE)
  2.               //手機端或者Shader Mode 2.0: 計算每個頂點的霧效因子
  3.               #define UNITY_TRANSFER_FOG(o,outpos) UNITY_CALC_FOG_FACTOR((outpos).z); o.fogCoord =unityFogFactor
  4.        #else
  5.               //Shader Mode 3.0和PC和遊戲機: 計算每畫素的霧距離,和每畫素的霧效因子
  6.               #define UNITY_TRANSFER_FOG(o,outpos) o.fogCoord = (outpos).z
  7.        #endif

2.2.3 UNITY_APPLY_FOG巨集

UNITY_APPLY_FOG巨集的定義稍微有些長,從UnityCG.cginc(Unity 5.2.1版本)的第787行起:

#if defined(FOG_LINEAR) || defined(FOG_EXP)|| defined(FOG_EXP2)  
       #if(SHADER_TARGET < 30) || defined(SHADER_API_MOBILE)  
              //mobile or SM2.0: fog factor was already calculated per-vertex, so just lerp thecolor  
              #defineUNITY_APPLY_FOG_COLOR(coord,col,fogCol) UNITY_FOG_LERP_COLOR(col,fogCol,coord)  
       #else  
              //SM3.0 and PC/console: calculate fog factor and lerp fog color  
              #define UNITY_APPLY_FOG_COLOR(coord,col,fogCol) UNITY_CALC_FOG_FACTOR(coord);UNITY_FOG_LERP_COLOR(col,fogCol,unityFogFactor)  
       #endif  
#else  
       #define UNITY_APPLY_FOG_COLOR(coord,col,fogCol)  
#endif  
   
#ifdef UNITY_PASS_FORWARDADD  
       #define UNITY_APPLY_FOG(coord,col) UNITY_APPLY_FOG_COLOR(coord,col,fixed4(0,0,0,0))  
#else  
       #define UNITY_APPLY_FOG(coord,col) UNITY_APPLY_FOG_COLOR(coord,col,unity_FogColor)  
#endif  

可以發現,UNITY_APPLY_FOG巨集的作用是從頂點著色器中輸出霧效資料,將第二個引數中的顏色值作為霧效的顏色值,且在正向附加渲染通道(forward-additive pass)中,自動設定純黑色(fixed4(0,0,0,0))的霧效。其在定義中藉助了UNITY_APPLY_FOG_COLOR巨集,而我們也可以使用UNITY_APPLY_FOG_COLOR來指定特定顏色的霧效。

2.3 影象特效著色器(Image Effect Shader) 模板原始碼解析

這裡的影象特效一般指的就是螢幕影象特效,在Camera加上各種濾鏡,比如說螢幕濺血,畫素化,色調的調整,畫面模糊等效果。其也是一個頂點&片段著色器,且一般主要的操作集中在片段著色函式中。Unity為我們提供的模板,經過詳細註釋後的原始碼如下:

Shader "淺墨Shader程式設計/Volume8/影象特效Shader模板"  
{  
       //------------------------------------【屬性值】------------------------------------  
       Properties  
       {  
              //主紋理  
              _MainTex("Texture", 2D) = "white" {}  
       }  
   
       //------------------------------------【唯一的子著色器】------------------------------------  
       SubShader  
       {  
              //關閉剔除操作  
              Cull Off  
              //關閉深度寫入模式  
              ZWrite Off  
              //設定深度測試模式:渲染所有畫素.等同於關閉透明度測試(AlphaTestOff)  
              ZTest Always  
   
              //--------------------------------唯一的通道-------------------------------  
              Pass  
              {  
                     //===========開啟CG著色器語言編寫模組===========  
                     CGPROGRAM  
   
                     //編譯指令:告知編譯器頂點和片段著色函式的名稱  
                     #pragma vertex vert  
                     #pragma fragment frag  
                      
                     //包含標頭檔案  
                     #include"UnityCG.cginc"  
   
                     //頂點著色器輸入結構  
                     struct appdata  
                     {  
                            float4 vertex : POSITION;//頂點位置  
                            float2 uv : TEXCOORD0;//一級紋理座標  
                     };  
   
                     //頂點著色器輸出結構(v2f,vertex to fragment)  
                     struct v2f  
                     {  
                            float2 uv : TEXCOORD0;//一級紋理座標  
                            float4 vertex : SV_POSITION;//畫素位置  
                     };  
   
                     //--------------------------------【頂點著色函式】-----------------------------  
                     //輸入:頂點輸入結構體  
                     //輸出:頂點輸出結構體  
                     //---------------------------------------------------------------------------------  
                     //頂點著色函式  
                     v2f vert (appdata v)  
                     {  
                            //【1】例項化一個輸入結構體  
                            v2f o;  
   
                            //【2】填充此輸出結構  
                            //輸出的頂點位置(畫素位置)為模型檢視投影矩陣乘以頂點位置,也就是將三維空間中的座標投影到了二維視窗  
                            o.vertex= mul(UNITY_MATRIX_MVP, v.vertex);  
                            //輸入的UV紋理座標為頂點輸出的座標  
                            o.uv= v.uv;  
   
                            //【3】返回此輸出結構物件  
                            return o;  
                     }  
   
                     //變數的宣告  
                     sampler2D _MainTex;  
   
                     //--------------------------------【片段著色函式】-----------------------------  
                     //輸入:頂點輸出結構體  
                     //輸出:float4型的畫素顏色值  
                     //---------------------------------------------------------------------------------  
                     fixed4 frag (v2f i) : SV_Target  
                     {  
                            //【1】取樣主紋理在對應座標下的顏色值  
                            fixed4 col = tex2D(_MainTex, i.uv);  
                            //【2】將顏色值反向  
                            col= 1 - col;  
   
                            //【3】返回最終的顏色值  
                            return col;  
                     }  
   
                     //===========結束CG著色器語言編寫模組===========  
                     ENDCG  
              }  
       }  
}  

2.4 Shader模板中文註釋格式調整版替換

其實可以將Unity5中自帶的上述三個著色器模板,替換成上文中貼出來的、經過詳細註釋和格式調整的Shader模板,這樣在每次新建Shader時,就已經得到了具有很高可讀性的Shader模板了,非常便捷。

一定要吐槽的是,Unity5.2.1自帶的三個Shader模板的縮排和空格完全是混用的,導致在通過他們新建出來的Shader裡面寫程式碼的時候,格式非常混亂,十分影響新版Unity中Shader的編碼體驗。很明顯,準備此Shader模板的Unity開發人員的編碼習慣有點欠缺,得在這裡點名批評,輕噴一下。

 淺墨在一發現他們格式有問題的時候就馬上替換掉了,所以現在在Unity中寫Shader程式碼的體驗是非常棒的。這邊也教大家如何替換掉自帶的3個模板。

Unity中Shader模板的位置是…Unity\Editor\Data\Resources\ScriptTemplates,比如說Unity安裝在D:\ProgramFiles\路徑下,整體路徑就是:

D:\ProgramFiles\Unity\Editor\Data\Resources\ScriptTemplates。

在此路徑下的3個txt,即為對應的三個Shader模板檔案:

  • 83-Shader__Standard SurfaceShader-NewSurfaceShader.shader.txt
  • 84-Shader__UnlitShader-NewUnlitShader.shader.txt
  • 85-Shader__Image EffectShader-NewImageEffectShader.shader

這邊已經將調整好格式,詳細註釋的三種模板準備好了,下載之後,找到上面提到的…Unity\Editor\Data\Resources\ScriptTemplates目錄。替換掉對應的3個txt檔案即可。需要注意的是,如果你想自己DIY Shader模板,需要將txt儲存為UTF-8編碼格式,否則可能會出現亂碼。

替換的模板下載地址在這裡:

另外還有一個小細節可以提一下。如果你安裝了兩個或者兩個以上的Unity5.1之後版本的Unity,如果你替換你當前使用的Unity路徑下的模板檔案後,新建的模板檔案沒有改變的話,你試著將所有的Unity5.1之後版本的路徑下的這三個模板檔案都進行替換,應該就可以實現想要的替換效果。淺墨的機器上就是同時存在Unity5.2.1和Unity5.2.0,然後使用Unity5.2.1,替換掉Unity5.2.1路徑下的三個模板檔案後,並沒有發生變換。之後我按圖索驥,替換了Unity 5.2.0版路徑下的三個模板文化,才使得替換的模板檔案生效。這估計是Unity多版本共存時,自身的一個小bug。

三、運動模糊螢幕特效的實現

關於運動模糊特效,如果把握要要點的話,實現起來其實比較簡單,就是一個指令碼檔案配合一個Shader,便可以實現較為出色的運動模糊特效。而其中的指令碼檔案用於控制Shader中的外部引數。

也就是說一個螢幕特效通常分為兩部分來實現:

  • Shader實現部分
  • 指令碼實現部分

下面我們對運動模糊螢幕特效的實現分別進行簡單的描述。

可以點選這裡跳轉到Github,檢視詳細註釋好的運動模糊螢幕特效的實現原始碼。

3.1 Shader實現部分

先看一下Shader程式碼的寫法,因為基本上已經逐行註釋,就不花時間和筆墨仔細講解了,詳細註釋的程式碼如下:

Shader "淺墨Shader程式設計/Volume8/運動模糊特效標準版"   
{  
    //------------------------------------【屬性值】------------------------------------  
    Properties  
    {  
        _MainTex("主紋理 (RGB)", 2D) = "white" {}  
        _IterationNumber("迭代次數", Int)=16   
    }  
  
    //------------------------------------【唯一的子著色器】------------------------------------  
    SubShader  
    {     
        //--------------------------------唯一的通道-------------------------------  
        Pass  
        {  
            //設定深度測試模式:渲染所有畫素.等同於關閉透明度測試(AlphaTest Off)  
            ZTest Always  
  
            //===========開啟CG著色器語言編寫模組===========  
            CGPROGRAM  
  
            //編譯指令: 指定著色器編譯目標為Shader Model 3.0  
            #pragma target 3.0  
  
            //編譯指令:告知編譯器頂點和片段著色函式的名稱  
            #pragma vertex vert  
            #pragma fragment frag  
  
            //包含輔助CG標頭檔案  
            #include "UnityCG.cginc"  
  
            //外部變數的宣告  
            uniform sampler2D _MainTex;  
            uniform float _Value;  
            uniform float _Value2;  
            uniform float _Value3;  
            uniform int _IterationNumber;  
  
            //頂點輸入結構  
            struct vertexInput  
            {  
                float4 vertex : POSITION;//頂點位置  
                float4 color : COLOR;//顏色值  
                float2 texcoord : TEXCOORD0;//一級紋理座標  
            };  
  
            //頂點輸出結構  
            struct vertexOutput  
            {  
                half2 texcoord : TEXCOORD0;//一級紋理座標  
                float4 vertex : SV_POSITION;//畫素位置  
                fixed4 color : COLOR;//顏色值  
            };  
  
  
            //--------------------------------【頂點著色函式】-----------------------------  
            // 輸入:頂點輸入結構體  
            // 輸出:頂點輸出結構體  
            //---------------------------------------------------------------------------------  
            vertexOutput vert(vertexInput Input)  
            {  
                //【1】宣告一個輸出結構物件  
                vertexOutput Output;  
  
                //【2】填充此輸出結構  
                //輸出的頂點位置為模型檢視投影矩陣乘以頂點位置,也就是將三維空間中的座標投影到了二維視窗  
                Output.vertex = mul(UNITY_MATRIX_MVP, Input.vertex);  
                //輸出的紋理座標也就是輸入的紋理座標  
                Output.texcoord = Input.texcoord;  
                //輸出的顏色值也就是輸入的顏色值  
                Output.color = Input.color;  
  
                //【3】返回此輸出結構物件  
                return Output;  
            }  
  
            //--------------------------------【片段著色函式】-----------------------------  
            // 輸入:頂點輸出結構體  
            // 輸出:float4型的顏色值  
            //---------------------------------------------------------------------------------  
            float4 frag(vertexOutput i) : COLOR  
            {  
                //【1】設定中心座標  
                float2 center = float2(_Value2, _Value3);  
                //【2】獲取紋理座標的x,y座標值  
                float2 uv = i.texcoord.xy;  
                //【3】紋理座標按照中心位置進行一個偏移  
                uv -= center;  
                //【4】初始化一個顏色值  
                float4 color = float4(0.0, 0.0, 0.0, 0.0);  
                //【5】將Value乘以一個係數  
                _Value *= 0.085;  
                //【6】設定座標縮放比例的值  
                float scale = 1;  
  
                //【7】進行紋理顏色的迭代  
                for (int j = 1; j < _IterationNumber; ++j)  
                {  
                    //將主紋理在不同座標取樣下的顏色值進行迭代累加  
                    color += tex2D(_MainTex, uv * scale + center);  
                    //座標縮放比例依據迴圈引數的改變而變化  
                    scale = 1 + (float(j * _Value));  
                }  
  
                //【8】將最終的顏色值除以迭代次數,取平均值  
                color /= (float)_IterationNumber;  
  
                //【9】返回最終的顏色值  
                return  color;  
            }  
  
            //===========結束CG著色器語言編寫模組===========  
            ENDCG  
        }  
    }  
}  

可以發現,這是一個單子著色器、單通道的頂點&片段著色器,頂點著色函式vert中基本上都是寫的比較中規中矩的程式碼,精髓之處在於片段著色器frag中,用一個for

迴圈,將畫素顏色按照一條直線(uv * scale + center)進行了迭代取樣累加,最終將取樣的顏色的總和除以取樣次數,得到了想要實現的運動模糊效果。

3.2 指令碼實現部分

指令碼檔案的實現方面,如下的即個點是要提出來專門講一下的,即Shader檔案的獲取方法和OnRenderImage函式、Blit函式。

3.2.1 Shader檔案的獲取

Shader檔案的獲取可以使用Shader.Find函式實現。需要注意,Shader.Find函式引數應該和Shader程式碼中的名稱一致,也就是下面的程式碼框架中xxx的值,而不是Shader的檔名:

Shader "xxxx"  
{  
   
}  

舉個例子,指令碼程式碼如果是這樣:

CurShader = Shader.Find ("淺墨Shader程式設計/Volume8/運動模糊特效標準版");  

那麼獲取到的Shader,檔名是任意的,但Shader程式碼框架肯定是這樣:

Shader "淺墨Shader程式設計/Volume8/運動模糊特效標準版"  
{  
 ……  
}  

3.2.2 OnRenderImage函式與Blit函式

OnRenderImage()函式是MonoBehaviour中提供的一個可供我們重寫的函式,它在unity完成所有圖片的渲染後被呼叫。所以我們想實現螢幕特效,主要依靠它來實現。而OnRenderImage函式的函式原型是:

void OnRenderImage(RenderTexture sourceTexture,RenderTexture destTexture);  

另外,我們需要配合一個Graphics.Blit函式,實現從源紋理到目標渲染紋理的拷貝過程,其原型如下三種:

public static void Blit(Texture source,RenderTexture dest);  
public static void Blit(Texture source,RenderTexture dest, Material mat, int pass = -1);  
public static void Blit(Texture source,Material mat, int pass = -1);  

其中。

第一個引數,Texture型別的source,原始紋理。

第二個引數,RenderTexture型別的dest,目標渲染紋理,若為null,表示直接將原始紋理拷貝到螢幕之上。

第三個引數,Material型別的mat,使用的材質(其實也就是Shader),根據不同材質的準備,就是在這裡實現後期的效果的。

第四個引數,int型別的pass,有預設值 -1,表示使用所有的pass。用於指定使用哪一個pass。

說個題外話,其實在很久之前的Win32 API遊戲程式設計中,同樣原理和相似用途的Blit函式用得太多了。

好的,最後看一下實現螢幕特效的核心程式碼,如下:

void OnRenderImage(RenderTexture sourceTexture, RenderTexture destTexture)  
 {  
    //著色器例項不為空,就進行引數設定  
    if (CurShader != null)  
    {  
        //設定Shader中的外部變數  
        material.SetFloat("_IterationNumber", IterationNumber);  
        material.SetFloat("_Value", Intensity);  
        material.SetFloat("_Value2", OffsetX);  
        material.SetFloat("_Value3", OffsetY);  
        material.SetFloat("_Value4", blurWidth);  
        material.SetVector("_ScreenResolution", new Vector4(sourceTexture.width, sourceTexture.height, 0.0f, 0.0f));  
  
        //拷貝源紋理到目標渲染紋理,加上我們的材質效果  
        Graphics.Blit(sourceTexture, destTexture, material);  
    }  
    //著色器例項為空,直接拷貝螢幕上的效果。此情況下是沒有實現螢幕特效的  
    else  
    {  
        //直接拷貝源紋理到目標渲染紋理  
        Graphics.Blit(sourceTexture, destTexture);  
    }  

最後看一下詳細註釋後的指令碼完整實現程式碼:

using UnityEngine;  
using System.Collections;  
  
[ExecuteInEditMode]  
  
public class MotionBlurEffects : MonoBehaviour  
{  
  
    //-------------------變數宣告部分-------------------  
    #region Variables  
    public Shader CurShader;//著色器例項  
    private Vector4 ScreenResolution;//螢幕解析度  
    private Material CurMaterial;//當前的材質  
  
    [Range(5, 50)]  
    public float IterationNumber = 15;  
    [Range(-0.5f, 0.5f)]  
    public float Intensity = 0.125f;  
    [Range(-2f, 2f)]  
    public float OffsetX = 0.5f;  
    [Range(-2f, 2f)]  
    public float OffsetY = 0.5f;  
  
  
    public static float ChangeValue;  
    public static float ChangeValue2;  
    public static float ChangeValue3;  
    public static float ChangeValue4;  
    #endregion  
  
  
    //-------------------------材質的get&set----------------------------  
    #region MaterialGetAndSet  
    Material material  
    {  
        get  
        {  
            if (CurMaterial == null)  
            {  
                CurMaterial = new Material(CurShader);  
                CurMaterial.hideFlags = HideFlags.HideAndDontSave;  
            }  
            return CurMaterial;  
        }  
    }  
    #endregion  
  
    //-----------------------------------------【Start()函式】---------------------------------------------    
    // 說明:此函式僅在Update函式第一次被呼叫前被呼叫  
    //--------------------------------------------------------------------------------------------------------  
    void Start()  
    {  
        //依此賦值  
        ChangeValue = Intensity;  
        ChangeValue2 = OffsetX;  
        ChangeValue3 = OffsetY;  
        ChangeValue4 = IterationNumber;  
  
        //找到當前的Shader檔案  
        CurShader = Shader.Find("淺墨Shader程式設計/Volume8/運動模糊特效標準版");  
  
        //判斷是否支援螢幕特效  
        if (!SystemInfo.supportsImageEffects)  
        {  
            enabled = false;  
            return;  
        }  
    }  
  
    //-------------------------------------【OnRenderImage()函式】------------------------------------    
    // 說明:此函式在當完成所有渲染圖片後被呼叫,用來渲染圖片後期效果  
    //--------------------------------------------------------------------------------------------------------  
    void OnRenderImage(RenderTexture sourceTexture, RenderTexture destTexture)  
    {  
        //著色器例項不為空,就進行引數設定  
        if (CurShader != null)  
        {  
            //設定Shader中的外部變數  
            material.SetFloat("_IterationNumber", IterationNumber);  
            material.SetFloat("_Value", Intensity);  
            material.SetFloat("_Value2", OffsetX);  
            material.SetFloat("_Value3", OffsetY);  
            material.SetVector("_ScreenResolution", new Vector4(sourceTexture.width, sourceTexture.height, 0.0f, 0.0f));  
  
            //拷貝源紋理到目標渲染紋理,加上我們的材質效果  
            Graphics.Blit(sourceTexture, destTexture, material);  
        }  
        //著色器例項為空,直接拷貝螢幕上的效果。此情況下是沒有實現螢幕特效的  
        else  
        {  
            //直接拷貝源紋理到目標渲染紋理  
            Graphics.Blit(sourceTexture, destTexture);  
        }  
          
    }  
  
  
    //-----------------------------------------【OnValidate()函式】--------------------------------------    
    // 說明:此函式在編輯器中該指令碼的某個值發生了改變後被呼叫  
    //--------------------------------------------------------------------------------------------------------  
    void OnValidate()  
    {  
        //將編輯器中的值賦值回來,確保在編輯器中值的改變立刻讓結果生效  
        ChangeValue4 = IterationNumber;  
        ChangeValue = Intensity;  
        ChangeValue2 = OffsetX;  
        ChangeValue3 = OffsetY;  
  
    }  
  
    //-----------------------------------------【Update()函式】------------------------------------------    
    // 說明:此函式在每一幀中都會被呼叫  
    //--------------------------------------------------------------------------------------------------------   
    void Update()  
    {  
        if (Application.isPlaying)  
        {  
            //賦值  
            IterationNumber = ChangeValue4;  
            Intensity = ChangeValue;  
            OffsetX = ChangeValue2;  
            OffsetY = ChangeValue3;  
  
        }  
  
        //找到對應的Shader檔案  
#if UNITY_EDITOR  
        if (Application.isPlaying != true)  
        {  
            CurShader = Shader.Find("淺墨Shader程式設計/Volume8/運動模糊特效標準版");  
  
        }  
#endif  
    }  
  
  
    //-----------------------------------------【OnDisable()函式】---------------------------------------    
    // 說明:當物件變為不可用或非啟用狀態時此函式便被呼叫    
    //--------------------------------------------------------------------------------------------------------  
    void OnDisable()  
    {  
        if (CurMaterial)  
        {  
            DestroyImmediate(CurMaterial);  
        }  
    }  
}  

3.3 關於如何使用此特效

使用方面的話比較簡單,把指令碼檔案拖到主攝像機上面,效果就出來了。

指令碼檔案中有如下這些引數可以調整,得到不同的模糊效果:


  • Iteration Number- 迭代次數
  • Intensity - 模糊強度
  • Offset X - X方向上的偏移
  • Offset Y - Y方向上的偏移