【淺墨Unity3D Shader編程】之七 靜謐之秋篇: 表面着色器的寫法(二)——自定義光照模式

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

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

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

郵箱: [email protected]

QQ交流羣:330595914

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

 

 

本文主要講解了Unity中SurfaceShader的自定義光照模式的寫法。

 

上篇文章中我們已經說到,表面着色器將分爲兩次講解,上一篇文章中介紹了表面着色器的基本概念和一些寫法,用內置的蘭伯特光照模式來進行Surface Shader的書寫,而本文將介紹Surface Shader+自定義的光照模式的寫法。

 

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

 

運行遊戲,音樂響起,金黃色的豐收之秋映入眼簾:

 


遠方:


 

池塘:


 

參天大樹:

 

 

小型村落:


 

 

風車:


 

 

 

池塘邊:

 


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

 

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

 

 

 好的,我們正式開始。

  
 

一、一些光照模型的概念講解

 


我們知道,光照模型是真實感圖形渲染的基礎,從 1967 年 Wylie 等人第一次在顯示物體的時候加進光照效果後,該領域迅速的發展。而這一節,我們主要看看最常見的漫反射、Lambert和鏡面反射、Phong、Blinn-Phong這五種光照模型。

 

 

1.1 漫反射


環境光是對光照現像的最簡單抽象,因而侷限性很大。它僅能描述光線在空間中無方向並均勻散佈時的狀態。很多情況下,入射光是帶有方向的,比如典型的陽光。

如果光照射到比較粗糙的物體表面,如粉筆,由於這些表面從各個方向等強度地反射光,因而從各個視角出發,物體表面呈現相同的亮度,所看到的物體表面某點的明暗程度不隨觀測者的位置變化的,這種等同地向各個方向散射的現象稱爲光的漫反射(diffuse reflection)。

簡單光照模型模擬物體表面對光的反射作用。光源被假定爲點光源,其幾何形狀爲一個點,向周圍所有方向上輻射等強度的光,在物體表面產生反射作用。

如圖:


 

1.2 Lambert模型



漫反射光的強度近似地服從於Lambert定律,即漫反射光的光強僅與入射光的方向和反射點處表面法向夾角的餘弦成正比。

 

由此可以構造出Lambert漫反射模型:

Idiffuse =Id Kd cosθ

Idiffuse表示物體表面某點的漫反射光強

Id爲點光源,Kd(0<Kd<1)表示物體表面該點對漫反射光的反射屬性

 

θ是入射光線的方向與物體表面該點處法線N的夾角,或稱爲入射角(0≤θ≤90°)

入射角爲零時,說明光線垂直於物體表面,漫反射光強最大;

90°時光線與物體表面平行,物體接收不到任何光線。

如圖:


 

把環境光模型添加進來,最後,Lambert光照模型可寫爲:

I= IaKa + Id Kdcosθ= IaKa + Id Kd(L·N)

該模型包含環境光和漫反射光。

 

1.3 鏡面反射


Lambert模型較好地表現了粗糙表面上的光照現象,如石灰粉刷的牆壁、紙張等

但在用於諸如金屬材質製成的物體時,則會顯得呆板,表現不出光澤,主要原因是該模型沒有考慮這些表面的鏡面反射效果。

如果光照射到相當光滑的表面,就產生鏡面反射(specular reflection),鏡面反射的特點是在光滑表面會產生一塊稱之爲高光(high light)的特亮區域 。

鏡面反射遵循光的反射定律:反射光與入射光位於表面法向兩側,對理想反射面(如鏡面),入射角等於反射角,觀察者只能在表面法向的反射方向一側才能看到反射。


 

 

1.4 Phong光照模型   


Lambert模型能很好的表示粗糙表面的光照,但不能表現出鏡面反射高光。1975年Phong Bui Tong發明的Phong模型,提出了計算鏡面高光的經驗模型,鏡面反射光強與反射光線和視線的夾角a相關:

 

     Ispecular = Ks*Is*(cos a) n

 

    其中Ks爲物體表面的高光係數,Is爲光強,a是反射光與視線的夾角,n爲高光指數,n越大,則表面越光滑,反射光越集中,高光範圍越小。如果V表示頂點到視點的單位向量,R表示反射光反向,則cos a可表示爲V和R的點積。模型可表示爲:


 Ispecular = Ks*Is*(V●R) n

 

    反射光放向R可由入射光放向L(頂點指向光源)和物體法向量N求出。

 

    R = (2N●L)N – L

 

