1. 程式人生 > >【Unity Shader】(七) ------ 複雜的光照(下)

【Unity Shader】(七) ------ 複雜的光照(下)

筆者使用的是 Unity 2018.2.0f2 + VS2017,建議讀者使用與 Unity 2018 相近的版本,避免一些因為版本不一致而出現的問題。

目錄

前言

二. 陰影

四. 總結

前言

本文承接上文【Unity Shader】(六) ------ 複雜的光照(上),介紹剩下的光照衰減和陰影部分,最後實現包含了對不同光照型別進行光照計算,光照衰減,陰影產生等部分的真正意義上的標準光照 shader 。因為本文會上文有所聯絡,所以個人建議讀者閱讀上文,以免在本文某些地方出現思路上的突兀。

一. 光照衰減

1.1 使用 LUT

前面說過,我們使用 LUT 來計算衰減,這種做法的優劣點如下:

  • 優點:因為直接計算光照衰減會涉及大量且複雜的數學運算,使用 LUT 可以不依賴數學表示式的複雜性,只需一個引數去取樣即可。
  • 缺點 : ① 需要預處理得到紋理,紋理大小影響衰減的精度。② 不直觀,且使用 LUT 後就無法使用其它數學公式來計算。

當然,Unity 預設這種方法也是因為其在一定程度上提升了效能且大部分情況下,得到的效果是良好的。

1.2 關於光照衰減紋理

Unity 內使用 _LightTexture0 的紋理來計算光照衰減,在之前的程式碼中,我們已經使用過了。通常情況下,我們只關心 _LightTexture0 對角線上的紋理顏色值,其代表了在光源空間下不同位置的點的衰減值。(0,0)表示與光源重合的點的衰減值,(1,1)表示距離最遠的點的光照衰減值。

上面說過,需要用一個點對紋理取樣,那麼就要先知道該點在光源空間下位置資訊。同樣是空間轉換,我們在這裡需要用到的轉換矩陣為 _LightMatrix0 。在 Unity 5.4 之後,這個矩陣更換為 unity_WorldToLight 了。

所以這裡轉換語句應該為

 1 float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz; 

 然後使用這個座標的摸的平方進行取樣。當然,如果用距離值來計算就需要開方操作了,為了,避免這個繁瑣的步驟,我們使用頂點距離的平方來取樣

 1 fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;

 

 其中巨集 UNITY_ATTEN_CHANNEL 可以得到衰減值所在的分量。

1.3 關於光照衰減的總結

上述所說的知識足夠讀者應付大部分的光照計算中的光照衰減部分,如果讀者著實不希望採用 LUT 的方法來計算衰減,也可以使用數學公式,只是這樣需要對公式有更深入的理解。很遺憾的是,筆者並沒有找到關於計算衰減的公式的資料,對於衰減方面的資料,著實所尋不多,日後如果我能找到相關知識,我會補充到這篇文章中。

二. 陰影

在許多遊戲製作中,為了追求真實,光影效果是必不可少的。光我們之前介紹了,現在來介紹陰影。

2.1 陰影是如何實現出來的

想象一下,一條光線從遠方射過來,當它遇到了第一個不透明的物體時,那麼理所當然的是,這條射線就無法再去照亮別的物體了。同時,擋住這條光線的物體會向附近的物體投射陰影。也就是說,陰影是光線無法到達的區域

在 Unity 的實時渲染中,我們採用的是 Shadow Map 技術。關於 Shadow Map,我們可以 WIKI 上看到它的解釋。

 

大意為:Shadow Map 是 Lance Williams 先生在 1978 年提出的技術,從此之後,它常用於預渲染和實時場景中。在 Unity 中,就是先把攝像機的位置與光源重合,然後攝像機看不到的區域就是陰影。這樣理解是不是很簡單?

我們先來看看 Shadow Map 是如何定義其工作原理的:

Algorithm overview

Rendering a shadowed scene involves two major drawing steps. The first produces the shadow map itself, and the second applies it to the scene. Depending on the implementation (and number of lights), this may require two or more drawing passes.

簡單地說就是 : ① 生成陰影紋理  ② 在場景中使用陰影紋理

