1. 程式人生 > >【淺墨Unity3D Shader程式設計】之五 聖誕夜篇: Unity中Shader的三種形態對比&混合操作合輯

【淺墨Unity3D Shader程式設計】之五 聖誕夜篇: Unity中Shader的三種形態對比&混合操作合輯

本系列文章由出品,轉載請註明出處。 

QQ交流群:330595914

本文算是固定功能Shader的最後一篇,下一次更新應該就會開始講解表面Shader,而講解完表面Shader,後續文章最終會講解到頂點著色器和片段著色器(也就是可程式設計Shader)。
文章第一部分複習和進一步瞭解了Unity中Shader的三種形態,然後講解了固定功能Shader中混合操作的方方面面,然後以6個Shader的書寫作為實戰內容,最後建立了一個溫馨美好的聖誕夜場景進行了Shader的測試。

依舊是國際慣例,先上本文配套程式的截圖吧。
聖誕節就快到了,而下次更新就已經過了聖誕節,於是這次更新淺墨就提前把這個場景放出來吧,預祝大家聖誕節快樂~



雪花飄落:



可愛的聖誕雪人:



精心裝扮的聖誕樹:



月是故鄉明:

霧氣瀰漫:

OK,圖先就上這麼多。文章末尾有更多的執行截圖,並提供了源工程的下載。可執行的exe下載在這裡:

【可執行的exe遊戲場景請點選這裡下載試玩】


 好的,我們正式開始。

一、再談Unity中Shader的三種形態
因為Unity中基礎的固定功能Shader的知識點基本上講完,下期開始就要準備講表面著色器(Surface Shader)了,所以在文章開頭,讓我們複習和更深入瞭解一下Unity中Shader的三種形態。

在Unity中,Shader便可以分成如下三種基本型別:



1.固定功能著色器(FixedFunction Shader)

2.表面著色器(SurfaceShader)

3.頂點著色器&片段著色器(Vertex Shader & Fragment Shader)



顧名思義,其中的固定功能著色器便是我們所說的固定功能渲染管線(fixed-functionrenderingpipelines)的具體表現,而表面著色器、頂點著色器以及片段著色器便屬於可程式設計渲染管線。下面分別對其進行簡單的介紹。





1.1Unity中的Shader形態之一:固定功能Shader


這裡的固定功能著色器可以說是Unity為Shader的書寫自帶的一層殼,Unity已經在內部為我們做了大量的工作,我們只要稍微記住一些關鍵字、一些規範就可以實現出很多不錯的效果。固定功能著色器是我們初學Unity Shader的最近幾篇文章中的主要學習物件。而後面的表面著色器、頂點著色器以及片段著色器就是在固定功能著色器的基礎上嵌套了CG語言的程式碼而成的更加複雜的著色器。我們來看看他們的一些基本概念。

固定管線是為了相容老式顯示卡。都為頂點光照,就是我們前四篇文章加上這篇文章中講到的內容。
其特徵是裡面的核心是下面Material材質屬性塊、沒有CGPROGRAM和ENDCG塊,以及各種頂點著色和片段著色的巨集命令。

一個光照材質完備版的固定功能Shader示例如下:

[cpp]view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
Shader "淺墨Shader程式設計/Volume5/固定功能的Shader示例"
{    
//-------------------------------【屬性】-----------------------------------------  
    Properties     
    {    
        _Color ("主顏色", Color) = (1,1,1,0)    
        _SpecColor ("高光顏色", Color) = (1,1,1,1)    
        _Emission ("自發光顏色", Color) = (0,0,0,0)    
        _Shininess ("光澤度", Range (0.01, 1)) = 0.7    
        _MainTex ("基本紋理", 2D) = "white" {}    
    }    

//--------------------------------【子著色器】--------------------------------  
    SubShader    
    {    
//----------------通道---------------  
        Pass    
        {    
//-----------材質------------  
            Material    
            {    
//可調節的漫反射光和環境光反射顏色  
                Diffuse [_Color]    
                Ambient [_Color]    
//光澤度  
                Shininess [_Shininess]    
//高光顏色  
                Specular [_SpecColor]    
//自發光顏色  
                Emission [_Emission]    
            }    
//開啟光照  
            Lighting On    
//開啟獨立鏡面反射  
            SeparateSpecular On    
//設定紋理並進行紋理混合  
            SetTexture [_MainTex]     
            {    
                Combine texture * primary DOUBLE, texture * primary    
            }    
        }    
    }    
}      我們將此Shader編譯後賦給材質,得到如下效果:


實際場景中的測試效果:



1.2 Unity中的Shader形態之二:表面著色器SurfaceShader


這部分算是Unity微創新自創的一套著色器標準。
表面著色器(Surface Shader)這個概念更多的只是在Unity中聽說,可以說是Unity自己發揚光大的一項使Shader的書寫門檻降低和更易用的技術。我們會在接下來的學習中逐漸意識到Unity是如何為我們把Shader的複雜性包裝起來,使其書寫的過程更便捷和易用
的。一些特性如下:

•      SurfaceShader可以認為是一個光照Shader的語法塊、一個光照VS/FS的生成器。減少了開發者寫重複程式碼的需要。
•      特徵是在SubShader裡出現CGPROGRAM和ENDCG塊。(而不是出現在Pass裡。因為SurfaceShader自己會編譯成多個Pass。)
•      編譯指令是:
#pragma surface surfaceFunction lightModel[optionalparams]
o     surfaceFunction:surfaceShader函式,形如void surf (Input IN, inoutSurfaceOutput o)
o     lightModel:使用的光照模式。包括Lambert(漫反射)和BlinnPhong(鏡面反射)。
     也可以自己定義光照函式。比如編譯指令為#pragma surface surf MyCalc
     在Shader裡定義half4 LightingMyCalc (SurfaceOutputs, 引數略)函式進行處理(函式名在簽名加上了“Lighting”)。
