【淺墨Unity3D Shader編程】之六 暗黑城堡篇: 表面着色器(Surface Shader)的寫法(一)

本系列文章由@淺墨_毛星雲 出品,轉載請註明出處。  

文章鏈接:http://hpw123.net/plus/view.php?aid=165

作者:毛星雲(淺墨)    微博:http://weibo.com/u/1723155442

郵箱: [email protected]

QQ交流羣:330595914

更多文章盡在:http://www.hpw123.net



 

本文主要講解了Unity中SurfaceShader的具體寫法,以及幾個常用的CG函數的用法。

在這裏先說明一下,表面着色器將分爲兩次講解,本文介紹表面着色器的基本概念和一些寫法,用內置的蘭伯特光照模式來進行Surface Shader的書寫,而下次將介紹Surface Shader+自定義的光照模式的寫法。

 

PS:最近幾天,在完美世界、騰訊互娛工作多年的幾個朋友們問了淺墨一些表面着色器相關的Shader寫法,淺墨當時回答他們的時候自己也是似懂非懂。通過這篇文章的書寫,現在算是對這方面知識有了進一步的理解。所以說嘛,寫作是總結自己所學的一種很好的方式~

 

OK,言歸正傳,依然是先來看看本文配套的遊戲場景截圖。


運行遊戲,音樂響起,首先是一個歐式風格的集市映入眼簾:

 

 

 

 

雨淅瀝瀝、天空、晚霞、海平面:

 

 

天空中,一羣飛鳥飛過:

 

 

 

峽谷:


 

暗黑城堡:

 


 

在小集市中逛蕩:

 

 

城堡概況圖:

 

 

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

 

【可運行的exe遊戲場景請點擊這裏下載試玩】

 

 

 好的,我們正式開始。



一、表面着色器的標準輸出結構(Surface Output)

 


 

要書寫Surface Shader,瞭解表面着色器的標準輸出結構必不可少。此爲表面着色器書寫的第一個要素。

而定義一個“表面函數(surface function)”,需要輸入相關的UV或數據信息,並在輸出結構中填充SurfaceOutput。SurfaceOutput基本上描述了表面的特性(光照的顏色反射率、法線、散射、鏡面等)。其實還是需要用CG或者HLSL編寫此部分的代碼。

我們其實是通過表面着色器(Surface Shader)來編譯這段CG或者HLSL代碼的,然後計算出需要填充輸入什麼,輸出什麼等相關信息,併產生真實的頂點(vertex)&像素(pixel)着色器,以及把渲染路徑傳遞到正向或延時渲染路徑。

說白了,還是那句話,Surface Shader是Unity微創新自創的一套着色器標準,是Unity自己發揚光大的一項使Shader的書寫門檻降低和更易用的技術。

 

我們之前的文章中已經稍微瞭解過,表面着色器(Surface Shader)的標準輸出結構是這樣的:



    struct SurfaceOutput   
    {  
        half3 Albedo;            //反射率,也就是紋理顏色值(r,g,b)   
        half3 Normal;            //法線,法向量(x, y, z)   
        half3 Emission;          //自發光顏色值(r, g,b)   
        half Specular;           //鏡面反射度   
        half Gloss;              //光澤度  
        half Alpha;              //透明度  
    };  




而這個結構體的用法,其實就是對這些需要用到的成員變量在surf函數中賦一下值,比如說這樣:



//表面着色函數的編寫  
void surf (Input IN, inout SurfaceOutput o)  
{  
    //反射率,也就是紋理顏色值賦爲(0.6, 0.6, 0.6)  
       o.Albedo= 0.6;  
} 



注意到Albedo是half3類型的。那麼o.Albedo = 0.6和o.Albedo = float3(0.6,0.6,0.6)是等價的。

 


二、表面着色器的編譯指令

  



表面着色器的編譯指令爲編寫表面着色器的第二個要素。

表面着色器放在CGPROGRAM .. ENDCG塊裏面,就像其他的着色器一樣。區別是:


其必須嵌在子着色器(SubShader)塊裏面。而不是Pass塊裏面。因爲表面着色器( Surface shader)將在多重通道(multiple passes)內編譯自己,而不是放在某個Pass中。

我們甚至可以這樣說,如果你寫表面着色器,用不到Pass代碼塊,一般直接在SubShader塊中完成就行了。

 


使用的 #pragma surface...指令,以聲明這是一個表面着色器。指令的句法是:

 

#pragma surface surfaceFunction lightModel[optionalparams]

 

 

