Shader基礎


  一、概念篇

1.基准:unity里的shader並不是一門獨特的語言,而是一種代碼生成方式,且可將低層次且復雜的shader編程進行簡化。但同時你也還是得使用Cg/HLSL來寫的。

2.原理:寫一個函數,以UVs或者一些數據為入口,然后以SurfaceOutput為輸出。同時在SurfaceOutput這個結構體里還有不同的 屬性。這樣對於這個函數來說,他的執行過程會生成vertex和pixel的Shader,並且傳遞一些渲染的路徑。

3.結構:輸出結構:

1
2
3
4
5
6
7
8
struct SurfaceOutput {
     half3 Albedo;
     half3 Normal;
     half3 Emission;
     half Specular;
     half Gloss;
     half Alpha;
};

Albedo,是漫反射的顏色值。
Normal,法線坐標
Emission,自發光顏色
Specular,鏡面反射系數
Gloss,光澤系數
Alpha,透明度系數

二、編程規則

1.要寫在CGPROGRAM..ENDCG的SubShader的塊里。不可寫在Pass里。

2.shader的名字是可以重復的,重復后,以后來的shader為主。

3.指令詳細:

1
#pragma surface surfaceFunction lightModel [optionalparams]

=>surfaceFunction,沒什么好說,肯定是函數名了。

=>lightModel是所采用的光照模型。可以自己寫也可使用內置如Lambert和BlinnPhong.

=>optionalparams:可選參數,一堆可選包括透明度,頂點與顏色函數,投射貼花shader等等。具體用到可以細選。

另外這里有一個功能。在Surface shader的CGPROGRAM里添加 #pragma debug [內容]。可在編譯結果的文件中看到。寫多少都行。但嘗試在其他種shader下不行。

三、實例學習:

1.Simple: 

1
2
3
4
5
6
7
8
9
10
11
12
Shader "Example/Diffuse Simple" {     
     SubShader {    
     Tags { "RenderType" = "Opaque" }   
     CGPROGRAM
     #pragma surface surf Lambert    
     struct Input {    float4 color : COLOR;     }; 
     void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = 1;  
     }    
     ENDCG  
     }      
     Fallback "Diffuse" }

第一個。行行來:

第一行:寫個名字。這也有講究的。斜線左邊為其父類的組,無則新增,有則累加,右邊才是真正的名字。注意,這些shader名不像C#腳本,無需文件名與shader名相同。

第二、三行:接下來就在SubShader里添加內容,SubShader是可以有多個的。然后上一個Tags,此處只用到RenderType這種,另 外的還有Rendering order, ForceNoShadowCasting..等。這些本階段暫不研究。

第四行:上一條指令,里面指定響應方法為surf且采用Lambert的光照模型。這個必須有的。

第五行:這個結構體,記得名字不能改,只能為Input。里面一個四元素的顏色值(RGBA)。

第七到第九行:第一個參數,純輸入的上述結構體參數。第 二個參數,inout標識,意思是可為輸入參數也可為輸出參數。Albedo根據前面介紹到的,是一個rgb的值,如果給一個1,其實就是 float3(1,1,1),就是反射出來的顏色為白色,如果為100,則是加強反射強度,並不會改變其顏色。為0或為負數時道理類似。

最后Fallback,后方的是自帶的shader,可以用自己自定義好的。這里這句的意思是,如果所有subshader在當前顯卡都不支持,則默認返回自帶的Diffuse。

2.Texture:

1
2
3
4
5
6
7
8
9
10
11
12
13
Shader "Example/Diffuse Texture"
{     Properties { _MainTex ( "Texture" , 2D) = "white" {} }     SubShader {
       Tags { "RenderType" = "Opaque" }
       CGPROGRAM
       #pragma surface surf Lambert
       struct Input { float2 uv_MainTex; };       sampler2D _MainTex;      
       void surf (Input IN, inout SurfaceOutput o) {
           o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
       }
       ENDCG
     }
     Fallback "Diffuse"
   }

