本文為翻譯,附上原文鏈接。
轉載請注明出處——polobymulberry-博客園。
剛開始接觸Unity3D Shader編程時,你會發現有關shader的文檔相當散,這也造成初學者對Unity3D Shader編程望而卻步。該系列教程的第一篇文章(譯者注:即本文,后續還有5篇文章)詳細介紹了Unity3D中的表面着色器(Surface Shader)的,為學習更復雜的Shader編程打下基礎。
動機
如果你是剛剛接觸Shader編程的新手,你可能不知道從何開始踏出Shader編程的第一步。本教程將帶你一步步完成一個表面着色器(Surface Shader)和片段着色器(Fragment Shader)。本教程也將介紹在Unity3D Shader編程中所使用的一些函數和變量,這些內容可能和你在網上看到的不一樣哦!
如果你滿足下面的條件,我覺得你應該看看這篇文章:
- 如果你是shader編程的新手。
- 你想在你的游戲中使用shader做一些很炫酷的效果,但是你在網上找不到可用的Shader(譯者注:o(╯□╰)o自己動手豐衣足食)。
- 由於缺乏對基礎知識的了解,造成不能隨心所欲使用Strumpy着色器編輯器(譯者注:Strumpy Shader Editor,一種圖形化編寫shader的方式,看着很誘人!)。
- 你想在你的shader代碼中手動處理紋理(Textures)
本文是該系列教程的第一篇文章,隨后我們會制作一些更復雜的shader。相比起來,第一篇文章確實很簡單。
關於作者
我也是Shader編程的新手----所以我決定寫這篇教程幫助大家入門——我當初也在入門上遇到很多苦惱。事實上我並不是一個Shader編程專家。
當我想了解Shader編程時,我曾反復閱讀官方文檔,但是我最終發現官方文檔講述的順序並不適合我學習shader。所以我覺得我應該寫一篇教程,並分享我所學到的知識。不過寫完教程之后,我發現再次閱讀官方文檔時,覺得明白多了。
盡管本教程中的所有例子都能正常運行,但是我相信肯定有更好shaders實現這些例子。如果聰明的你對這些例子中的shaders有更好的建議,請在評論區留言!
我之所以學習shader編程是因為我需要在我創建的游戲世界中創建些東西,但這個游戲世界創建起來有個麻煩之處,因為它是由不同角色組成的,而我必須創建由多個部分組成的一個統一網格(mesh)。所以我只能對每個角色使用一次繪制調用(draw call)。(譯者注:完全不知道他在講什么?所以我把原文放在下面給大家評評理)
My reason for getting into shader programming was to build something that I needed for a world populated with an endless array of different characters. I needed to build a combined mesh out of multiple parts so I only have one draw call per character.
通過打開和關閉角色的穿衣效果,我使用Megafiers(一個變形插件)修改了角色的基本網格(base meshes)。其中的困難在於我只有一個紋理(texture),但是我卻想給每個角色的皮膚,服飾以及其他的特征使用不同的顏色。我想到一個方法----對每個角色使用不同的3個4x4紋理,並使用一個shader來給模型上色。我將在整個教程中詳細描述我做的這個shader,但是現在—我想你們已經迫不及待地想看我創建的角色表演一段即興的快閃舞(flash mob dance)(譯者注:網上截的圖片)。
着色器和材質(shaders&materials)
一個shader所做的就是將一個模型的網格(mesh)渲染到屏幕上。Shader可以被定義為一系列的屬性(譯者注:就像一個函數里面的參數一樣,你可以改變函數的不同賦值來改變函數的輸出結果),你可以通過改變這些屬性來改變模型渲染到屏幕上的效果。而這些屬性被存放起來,放到一個叫做材質(material)的地方。
Unity3D Shader有以下幾種
- 表面着色器(surface shader)----后台自動為你做的絕大部分的工作,減少了你工作量,並且適合絕大多數需要shader的情況。
- 片段着色器(fragment shader)----可以讓你做更多的效果,但是此shader更難寫。你也可以用它做一些底層的工作,比如頂點光照(Vertex lighting,即在每個頂點存儲該點的光照信息)。頂點光照對於移動設備很有用(譯者注:估計省內存吧)。該shader對於一些需要多通道(multiple passes)的高級渲染效果也很有效。
本文中我們將關注點放在表面着色器上。
學習Shader的資源
如果你要學習Shader編程,我向你推薦下面幾個資源
- Martin Kraus's fantastic Wiki Book GLSL Programming/Unity
- Unity's Shader Reference
- NVidia's tutorial on the CG programming language
- CreativeTD's video series on writing surface shaders
Shader的流水化工作方式
(譯者注:Shader的工作方式也稱為shader流水線(pipeline),因為shader工作方式很類似汽車流水線,將模型上一系列頂點數據和其他各種數據作為輸入,用這個shader組成的流水線加工下,出來的就成了炫酷的效果了。)
你將在shader流水線中看到不明覺厲的各種術語,我將用我自己的語言盡量降低理解的難度。
Shader的工作就是輸入一些3D幾何信息,經過shader處理后將其變為2D的像素呈現在屏幕上。好處是在shader處理過程中,你只需要改變少數幾個屬性就可以產生不同的效果。對於表面着色器,該工作流程看起來像下面這樣:
(譯者注:簡單講解一下這個流程圖,首先要渲染的物體將自己的幾何信息傳遞到Shader中,並且系統得到了該物體的頂點信息,然后你可以選擇經不經過Vertex Function來處理這些頂點信息,隨后經過光柵化(將三維幾何信息映射到二維屏幕上,打個不恰當的比喻,相當於把3D模型拍扁到屏幕上,然后你就可以專心處理屏幕上的像素了),每個像素經過你的shader代碼將得到最終的顏色值)
注意在表面着色器(Surface Shader)中的函數退出之前,像素的顏色還沒有計算出來。這意味着你可以再次之前傳入頂點的法向量來影響光照的計算。
片段着色器(Fragment Shader)有着同樣的工作流程,但事實上,片段着色器中必須有Vertex Function(上圖中的Vertex Function部分就是可選的(Optional)),而且需要在像素處理階段做很多的工作才能產生最終的像素。而表面着色器隱藏了這些。(譯者注:給我的感覺就是片段着色器向用戶提供了更多的接口進行更高級的渲染)。
下圖展示了你的代碼如何被調用以及代碼構成
從上圖我們可以看到,當你寫一個shader的時候,你可能得有一些屬性值(properties),並且有一個或多個Subshaders。具體使用哪個Subshader進行處理取決於你的運行平台。你應該還要指定一個Fallback shader,當你的subshader沒有一個能運行在你的目標設備上,將使用Fallback shader(譯者注:有點像備胎)。
每個Subshader都至少有一個通道(pass)作為數據的輸入和輸出。你可以使用多個通道(passes)執行不同的操作,比如在一個Grab Pass中,你可以獲取將要呈現到屏幕上的像素值(譯者注:類似於glsl中的fragment buffer)。當你想制作高級的扭曲效果,這非常有用。雖然當你開始學習shader編程時,你可能並不會使用到它。另外一個使用多通道(multiple passes)的原因是在不同時刻,你可能需要寫入或者禁止寫入深度緩存的使用。
當你寫表面着色器時,我們將直接在Subshader這個層次上寫代碼,系統將把我們的代碼編譯成若干個合適的通道(pass)。
盡管shader最終產生的是二維像素,但是其實這些像素除了保存xy坐標外,本身保存着深度值(即每個像素點上的內容在原先3D場景中離照相機的遠近),這樣距離照相機近的物體就會把距離照相機遠的物體遮擋住,在屏幕上顯示時,就是將其像素值覆蓋。
你可以控制是否在你的shader中使用深度緩存(Z-buffer)產生一些特效,或者在Pass中使用一些指令決定shader是否可以寫入Z-buffer:比如使用ZWrite Off時,任何你輸出的東西都不會更新Z-buffer的值,即關閉的Z-Buffer的寫入功能。
你可以使用Z-buffer技術在別的物體上掏出一個洞,你可以先寫入需要打洞區域的深度值,但不輸出打洞區域所屬的像素值,然后在你模型后面的物體的深度值將無法寫入(因為Z-buffer覺得你的模型已經擋住了后面的物體)(譯者注:這樣你打洞區域顯示的就是一開始使用的背景色,會造成一個洞穿過了這些物體的效果)。
下面是一些shader代碼:
希望你能看出上面代碼是由Properties,SubShader,Fallback三段代碼組成的。
理解Shader代碼
文章剩下的部分將講述上面那段簡單代碼到底做了什么?真正的干貨馬上就來了,你必須好好掌握這些內容。
當你進行shader編程時,你必須使用正確的變量名和函數名來調用它們,事實上變量的名稱在某些情況下能讓人一眼看出它的特定含義。
創建並使用默認Shader
(譯者注:在詳細介紹Shader之前,我們先簡單介紹下shader如何使用。)
1. 我們先打開Unity(我的版本是4.6.1),創建新工程,並在Assets文件夾下創建三個目錄,如下:
2. 我們再創建一個cube。
可以在Inspector面板看到新創建的cube所使用的Material如下。
3. 打開Material文件夾,我們在其中創建一個Shader和一個Material。
此時New Material的默認Shader為Diffuse。
我們將NewShader拖到New Material上。
可以看到該材質所使用的Shader變成我們新建的NewShader了。當然你也可以直接點擊材質編輯器中Shader下拉框,選擇相應的Shader。
4. 最后將New Material拖到cube上。可以看到cube所使用的材質和Shader都變成了我們新創建的材質和Shader了。
Properties(屬性值)簡介
你在shader代碼中的Properties{…}部分定義Shader中的屬性值(屬性值就是用戶傳入給shader的數據,比如紋理之類的,然后shader處理這些紋理,產生特效。可以理解為屬性值相當於一種全局變量,而Shader就是那個主函數,Unity的優勢在於給這個全局變量賦值可以在Inspector面板進行)。注意Properties(屬性值)是所有Subshader代碼中的共享的,意味着所有SubShader代碼中都可以使用這些屬性值。
屬性值(property)定義的形式:
_Name(“Displayed Name”,type) = default value[{options}]
- _Name 屬性值的名稱,是在shader代碼內部使用的,區別於下面的Displayed Name,后者是在Inspector 面板上顯示的,作為外界(用戶)的輸入提示。
- Displayed Name 呈現在材質編輯器中的屬性值名稱,在Inspector面板上顯示。
總結:打開我們創建的NewShader。可以看到_MainTex是在代碼中使用的,而Base (RGB)是在材質編輯器中使用的
- type 屬性值的類型,包括:
- Color – 表示純色,使用了RGBA表示法
- 2D – 代表尺寸為2的冪次的紋理(如2,4,8,16…256,512)
- Rect – 代表紋理(texture),不同於上面的紋理,此處紋理的大小不一定是2的冪次。
- Cube – 用於3d中的cube map,經常提到的天空盒就是使用了cube map。
- Range(min, max) – 在min和max之間的一個值,在面板中可用滑動條改變其值大小。
- Float – 任意一浮點數。
- Vector – 4維向量值,本質就是4個浮點數組成的類型。
來張全家福:
- default value 屬性值的初始值,就相當於你的變量初始化的那個值。
- Color – (red,green,blue,alpha) 使用了RGBA這種格式的顏色,alpha指的是透明度– 比如 (1,1,1,1)
- 2D/Rect/Cube – 紋理的類型,上面已經介紹過了。初始化值可以使一個空字符串,或者"white", "black", "gray", "bump"(說明此紋理是一個凹凸紋理)
- Float/Range – 這個沒啥說的,跟浮點數初始化一樣一樣的
- Vector – 4維向量,其中4個數均為浮點數 (x,y,z,w)
- { options } 這里注意了,{options} 僅僅用於紋理類型,比如上面提到的2D,Rect,Cube,對於這些類型,如果沒有options可填,至少要寫一個空的{},否則編譯出錯。可以使用空格將多個options(選項)分開 ,可用的options(選項)如下:
- TexGen texgenmode:紋理坐標自動生成的方式。可以是ObjectLinear, EyeLinear, SphereMap, CubeReflect, CubeNormal其中之一,這些方式和OpenGL中的紋理坐標生成方式相對應,具體詳見這篇博文。注意當你寫Vertex Function時,紋理坐標產生方式將被忽略。
下面舉幾個屬性值寫法的例子:
// 定義了一個半透明(alpha=0.5)效果的紅色作為默認顏色值
_MainColor(“Main Color”,Color)=(1,0,0,0.5)
// 定義了一個默認值為白色的紋理
_Texture(“Texture”,2D) =”white” {}
注意屬性值的定義末尾處不需添加分號。
標簽(Tags)
你的表面着色器可以用一個或多個標簽(tags)進行修飾。這些標簽的作用是告訴硬件何時去調用你的shader代碼。
在我們的例子中,我們使用:Tags {“RenderType” = “Opaque”},這意味着當程序去渲染不透明的幾何體時,將調用我們的shader,Unity定義了一系列這樣的渲染過程。另一個很容易理解的標簽就是Tags {“RenderType” = “Transparent”},意味着我們的shader只會輸出半透明或透明的像素值。
其它一些有用的標簽,比如“IgnoreProjector”=“True”,意味着你渲染的物體不會受到projectors(投影儀)的影響。
“Queue”=“xxxx”(給shader所屬的對象貼上渲染隊列的標簽)。當渲染的對象類型是透明物體時,Queue標簽能產生一些非常有趣的效果。該標簽決定了物體渲染的順序(譯者注:我猜測它的工作方式是這樣的,一個場景中有很多個物體,當這些物體被渲染時,必須有一個渲染的順序,比如背景應該比其他物體先渲染出來,否則背景會將之前渲染的物體遮擋住,具體方法是將背景使用的shader中貼上一個“Queue”=“Backfround”標簽,這樣使用該shader的物體將被貼上Background的標簽。總之當渲染整個場景時,unity會根據這些渲染隊列的標簽決定按什么順序去渲染對應標簽所屬的物體)。
- Background – 在所有其他物體渲染之前渲染,被用於天空盒或類似的背景效果。
- Geometry(默認tags為geometry) – 適用於大多數物體。非透明物體使用這種渲染順序。
- AlphaTest – 進行alpha測試的像素(alpha-test是指當前像素的alpha小於一定的值就舍棄該像素)應該使用該渲染順序。單獨設置該渲染順序是因為當在渲染完所有實體過后,渲染alpha測試的物體將更有效率。
- Transparent – 該渲染標簽所屬的物體將在標簽為Geometry和AlphaTest之后的物體渲染,並且這些貼着Transparent的所有物體本身是從后往前依次渲染的。任何經過alpha-blended的物體都應該使用該標簽(譯者注:alpha-blended是指使用當前像素的alpha作為混合因子,來混合之前寫入到緩存中像素值,注意此時shader是不能寫入深度緩存的,因為關閉了寫入深度緩存的功能,如果不關閉寫入深度緩存,那么在進行深度檢測的時候,它背后的物體本來我們是可以透過它被我們看到的,但由於深度檢測時,小於它的深度就被剔除了,從而我們就看不到它后面的物體了),玻璃和粒子效果比較適合該渲染標簽。
- Overlay – 該渲染標簽適合覆蓋效果,任何最后渲染的效果都可以使用該標簽,比如透鏡光暈。
有趣的是你可以給這些基本的渲染標簽進行加加減減。這些預定義的值本質上是一組定義整數,Background = 1000, Geometry = 2000, AlphaTest = 2450, Transparent = 3000,最后Overlay = 4000。(譯者注:從此處我們也可以一窺究竟,貌似數值大的后渲染。)這些預設值這對透明物體有很大影響,比如一個湖水的平面覆蓋了你用廣告牌制作的樹,你可以對你的樹使用“Queue”=”Transparent-102”,這樣你的樹就會繪制在湖水前面了。
Shader的整體結構
讓我們回顧下shader代碼的結構。
#pragma surface surf Lambert 這段代碼表示其中surface表示這是一個表面着色器,進行結果輸出的函數名稱為surf,其使用的光照模型為Lambert光照模型。
我們的CG程序使用了一種經過修飾的類C語言 —— CG語言(是Nvidia和微軟共同出品的一種shader語言)。詳見Nvidia的文檔 —— 我在文中也會介紹一些基本的Cg使用方法。
浮點數類型(float)和向量值類型(vec)一般都會在末尾加上2,3,4這些數字(float2,float4,vec3…)表示該類型具體有幾個元素組成。這種定義方式使數值操作變得更方便,你可以將其當做一個整體使用,或者單獨使用其分量。
//定義一個浮點類型的二維坐標
vec2 coordinate;
//定義一個顏色變量(4個浮點值分量的顏色值)
float4 color;
//通過點乘得到3個浮點值分量的顏色值
float3 multipliedColor = color.rgb * coordinate.x;
你可以使用.xyzw或.rgba來表明你使用的變量類型具體的含義,比如.xyzw可能表示的是旋轉四元數,而.xyz表示位置或法向量,.rgba表示顏色。當然,你可以僅僅使用float作為單個浮點值類型。其實對.rgba等分量訪問符的使用也稱作swizzle,尤其是對顏色的處理,比如顏色空間的轉換可能會用到它,比如color=color.abgr;
你將會遇到half(半精度)和double(雙精度)類型,half(一般16bit)即正常float(一般32bit)的一半精度,double(一般64bit)是正常float的兩倍精度(此處的倍數衡量的方式不是指表示的范圍,而是表示可以使用的bit位數)。使用half經常是出於性能考慮的原因。還有一種區別於浮點數的定點數fixed,精度更低。
當你想將顏色值規范到0~1之間時,你可能會想到使用saturate函數(saturate(x)的作用是如果x取值小於0,則返回值為0。如果x取值大於1,則返回值為1。若x在0到1之間,則直接返回x的值.),當然saturate也可以使用變量的swizzled版本,比如saturate(somecolor.rgb);
你可以使用length函數得到一個向量的長度,比如float size = length(someVec4.xz);
如何從表面着色器輸出信息
我們的surface function(表面函數)每個像素調用一次,系統已經事先計算出當前處理的像素的輸入值(准確來說應該是輸入結構體,即Input IN中的Input類型)。 它是根據每個網格上的面片,並進行插值得到的結果。
來看看我們的surf函數
void surf (Input IN, inout SurfaceOutput o) { o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb; }
很明顯我們可以看出,我們返回了o.Albeodo值 – 該值是Unity為我們定義的SurfaceOutput結構體中的某個成員。接下來讓我們看看SurfaceOutput具體定義了哪些成員。該Albedo表示像素的顏色。
struct SurfaceOutput { half3 Albedo; //該像素的顏色值 half3 Normal; //該像素的法向量 half3 Emission; //該像素的輻射光,輻射光是最簡單的一種光,它直接從物體發出並且不受任何光源影響 half Specular; //該像素的鏡面高光 half Gloss; //該像素的發光強度 half Alpha; //該像素的透明度 };
你只要將該結構體中值交給Unity,Unity會自動根據這些值產生最終效果,而不需要你關心其中的細節。
我答應你們的干貨就在下面
首先看看作為我們surf函數的輸入是啥?
我們定義了一個輸入結構體如下:
struct Input { float2 uv_MainTex; };
通過簡單地創建結構體,我們告訴系統當我們每次調用surf函數時,獲取MainTex在該像素的紋理坐標。如果我們有第二個紋理叫做—_OtherTexture,我們可以通過在輸入結構體中添加下面代碼得到它的紋理坐標
struct Input { float2 uv_MainTex; float2 uv_OtherTexture; };
如果一個模型還有第二套紋理坐標,我們可以這樣做:
struct Input { float2 uv_MainTex; float2 uv2_OtherTexture; };
此時對於我們所使用的所有紋理,我們的輸入結構體包含一套uv坐標或者一套uv2坐標。
如果我們的shader很復雜並且需要知道像素的其他相關信息,我們就可以將以下變量包含在輸入結構體中,以此來查詢其他的相關變量。
- float3 viewDir – 視圖方向( view direction)值。為了計算視差效果(Parallax effects),邊緣光照(rim lighting)等,需要包含視圖方向(view direction)值。
- float4 with COLOR semantic(比如float4 currentColor,即用戶自定義和顏色相關的變量名稱) – 每個頂點(per-vertex)顏色的插值。
- float4 screenPos – 為了反射效果,需要包含屏幕坐標系中的位置信息時包含此參數。
- float3 worldPos – 世界坐標系中的位置。
- float3 worldRefl – 表示世界坐標系中的反射向量(reflect vector)。如果表面着色器(surface shader) 不為SurfaceOutput結構中的Normal賦值,也就是說Normal不會發生變化,也就不需要重新求取worldRefl值了,那么就可以直接通過Input結構體傳遞該參數。
- float3 worldNormal – 表示世界坐標系中的法線向量(normal vector)。如果表面着色器(surface shader) 不為SurfaceOutput結構中的Normal賦值,也就是說Normal不會發生變化,也就不需要重新求取worldNormal值了,那么就可以直接通過Input結構體傳遞該參數。
- 下面這個參數看不明白沒關系,請移步《關於INTERNAL_DATA的詳細剖析》小節。
- INTERNAL_DATA – 相對於上面的float3 worldRefl和float3 worldNormal,如果表面着色器為SurfaceOutput結構中的Normal賦值了,比如在surf函數中使用了o.Normal = …,此時表面着色器的法向值發生了改變,因此我們就只能借助賦值后的o.Normal來對世界坐標系下的反射向量進行修改。借助Input結構體我們傳遞worldRefl參數,並且我們用內置的INTERNAL_DATA訪問局部坐標系轉化到世界坐標系的變化。最后通過(WorldReflectionVector (IN, o.Normal))計算世界坐標系下的反射向量,其中o.Normal表示的是切空間的法向量,而非世界坐標系下的法向量。
關於INTERNAL_DATA的詳細剖析
為了更清楚的弄懂INTERNAL_DATA的含義,我們首先在shader中添加#pragma debug。
然后點擊Show generated code。
我們查找INTERNAL_DATA,得到如下代碼。
#define INTERNAL_DATA half3 TtoW0; half3 TtoW1; half3 TtoW2; #define WorldReflectionVector(data,normal) reflect (data.worldRefl, half3(dot(data.TtoW0,normal), dot(data.TtoW1,normal), dot(data.TtoW2,normal))) #define WorldNormalVector(data,normal) fixed3(dot(data.TtoW0,normal), dot(data.TtoW1,normal), dot(data.TtoW2,normal))
我們發現INTERNAL_DATA其實定義了3個half TtoWi(i=0,1,2)的變量,這三個變量合並在一起是一個3x3的矩陣,表示局部坐標系到世界坐標系的轉換(Translate To World)。所以我們看到如果要使用o.Normal重新計算worldRefl和worldNormal,就得使用到INTERNAL_DATA這個內置變量表示的坐標系變化矩陣!
- 你可能會問上面的COLOR semantic是什么意思?當你寫一個正常的片段着色器時,你得告訴別人你的輸入結構體每個變量代表什么意思?如果你夠瘋狂,你可以試試下面這樣定義:float2 MyUncleFred : TEXCOORD0; 並告訴別人MyUncleFred表示該模型的uv坐標。(畫外音就是這種變量命名方式很令人費解)在表面着色器中你唯一擔心的就是對COLOR類型的定義。float4 currentColor : COLOR;可以看做目前已經經過插值后的像素顏色。當然你也可以不用關心這些,不過建議你命名上最好規范些,方便自己也方便別人。
該shader實際做了哪些事?
現在我們還有兩行代碼沒有詳細討論:
Sampler2D _MainTex;
對每一個屬性值,我們定義了屬性值區域(Properties Section),該區域用來定義CG程序中使用的變量。在使用中,我們必須保證屬性名稱一致。
注意輸入結構體中的uv_MainTex是uv+對應屬性值(文中為_MainTex,注意前面帶下划線是CG官方推薦的寫法),如果你使用uv2,那將寫作uv2_MainTex。注意Sampler2D _MainTex中的_MainTex變量是一個Sampler2D(這個Sampler2D,可以理解為引用一個2D Texture),它引用了Properties中的_MainTex(譯者注:注意兩者同名。解釋通了sampler2D是什么之后,還需要解釋下為什么在這里需要一句對_MainTex的聲明,之前我們不是已經在Properties里聲明過它是貼圖了么。答案是我們用來實例的這個shader其實是由兩個相對獨立的塊組成的,外層的屬性聲明,回滾等等是Unity可以直接使用和編譯的ShaderLab;而現在我們是在CGPROGRAM...ENDCG這樣一個代碼塊中,這是一段CG程序。對於這段CG程序,要想訪問在Properties中所定義的變量的話,必須使用和之前變量相同的名字進行聲明。於是其實sampler2D _MainTex;做的事情就是再次聲明並鏈接了_MainTex,使得接下來的CG程序能夠使用這個變量。),他可以根據指定的uv坐標來提供對應紋理上的像素值,而此處uv_MainTex的作用就是提供紋理_MainTex的uv坐標值。
如果我們定義了一個_Color變量,我們可以定義它的屬性為
float4 _Color;
我們surf函數中唯一一行代碼
o.Albedo = tex2d( _MainTex, IN.uv_MainTex).rgb;
tex2d的作用是利用IN.uv_MainTex所代表的uv坐標(注意我們上面指定了uv坐標產生的方式,所以此處的IN.uv_MainTex是自動生成的)對紋理_MainTex進行采樣。此處,對於o.Albedo我們只取顏色分量中的rgb三分量,其中alpha值(透明度)目前不需要,至少對於非透明物體alpha值得作用不大。
如果你要設置alpha值的話,可以像下面這樣賦值
float4 texColor = tex2d( _MainTex, IN.uv_MainTex ); o.Albedo = texColor.rgb; o.Alpha = texColor.a;
總結
你已經了解了很多的術語,但是目前我們所寫的shader還相當有限,但是當學習完第二部分教程后,我們就可以做一些很酷炫的shader了,因為第二部分我們將開始使用多重紋理,法向量等等酷炫技術。
- 在第二部分中,我們創建了一個實現積雪效果的shader,根據積雪的程度來修改模型,以呈現不同效果。
- 在第三部分我們改進了我們的shader來混合岩石邊緣的積雪。
- 在第四部分,我們使用黑色邊緣和漸變紋理來創建了具有卡通效果的shader。
- 在第五部分,我們創建了一個頂點/片段多通道凹凸紋理着色器(vertex/fragment multipass bumped shader) – 其復雜程度遠遠超越表面着色器
- 在第六部分,我們創建了一個頂點/片段着色器(vertex/fragment shader)來制作相比於我們第四部分使用表面着色器制作的卡通效果shader更好的shader。