所需參數的講解:

  • surfaceFunction - 表示指定名稱的Cg函數中有表面着色器(surface shader)代碼。這個函數的格式應該是這樣:void surf (Input IN,inout SurfaceOutput o), 其中Input是我們自己定義的結構。Input結構中應該包含所需的紋理座標(texture coordinates)和和表面函數(surfaceFunction)所需要的額外的必需變量。
  • lightModel -使用的光照模式。內置的是Lambert (diffuse)和 BlinnPhong (specular)兩種,一般習慣用Lambert,也就是蘭伯特光照模式。而編寫自己的光照模式我們將在下次更新中講解。

 

 

可以根據自己的需要,進階選這樣的一些可選參數:

 

  • alpha -透明( Alpha)混合模式。使用它可以寫出半透明的着色器。
  • alphatest:VariableName -透明( Alpha)測試模式。使用它可以寫出 鏤空效果的着色器。鏤空大小的變量(VariableName)是一個float型的變量。
  • vertex:VertexFunction - 自定義的頂點函數(vertex function)。相關寫法可參考Unity內建的Shader:樹皮着色器(Tree Bark shader),如Tree Creator Bark、Tree Soft Occlusion Bark這兩個Shader。

  • finalcolor:ColorFunction - 自定義的最終顏色函數(final color function)。 比如說這樣:


    #pragma surfacesurf Lambert finalcolor:mycolor。  


相關Shader示例可見下文Shader實戰部分的第五個Shader(紋理載入+顏色可調)。


  • exclude_path:prepass 或者 exclude_path:forward - 使用指定的渲染路徑,不需要生成通道。
  • addshadow - 添加陰影投射 & 收集通道(collector passes)。通常用自定義頂點修改,使陰影也能投射在任何程序的頂點動畫上。
  • dualforward - 在正向(forward)渲染路徑中使用 雙重光照貼圖(dual lightmaps)
  • fullforwardshadows - 在正向(forward)渲染路徑中支持所有陰影類型。
  • decal:add - 添加貼圖着色器(decal shader) (例如: terrain AddPass)。
  • decal:blend - 混合半透明的貼圖着色器(Semitransparent decal shader)。
  • softvegetation - 使表面着色器(surface shader)僅能在Soft Vegetation打開時渲染。
  • noambient - 不適用於任何環境光照(ambient lighting)或者球面調和光照(spherical harmonics lights)。
  • novertexlights - 在正向渲染(Forward rendering)中不適用於球面調和光照(spherical harmonics lights)或者每個頂點光照(per-vertex lights)。
  • nolightmap - 在這個着色器上禁用光照貼圖(lightmap)。(適合寫一些小着色器)
  • nodirlightmap - 在這個着色器上禁用方向光照貼圖(directional lightmaps)。 (適合寫一些小着色器)。
  • noforwardadd - 禁用正向渲染添加通道(Forward rendering additive pass)。 這會使這個着色器支持一個完整的方向光和所有光照的per-vertex/SH計算。(也是適合寫一些小着色器).
  • approxview - 着色器需要計算標準視圖的每個頂點(per-vertex)方向而不是每個像索(per-pixel)方向。 這樣更快,但是視圖方向不完全是當前攝像機(camera) 所接近的表面。
  • halfasview - 在光照函數(lighting function)中傳遞進來的是half-direction向量,而不是視圖方向(view-direction)向量。 Half-direction會計算且會把每個頂點(per vertex)標準化。這樣做會提高執行效率,但是準確率會打折扣。


此外,我們還可以在 CGPROGRA內編寫 #pragma debug,然後表面編譯器(surface compiler)會進行解釋生成代碼。

 


三、表面着色器輸入結構(Input Structure)




表面着色器書寫的第三個要素是指明表面輸入結構(Input Structure)。

Input 這個輸入結構通常擁有着色器需要的所有紋理座標(texture coordinates)。紋理座標(Texturecoordinates)必須被命名爲“uv”後接紋理(texture)名字。(或者uv2開始,使用第二紋理座標集)。

可以在輸入結構中根據自己的需要,可選附加這樣的一些候選值:


  • float3 viewDir - 視圖方向( view direction)值。爲了計算視差效果(Parallax effects),邊緣光照(rim lighting)等,需要包含視圖方向( view direction)值。
  • float4 with COLOR semantic -每個頂點(per-vertex)顏色的插值。
  • float4 screenPos - 屏幕空間中的位置。 爲了反射效果,需要包含屏幕空間中的位置信息。比如在Dark Unity中所使用的 WetStreet着色器。
  • float3 worldPos - 世界空間中的位置。
  • float3 worldRefl - 世界空間中的反射向量。如果表面着色器(surface shader)不寫入法線(o.Normal)參數,將包含這個參數。 請參考這個例子:Reflect-Diffuse 着色器。
  • float3 worldNormal - 世界空間中的法線向量(normal vector)。如果表面着色器(surface shader)不寫入法線(o.Normal)參數,將包含這個參數。
  • float3 worldRefl; INTERNAL_DATA - 世界空間中的反射向量。如果表面着色器(surface shader)不寫入法線(o.Normal)參數,將包含這個參數。爲了獲得基於每個頂點法線貼圖( per-pixel normal map)的反射向量(reflection vector)需要使用世界反射向量(WorldReflectionVector (IN, o.Normal))。請參考這個例子: Reflect-Bumped着色器。
  • float3 worldNormal; INTERNAL_DATA -世界空間中的法線向量(normal vector)。如果表面着色器(surface shader)不寫入法線(o.Normal)參數,將包含這個參數。爲了獲得基於每個頂點法線貼圖( per-pixel normal map)的法線向量(normal vector)需要使用世界法線向量(WorldNormalVector (IN, o.Normal))。

 

 

 

