Unity 燈光Shader

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

我們知道,光照模型是真實感圖形渲染的基礎,從 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源代碼在這裏貼出來:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#ifndef LIGHTING_INCLUDED
#define LIGHTING_INCLUDED
 
struct SurfaceOutput {
fixed3 Albedo;
fixed3 Normal;
fixed3 Emission;
half Specular;
fixed Gloss;
fixed Alpha;
};
 
#ifndef USING_DIRECTIONAL_LIGHT
#if defined (DIRECTIONAL_COOKIE) || defined (DIRECTIONAL)
#define USING_DIRECTIONAL_LIGHT
#endif
#endif
// NOTE: you would think using half is fine here, but that would make
// Cg apply some precision emulation, making the constants in the shader
// much different; and do some other stupidity that actually increases ALU
// count on d3d9 at least. So we use float.
//
// Also, the numbers in many components should be the same, but are changed
// very slightly in the last digit, to prevent Cg from mis-optimizing
// the shader (it tried to be super clever at saving one shader constant
// at expense of gazillion extra scalar moves). Saves about 6 ALU instructions
// on d3d9 SM2.
#define UNITY_DIRBASIS \
const float3x3 unity_DirBasis = float3x3( \
float3( 0.81649658,  0.0,        0.57735028), \
float3(-0.40824830,  0.70710679, 0.57735027), \
float3(-0.40824829, -0.70710678, 0.57735026) \
);
inline half3 DirLightmapDiffuse(in half3x3 dirBasis, fixed4 color, fixed4 scale, half3 normal, bool surfFuncWritesNormal, out half3 scalePerBasisVector)
{
half3 lm = DecodeLightmap (color);
 
// will be compiled out (and so will the texture sample providing the value)
// if it's not used in the lighting function, like in LightingLambert
scalePerBasisVector = DecodeLightmap (scale);
 
// will be compiled out when surface function does not write into o.Normal
if (surfFuncWritesNormal)
{
half3 normalInRnmBasis = saturate (mul (dirBasis, normal));
lm *= dot (normalInRnmBasis, scalePerBasisVector);
}
 
return lm;
}
fixed4 _LightColor0;
fixed4 _SpecColor;
 
inline fixed4 LightingLambert (SurfaceOutput s, fixed3 lightDir, fixed atten)
{
fixed diff = max (0, dot (s.Normal, lightDir));
 
fixed4 c;
c.rgb = s.Albedo * _LightColor0.rgb * (diff * atten * 2);
c.a = s.Alpha;
return c;
}
inline fixed4 LightingLambert_PrePass (SurfaceOutput s, half4 light)
{
fixed4 c;
c.rgb = s.Albedo * light.rgb;
c.a = s.Alpha;
return c;
}
 
inline half4 LightingLambert_DirLightmap (SurfaceOutput s, fixed4 color, fixed4 scale, bool surfFuncWritesNormal)
{
UNITY_DIRBASIS
half3 scalePerBasisVector;
 
half3 lm = DirLightmapDiffuse (unity_DirBasis, color, scale, s.Normal, surfFuncWritesNormal, scalePerBasisVector);
 
return half4(lm, 0);
}
// NOTE: some intricacy in shader compiler on some GLES2.0 platforms (iOS) needs 'viewDir' & 'h'
// to be mediump instead of lowp, otherwise specular highlight becomes too bright.
inline fixed4 LightingBlinnPhong (SurfaceOutput s, fixed3 lightDir, half3 viewDir, fixed atten)
{
half3 h = normalize (lightDir + viewDir);
 
fixed diff = max (0, dot (s.Normal, lightDir));
 
float nh = max (0, dot (s.Normal, h));
float spec = pow (nh, s.Specular*128.0) * s.Gloss;
 
fixed4 c;
c.rgb = (s.Albedo * _LightColor0.rgb * diff + _LightColor0.rgb * _SpecColor.rgb * spec) * (atten * 2);
c.a = s.Alpha + _LightColor0.a * _SpecColor.a * spec * atten;
return c;
}
 
inline fixed4 LightingBlinnPhong_PrePass (SurfaceOutput s, half4 light)
{
fixed spec = light.a * s.Gloss;
 
fixed4 c;
c.rgb = (s.Albedo * light.rgb + light.rgb * _SpecColor.rgb * spec);
c.a = s.Alpha + spec * _SpecColor.a;
return c;
}
 
inline half4 LightingBlinnPhong_DirLightmap (SurfaceOutput s, fixed4 color, fixed4 scale, half3 viewDir, bool surfFuncWritesNormal, out half3 specColor)
{
UNITY_DIRBASIS
half3 scalePerBasisVector;
 
half3 lm = DirLightmapDiffuse (unity_DirBasis, color, scale, s.Normal, surfFuncWritesNormal, scalePerBasisVector);
 
half3 lightDir = normalize (scalePerBasisVector.x * unity_DirBasis[0] + scalePerBasisVector.y * unity_DirBasis[1] + scalePerBasisVector.z * unity_DirBasis[2]);
half3 h = normalize (lightDir + viewDir);
 
float nh = max (0, dot (s.Normal, h));
float spec = pow (nh, s.Specular * 128.0);
 
// specColor used outside in the forward path, compiled out in prepass
specColor = lm * _SpecColor.rgb * s.Gloss * spec;
 
// spec from the alpha component is used to calculate specular
// in the Lighting*_Prepass function, it's not used in forward
return half4(lm, spec);
}
 
#ifdef UNITY_CAN_COMPILE_TESSELLATION
struct UnityTessellationFactors {
float edge[3] : SV_TessFactor;
float inside : SV_InsideTessFactor;
};
#endif // UNITY_CAN_COMPILE_TESSELLATION
 
#endif

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

 

 

 

三、光照模式的聲明方式

 

 

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

即,函數名爲:

Lighting+ [自定義部分]

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

 

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

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

 

 

【形式一】

half4 LightingName (SurfaceOutput s, half3 lightDir, half atten);

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

 

【形式二】

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

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

【形式三】

half4 LightingName_PrePass (SurfaceOutput s, 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
2
#pragma surface surf QianMoLigtingMode
half4 LightingQianMoLigtingMode (SurfaceOutputs, half3 lightDir, half3 viewDir, half atten);

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

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

 

 

 

四、寫Shader實戰

 

 

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

 

 

0.內置的漫反射光照

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
Shader "淺墨Shader編程/Volume7/33.內置的漫反射"
{
//--------------------------------【屬性】----------------------------------
Properties
{
_MainTex ("【主紋理】Texture", 2D) = "white" {}
}
//--------------------------------【子着色器】----------------------------------
SubShader
{
//-----------子着色器標籤----------
Tags { "RenderType" = "Opaque" }
 
//-------------------開始CG着色器編程語言段-----------------
CGPROGRAM
 
//【1】光照模式聲明:使用蘭伯特光照模式
#pragma surface surf Lambert
 
//【2】輸入結構
struct Input
{
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"
}

實現效果:

 

 

1.簡單的高光光照模型

 

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

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

實現效果:

 

 

2.自制簡單的Lambert光照

 

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

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

實現效果如下:

 

 

 

3.自定義的半Lambert光照

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

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

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

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

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

 

半Lambert光照Shader的代碼如下:

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

實現效果如下:

 

 

4.自定義卡通漸變光照

 

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

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

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

第一組:

第二組:

第三組:

第四組:

 

第五組:

 

 

 

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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代碼,自己回去調着玩吧~

 

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

 

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

 

 

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

 

 

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

 

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