我們重新來看Phong光照的另一種表現形式:


Ispec = IsKscosns α(α∈(0,90º)) 

Ks爲物體表面某點的高亮光係數

ns爲物體表面的鏡面反射指數,反映了物體表面的光澤程度, ns越大,表示物體越接近於鏡面。

只有視線與光源在物體表面的反射光線非常接近時才能看到鏡面反射光,此時,鏡面反射光將會在反射方向附近形成很亮的光斑,稱爲高光現象。ns越小,表示物體越粗糙;當ns爲零時,鏡面反射光便退化爲與視點、光源均無關的環境光。 

另外,將鏡面反射光與環境光、漫反射光疊加起來形成單一光源照射下更爲真實的Phong光照模型:


I = Ia Ka+IdKdcosθ+IsKscosns α 

θ :入射角

α :視線與鏡面反射方向的夾角


 

 

1.5 Blinn-Phong光照模型

    

 
 

 

 圖形學界大牛Jim Blinn對Phong模型進行了改進,提出了Blinn-Phong模型。Blinn-Phong模型與Phong模型的區別是,把dot(V,R)換成了dot(N,H),其中H爲半角向量,位於法線N和光線L的角平分線方向。Blinn-Phong模型可表示爲:

 

        Ispecular = Ks*Is* pow(( dot(N,H), n )

 

其中H = (L + V) / | L+V |,計算H比計算反射向量R更快速。

 

Unity中,Phong實際上指的就是Blinn-Phong,兩者指的同一種內置光照模型。

 

PS:光照模型部分的內容主要參考爲:http://cg.sjtu.edu.cn/

  

 

二、關於自定義光照模式(custom lighting model)

 


 

在編寫表面着色器的時候,我們通常要描述一個表面的屬性(反射率顏色,法線,…)、並通過光照模式來計算燈光的相互作用。

通過上篇文章的學習,我們已經知道,Unity中的內置的光照模式有兩種, 分別是Lambert (漫反射光diffuse lighting) 和 Blinn-Phong (也就是鏡面反射光(高光),specular lighting)模式。

 

然而,內置的光照模式自然有其侷限性。想要自己做主的話,我們可以自定義光照模式。

也就是使用自定義光照模式( custom lighting model)。

其實說白了,光照模式(lighting model)無外乎是幾個Cg/HLSL函數的組合。

 

內置的 Lambert 和 Blinn-Phong定義在 Lighting.cginc文件中。我們不妨先來人肉一下他們的實現源代碼。

 

windows系統下位於:

…Unity\Editor\Data\CGIncludes\

 

Mac系統下位於:

/Applications/Unity/Unity.app/Contents/CGIncludes/Lighting.cginc)

 

 

Unity內置的 Lambert 和 Blinn-Phong模型的Shader源代碼在這裏貼出來:

001 #ifndef LIGHTING_INCLUDED
002 #define LIGHTING_INCLUDED
003  
004 struct SurfaceOutput {
005     fixed3 Albedo;
006     fixed3 Normal;
007     fixed3 Emission;
008     half Specular;
009     fixed Gloss;
010     fixed Alpha;
011 };
012  
013 #ifndef USING_DIRECTIONAL_LIGHT
014 #if defined (DIRECTIONAL_COOKIE) || defined (DIRECTIONAL)
015 #define USING_DIRECTIONAL_LIGHT
016 #endif
017 #endif
018  
019  
020 // NOTE: you would think using half is fine here, but that would make
021 // Cg apply some precision emulation, making the constants in the shader
022 // much different; and do some other stupidity that actually increases ALU
023 // count on d3d9 at least. So we use float.
024 //
025 // Also, the numbers in many components should be the same, but are changed
026 // very slightly in the last digit, to prevent Cg from mis-optimizing
027 // the shader (it tried to be super clever at saving one shader constant
028 // at expense of gazillion extra scalar moves). Saves about 6 ALU instructions
029 // on d3d9 SM2.
030 #define UNITY_DIRBASIS \
031 const float3x3 unity_DirBasis = float3x3( \
032   float3( 0.81649658,  0.0,        0.57735028), \
033   float3(-0.40824830,  0.70710679, 0.57735027), \
034   float3(-0.40824829, -0.70710678, 0.57735026) \
035 );
036  
037  
038 inline half3 DirLightmapDiffuse(in half3x3 dirBasis, fixed4 color, fixed4 scale, half3 normal, bool surfFuncWritesNormal, out half3 scalePerBasisVector)
039 {
040     half3 lm = DecodeLightmap (color);
041      
042     // will be compiled out (and so will the texture sample providing the value)
043     // if it's not used in the lighting function, like in LightingLambert
044     scalePerBasisVector = DecodeLightmap (scale);
045  
046     // will be compiled out when surface function does not write into o.Normal
047     if (surfFuncWritesNormal)
048     {
049         half3 normalInRnmBasis = saturate (mul (dirBasis, normal));
050         lm *= dot (normalInRnmBasis, scalePerBasisVector);
051     }
052  
053     return lm;
054 }
055  
056  
057 fixed4 _LightColor0;
058 fixed4 _SpecColor;
059  
060 inline fixed4 LightingLambert (SurfaceOutput s, fixed3 lightDir, fixed atten)
061 {
062     fixed diff = max (0, dot (s.Normal, lightDir));
063      
064     fixed4 c;
065     c.rgb = s.Albedo * _LightColor0.rgb * (diff * atten * 2);
066     c.a = s.Alpha;
067     return c;
068 }
069  
070  
071 inline fixed4 LightingLambert_PrePass (SurfaceOutput s, half4 light)
072 {
073     fixed4 c;
074     c.rgb = s.Albedo * light.rgb;
075     c.a = s.Alpha;
076     return c;
077 }
078  
079 inline half4 LightingLambert_DirLightmap (SurfaceOutput s, fixed4 color, fixed4 scale, bool surfFuncWritesNormal)
080 {
081     UNITY_DIRBASIS
082     half3 scalePerBasisVector;
083      
084     half3 lm = DirLightmapDiffuse (unity_DirBasis, color, scale, s.Normal, surfFuncWritesNormal, scalePerBasisVector);
085      
086     return half4(lm, 0);
087 }
088  
089  
090 // NOTE: some intricacy in shader compiler on some GLES2.0 platforms (iOS) needs 'viewDir' & 'h'
091 // to be mediump instead of lowp, otherwise specular highlight becomes too bright.
092 inline fixed4 LightingBlinnPhong (SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten)
093 {
094     half3 h = normalize (lightDir + viewDir);
095      
096     fixed diff = max (0, dot (s.Normal, lightDir));
097      
098     float nh = max (0, dot (s.Normal, h));
099     float spec = pow (nh, s.Specular*128.0) * s.Gloss;
100      
101     fixed4 c;
102     c.rgb = (s.Albedo * _LightColor0.rgb * diff + _LightColor0.rgb * _SpecColor.rgb * spec) * (atten * 2);
103     c.a = s.Alpha + _LightColor0.a * _SpecColor.a * spec * atten;
104     return c;
105 }
106  
107 inline fixed4 LightingBlinnPhong_PrePass (SurfaceOutput s, half4 light)
108 {
109     fixed spec = light.a * s.Gloss;
110      
111     fixed4 c;
112     c.rgb = (s.Albedo * light.rgb + light.rgb * _SpecColor.rgb * spec);
113     c.a = s.Alpha + spec * _SpecColor.a;
114     return c;
115 }
116  
117 inline half4 LightingBlinnPhong_DirLightmap (SurfaceOutput s, fixed4 color, fixed4 scale, half3 viewDir, bool surfFuncWritesNormal, out half3 specColor)
118 {
119     UNITY_DIRBASIS
120     half3 scalePerBasisVector;
121      
122     half3 lm = DirLightmapDiffuse (unity_DirBasis, color, scale, s.Normal, surfFuncWritesNormal, scalePerBasisVector);
123      
124     half3 lightDir = normalize (scalePerBasisVector.x * unity_DirBasis[0] + scalePerBasisVector.y * unity_DirBasis[1] + scalePerBasisVector.z * unity_DirBasis[2]);
125     half3 h = normalize (lightDir + viewDir);
126  
127     float nh = max (0, dot (s.Normal, h));
128     float spec = pow (nh, s.Specular * 128.0);
129      
130     // specColor used outside in the forward path, compiled out in prepass
131     specColor = lm * _SpecColor.rgb * s.Gloss * spec;
132      
133     // spec from the alpha component is used to calculate specular
134     // in the Lighting*_Prepass function, it's not used in forward
135     return half4(lm, spec);
136 }
137  
138  
139  
140 #ifdef UNITY_CAN_COMPILE_TESSELLATION
141 struct UnityTessellationFactors {
142     float edge[3] : SV_TessFactor;
143     float inside : SV_InsideTessFactor;
144 };
145 #endif // UNITY_CAN_COMPILE_TESSELLATION
146  
147 #endif
 