•      我們自己定義輸入資料結構(比如上面的Input)、編寫自己的Surface函式處理輸入、最終輸出修改過後的SurfaceOutput。而SurfaceOutput的定義為:
[cpp]view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
struct SurfaceOutput  
{  
    half3 Albedo; // 紋理顏色值(r, g, b)
    half3 Normal; // 法向量(x, y, z)
    half3 Emission; // 自發光顏色值(r, g, b)
    half Specular; // 鏡面反射度
    half Gloss; // 光澤度
    half Alpha; // Alpha不透明度
};  
上面是一些特性總結,讓我們看一個具體Shader示例:

[cpp]view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
Shader "淺墨Shader程式設計/Volume5/表面Shader示例 "
{    
//-------------------------------【屬性】-----------------------------------------  
    Properties     
    {    
        _MainTex ("【紋理】Texture", 2D) = "white" {}    
        _BumpMap ("【凹凸紋理】Bumpmap", 2D) = "bump" {}    
        _RimColor ("【邊緣顏色】Rim Color", Color) = (0.17,0.36,0.81,0.0)    
        _RimPower ("【邊緣顏色強度】Rim Power", Range(0.6,9.0)) = 1.0    
    }    

//----------------------------【開始一個子著色器】---------------------------  
    SubShader     
    {    
//渲染型別為Opaque,不透明  
        Tags { "RenderType" = "Opaque" }    

//-------------------開始CG著色器程式語言段-----------------  
        CGPROGRAM    

//使用蘭伯特光照模式  
        #pragma surface surf Lambert  

//輸入結構  
struct Input     
        {    
            float2 uv_MainTex;//紋理貼圖  
            float2 uv_BumpMap;//法線貼圖  
            float3 viewDir;//觀察方向  
        };    

//變數宣告  
        sampler2D _MainTex;//主紋理  
        sampler2D _BumpMap;//凹凸紋理  
        float4 _RimColor;//邊緣顏色  
float _RimPower;//邊緣顏色強度  

//表面著色函式的編寫  
void surf (Input IN, inout SurfaceOutput o)    
        {    
//表面反射顏色為紋理顏色  
            o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;    
//表面法線為凹凸紋理的顏色  
            o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));    
//邊緣顏色  
            half rim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));    
//邊緣顏色強度  
            o.Emission = _RimColor.rgb * pow (rim, _RimPower);    
        }    

//-------------------結束CG著色器程式語言段------------------  
        ENDCG    
    }     

//“備胎”為普通漫反射  
    Fallback "Diffuse"
}  
我們將此Shader編譯後賦給材質,得到如下效果:

調各種顏色玩一玩:




而實際場景中的測試效果(對應於一開始的金色):


1.3 Unity中的Shader形態之三:可程式設計Shader

可程式設計Shader其實就是頂點著色器和片段著色器,這一部分和DirectX系的HLSL和CG著色器語言聯絡緊密。其實就是Unity給HLSL和CG報了一個ShaderLab的殼。
研究過Direct3D和OpenGL著色器程式設計的童鞋們一定對頂點著色器和片段著色器不陌生。我們來簡單介紹一下他們的用途。
頂點著色器:產生紋理座標,顏色,點大小,霧座標,然後把它們傳遞給裁剪階段。
片段著色器:進行紋理查詢,決定什麼時候執行紋理查詢,是否進行紋理查詢,及把什麼作為紋理座標。

可程式設計Shader的特點為:

功能最強大、最自由的形態。
特徵是在Pass裡出現CGPROGRAM和ENDCG塊
編譯指令#pragma。詳見官網Cg snippets。其中重要的包括:

編譯指令

示例/含義

#pragma vertex name
#pragma fragment name

替換name,來指定Vertex Shader函式、Fragment Shader函式。

#pragma target name

替換name(為2.0、3.0等)。設定編譯目標shader model的版本。

#pragma only_renderers name name ...
#pragma exclude_renderers name name...

#pragma only_renderers gles gles3,
#pragma exclude_renderers d3d9 d3d11 opengl,
只為指定渲染平臺(render platform)編譯

關於引用庫。通過形如#include "UnityCG.cginc"引入指定的庫。常用的就是UnityCG.cginc了。其他庫詳見官網Built-in shader include files
ShaderLab內建值。Unity給Shader程式提供了便捷的、常用的值,比如下面例子中的UNITY_MATRIX_MVP就代表了這個時刻的MVP矩陣。詳見官網ShaderLab built-in values
Shader輸入輸出引數語義(Semantics)。在管線流程中每個階段之間(比如Vertex Shader階段和FragmentShader階段之間)的輸入輸出引數,通過語義字串,來指定引數的含義。常用的語義包括:COLOR、SV_Position、TEXCOORD[n]。完整的引數語義可見HLSL Semantic(由於是HLSL的連線,所以可能不完全在Unity裡可以使用)。
特別地,因為Vertex Shader的的輸入往往是管線的最開始,Unity為此內建了常用的資料結構:

資料結構

含義

appdata_base

頂點著色器輸入位置、法線以及一個紋理座標。

appdata_tan