四、一些本次寫Shader用到的CG函數講解

 




本次Shader書寫用到了四個CG着色器編程語言中的函數——UnpackNormal、saturate、dot、tex2D。下面將分別對其進行講解。




4.1UnpackNormal( )函數


UnpackNormal接受一個fixed4的輸入,並將其轉換爲所對應的法線值(fixed3),並將其賦給輸出的Normal,就可以參與到光線運算中完成接下來的渲染工作了。

一個調用示例:

 

o.Normal = UnpackNormal (tex2D (_BumpMap,IN.uv_BumpMap));  



4.2saturate( )函數


saturate的字面解釋是浸溼,浸透。其作用其實也就是將取值轉化爲[0,1]之內的一個值。

 

其可選的原型如下:

 

    float saturate(float x);  
    float1 saturate(float1 x);  
    float2 saturate(float2 x);  
    float3 saturate(float3 x);  
    float4 saturate(float4 x);  
    half saturate(half x);  
    half1 saturate(half1 x);  
    half2 saturate(half2 x);  
    half3 saturate(half3 x);  
    half4 saturate(half4 x);  
    fixed saturate(fixed x);  
    fixed1 saturate(fixed1 x);  
    fixed2 saturate(fixed2 x);  
    fixed3 saturate(fixed3 x);  
    fixed4 saturate(fixed4 x);  



其唯一的一個參數x表示矢量或者標量的飽和值(Vector or scalar to saturate.),也就是將這個x轉化爲[0,1]之內的值。

 

其返回值:

  • 如果x取值小於0,則返回值爲0.
  • 如果x取值大於1,則返回值爲1.
  • 若x在0到1之間,則直接返回x的值。

 

其代碼實現大致如下:


    float saturate(float x)  
    {  
        return max(0,min(1, x));  
    }  


 

一個調用示例:

 

half rim = 1.0 - saturate(dot(normalize(IN.viewDir), o.Normal));  


 

 

4.3 dot( )函數


dot函數顧名思義,是高等數學中的點積操作,用於返回兩個向量的標量積。

 

可選原型如下:

 

派生到我的代碼片

    float dot(float a, float b);  
    float dot(float1 a, float1 b);  
    float dot(float2 a, float2 b);  
    float dot(float3 a, float3 b);  
    float dot(float4 a, float4 b);  
    half dot(half a, half b);  
    half dot(half1 a, half1 b);  
    half dot(half2 a, half2 b);  
    half dot(half3 a, half3 b);  
    half dot(half4 a, half4 b);  
    fixed dot(fixed a, fixed b);  
    fixed dot(fixed1 a, fixed1 b);  
    fixed dot(fixed2 a, fixed2 b);  
    fixed dot(fixed3 a, fixed3 b);  
    fixed dot(fixed4 a, fixed4 b); 


 

其代碼實現大致是這樣的:



派生到我的代碼片

    float dot(float4 a, float4 b)  
    {  
        return a.x*b.x +a.y*b.y + a.z*b.z + a.w*b.w;  
    }  


 

一個調用示例:


    float answer= dot (normalize(IN.viewDir),o.Normal);  

 

4.4 tex2D( )函數

 

讓我們看一看CG中用得比較多的用於2D紋理採樣的tex2D函數的用法。其備選的原型也是非常之多:

 

    float4 tex2D(sampler2D samp, float2 s)  
    float4 tex2D(sampler2D samp, float2 s, inttexelOff)  
    float4 tex2D(sampler2D samp, float3 s)  
    float4 tex2D(sampler2D samp, float3 s, inttexelOff)  
    float4 tex2D(sampler2D samp, float2 s,float2 dx, float2 dy)  
    float4 tex2D(sampler2D samp, float2 s,float2 dx, float2 dy, int texelOff)  
    float4 tex2D(sampler2D samp, float3 s,float2 dx, float2 dy)  
    float4 tex2D(sampler2D samp, float3 s,float2 dx, float2 dy, int texelOff)  
    int4 tex2D(isampler2D samp, float2 s)  
    int4 tex2D(isampler2D samp, float2 s, inttexelOff)  
    int4 tex2D(isampler2D samp, float2 s,float2 dx, float2 dy)  
    int4 tex2D(isampler2D samp, float2 s,float2 dx, float2 dy, int texelOff)  
    unsigned int4 tex2D(usampler2D samp, float2s)  
    unsigned int4 tex2D(usampler2D samp, float2s, int texelOff)  
    unsigned int4 tex2D(usampler2D samp, float2s, float2 dx, float2 dy)  
    unsigned int4 tex2D(usampler2D samp, float2s, float2 dx, float2 dy,int texelOff)  


 