比如:在前向渲染中,如果平行光開啟了陰影(要注意需要手動開啟,建立了一個新光源,預設是沒有陰影的)

Unity 就會為這個平行光計算陰影對映紋理。這張陰影對映紋理實質就是一張深度紋理,記錄著從光源出發,距離光源最近的表面資訊

通常情況下,是通過呼叫 Base Pass 和 Additional Pass 來更新深度資訊,但我們之前也說過,這兩個 Pass 中包含了各種光照計算。為了避免多餘的光照計算所造成的效能損耗,Unity 選擇使用另外一個特別的 Pass 來管理光源的對映紋理。這個 Pass 就是 LightMode 標籤中設定為 Shadow Caster 的那個 Pass。這個 Pass 的渲染目標是深度紋理。所以當一個光源開啟了陰影效果之後,引擎就會在當前渲染物體的 shader 中尋找這個 Pass ,如果找不到,就去 Fallback 裡面找;還找不到,就去 Fallback 的 Fallback 裡面找。如果這樣都找不到,那麼該物體就無法向其它物體投射陰影,但是可以接收來自其它物體的陰影。

 

文字有點多,總結一下:

  • 如果想要一個物體接收其它的物體的陰影,就要在 shader 中對陰影對映紋理進行取樣,把取樣結果和光照結果相乘得到陰影效果。
  • 如果想要一個物體向其它物體投射陰影,就要把該物體加入到陰影對映紋理之中,這一步驟是在 Shadow Pass 中實現的。
  • 如果想要一個光源產生陰影效果,則需要手動選擇陰影型別:No Shadows , Hard Shadows , Soft Shadows。Hard Shadows 相對於 Soft Shadows 計算量少一些,能滿足大部分場景,邊緣不平滑,鋸齒明顯。

2.2  普通非透明物體陰影的實現

這一節我們來實現對一個不透明的物體的陰影處理,包括讓它投射陰影和接收陰影。

2.2.1 準備工作

建立場景,去掉預設的天空盒子;新建一個 Material 和 一個 shader,命名為 Shadow;建立一個 Cube 和兩個 plane,位置擺放如下;開啟平行光的陰影;新建的 shader 中使用我上一篇最後給出的前向渲染的程式碼。

 

看到上面的圖,不知道讀者有沒有一種細思極恐的感覺,因為上圖有兩處詭異的地方。

  • 前文說過,需要一個 ShadowCaster 的 Pass 來處理陰影,但是在上一篇中實現的前向渲染的程式碼中,我們並沒有定義這樣的一個 Pass,也沒有做出對陰影處理的操作,那麼為什麼正方體會有陰影呢?
  • 可以確定的是兩個 plane 都開啟了投射陰影和接收陰影,圖中就可以看到地面上的 plane 接收了正方體的陰影,那麼,為什麼右邊的 plane 沒有投影呢?

其實這是兩個需要注意的地方

  • 前文說過,當 shader 中沒有 ShadowCaster 的 Pass 時會去它的 Fallback 裡面找,我們之前的 Fallback 為 Specular,Specular 中也沒有這個 Pass,最後在某個角落找到了它。想看原始碼的讀者,可以在 Unity 官方下載 內建著色器 ,解壓之後,在 DefaultResourcesExtra 資料夾中的 Normal-VertexLit 這個 shader 中找到以下程式碼。
     1 // Pass to render object as a shadow caster
     2     Pass 
     3     {
     4         Name "ShadowCaster"
     5         Tags { "LightMode" = "ShadowCaster" }
     6 
     7         CGPROGRAM
     8         #pragma vertex vert
     9         #pragma fragment frag
    10         #pragma target 2.0
    11         #pragma multi_compile_shadowcaster
    12         #pragma multi_compile_instancing // allow instanced shadow pass for most of the shaders
    13         #include "UnityCG.cginc"
    14 
    15         struct v2f {
    16             V2F_SHADOW_CASTER;
    17             UNITY_VERTEX_OUTPUT_STEREO
    18         };
    19 
    20         v2f vert( appdata_base v )
    21         {
    22             v2f o;
    23             UNITY_SETUP_INSTANCE_ID(v);
    24             UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
    25             TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
    26             return o;
    27         }
    28 
    29         float4 frag( v2f i ) : SV_Target
    30         {
    31             SHADOW_CASTER_FRAGMENT(i)
    32         }
    33         ENDCG
    34 
    35    }

     所以事實上,我們之前實現的 ForwardRendering 也是可以產生陰影效果的。

  • 右邊的 plane 確定是打開了 CasterShadow 的,而且 shader 為 Unity 內建標準shader。而它卻無法投射陰影是因為在預設情況下,在計算光源的陰影對映紋理時剔除物體背面。而內建的平面其實只有一個面。不過我們可以設定為 Two Sided,就可以令物體的所有面都計算光照資訊了。