這個例子呢。其實只是第一個的基礎上添加了一個2D屬性顯示名為Texture。以下解析:

第一個黑體:添加一個名叫_MainTex的屬性,指定其為2D類型且顯示為Texture。"white"那塊可不是亂寫的,是unity的 build-in的一些textures的名稱,而不是單純顏色名字。意思是當默認時顯示為名叫white的材質。如改成red(即使用名叫red的材 質,如果有其他也可叫其名字),則效果如下:

第二個黑體:uv_MainTex。這其中大有玄機,uv開頭指代后方材質的uv值,因此uv不變,后面的可以根據開頭起的名字動態換。還有哦,這種類似於_MainTex的命名方式是CG推薦的,其實不用下划線也OK的。

第三個黑體:這個Sampler2D,可以理解為引用一個2D Texture。因為下面的Tex2D函數需要這種類型。所以說這個后面的名字要與Properties里的對應一樣才行。

第四個黑體:Tex2D,這玩意就是根據對應材質上所有的點找指定 2DSample上的Texture信息,此處需要其RGB信息,就打出來賦給了其反射值。所以對有材質圖的情況下,要顯示出圖,還是要相應的反射其原圖的rgb值。

3.Normal mapping

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Shader "Example/Diffuse Bump" {
     Properties {
       _MainTex (
         "Texture" , 2D) = "white" {}     
          _BumpMap ( "Bumpmap" , 2D) = "bump" {}  
     }
     SubShader {
       Tags { "RenderType" = "Opaque" }
       CGPROGRAM
       #pragma surface surf Lambert
       struct Input {
         float2 uv_MainTex;
         float2 uv_BumpMap;
       };
       sampler2D _MainTex;      
       sampler2D _BumpMap;     
      void surf (Input IN, inout SurfaceOutput o) {
         o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;      
           o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));    
       }
       ENDCG
     }
     Fallback "Diffuse"
   }

這個例子里加了個凹凸貼圖,可實現類似一些很漂亮的凹凸效果。

第一個黑體:加一個2D類型的材質,默認為bump。(即帶有凹凸效果的)。

第二個黑體:上一個采集器。采集下來上面的材質。

第三個黑體:有講究,這個UnpackNormal是unity自帶的標准解壓法線用的,所謂解壓,我暫時學習到的只是將法線的區間進行變換。由於 tex2D(_BumpMap, IN.uv_BumpMap)取出的是帶壓縮的[0,1]之間,需要轉成[-1,1]。這個函數會針對移動平台或OPENGL ES平台采用 RGB法線貼圖,其他采用DXT5nm貼圖。為此也可自己寫。也在網上找到了一些資料,如下參考:

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
//  Shader: 帶法線貼圖的Surface Shader
//  Author: 風宇沖
Shader "Custom/3_NormalMap" {
   Properties
   {
     _MainTex ( "Texture" , 2D) = "white" {}
     _NormalMap ( "NormalMap" , 2D) = "white" {}
   }
   Subshader
   {
   CGPROGRAM
   #pragma surface surf BlinnPhong
   struct Input
   {
   float2 uv_MainTex;
   };
   //法線范圍轉換:單位法線 float3(x,y,z),x,y,z的取值范圍是 [-1,1]。在法線貼圖中被壓縮在顏色的范圍[0,1]中,所以需要轉換
   //(1)RGB法線貼圖
   float3 expand(float3 v) { return (v - 0.5) * 2; }
   //(2)DXT5nm法線貼圖
   float3 expand2(float4 v)
{
fixed3 normal;
normal.xy = v.wy * 2 - 1;
normal.z = sqrt(1 - normal.x*normal.x - normal.y * normal.y);
return normal;
}
   sampler2D _MainTex;
   sampler2D _NormalMap;
   void surf(Input IN,inout SurfaceOutput o)
   {
   half4 c = tex2D(_MainTex, IN.uv_MainTex);
   o.Albedo = c.rgb;
   o.Alpha = c.a;
//對法線貼圖進行采樣,取得壓縮在顏色空間里的法線([0,1])
   float4 packedNormal = tex2D(_NormalMap, IN.uv_MainTex);
   //要將顏色空間里的法線[0,1],轉換至真正3D空間里的法線范圍[-1,1]
   //注意:范圍基本都是從[0,1]轉換至[-1,1].主要是圖的通道與法線xyz的對應關系要根據法線貼圖格式而定
   //UnpackNormal, UnityCG.cginc里的函數
   //o.Normal = UnpackNormal(packedNormal);
   //expand,標准法線解壓函數
   o.Normal = expand(packedNormal.xyz);
   }
   ENDCG
   }
}