參數簡介:

samp-需要查找的採樣對象,也就是填個紋理對象在這裏。

s-需進行查找的紋理座標。

dx-預計算的沿X軸方向的導數。

dy-預計算的沿Y軸方向的導數。

texelOff-添加給最終紋理的偏移量


而其返回值,自然是查找到的紋理。

 

最後,看一個綜合了本次講解的四個函數(UnpackNormal、saturate、tex2D、dot)的Surface Shader中surf函數的示例:

 

    //【2】表面着色函數的編寫  
    void surf (Input IN, inout SurfaceOutput o)  
    {  
           //從主紋理獲取rgb顏色值  
           o.Albedo= tex2D (_MainTex, IN.uv_MainTex).rgb;  
           //從凹凸紋理獲取法線值  
           o.Normal= UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));  
           //從_RimColor參數獲取自發光顏色  
           halfrim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));  
           o.Emission= _RimColor.rgb * pow (rim, _RimPower);  
    }  

  

 

五、寫Shdaer實戰

 

  


上面都是些概念,下面我們將進行一些實戰的Shader書寫,將學到的這些概念用到實際當中去。

本次我們將講解9個表面SurfaceShader的寫法,從最基本的Surface Shader,循序漸進,一點一點加功能,到最後的稍微有點複雜的“凹凸紋理+顏色可調+邊緣光照+細節紋理“表面Shader的寫法。本期的全部Shader的合照如下:


 

在材質界面菜單中的顯示:



OK,下面開始講解,從最基本的開始。

 


1.最基本的Surface Shader



先看一個使用內建光照模式的最基本的Surface Shader應該怎麼寫:



    Shader "淺墨Shader編程/Volume6/24.最基本的SurfaceShader"  
    {  
        //--------------------------------【子着色器】----------------------------------  
        SubShader   
        {  
            //-----------子着色器標籤----------  
            Tags { "RenderType" = "Opaque" }  
      
            //-------------------開始CG着色器編程語言段-----------------    
            CGPROGRAM  
              
            //【1】光照模式聲明:使用蘭伯特光照模式    
            #pragma surface surf Lambert  
      
            //【2】輸入結構    
            struct Input   
            {  
                //四元素的顏色值(RGBA)  
                float4 color : COLOR;  
            };  
      
            //【3】表面着色函數的編寫  
            void surf (Input IN, inout SurfaceOutput o)   
            {  
                //反射率  
                o.Albedo = float3(0.5,0.8,0.3);//(0.5,0.8,0.3)分別對應於RGB分量  
                //而o.Albedo = 0.6;等效於寫o.Albedo = float3(0.6,0.6,0.6);  
            }  
      
            //-------------------結束CG着色器編程語言段------------------    
            ENDCG  
        }  
      
        //“備胎”爲普通漫反射    
        Fallback "Diffuse"  
    }  




可以發現,一個最基本的Surface Shader,至少需要有光照模式的聲明、輸入結構和表面着色函數的編寫這三部分。

 


另外,主要注意其中的surf函數的寫法,就是把上文講到的Surface Output結構體中需要用到的成員變量拿來賦值:

 

    //【2】表面着色函數的編寫  
    void surf (Input IN, inout SurfaceOutput o)  
    {  
           //反射率  
           o.Albedo= float3(0.5,0.8,0.3);//(0.5,0.8,0.3)分別對應於RGB分量  
           //而o.Albedo = 0.6;等效於寫o.Albedo =float3(0.6,0.6,0.6);  
    }  


註釋中已經寫得很明白,且之前也已經講過,o.Albedo = 0.6;等效於寫o.Albedo = float3(0.6,0.6,0.6);

來個舉一反三,則o.Albedo =1;等效於寫o.Albedo= float3(1,1,1);

 

我們將此Shader編譯後賦給材質,得到如下效果:

 

 

 

而在場景中的實測效果爲:

 

  

2.顏色可調

 


在最基本的Surface Shader的基礎上,加上一點代碼,就成了這裏的可調顏色的Surface Shader:

 

    Shader "淺墨Shader編程/Volume6/25.顏色可調的SurfaceShader"  
    {  
        //--------------------------------【屬性】---------------------------------------  
        Properties    
        {  
            _Color ("【主顏色】Main Color", Color) = (0.1,0.3,0.9,1)  
        }  
      
        //--------------------------------【子着色器】----------------------------------  
        SubShader   
        {  
            //-----------子着色器標籤----------  
            Tags { "RenderType"="Opaque" }  
      
            //-------------------開始CG着色器編程語言段-----------------   
            CGPROGRAM  
      
            //【1】光照模式聲明:使用蘭伯特光照模式  
            #pragma surface surf Lambert  
      
            //變量聲明  
            float4 _Color;  
      
            //【2】輸入結構    
            struct Input   
            {  
                //四元素的顏色值(RGBA)  
                float4 color : COLOR;  
            };  
      
            //【3】表面着色函數的編寫  
            void surf (Input IN, inout SurfaceOutput o)   
            {  
                //反射率  
                o.Albedo = _Color.rgb;  
                //透明值  
                o.Alpha = _Color.a;  
            }  
      
            //-------------------結束CG着色器編程語言段------------------    
            ENDCG  
        }   
      
        //“備胎”爲普通漫反射    
        FallBack "Diffuse"  
    }  

 

我們將此Shader編譯後賦給材質,得到如下效果,和之前的固定功能Shader一樣,可以自由調節顏色:


 

其在場景中的實測效果爲:


 

 

3.基本紋理載入




再來看看如何實現一個基本的紋理載入Surface Shader:



    Shader "淺墨Shader編程/Volume6/26.基本紋理載入"  
    {  
        //--------------------------------【屬性】----------------------------------------  
        Properties  
        {  
            _MainTex ("【主紋理】Texture", 2D) = "white" {}  
        }  
      
        //--------------------------------【子着色器】----------------------------------  
        SubShader   
        {  
            //-----------子着色器標籤----------  
            Tags { "RenderType" = "Opaque" }  
      
            //-------------------開始CG着色器編程語言段-----------------   
            CGPROGRAM  
      
            //【1】光照模式聲明:使用蘭伯特光照模式  
            #pragma surface surf Lambert  
      
            //【2】輸入結構    
            struct Input   
            {  
                //紋理的uv值  
                float2 uv_MainTex;  
            };  
      
            //變量聲明  
            sampler2D _MainTex;  
      
            //【3】表面着色函數的編寫  
            void surf (Input IN, inout SurfaceOutput o)   
            {  
                //從紋理獲取rgb顏色值  
                o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;  
            }  
      
            //-------------------結束CG着色器編程語言段------------------    
            ENDCG  
        }   
      
        //“備胎”爲普通漫反射    
        Fallback "Diffuse"  
    }  


 

我們將此Shader編譯後賦給材質,得到如下效果:



場景中的實測效果圖爲:


 

 

4.凹凸紋理載入

 



讓我們慢慢添加特性,使得到的Surface Shader的效果與功能越來越強大。接着來看看Surface Shader的凹凸紋理如何實現:



    Shader "淺墨Shader編程/Volume6/27.凹凸紋理載入"  
    {  
        //--------------------------------【屬性】----------------------------------------  
        Properties   
        {  
            _MainTex ("【主紋理】Texture", 2D) = "white" {}  
            _BumpMap ("【凹凸紋理】Bumpmap", 2D) = "bump" {}  
        }  
      
        //--------------------------------【子着色器】----------------------------------  
        SubShader   
        {  
            //-----------子着色器標籤----------  
            Tags { "RenderType" = "Opaque" }  
      
            //-------------------開始CG着色器編程語言段-----------------   
            CGPROGRAM  
      
            //【1】光照模式聲明:使用蘭伯特光照模式  
            #pragma surface surf Lambert  
      
            //【2】輸入結構    
            struct Input   
            {  
                //主紋理的uv值  
                float2 uv_MainTex;  
                //凹凸紋理的uv值  
                float2 uv_BumpMap;  
            };  
      
            //變量聲明  
            sampler2D _MainTex;//主紋理  
            sampler2D _BumpMap;//凹凸紋理  
      
            //【3】表面着色函數的編寫  
            void surf (Input IN, inout SurfaceOutput o)   
            {  
                //從主紋理獲取rgb顏色值  
                o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;  
                //從凹凸紋理獲取法線值  
                o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));  
            }  
      
            //-------------------結束CG着色器編程語言段------------------    
            ENDCG  
        }  
      
        //“備胎”爲普通漫反射    
        Fallback "Diffuse"  
    }  



我們將此Shader編譯後賦給材質,得到如下效果:


 

 

場景中的實測效果圖爲:


 

 

5.紋理載入+顏色可調


接着看一看紋理如何通過一個finalcolor關鍵字自定義函數,來達到調色的目的:

 


    Shader "淺墨Shader編程/Volume6/28.紋理+顏色修改"  
    {  
        //--------------------------------【屬性】----------------------------------------  
        Properties  
        {  
            _MainTex ("【主紋理】Texture", 2D) = "white" {}  
            _ColorTint ("【色澤】Tint", Color) = (0.6, 0.3, 0.6, 0.3)  
        }  
      
        //--------------------------------【子着色器】----------------------------------  
        SubShader   
        {  
            //-----------子着色器標籤----------  
            Tags { "RenderType" = "Opaque" }  
      
            //-------------------開始CG着色器編程語言段-----------------   
            CGPROGRAM  
      
            //【1】光照模式聲明:使用蘭伯特光照模式+自定義顏色函數  
            #pragma surface surf Lambert finalcolor:setcolor  
      
            //【2】輸入結構    
                struct Input   
                {  
                //紋理的uv值  
                float2 uv_MainTex;  
                };  
      
            //變量聲明  
            fixed4 _ColorTint;  
            sampler2D _MainTex;  
      
            //【3】自定義顏色函數setcolor的編寫  
                void setcolor (Input IN, SurfaceOutput o, inout fixed4 color)  
                {  
                //將自選的顏色值乘給color  
                color *= _ColorTint;  
                }  
      
            //【4】表面着色函數的編寫  
            void surf (Input IN, inout SurfaceOutput o)   
            {  
                //從主紋理獲取rgb顏色值  
                o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;  
            }  
      
            //-------------------結束CG着色器編程語言段------------------    
            ENDCG  
        }   
      
        //“備胎”爲普通漫反射    
        Fallback "Diffuse"  
    }  



我們將此Shader編譯後賦給材質,得到如下效果:


 

調些顏色玩一玩:







場景中的實測效果圖爲:


 

 

 

6. 凹凸紋理+邊緣光照

 

 

在之前凹凸紋理的基礎上讓我們加上喜聞樂見的邊緣光照:

 


    Shader "淺墨Shader編程/Volume6/29.凹凸紋理+邊緣光照"  
    {  
        //--------------------------------【屬性】----------------------------------------  
        Properties   
        {  
            _MainTex ("【主紋理】Texture", 2D) = "white" {}  
            _BumpMap ("【凹凸紋理】Bumpmap", 2D) = "bump" {}  
            _RimColor ("【邊緣顏色】Rim Color", Color) = (0.26,0.19,0.16,0.0)  
            _RimPower ("【邊緣顏色強度】Rim Power", Range(0.5,8.0)) = 3.0  
        }  
      
        //--------------------------------【子着色器】----------------------------------  
        SubShader   
        {  
            //-----------子着色器標籤----------  
            Tags { "RenderType" = "Opaque" }  
      
            //-------------------開始CG着色器編程語言段-----------------   
            CGPROGRAM  
      
            //【1】光照模式聲明:使用蘭伯特光照模式+自定義顏色函數  
            #pragma surface surf Lambert  
      
            //【2】輸入結構    
            struct Input   
            {  
                //主紋理的uv值  
                float2 uv_MainTex;  
                //凹凸紋理的uv值  
                float2 uv_BumpMap;  
                //當前座標的視角方向  
                float3 viewDir;  
            };  
      
            //變量聲明  
            sampler2D _MainTex;//主紋理  
            sampler2D _BumpMap;//凹凸紋理  
            float4 _RimColor;//邊緣顏色  
            float _RimPower;//邊緣顏色強度  
      
            //【3】表面着色函數的編寫  
            void surf (Input IN, inout SurfaceOutput o)   
            {  
                //從主紋理獲取rgb顏色值  
                o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;  
                //從凹凸紋理獲取法線值  
                o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));  
                //從_RimColor參數獲取自發光顏色  
                half rim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));  
                o.Emission = _RimColor.rgb * pow (rim, _RimPower);  
            }  
      
            //-------------------結束CG着色器編程語言段------------------  
            ENDCG  
        }   
      
        //“備胎”爲普通漫反射    
        Fallback "Diffuse"  
    }  




其中的viewDir 意爲WorldSpace View Direction,也就是當前座標的視角方向:


                       

 

關於surf中的兩句新加的代碼在這裏也講一下。

上面已經提到過,Normalize函數,用於獲取到的viewDir座標轉成一個單位向量且方向不變,外面再與點的法線做點積。最外層再用 saturate算出一[0,1]之間的最靠近的值。這樣算出一個rim邊界。原理可以這樣解釋:



                            

 

 

這裏o.Normal就是單位向量。外加Normalize了viewDir。因此求得的點積就是夾角的cos值。因爲cos值越大,夾角越小,所以,這時取反來。這樣,夾角越大,所反射上的顏色就越多。於是就得到的兩邊發光的效果。哈哈這樣明瞭吧。

這裏再介紹一下這個half。CG裏還有類似的float和fixed。half是一種低精度的float,但有時也會被選擇成與float一樣的精度。先就說這麼多吧,後面還會遇到的,到時候再講。

 

我們將此Shader編譯後賦給材質,得到如下效果,除了凹凸紋理的選擇之外,還有邊緣發光顏色和強度可供自由定製:


 

依然是調一些顏色玩一玩:

  

  

 

場景中的實測截圖爲:


 

 

7.凹凸紋理+顏色可調

 



接下來我們看看凹凸紋理+顏色可調的Shader怎麼寫:


    Shader "淺墨Shader編程/Volume6/30.凹凸紋理+顏色可調+邊緣光照"  
    {  
        //--------------------------------【屬性】----------------------------------------  
        Properties   
        {  
            _MainTex ("【主紋理】Texture", 2D) = "white" {}  
            _BumpMap ("【凹凸紋理】Bumpmap", 2D) = "bump" {}  
            _ColorTint ("【色澤】Tint", Color) = (0.6, 0.3, 0.6, 0.3)  
            _RimColor ("【邊緣顏色】Rim Color", Color) = (0.26,0.19,0.16,0.0)  
            _RimPower ("【邊緣顏色強度】Rim Power", Range(0.5,8.0)) = 3.0  
        }  
      
        //--------------------------------【子着色器】----------------------------------  
        SubShader   
        {  
            //-----------子着色器標籤----------  
            Tags { "RenderType" = "Opaque" }  
      
            //-------------------開始CG着色器編程語言段-----------------   
            CGPROGRAM  
      
            //【1】光照模式聲明:使用蘭伯特光照模式+自定義顏色函數  
            #pragma surface surf Lambert finalcolor:setcolor  
      
            //【2】輸入結構    
            struct Input   
            {  
                //主紋理的uv值  
                float2 uv_MainTex;  
                //凹凸紋理的uv值  
                float2 uv_BumpMap;  
                //當前座標的視角方向  
                float3 viewDir;  
            };  
      
            //變量聲明  
            sampler2D _MainTex;  
            sampler2D _BumpMap;  
            fixed4 _ColorTint;  
            float4 _RimColor;  
            float _RimPower;  
      
            //【3】自定義顏色函數setcolor的編寫  
            void setcolor (Input IN, SurfaceOutput o, inout fixed4 color)  
                {  
                color *= _ColorTint;  
                }  
      
            //【4】表面着色函數的編寫  
            void surf (Input IN, inout SurfaceOutput o)   
            {  
                //從主紋理獲取rgb顏色值  
                o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;  
                //從凹凸紋理獲取法線值  
                o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));  
                //從_RimColor參數獲取自發光顏色  
                half rim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));  
                o.Emission = _RimColor.rgb * pow (rim, _RimPower);  
            }  
      
            //-------------------結束CG着色器編程語言段------------------  
            ENDCG  
        }   
      
        //“備胎”爲普通漫反射    
        Fallback "Diffuse"  
    }  


我們將此Shader編譯後賦給材質,得到如下非常讚的效果。除了載入紋理,還有色澤,邊緣顏色和強度可供調節:



 

依然是調些效果玩一玩:

  

  

   

 

 

而在場景中的實測效果爲:


 



8.細節紋理


接着我們來看一個在屏幕上顯示紋理細節的Shader:



    Shader "淺墨Shader編程/Volume6/31.細節紋理"  
    {  
        //--------------------------------【屬性】----------------------------------------  
        Properties   
        {  
            _MainTex ("【主紋理】Texture", 2D) = "white" {}  
            _Detail ("【細節紋理】Detail", 2D) = "gray" {}  
        }  
      
        //--------------------------------【子着色器】----------------------------------  
        SubShader   
        {  
            //-----------子着色器標籤----------  
                Tags { "RenderType" = "Opaque" }  
      
            //-------------------開始CG着色器編程語言段-----------------   
            CGPROGRAM  
      
            //【1】光照模式聲明:使用蘭伯特光照模式  
                #pragma surface surf Lambert  
      
            //【2】輸入結構    
                struct Input   
                {  
                //主紋理的uv值  
                float2 uv_MainTex;  
                //細節紋理的uv值  
                float2 uv_Detail;   
                };  
      
            //變量聲明  
            sampler2D _MainTex;  
            sampler2D _Detail;  
      
            //【3】表面着色函數的編寫  
            void surf (Input IN, inout SurfaceOutput o)   
            {  
                //先從主紋理獲取rgb顏色值  
                o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;     
                //設置細節紋理  
                    o.Albedo *= tex2D (_Detail, IN.uv_Detail).rgb * 2;   
            }  
      
            //-------------------結束CG着色器編程語言段------------------  
            ENDCG  
        }  
      
        //“備胎”爲普通漫反射  
        Fallback "Diffuse"  
    }  



我們將此Shader編譯後賦給材質,不加細節紋理如下:


 

加紋理細節的效果圖如下:



而在場景中的實測效果爲:


 

 

 

9.凹凸紋理+顏色可調+邊緣光照+細節紋理




結合上面的8個Shader,我們可以完成本期文章這個結合了凹凸紋理+顏色可調+邊緣光照+細節紋理的稍微複雜一點的Surface Shader:



    Shader "淺墨Shader編程/Volume6/32.凹凸紋理+顏色可調+邊緣光照+細節紋理"  
    {  
        Properties   
        {  
            _MainTex ("【主紋理】Texture", 2D) = "white" {}  
            _BumpMap ("【凹凸紋理】Bumpmap", 2D) = "bump" {}  
            _Detail ("【細節紋理】Detail", 2D) = "gray" {}  
            _ColorTint ("【色澤】Tint", Color) = (0.6, 0.3, 0.6, 0.3)  
            _RimColor ("【邊緣顏色】Rim Color", Color) = (0.26,0.19,0.16,0.0)  
            _RimPower ("【邊緣顏色強度】Rim Power", Range(0.5,8.0)) = 3.0  
        }  
      
        //--------------------------------【子着色器】----------------------------------  
        SubShader   
        {  
            //-----------子着色器標籤----------  
            Tags { "RenderType" = "Opaque" }  
      
            //-------------------開始CG着色器編程語言段-----------------   
            CGPROGRAM  
      
            //【1】光照模式聲明:使用蘭伯特光照模式+自定義顏色函數  
            #pragma surface surf Lambert finalcolor:setcolor  
      
            //【2】輸入結構    
            struct Input   
            {  
                //主紋理的uv值  
                float2 uv_MainTex;  
                //凹凸紋理的uv值  
                float2 uv_BumpMap;  
                //細節紋理的uv值  
                float2 uv_Detail;   
                //當前座標的視角方向  
                float3 viewDir;  
            };  
      
            //變量聲明  
            sampler2D _MainTex;  
            sampler2D _BumpMap;  
            sampler2D _Detail;  
            fixed4 _ColorTint;  
            float4 _RimColor;  
            float _RimPower;  
      
            //【3】自定義顏色函數setcolor的編寫  
            void setcolor (Input IN, SurfaceOutput o, inout fixed4 color)  
            {  
                color *= _ColorTint;  
            }  
      
            //【4】表面着色函數的編寫  
            void surf (Input IN, inout SurfaceOutput o)   
            {  
                //先從主紋理獲取rgb顏色值  
                o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;     
                //設置細節紋理  
                    o.Albedo *= tex2D (_Detail, IN.uv_Detail).rgb * 2;   
                //從凹凸紋理獲取法線值  
                o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));  
                //從_RimColor參數獲取自發光顏色  
                half rim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal));  
                o.Emission = _RimColor.rgb * pow (rim, _RimPower);  
            }  
      
            //-------------------結束CG着色器編程語言段------------------  
            ENDCG  
        }   
      
        //“備胎”爲普通漫反射    
        Fallback "Diffuse"  
    }  



我們將此Shader編譯後賦給材質,得到如下效果:

 



依然是調一些效果玩一玩:

  

  

 

而在場景中的實測效果爲:



 

六、場景搭建

 


以大師級美工鬼斧神工的場景作品爲基礎,淺墨調整了場景佈局,加入了音樂,並加入了更多高級特效,於是便得到了如此這次非常炫酷的暗黑城堡場景。

運行遊戲,史詩級音樂漸漸響起,雨淅瀝瀝地下,我們來到了神祕的暗黑城堡:


集市:


 

 

在集市中逛蕩1:


 

在集市中逛蕩2:


 

 

在集市中逛蕩3:

 

 

 

 

通向遠方的橋樑:

 

 

壯觀的瀑布:


 

感受瀑布濺起的水花:

 

 

 

在瀑布旁看遠方,霧氣氤氳:


 

 

成羣的飛鳥飛過:


 

 

遠方:

 


一片荒涼:


 

孤島:



透過柵欄遠眺:

 


畫面太美,簡直亂真:



海面上倒映出晚霞:


 

城堡概況:

 

 

 

最後一張本次的Shader全家福:

 

 

 

 

OK,美圖就放這麼多。遊戲場景可運行的exe可以在文章開頭中提供的鏈接下載。而以下是源工程的下載鏈接。



本篇文章的示例程序源工程請點擊此處下載:

 

     【淺墨Unity3D Shader編程】之六 暗黑城堡篇配套Unity工程下載




好的,本篇文章到這裏就全部結束了。

OK,新的遊戲編程之旅正在繼續,我們下次再會~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章