這樣就看到陰影了。不過還是有一個奇怪的地方,那就是正方體為什麼沒有接收右邊平面的陰影?因為 ForwardRendering 裡面沒有對接收的陰影做出處理。我們接下來就要完善這一步了。

2.2.2 接收陰影

我們開始對 Shadow 改造

I. 新增一個頭檔案,我們計算陰影所需要的巨集都是在這個檔案中宣告的

II. 在輸出結構體新增一個內建巨集

這個巨集用於宣告對陰影紋理取樣的座標,引數為下一個可用的插值暫存器的索引,在上面,我們在 worldNormal 和 worldPos 都使用了一個,所以此時這個巨集的引數應該為2

III. 在頂點著色器中新增一個巨集

TRANSFER_SHADOW 這個巨集會計算上一步定義的陰影紋理座標。我們可以在 AutoLight 中看到它的定義

IV. 在片元著色器中計算陰影,同樣使用一個內建巨集

V. 將得到的陰影值與漫反射顏色,高光反射顏色相乘

VI. 儲存,檢視效果

可以看到,正方體已經接收到了右邊平面的陰影。

此時,讀者可能會有疑惑,上面步驟中那些程式碼應該新增在哪裡,因為前向渲染中我們定義了兩個 Pass,Base Pass 和 Additional Pass。事實上,兩個 Pass 對陰影處理的原理是一樣的,上面的步驟,我只對 Base Pass 做了修改,但這是不夠完善的,所以接下來,我們來介紹完整的陰影管理。

在這裡,還需注意的是 SHADOW_COORDS,TRANSFER_SHADOW,SHADOW_ATTENUATION 這三個巨集會根據不同的情況有不同的定義

 1 // ---- Screen space direction light shadows helpers (any version)
 2 #if defined (SHADOWS_SCREEN)
 3 
 4     #if defined(UNITY_NO_SCREENSPACE_SHADOWS)
 5         UNITY_DECLARE_SHADOWMAP(_ShadowMapTexture);
 6         #define TRANSFER_SHADOW(a) a._ShadowCoord = mul( unity_WorldToShadow[0], mul( unity_ObjectToWorld, v.vertex ) );
 7         inline fixed unitySampleShadow (unityShadowCoord4 shadowCoord)
 8         {
 9             #if defined(SHADOWS_NATIVE)
10                 fixed shadow = UNITY_SAMPLE_SHADOW(_ShadowMapTexture, shadowCoord.xyz);
11                 shadow = _LightShadowData.r + shadow * (1-_LightShadowData.r);
12                 return shadow;
13             #else
14                 unityShadowCoord dist = SAMPLE_DEPTH_TEXTURE(_ShadowMapTexture, shadowCoord.xy);
15                 // tegra is confused if we use _LightShadowData.x directly
16                 // with "ambiguous overloaded function reference max(mediump float, float)"
17                 unityShadowCoord lightShadowDataX = _LightShadowData.x;
18                 unityShadowCoord threshold = shadowCoord.z;
19                 return max(dist > threshold, lightShadowDataX);
20             #endif
21         }
22 
23     #else // UNITY_NO_SCREENSPACE_SHADOWS
24         UNITY_DECLARE_SCREENSPACE_SHADOWMAP(_ShadowMapTexture);
25         #define TRANSFER_SHADOW(a) a._ShadowCoord = ComputeScreenPos(a.pos);
26         inline fixed unitySampleShadow (unityShadowCoord4 shadowCoord)
27         {
28             fixed shadow = UNITY_SAMPLE_SCREEN_SHADOW(_ShadowMapTexture, shadowCoord);
29             return shadow;
30         }
31 
32     #endif
33 
34     #define SHADOW_COORDS(idx1) unityShadowCoord4 _ShadowCoord : TEXCOORD##idx1;
35     #define SHADOW_ATTENUATION(a) unitySampleShadow(a._ShadowCoord)
36 #endif

當然,你可以不必過於深究,但需要注意的是,這三個巨集是天生的三個好基友,如果關閉了陰影,那麼 SHADOW_COORDS,TRANSFER_SHADOW 會不起作用,而 SHADOW_ATTENUATION 的值為 1 。那麼漫反射顏色和高光反射顏色不受 shadow 影響。而且這些巨集會使用 v.vertex 和 a.pos 等變數來計算,所以 a2v 頂點座標變數必須為 vertex,輸入結構體 a2v 必須命名為 v ,且 v2f 中頂點位置座標為 pos。

2.2.3 完善的的光照衰減和陰影管理

在之前實現前向渲染的時候,我們為了得到光照衰減值,對不同光源做了判斷,然後將得到的結果與反射顏色相乘,在這一點上,陰影的計算過程類似。而幸運的是,Unity 為我們提供了一個內建巨集 UNITY_LIGHT_ATTENUATION 來同時得到光照衰減因子和陰影值。我們可以在 AutoLight 中找到它的定義

1 #define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) \
2         unityShadowCoord3 lightCoord = mul(unity_WorldToLight, unityShadowCoord4(worldPos, 1)).xyz; \
3         fixed shadow = UNITY_SHADOW_ATTENUATION(input, worldPos); \
4         fixed destName = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).r * shadow;
5 #endif

接下來,我們用它替換 Base Pass 和 Additional Pass 中對光照衰減和陰影的計算。程式碼和之前大部分都是一樣的,所以這裡就不分步講解,給出完整程式碼。讀者可以自行實現一下。

  1 Shader "Unity/01-Shadow"
  2 {
  3     Properties
  4     {
  5         _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
  6         _Specular ("Specular", Color) = (1, 1, 1, 1)
  7         _Gloss ("Gloss", Range(8.0, 256)) = 20
  8     }
  9     SubShader
 10     {
 11         Tags { "RenderType"="Opaque" }
 12         
 13         Pass {
 14 
 15             Tags { "LightMode"="ForwardBase" }
 16         
 17             CGPROGRAM
 18             
 19             #pragma multi_compile_fwdbase    
 20             
 21             #pragma vertex vert
 22             #pragma fragment frag
 23             
 24             #include "Lighting.cginc"
 25             #include "AutoLight.cginc"
 26             
 27             fixed4 _Diffuse;
 28             fixed4 _Specular;
 29             float _Gloss;
 30             
 31             struct a2v {
 32                 float4 vertex : POSITION;
 33                 float3 normal : NORMAL;
 34             };
 35             
 36             struct v2f {
 37                 float4 pos : SV_POSITION;
 38                 float3 worldNormal : TEXCOORD0;
 39                 float3 worldPos : TEXCOORD1;
 40                 SHADOW_COORDS(2)
 41             };
 42             
 43             v2f vert(a2v v) {
 44                 v2f o;
 45                 o.pos = UnityObjectToClipPos(v.vertex);
 46                 
 47                 o.worldNormal = UnityObjectToWorldNormal(v.normal);
 48                 
 49                 o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
 50                 
 51                 TRANSFER_SHADOW(o);
 52                 return o;
 53             }
 54             
 55             fixed4 frag(v2f i) : SV_Target {
 56                 fixed3 worldNormal = normalize(i.worldNormal);
 57                 fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
 58                 
 59                 UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
 60 
 61                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
 62                 
 63                  fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
 64 
 65                  fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
 66                  fixed3 halfDir = normalize(worldLightDir + viewDir);
 67                  fixed3 specular = _LightColor0.rgb * _Specular.rgb  * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
 68 
 69 
 70                 
 71                 return fixed4(ambient + (diffuse + specular) * atten, 1.0);
 72             }
 73             
 74             ENDCG
 75         }
 76     
 77         Pass {
 78 
 79             Tags { "LightMode"="ForwardAdd" }
 80             
 81             Blend One One
 82         
 83             CGPROGRAM
 84             
 85             #pragma multi_compile_fwdadd
 86             
 87             #pragma vertex vert
 88             #pragma fragment frag
 89             
 90             #include "Lighting.cginc"
 91             #include "AutoLight.cginc"
 92             
 93             fixed4 _Diffuse;
 94             fixed4 _Specular;
 95             float _Gloss;
 96             
 97             struct a2v {
 98                 float4 vertex : POSITION;
 99                 float3 normal : NORMAL;
100             };
101             
102             struct v2f {
103                 float4 pos : SV_POSITION;
104                 float3 worldNormal : TEXCOORD0;
105                 float3 worldPos : TEXCOORD1;
106                 SHADOW_COORDS(2)
107 
108             };
109             
110             v2f vert(a2v v) {
111                 v2f o;
112                 o.pos = UnityObjectToClipPos(v.vertex);
113                 
114                 o.worldNormal = UnityObjectToWorldNormal(v.normal);
115                 
116                 o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
117                 TRANSFER_SHADOW(o);
118                 return o;
119             }
120             
121             fixed4 frag(v2f i) : SV_Target {
122                 fixed3 worldNormal = normalize(i.worldNormal);
123                 #ifdef USING_DIRECTIONAL_LIGHT
124                     fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
125                 #else
126                     fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
127                 #endif
128                 
129                 fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
130                 
131                 fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
132                 fixed3 halfDir = normalize(worldLightDir + viewDir);
133                 fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
134                 
135                 UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
136                 
137 
138                 return fixed4((diffuse + specular) * atten, 1.0);
139             }
140             
141             ENDCG
142         }
143     }
144     FallBack "Specular"
145 }

2.3 普通透明物體的陰影

光源效果體現於實物,如果一個物體是透明的,那麼它肯定是沒有影子,那在 shader 中,這是如何實現呢? 用回我們在 【Unity Shader】(五) ------ 透明效果之半透明效果的實現及原理 實現的透明度測試並結合上面的程式碼,並把 Fallback 改為 VertexLit。看一下效果

 

可以看到陰影部分其實是相當於整個正方體的陰影,但事實上,鏤空區域不應該有陰影。這是因為 VertexLit 中處理陰影的 Pass 並沒有做透明度測試的計算。所以為了提供這樣的一個 Pass ,我們可以更改 Fallback 為 "Transparent/Cutout/VertexLit" 。要注意的是,需要提供一個 _CutOff 的屬性和 SHADOW_COORDS 的索引值。現在看一下效果:

這是屬於正常的結果。另外關於透明度混合的物體新增陰影會更加的複雜麻煩,有興趣的讀者可以自行思考與實現一下,這裡就不再贅述了。

三. 完整的光照 shader

emmm,在之前就提到過會給出完整的一個光照 shader ,現在我們給出一個包含了紋理計算,光照計算,陰影計算,基於 Blinn-Phong 的高光發射 shader。

  1 Shader "Unity/BumpedSpecular" {
  2     Properties {
  3         _Color ("Color Tint", Color) = (1, 1, 1, 1)
  4         _MainTex ("Main Tex", 2D) = "white" {}
  5         _BumpMap ("Normal Map", 2D) = "bump" {}
  6         _Specular ("Specular Color", Color) = (1, 1, 1, 1)
  7         _Gloss ("Gloss", Range(8.0, 256)) = 20
  8     }
  9     SubShader {
 10         Tags { "RenderType"="Opaque" "Queue"="Geometry"}
 11         
 12         Pass { 
 13             Tags { "LightMode"="ForwardBase" }
 14         
 15             CGPROGRAM
 16             
 17             #pragma multi_compile_fwdbase    
 18             
 19             #pragma vertex vert
 20             #pragma fragment frag
 21             
 22             #include "UnityCG.cginc"
 23             #include "Lighting.cginc"
 24             #include "AutoLight.cginc"
 25             
 26             fixed4 _Color;
 27             sampler2D _MainTex;
 28             float4 _MainTex_ST;
 29             sampler2D _BumpMap;
 30             float4 _BumpMap_ST;
 31             fixed4 _Specular;
 32             float _Gloss;
 33             
 34             struct a2v {
 35                 float4 vertex : POSITION;
 36                 float3 normal : NORMAL;
 37                 float4 tangent : TANGENT;
 38                 float4 texcoord : TEXCOORD0;
 39             };
 40             
 41             struct v2f {
 42                 float4 pos : SV_POSITION;
 43                 float4 uv : TEXCOORD0;
 44                 float4 TtoW0 : TEXCOORD1;  
 45                                 float4 TtoW1 : TEXCOORD2;  
 46                                 float4 TtoW2 : TEXCOORD3; 
 47                 SHADOW_COORDS(4)
 48             };
 49             
 50             v2f vert(a2v v) {
 51                  v2f o;
 52                  o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
 53              
 54                  o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
 55                  o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
 56 
 57                 TANGENT_SPACE_ROTATION;
 58                 
 59                 float3 worldPos = mul(_Object2World, v.vertex).xyz;  
 60                                 fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);  
 61                                 fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);  
 62                                 fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; 
 63                 
 64                                 o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);  
 65                                 o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);  
 66                                 o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);  
 67                   
 68                   TRANSFER_SHADOW(o);
 69                  
 70                  return o;
 71             }
 72             
 73             fixed4 frag(v2f i) : SV_Target {
 74                 float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
 75                 fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
 76                 fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
 77                 
 78                 fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
 79                 bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
 80 
 81                 fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;
 82                 
 83                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
 84                 
 85                  fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));
 86                  
 87                  fixed3 halfDir = normalize(lightDir + viewDir);
 88                  fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);
 89             
 90                 UNITY_LIGHT_ATTENUATION(atten, i, worldPos);
 91 
 92                 return fixed4(ambient + (diffuse + specular) * atten, 1.0);
 93             }
 94             
 95             ENDCG
 96         }
 97         
 98         Pass { 
 99             Tags { "LightMode"="ForwardAdd" }
100             
101             Blend One One
102         
103             CGPROGRAM
104             
105             #pragma multi_compile_fwdadd
106             // Use the line below to add shadows for point and spot lights
107 //            #pragma multi_compile_fwdadd_fullshadows
108             
109             #pragma vertex vert
110             #pragma fragment frag
111             
112             #include "Lighting.cginc"
113             #include "AutoLight.cginc"
114             
115             fixed4 _Color;
116             sampler2D _MainTex;
117             float4 _MainTex_ST;
118             sampler2D _BumpMap;
119             float4 _BumpMap_ST;
120             float _BumpScale;
121             fixed4 _Specular;
122             float _Gloss;
123             
124             struct a2v {
125                 float4 vertex : POSITION;
126                 float3 normal : NORMAL;
127                 float4 tangent : TANGENT;
128                 float4 texcoord : TEXCOORD0;
129             };
130             
131             struct v2f {
132                 float4 pos : SV_POSITION;
133                 float4 uv : TEXCOORD0;
134                 float4 TtoW0 : TEXCOORD1;  
135                                 float4 TtoW1 : TEXCOORD2;  
136                                 float4 TtoW2 : TEXCOORD3;
137                 SHADOW_COORDS(4)
138             };
139             
140             v2f vert(a2v v) {
141                  v2f o;
142                  o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
143              
144                  o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
145                  o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
146 
147                 float3 worldPos = mul(_Object2World, v.vertex).xyz;  
148                                 fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);  
149                                 fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);  
150                                 fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; 
151     
152                   o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
153                   o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
154                   o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);  
155                  
156                  TRANSFER_SHADOW(o);
157                  
158                  return o;
159             }
160             
161             fixed4 frag(v2f i) : SV_Target {
162                 float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
163                 fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
164                 fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
165                 
166                 fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
167                 bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
168