OK,下面讓我們一起來看看光照模式應該怎樣聲明和定義。

 

 

 

 

 

三、光照模式的聲明方式


 

 

 

在Unity Shaderlab和CG語言中,光照模式是一個以Lighting開頭+自定義文字組合在一起的函數。

即,函數名爲:

Lighting+ [自定義部分]

比如,一個可行的函數名爲:LightingQianMoLightingMode

 

我們可以在着色器文件(shader file)或導入文件(included files)中的任何一個地方聲明此函數。一般情況下,此函數有五種原型可供選擇,具體如下。

一般情況下,於以下五種函數原型中選一種,進行實現就行了。

 

 

【形式一】

half4 LightingName (SurfaceOutput s, half3lightDir, half atten);

此種形式的函數可以表示在正向渲染路徑(forward rendering path)中的光照模式,且此函數不取決於視圖方向(view direction)。例如:漫反射(diffuse)。

 

【形式二】

half4 LightingName (SurfaceOutput s, half3lightDir, half3 viewDir, half atten);

此種形式的函數可以表示在正向渲染路徑(forward rendering path)中使用的光照模式,且此函數包含了視圖方向(view direction)。


【形式三】

half4 LightingName_PrePass (SurfaceOutputs, half4 light);

此種形式的函數可以在延時光照路徑(deferred lighting path)中使用的。

 

【形式四】

half4 LightingName_DirLightmap(SurfaceOutput s, fixed4 color, fixed4 scale, bool surfFuncWritesNormal);

這種形式也是不依賴於視圖方向(view direction)的光照模式。例如:漫反射(diffuse)。

 

【形式五】

half4 LightingName_DirLightmap(SurfaceOutput s, fixed4 color, fixed4 scale, half3 viewDir, bool surfFuncWritesNormal,out half3 specColor);這是使用的依賴於視圖方向(view direction)的光照模式(light model)。

 

 

比如,一個光照模式(lighting model)要麼使用視圖方向(view direction)要麼不使用。同樣的,如果光照模式(lightingmodel)在延時光照(deferred lighting)中不工作,只要不聲明成 _PrePass(第三種形式),就是行的。

另外,對於形式四和形式五的選擇,主要取決於我們的光照模式(light model)是否依賴視圖方向(view direction)。需要注意的是,這兩個函數將自動處理正向和延時光照路徑(forwardand deferred lighting rendering paths)。

PS: Unity在移動平臺中暫時不支持延遲光照渲染。

 

 
 

 

做個總結,在自定義自己的光照模型函數時,根據需要在五種函數原型中選擇一種,且:


光照模式的函數名爲:Lighting+ [自定義函數名]

pragma聲明爲: #pragmasurface surf  [自定義函數名]

 

然後就是需要,仿照着其他的光照模式來填充函數體了。

 

 

我們舉個例子:

1 1.#pragma surface surf QianMoLigtingMode 
2 2.half4 LightingQianMoLigtingMode (SurfaceOutputs, half3 lightDir, half3 viewDir, half atten); 


 

OK,光照模式的聲明就是這樣。光照模式的函數體是其最核心的部分,需要根據具體的光照模式數學公式進行書寫,我們將在接下來的寫Shader實戰中進行學習。

PS:估計這節的概念有些難懂,大家肯定在第一時間不能完全理解,沒事,讓我們依舊在Shader實戰中把狀態找回來。

 

 

 

 

四、寫Shader實戰

 

 

 

上文已經提到過了,: Unity在移動平臺中暫時不支持延遲光照(Deferred lighting)渲染。因爲延時光照不能與一些自定義per-material 光照模式很好的共同運行,所以在下面的例子中我們只在着色器正向渲染(ForwardRendering)中進行實現。

 

 

 

0.內置的漫反射光照

 
 

首先,我們根據上一節所學,寫一個依靠內置的蘭伯特光照模式的漫反射光的Surface Shader:

 

01 Shader "淺墨Shader編程/Volume7/33.內置的漫反射"
02 {
03     //--------------------------------【屬性】----------------------------------
04     Properties
05     {
06         _MainTex ("【主紋理】Texture", 2D) = "white" {}
07     }
08      //--------------------------------【子着色器】----------------------------------
09     SubShader
10     {
11         //-----------子着色器標籤---------- 
12         Tags { "RenderType" = "Opaque" }
13  
14         //-------------------開始CG着色器編程語言段-----------------  
15         CGPROGRAM
16  
17         //【1】光照模式聲明:使用蘭伯特光照模式
18          #pragma surface surf Lambert
19  
20         //【2】輸入結構  
21         struct Input
22         {
23             float2 uv_MainTex;
24         };
25  
26         //變量聲明
27         sampler2D _MainTex;
28  
29         //【3】表面着色函數的編寫
30         void surf (Input IN, inout SurfaceOutput o)
31         {
32             //從主紋理獲取rgb顏色值
33            o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
34         }
35  
36         //-------------------結束CG着色器編程語言段------------------
37         ENDCG
38     }
39  
40     Fallback "Diffuse"
41   }

 

實現效果:


 

 

 


 

1.簡單的高光光照模型

 

下面是一個簡單的高光光照模式(specular lighting model)Shader。實際上他就是Unity內置的Blinn-Phong光照模型,實際上做起來並不困難。這邊我們將它單獨拿出來實現一下:

 

01 Shader "淺墨Shader編程/Volume7/34.自定義高光"
02 {
03  
04     //--------------------------------【屬性】----------------------------------
05     Properties
06     {
07         _MainTex ("【主紋理】Texture", 2D) = "white" {}
08     }
09  
10     //--------------------------------【子着色器】----------------------------------
11     SubShader
12     {
13         //-----------子着色器標籤---------- 
14         Tags { "RenderType" = "Opaque" }
15  
16         //-------------------開始CG着色器編程語言段-----------------  
17         CGPROGRAM
18  
19         //【1】光照模式聲明:使用自定義的光照模式
20         #pragma surface surf SimpleSpecular
21  
22         //【2】實現自定義的光照模式SimpleSpecular
23         half4 LightingSimpleSpecular (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
24         {
25             half3 h = normalize (lightDir + viewDir);
26  
27             half diff = max (0, dot (s.Normal, lightDir));
28  
29             float nh = max (0, dot (s.Normal, h));
30             float spec = pow (nh, 48.0);
31  
32             half4 c;
33             c.rgb = (s.Albedo * _LightColor0.rgb * diff + _LightColor0.rgb * spec) * (atten * 2);
34             c.a = s.Alpha;
35             return c;
36         }
37  
38         //【3】輸入結構
39         struct Input
40         {
41             float2 uv_MainTex;
42         };
43          
44         //變量聲明      
45         sampler2D _MainTex;
46  
47         //【4】表面着色函數的編寫 
48         void surf (Input IN, inout SurfaceOutput o)
49         {
50             //從主紋理獲取rgb顏色值
51             o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
52         }
53  
54         //-------------------結束CG着色器編程語言段------------------ 
55         ENDCG
56     }
57  
58     //“備胎”爲普通漫反射
59     Fallback "Diffuse"
60   }

 

實現效果:


 


 

 

2.自制簡單的Lambert光照

 

 
 

 

對應於Unity內建的Lambert光照,我們可以自定義原理類似的光照模式,實現自己Lambert光照:

 

 
01 Shader "淺墨Shader編程/Volume7/35.自制簡單的Lambert光照"
02 {
03     //--------------------------------【屬性】---------------------------------------- 
04     Properties
05     {
06       _MainTex ("【主紋理】Texture", 2D) = "white" {}
07     }
08  
09     //--------------------------------【子着色器】---------------------------------- 
10     SubShader
11     {
12         //-----------子着色器標籤---------- 
13         Tags { "RenderType" = "Opaque" }
14         //-------------------開始CG着色器編程語言段-----------------  
15         CGPROGRAM
16  
17         //【1】光照模式聲明:使用自制的蘭伯特光照模式
18         #pragma surface surf QianMoLambert
19  
20         //【2】實現自定義的蘭伯特光照模式
21         half4 LightingQianMoLambert (SurfaceOutput s, half3 lightDir, half atten)
22         {
23             half NdotL =max(0, dot (s.Normal, lightDir));
24             half4 color;
25             color.rgb = s.Albedo * _LightColor0.rgb * (NdotL * atten * 2);
26             color.a = s.Alpha;
27             return color;
28         }
29  
30         //【3】輸入結構 
31         struct Input
32         {
33             float2 uv_MainTex;
34         };
35  
36         //變量聲明
37         sampler2D _MainTex;
38  
39         //【4】表面着色函數的編寫
40         void surf (Input IN, inout SurfaceOutput o)
41         {
42             //從主紋理獲取rgb顏色值
43             o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
44         }
45  
46         //-------------------結束CG着色器編程語言段------------------
47         ENDCG
48     }
49     Fallback "Diffuse"
50   }

 

實現效果如下:

 

 

3.自定義的半Lambert光照

 

 

接下來,讓我們自制一個半Lambert光照。

Lambert定律認爲,在平面某點漫反射光的光強與該反射點的法向量和入射光角度的餘弦值成正比(即我們之前使用dot函數得到的結果)。Half Lambert最初是由Valve(大V社)提出來的,用於提高物體在一些光線無法照射到的區域的亮度的。

簡單說來,半Lambert光照提高了漫反射光照的亮度,使得漫反射光線可以看起來照射到一個物體的各個表面。

而半Lambert最初也是被用於《半條命2》的畫面渲染,爲了防止某個物體的背光面丟失形狀並且顯得太過平面化。這個技術是完全沒有基於任何物理原理的,而僅僅是一種感性的視覺增強。

遮蔽的漫反射-漫反射光照的一種改進。照明"環繞(wraps around)"在物體的邊緣。它對於假冒子表面(subsurface)散射效果(scattering effect)非常有用。

 

半Lambert光照Shader的代碼如下:

 

01 Shader "淺墨Shader編程/Volume7/36.自制半Lambert光照"
02 {
03       //--------------------------------【屬性】---------------------------------------- 
04       Properties
05       {
06           _MainTex ("【主紋理】Texture", 2D) = "white" {}
07       }
08  
09       //--------------------------------【子着色器】---------------------------------- 
10       SubShader
11       {
12           //-----------子着色器標籤---------- 
13           Tags { "RenderType" = "Opaque" }
14           //-------------------開始CG着色器編程語言段----------------- 
15           CGPROGRAM
16  
17           //【1】光照模式聲明:使用自制的半蘭伯特光照模式
18           #pragma surface surf QianMoHalfLambert
19  
20           //【2】實現自定義的半蘭伯特光照模式
21           half4 LightingQianMoHalfLambert (SurfaceOutput s, half3 lightDir, half atten)
22           {
23               half NdotL =max(0, dot (s.Normal, lightDir));
24  
25               //在蘭伯特光照的基礎上加上這句,增加光強
26               float hLambert = NdotL * 0.5 + 0.5; 
27               half4 color;
28  
29               //修改這句中的相關參數
30               color.rgb = s.Albedo * _LightColor0.rgb * (hLambert * atten * 2);
31               color.a = s.Alpha;
32               return color;
33           }
34  
35           //【3】輸入結構 
36           struct Input
37           {
38               float2 uv_MainTex;
39           };
40  
41           //變量聲明
42           sampler2D _MainTex;
43  
44           //【4】表面着色函數的編寫
45           void surf (Input IN, inout SurfaceOutput o)
46           {
47               //從主紋理獲取rgb顏色值
48               o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
49           }
50  
51           //-------------------結束CG着色器編程語言段------------------
52           ENDCG
53       }
54  
55       Fallback "Diffuse"
56 }

實現效果如下:


 

 

 

 

4.自定義卡通漸變光照

 
 

 

下面,我們一起實現一個自定義卡通漸變光照,通過一個不同的漸變紋理(漸變紋理可由PS製作),實現各種不同的漸變效果。

自定義卡通漸變光照Shader代碼如下:

 

01 Shader "淺墨Shader編程/Volume7/37.自定義卡通漸變光照"
02 {
03     //--------------------------------【屬性】---------------------------------------- 
04     Properties
05     {
06         _MainTex ("【主紋理】Texture", 2D) = "white" {}
07         _Ramp ("【漸變紋理】Shading Ramp", 2D) = "gray" {}
08     }
09  
10     //--------------------------------【子着色器】----------------------------------
11     SubShader
12     {
13         //-----------子着色器標籤---------- 
14         Tags { "RenderType" = "Opaque" }
15         //-------------------開始CG着色器編程語言段----------------- 
16         CGPROGRAM
17  
18         //【1】光照模式聲明:使用自制的卡通漸變光照模式
19         #pragma surface surf Ramp
20  
21         //變量聲明
22         sampler2D _Ramp;
23  
24         //【2】實現自制的卡通漸變光照模式
25         half4 LightingRamp (SurfaceOutput s, half3 lightDir, half atten)
26         {
27             //點乘反射光線法線和光線方向
28             <span style="white-space: pre;">      </span>half NdotL = dot (s.Normal, lightDir);
29             //增強光強
30             <span style="white-space: pre;">      </span>half diff = NdotL * 0.5 + 0.5;
31             //從紋理中定義漸變效果
32             half3 ramp = tex2D (_Ramp, float2(diff,diff)).rgb;
33             //計算出最終結果
34             <span style="white-space: pre;">      </span>half4 color;
35             color.rgb = s.Albedo * _LightColor0.rgb * ramp * (atten * 2);
36             color.a = s.Alpha;
37  
38             return color;
39         }
40  
41         //【3】輸入結構 
42         struct Input
43         {
44             float2 uv_MainTex;
45         };
46  
47         //變量聲明
48         sampler2D _MainTex;
49  
50         //【4】表面着色函數的編寫
51         void surf (Input IN, inout SurfaceOutput o)
52         {
53             //從主紋理獲取rgb顏色值
54             o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
55         }
56  
57         //-------------------結束CG着色器編程語言段------------------
58         ENDCG
59  
60     }
61     Fallback "Diffuse"
62   }


 

我們取不同的漸變紋理,可得到不同的效果。以下是五種不同漸變紋理和對應的效果圖。

第一組:


 

 

 

 

 

 第二組:


 

  

 

 

 

第三組:


 

  

 

 
 

第四組:

 

 

 

 

第五組:

 

 

  

 

 

5.自定義卡通漸變光照v2

 
 

 

讓我們在上面這個Shader的基礎上,加入更多可選的屬性,成爲一個功能完備的漸變光照Shader:

01 Shader "淺墨Shader編程/Volume7/38.自定義卡通漸變光照v2"
02
03     //--------------------------------【屬性】----------------------------------------
04     Properties  
05     
06         _MainTex ("【主紋理】Texture", 2D) = "white" {} 
07         _Ramp ("【漸變紋理】Ramp Texture", 2D) = "white"{} 
08         _BumpMap ("【凹凸紋理】Bumpmap", 2D) = "bump" {} 
09         _Detail ("【細節紋理】Detail", 2D) = "gray" {} 
10         _RimColor ("【邊緣顏色】Rim Color", Color) = (0.26,0.19,0.16,0.0) 
11         _RimPower ("【邊緣顏色強度】Rim Power", Range(0.5,8.0)) = 3.0 
12     
13  
14     //--------------------------------【子着色器】----------------------------------
15     SubShader
16     
17         //-----------子着色器標籤---------- 
18         Tags { "RenderType"="Opaque"
19         LOD 200 
20  
21         //-------------------開始CG着色器編程語言段----------------- 
22         CGPROGRAM 
23  
24         //【1】光照模式聲明:使用自制的卡通漸變光照模式
25         #pragma surface surf QianMoCartoonShader 
26          
27          
28         //變量聲明 
29         sampler2D _MainTex; 
30         sampler2D _Ramp; 
31         sampler2D _BumpMap; 
32         sampler2D _Detail; 
33         float4 _RimColor; 
34         float _RimPower; 
35  
36         //【2】實現自制的卡通漸變光照模式
37         inline float4 LightingQianMoCartoonShader(SurfaceOutput s, fixed3 lightDir, fixed atten) 
38         
39             //點乘反射光線法線和光線方向
40             half NdotL = dot (s.Normal, lightDir);
41             //增強光強
42             half diff = NdotL * 0.5 + 0.5;
43             //從紋理中定義漸變效果
44             half3 ramp = tex2D (_Ramp, float2(diff,diff)).rgb;
45             //計算出最終結果
46             half4 color;
47             color.rgb = s.Albedo * _LightColor0.rgb * ramp * (atten * 2);
48             color.a = s.Alpha;
49  
50             return color;
51         
52  
53         //【3】輸入結構   
54         struct Input  
55         
56             //主紋理的uv值 
57             float2 uv_MainTex; 
58             //凹凸紋理的uv值 
59             float2 uv_BumpMap; 
60             //細節紋理的uv值 
61             float2 uv_Detail;  
62             //當前座標的視角方向 
63             float3 viewDir; 
64         }; 
65  
66          
67         //【4】表面着色函數的編寫
68         void surf (Input IN, inout SurfaceOutput o) 
69         
70              //先從主紋理獲取rgb顏色值 
71             o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;    
72             //設置細節紋理 
73             o.Albedo *= tex2D (_Detail, IN.uv_Detail).rgb * 2;  
74             //從凹凸紋理獲取法線值 
75             o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap)); 
76             //從_RimColor參數獲取自發光顏色 
77             half rim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal)); 
78             o.Emission = _RimColor.rgb * pow (rim, _RimPower); 
79  
80         
81  
82         //-------------------結束CG着色器編程語言段------------------
83         ENDCG 
84     }  
85     FallBack "Diffuse" 
86 }

 

Shader "淺墨Shader編程/Volume7/38.自定義卡通漸變光照v2" 
{  
	//--------------------------------【屬性】---------------------------------------- 
	Properties   
    {  
        _MainTex ("【主紋理】Texture", 2D) = "white" {}  
		_Ramp ("【漸變紋理】Ramp Texture", 2D) = "white"{}  
        _BumpMap ("【凹凸紋理】Bumpmap", 2D) = "bump" {}  
        _Detail ("【細節紋理】Detail", 2D) = "gray" {}  
        _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" }  
        LOD 200  

        //-------------------開始CG着色器編程語言段-----------------  
        CGPROGRAM  

		//【1】光照模式聲明:使用自制的卡通漸變光照模式
        #pragma surface surf QianMoCartoonShader  
        
		
		//變量聲明  
        sampler2D _MainTex;  
		sampler2D _Ramp;  
        sampler2D _BumpMap;  
        sampler2D _Detail;  
        float4 _RimColor;  
        float _RimPower;  

		//【2】實現自制的卡通漸變光照模式
        inline float4 LightingQianMoCartoonShader(SurfaceOutput s, fixed3 lightDir, fixed atten)  
        {  
			//點乘反射光線法線和光線方向
            half NdotL = dot (s.Normal, lightDir); 
			//增強光強
            half diff = NdotL * 0.5 + 0.5;
			//從紋理中定義漸變效果
			half3 ramp = tex2D (_Ramp, float2(diff,diff)).rgb;
			//計算出最終結果
            half4 color;
			color.rgb = s.Albedo * _LightColor0.rgb * ramp * (atten * 2);
			color.a = s.Alpha;

			return color;
        }  

        //【3】輸入結構    
        struct Input   
        {  
            //主紋理的uv值  
            float2 uv_MainTex;  
            //凹凸紋理的uv值  
            float2 uv_BumpMap;  
            //細節紋理的uv值  
            float2 uv_Detail;   
            //當前座標的視角方向  
            float3 viewDir;  
        };  

		
		//【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編譯後賦給材質,得到如下效果:


 

 



可供調節的屬性非常多,稍微放幾張效果圖,剩下的大家可以下載工程源代碼,或者拷貝Shader代碼,自己回去調着玩吧~

 

布料細節紋理+灰白漸變紋理+紅色邊緣光:


 

 

 

布料細節紋理+灰白漸變紋理+淺綠色邊緣光:

 

 

 

 

布料細節紋理+灰白漸變紋理+白色邊緣光:

 

 

 

布料細節紋理+灰白漸變紋理+無邊緣光(黑色):

 

 

 

五、場景搭建

 

 

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

 

運行遊戲,樹影搖曳,我們來到金黃色的豐收之秋。

 

   

    

    

    

    

    

    

    

 

 
 

 

最後,放一張本篇文章中實現的Shader全家福:


 

 

 

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

 


 

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

 

     【淺墨Unity3D Shader編程】之七 靜謐之秋篇配套Unity工程下載

 

 

 


 

Unity Shader系列文章到目前已經更新了7篇,一共實現了38個詳細註釋、循序漸進、功能各異的Shader,對Unity中的固定功能Shader、Surface Shader都已經有了比較詳細、系統的講解和實現。而可編程Shader按學習計劃來說是以後的計劃,目前還是未涉及,有機會在以後的文章中一起和大家一起探討。

而大家如果仔細參考和閱讀這七篇文章,會發現Unity中Shader的書寫其實是非常容易和有章可循的。這大概就是淺墨寫這個系列文章的初衷吧。

 
 
 

 

天下沒有不散的宴席。

淺墨接下來的一段時間有一些大的遊戲項目要接觸,所以,Unity Shader系列文章每週週一的固定更新只能改爲不定期的更新了。以後淺墨有了空餘時間,會繼續寫博文來與大家交流分享。

OK,於未來某天更新的下篇文章中,我們後會有期。:)

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