4.Rim Lighting 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Shader "Example/Rim" {
     Properties {
       _MainTex ( "Texture" , 2D) = "white" {}
       _BumpMap ( "Bumpmap" , 2D) = "bump" {}        _RimColor ( "Rim Color" , Color) = (0.26,0.19,0.16,0.0) //1        _RimPower ("Rim Power", Range(0.5,8.0)) = 3.0 //2     }
     SubShader {
       Tags { "RenderType" = "Opaque" }
       CGPROGRAM
       #pragma surface surf Lambert
       struct Input {
           float2 uv_MainTex;
           float2 uv_BumpMap; float3 viewDir; //3      };
       sampler2D _MainTex;
       sampler2D _BumpMap;       float4 _RimColor; //4       float _RimPower;//5 void surf (Input IN, inout SurfaceOutput o) {
           o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
           o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));             half rim = 1.0 - saturate(dot (normalize(IN.viewDir), o.Normal)); //6         o.Emission = _RimColor.rgb * pow (rim, _RimPower);//7 }
       ENDCG
     }
     Fallback "Diffuse"

新增的一些東西,我都用數字標注了。以下進行詳細解讀:

第一處(//1):上一個Color類型的顯示為Rim Color的變量。顏色值RGBA對應0.26,0.19,0.16,0.0

第二處(//2):這個Range類型的變量,結果還是一個float。只是這個float是在這個range之內。為什么這么定義呢。如果超多,或過小,則使用range內指明的值代替。

第三處(//3):viewDir 意為World Space View Direction。就是當前坐標的視角方向。這里有個從相關網上找的圖:鏈接:http://game.ceeger.com/forum/read.php?tid=11367

第四、五處(//4,//5):定義兩個變量對應properties里的值,取出使用。

第 六、七處:最里層是Normalize函數,用於獲取到的viewDir坐標轉成一個單位向量且方向不變,外面再與點的法線做點積。最外層再用 saturate算出一[0,1]之間的最靠近(最小值但大於所指的值)的值。這樣算出一個rim邊界。為什么這么做。原理以下解釋:

=>看圖。

=>這里o.Normal就是單位向量。外加Normalize了viewDir。因此求得的點積就是夾角的cos值。

=>因為cos值越大,夾角越小,所以,這時取反來。這樣,夾角越大,所反射上的顏色就越多。於是就得到的兩邊發光的效果。哈哈這樣明了吧。

這 里介紹一下這個half。CG里還有類似的float和fixed。half是一種低精度的float,但有時也會被選擇成與float一樣的精度。 fragment是一定會支持fixed類型,同時也會有可能將其精度設成與float一樣,這個比較復雜,后面篇章學到fragment時再深入探討。

以下為與3的對比,大家一下就知道誰是用了rim color的吧。對!下面那個盒子就是用些shader的效果。

5.Detail Texture

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
Shader "Example/Detail" {
     Properties {
       _MainTex ( "Texture" , 2D) = "white" {}
       _BumpMap ( "Bumpmap" , 2D) = "bump" {}       _Detail ( "Detail" , 2D) = "gray" {}     }
     SubShader {
       Tags { "RenderType" = "Opaque" }
       CGPROGRAM
       #pragma surface surf Lambert
       struct Input {
           float2 uv_MainTex;
           float2 uv_BumpMap;          
           float2 uv_Detail;      
       };
       sampler2D _MainTex;
       sampler2D _BumpMap;     
       sampler2D _Detail;     
      void surf (Input IN, inout SurfaceOutput o) {
           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));
       }
       ENDCG
     }
     Fallback "Diffuse"
   }

這個最好理解了。

前面三個一樣。上一個2D Texture。

最后一個黑體:在原先的反射基礎上,在加一層,Texture的反射。

就是這樣啦。最后上幾個截圖,大家一定就明白。

6.Detail Texture in Screen Space

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Shader "Example/ScreenPos" {
     Properties {
       _MainTex ( "Texture" , 2D) = "white" {}
       _Detail ( "Detail" , 2D) = "gray" {}
     }
     SubShader {
       Tags { "RenderType" = "Opaque" }
       CGPROGRAM
       #pragma surface surf Lambert
       struct Input {
           float2 uv_MainTex; float4 screenPos; };
       sampler2D _MainTex;
       sampler2D _Detail;
       void surf (Input IN, inout SurfaceOutput o) {
           o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;           float2 screenUV = IN.screenPos.xy / IN.screenPos.w; //2          screenUV *= float2(8,6);          o.Albedo *= tex2D (_Detail, screenUV).rgb * 2;       }
       ENDCG
     }
     Fallback "Diffuse"
   }

這個比較有趣,是從上個例子的基礎上將第二層疊加上的2D Texture根據當前屏幕的UV進行疊加,而不是根據自身的UV。這樣帶有含此shader材質的物體的貼圖就會跟着移動到的位置而變換圖片。

這里只需要說三點:

1.關於screenPos:screenPos是一個三維點,但是用齊次坐標的形式表示出來就是(x,y,z,w),根據齊次坐標的性質。 (x,y,z,w)的齊次坐標對應三維點(x/w,y/w,z/w)。因此把w值除掉可以看來是一種Normalize的作法,這樣就取出了實際的屏幕 xy的UV值。

2.對screenUV進行倍剩:此處剩float2(8,6)意為將原獲取到屏幕尺寸進行拉大的倍數。即x軸拉大8倍,y軸拉大6倍。

3.如何就平鋪了剛好一行8個,一列6個了呢? 原因我覺得是在於2d Texture自己是按Normalize后進行鋪的,因此在//2(剛轉完標准的)screenPos后,將其剩多少即便將原圖鋪多少張。

OK。明了。其實這個東西可以拿來做放大鏡的應用。上圖:

7. Cubemap reflection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Shader "Example/WorldRefl" {
     Properties {
       _MainTex (
         "Texture" , 2D) = "white" {}         _Cube ( "Cubemap" , CUBE) = "" {}     }
     SubShader {
       Tags { "RenderType" = "Opaque" }
       CGPROGRAM
       #pragma surface surf Lambert
       struct Input {
            float2 uv_MainTex;            float3 worldRefl;       };
       sampler2D _MainTex;       samplerCUBE _Cube;       void surf (Input IN, inout SurfaceOutput o) {
           o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb * 0.5;           o.Emission = texCUBE (_Cube, IN.worldRefl).rgb;       }
       ENDCG
     }
     Fallback "Diffuse"
   }
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
Shader "Example/WorldRefl Normalmap" {
     Properties {
       _MainTex ( "Texture" , 2D) = "white" {}
       _BumpMap ( "Bumpmap" , 2D) = "bump" {}
       _Cube ( "Cubemap" , CUBE) = "" {}
     }
     SubShader {
       Tags { "RenderType" = "Opaque" }
       CGPROGRAM
       #pragma surface surf Lambert
       struct Input {
           float2 uv_MainTex;
           float2 uv_BumpMap;
           float3 worldRefl; INTERNAL_DATA };
       sampler2D _MainTex;
       sampler2D _BumpMap;
       samplerCUBE _Cube;
       void surf (Input IN, inout SurfaceOutput o) {
           o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb * 0.5;
           o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
           o.Emission = texCUBE (_Cube, WorldReflectionVector (IN, o.Normal)).rgb;
       }
       ENDCG
     }
     Fallback "Diffuse"
   }

這兩段都是加一個cubemap的反射。第二段相比之下是在有normal反射的基礎上加的。Cubemap這東西,可設置幾種面的不能渲染圖,這方面可用於做天空盒。因為這樣可以從各個角度看過去以顯示不同的渲染效果。

以下說明:

1. worldRefl:即為世界空間的反射向量。

2. texCUBE:將反射向量一個個的往_Cube反射盒上找出然后做為Emission反射出來。

3. 第二個例子只是將其用在Normal反射后,這樣一定要多添加一個INTERNAL_DATA的屬性,另外也需用到WorldReflectionVectore方法取其利用Normal后的反射向量值。

類似於的效果,可見官網中的。我這也有一個,有點像打了光的樣子。

8.Slices via World Space Position

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
Shader "Example/Slices" {
    Properties {
      _MainTex ( "Texture" , 2D) = "white" {}
      _BumpMap ( "Bumpmap" , 2D) = "bump" {}
    }
    SubShader {
      Tags { "RenderType" = "Opaque" }     
      Cull Off      
      CGPROGRAM
      #pragma surface surf Lambert
      struct Input {
          float2 uv_MainTex;
          float2 uv_BumpMap;         
          float3 worldPos;      
      };
      sampler2D _MainTex;
      sampler2D _BumpMap;
      void surf (Input IN, inout SurfaceOutput o) {      
          clip (frac((IN.worldPos.y+IN.worldPos.z*0.1) * 5) - 0.5);           o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
          o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
      }
      ENDCG
    }
    Fallback "Diffuse"
  }

在看完這段后,我自己另外又加一段,以作對比:

1
2
3
4
5
6
7
8
float3 _tWorldPos;
void surf (Input IN, inout SurfaceOutput o) {      
      _tWorldPos = IN.screenPos.xyz / IN.screenPos.w;
      //clip (frac((IN.worldPos.y+IN.worldPos.z*0.1) * 5) - 0.5);
      clip (frac((_tWorldPos.y+_tWorldPos.z*0.1) * 3) - 0.5);
      o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
      o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
}

第二個黑體:frac是取小數的函數,如1.23 取出來是 0.23。clip函數用於清Pixel的,負值情況下才進行清pixel。且越小,即絕對值越大則清越多。 這里注意那個* 5,仔細一想,如果frac出來的值越大,-0.5值就越大,絕對值就越小,因此這樣清掉的pixel越少,所以就可以間接的增加分段的次數。那為什么要+IN.worldPos.z*0.1呢,主要原因就是空開的斷添加一個傾斜角度,可以用空間思想想下。

我的那段,就是將要clip的坐標換掉,換成屏幕的。這樣你移動物體時,clip掉的部分會變化。

最后,上下效果圖:

9.Normal Extrusion with Vertex Modifier

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Shader "Example/Normal Extrusion" {
     Properties {
       _MainTex ( "Texture" , 2D) = "white" {}       _Amount ( "Extrusion Amount" , Range(-1,1)) = 0.5     }
     SubShader {
       Tags { "RenderType" = "Opaque" }
       CGPROGRAM
       #pragma surface surf Lambert vertex:vert //1
       struct Input {
           float2 uv_MainTex;
       };       float _Amount; //2       void vert (inout appdata_full v) { //3          v.vertex.xyz += v.normal * _Amount;  //4       }       sampler2D _MainTex;
       void surf (Input IN, inout SurfaceOutput o) {
           o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
       }
       ENDCG
     }
     Fallback "Diffuse"
   }

這是個自定義vertex的例子,效果可以實現點坐標的放大縮小,以形成肥仔與瘦棍的效果,哈哈。

第一個黑體(//1):添加一個可選參數為vertex,主要是為了給其添加一個函數vert。

第二個黑體(//2):這個_Amount對應開頭的那個屬性_Amount。具體是個Range值,可在shader界面外通過滑動條改變這個值。默認為0.5。

第三個黑體(//3):這里除了之前學過的東西外,多了個appdata_full的結構體。這里面的結構(載自UNITY官方論壇)如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct appdata_full {
     float4 vertex : POSITION;
     float4 tangent : TANGENT;
     float3 normal : NORMAL;
     float4 texcoord : TEXCOORD0;
     float4 texcoord1 : TEXCOORD1;
     fixed4 color : COLOR;
#if defined(SHADER_API_XBOX360)
     half4 texcoord2 : TEXCOORD2;
     half4 texcoord3 : TEXCOORD3;
     half4 texcoord4 : TEXCOORD4;
     half4 texcoord5 : TEXCOORD5;
#endif
};

第四個黑體(//4):就是像為個點,換當前法線向量的指定倍數進行擴展。

上效果:

10.Custom data computed per-vertex

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Shader "Example/Custom Vertex Data" {
     Properties {
       _MainTex ( "Texture" , 2D) = "white" {}
     }
     SubShader {
       Tags { "RenderType" = "Opaque" }
       CGPROGRAM
       #pragma surface surf Lambert vertex:vert       struct Input {
           float2 uv_MainTex;          
           float3 customColor;      //1
         };
       void vert (inout appdata_full v, out Input o) { //2
             UNITY_INITIALIZE_OUTPUT(Input,o);           //3
             o.customColor = abs(v.normal);       //4
       }
       sampler2D _MainTex;
       void surf (Input IN, inout SurfaceOutput o) {
           o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
           o.Albedo *= IN.customColor;     //5
       }
       ENDCG
     }
     Fallback "Diffuse"
   }

這個例子是用來渲染顏色的。我的分析如下:

第一處(//1):取一個顏色值,float3,對應RGB。

第二處(//2):較前個例子,多一個Input類型的參數,只為輸出使用。

第三處(//3):UNITY_INITIALIZE_OUTPUT(type,name)這個函數大有用處,主要是將叫[name]的變量請空改成type類型。以下是從HLSLSupport.cginc里找到的定義:

1
2
3
4
5
#if defined(UNITY_COMPILER_HLSL)
#define UNITY_INITIALIZE_OUTPUT(type,name) name = (type)0;
#else
#define UNITY_INITIALIZE_OUTPUT(type,name)
#endif

第四處(//4):RGB顏色值當然只能為正值,所以使用絕對值去取normal的值。

第五處(//5):在原先已經渲染上texture顏色值的基礎上,加上這層自定義的顏色值。

上效果:

11.Final Color Modifier

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Shader "Example/Tint Final Color" {
     Properties {
       _MainTex (
         "Texture" , 2D) = "white" {}
          _ColorTint ( "Tint" , Color) = (1.0, 0.6, 0.6, 1.0)       }
     SubShader {
       Tags { "RenderType" = "Opaque" }
       CGPROGRAM
       #pragma surface surf Lambert finalcolor:mycolor        struct Input {
           float2 uv_MainTex;
       };       fixed4 _ColorTint;       void mycolor (Input IN, SurfaceOutput o, inout fixed4 color) {
            color *= _ColorTint;       }
       sampler2D _MainTex;
       void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
       }
       ENDCG
     }
     Fallback "Diffuse"
   }

這個例子是跟上面例子的對比,前種使用普通反射進行疊加上顏色,此處則是直接使用finalcolor對其顏色進行處理,這種可以處理整個模型的固定顏色值的渲染。以下做簡要的分析:

1.finalcolor:mycolor :這個是另一種可選參數,就是用戶自定義的顏色處理函數。函數名為mycolor.

2.mycolor函數:注意到函數除了有surf的兩個參數外,還多了個顏色參數,這個顏色參數就是當前模型上顏色對象,對他的更改將直接影響全部來自於lightmap,light probe和一些相關資源的顏色值。

效果:

12.Custom Fog with Final Color Modifier

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
Shader "Example/Fog via Final Color" {
     Properties {
       _MainTex ( "Texture" , 2D) = "white" {}       _FogColor ( "Fog Color" , Color) = (0.3, 0.4, 0.7, 1.0)     }
     SubShader {
       Tags { "RenderType" = "Opaque" }
       CGPROGRAM
       #pragma surface surf Lambert finalcolor:mycolor vertex:myvert      
       struct Input {
           float2 uv_MainTex;        
           half fog;     
       };
       void myvert (inout appdata_full v, out Input data)
       {        
           UNITY_INITIALIZE_OUTPUT(Input,data);        
           float4 hpos = mul (UNITY_MATRIX_MVP, v.vertex); //1      
           data.fog = min (1, dot (hpos.xy, hpos.xy) * 0.1); //2 
       }
       fixed4 _FogColor;
       void mycolor (Input IN, SurfaceOutput o, inout fixed4 color)
       {      
             fixed3 fogColor = _FogColor.rgb;       
             #ifdef UNITY_PASS_FORWARDADD   //3                   
                fogColor = 0;        //3     
             #endif          //3
             color.rgb = lerp (color.rgb, fogColor, IN.fog);   //4       }
       sampler2D _MainTex;
       void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
       }
       ENDCG
     }
     Fallback "Diffuse"
   }

這個很高難度,里面還有些之前沒用到過的函數,以下是我的理解:

第一處(//1):mul是矩陣相乘的函數。UNITY_MATRIX_MVP是model、view、projection三個矩陣相乘出來的4x4的 矩陣。v.vertex是一個float4的變量,可理解成4x1的矩陣,兩者相乘,則得出一個float4,這個值就是視角窗口的坐標值,這個坐標就跟 camera的關聯了。

第二處(//2):這個fog的浮點值就是其強度,范圍一般在-1到1之間,說一般,只是我個人建議的值,設成其他也行,只是沒多大意義。越負就越黑。再 看后面這個點積,這個仔細一想,不難理解,其實就是為了達到一種擴散的效果,因此兩個一樣的向量相乘,其實就是直接對坐標做平方擴展,這樣fog就更有霧 的感覺。

第三處(//3):這個宏不好找,就看官方對這個例子的解釋為正向渲染時的額外通道。字面不好理解,多多嘗試過可以有所發現,其實就是在霧氣漸漸消失處那 塊額外的渲染區。可以將fogColor = 0; 改成fogColor = fixed3(1,0,0)。外面霧氣顏色再選成白色,效果則如下:



霧氣改成綠色后:效果如下:

第四處(//4):lerp函數是個有趣的函數。第一個參數是左邊界,第二個參數是右邊界,第三個相當於一個值介於0到1之間的游標。游標為0,則為左邊 界,為1為右邊界,取中間值則是以此類推,取插值。其實也可以把它看成百分比。這里的fog則可以看來那個游標,值越大,則越接近fogColor,越小 越接近原色。

原shader所出來的效果再來張:

13.Linear Fog

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
Shader "Example/Linear Fog"
   Properties {
     _MainTex ( "Base (RGB)" , 2D) = "white" {}
   }
   SubShader {
     Tags { "RenderType" = "Opaque" }
     LOD 200  //1
 
     CGPROGRAM
     #pragma surface surf Lambert finalcolor:mycolor vertex:myvert
 
     sampler2D _MainTex;
     uniform half4 unity_FogColor;  //2
     uniform half4 unity_FogStart;
     uniform half4 unity_FogEnd;
 
     struct Input {
       float2 uv_MainTex;
       half fog;
     };
 
     void myvert (inout appdata_full v, out Input data) {
       UNITY_INITIALIZE_OUTPUT(Input,data);
       float pos = length(mul (UNITY_MATRIX_MV, v.vertex).xyz); //3
       float diff = unity_FogEnd.x - unity_FogStart.x; //4
       float invDiff = 1.0f / diff;  //5
       data.fog = clamp ((unity_FogEnd.x - pos) * invDiff, 0.0, 1.0); //6
     }
     void mycolor (Input IN, SurfaceOutput o, inout fixed4 color) {
       fixed3 fogColor = unity_FogColor.rgb;
       #ifdef UNITY_PASS_FORWARDADD
       fogColor = 0;
       #endif
       color.rgb = lerp (fogColor, color.rgb, IN.fog);
     }
 
     void surf (Input IN, inout SurfaceOutput o) {
       half4 c = tex2D (_MainTex, IN.uv_MainTex);
       o.Albedo = c.rgb;
       o.Alpha = c.a;
     }
     ENDCG
   }
   FallBack "Diffuse"
}

這個官方只貼出了代碼,無任何解釋。網上也未曾看到有人解答,在此為大家分析下。其實這個與上面那個例子相比之下,采用的fog的源頭變了,這里是獲取rendersettings里的fog來設置Fog的顏色、強度與起點終點等。以下進行解析:

第 一處(//1):LOD 200,200是個代號,設成此的目的就是限制shader級別只到200為止,高過200的不采用,即使顯卡支持,也不會使用高過200的shader 級別的渲染方式。官方的解釋:http://docs.unity3d.com/Documentation/Components/SL- ShaderLOD.html

第二處(//2):此處標記uniform的意圖就是讓Cg可以使用此變量。因此這三個uniform變量均來自於RenderSetting中。你可以預先設置好三個值。

第三處(//3):length函數用於取一個向量的長度,如果是float3則采取如下形式:

1
2
3
4
float length(float3 v)
{
     return sqrt(dot(v,v));
}

就是點積取平方根。

第四處(//4):計算fog起終點間的反差。

第五處(//5):將4中算得的diff置反過來。

第六處(//6):則將算出來的離視角的距離與0到1之間進行比對,小於0則為0,大於1則為1,范圍之內就是其原值,總的來說,利用clamp函數防止其出界。

分析下原理:咱們先將rendersetting里的顏色設成紅色,fog start 設成0, fog end設成50。
這時算出的diff = 50, invdiff = 1/50。將原fog的計算稍做簡化,得出如下結果:

fog = clamp((1 - pos/50) , 0 , 1);這個式子很是明了,pos是距離,即距離越遠,clamp里值越小,根據后面這句:

1
color.rgb = lerp (fogColor, color.rgb, IN.fog);

我們就可以判斷出其越靠近fogColor,霧氣就會越重。

最后上個效果圖:這里選的是Linear的fog。

到此,所有的surface shader的官方例子都詳細的介紹完了。哎,開源中國對cg無什么代碼顯示支持,大家要代碼看不清,可以直接去官網上去面看。 http://docs.unity3d.com/Documentation/Components/SL-SurfaceShaderExamples.html

這里送上本文的項目工程:http://pan.baidu.com/s/1xindN

四、學習技巧

這里是我個人的一些觀點:

1.遇問題先找官網,找官網論壇,找官網文檔。

2.學會從軟件根目錄下的CGIncludes文件夾下找相關的函數宏定義。

3.積累相關線性代數與計算機圖形學的知識,學習會更輕松些。 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM