Unity3D Shader 入門

來源:互聯網
上載者:User

標籤:浮點數   運算   增加   容器   ons   input   允許   關係   targe   

什麼是Shader

Shader(著色器)是一段能夠針對3D對象進行操作、並被GPU所執行的程式,它負責將輸入的Mesh(網格)以指定的方式和輸入的貼圖或者顏色等組合作用,然後輸出。繪圖單元可以依據這個輸出來將映像繪製到螢幕上。輸入的貼圖或者顏色等,加上對應的Shader,以及對Shader的特定的參數設定,將這些內容(Shader及輸入參數)打包儲存在一起,得到的就是一個Material(材質)。之後,我們便可以將材質賦予合適的renderer(渲染器)來進行渲染(輸出)了。Shader並不是一個統一的標準,不同的圖形介面的Shader並不相同。OpenGL的著色語言是GLSL, NVidia開發了Cg,而微軟的Direct3D使用進階著色器語言(HLSL)。而Unity的Shader 是將傳統的圖形介面的Shader(由 Cg / HLSL編寫)嵌入到專屬的描述性結構中而形成的一種代碼產生架構,最終會自動產生各硬體平台自己的Shader,從而實現跨平台。

Shader種類

OpenGL和Direct3D都提供了三類著色器:

  • 頂點著色器:處理每個頂點,將頂點的空間位置投影在螢幕上,即計算頂點的二維座標。同時,它也負責頂點的深度緩衝(Z-Buffer)的計算。頂點著色器可以掌控頂點的位置、顏色和紋理座標等屬性,但無法產生新的頂點。頂點著色器的輸出傳遞到流水線的下一步。如果有之後定義了幾何著色器,則幾何著色器會處理頂點著色器的輸出資料,否則,光柵化器繼續流水線任務。
  • 像素著色器(Direct3D),常常又稱為片斷著色器(OpenGL):處理來自光柵化器的資料。光柵化器已經將多邊形填滿並通過流水線傳送至像素著色器,後者逐像素計算顏色。像素著色器常用來處理情境光照和與之相關的效果,如凸凹紋理映射和調色。名稱片斷著色器似乎更為準確,因為對於著色器的調用和螢幕上像素的顯示並非一一對應。舉個例子,對於一個像素,片斷著色器可能會被調用若干次來決定它最終的顏色,那些被遮擋的物體也會被計算,直到最後的深度緩衝才將各物體前後排序。
  • 幾何著色器:可以從多邊形網格中增刪頂點。它能夠執行對CPU來說過於繁重的產生幾何結構和增加模型細節的工作。Direct3D版本10增加了支援幾何著色器的API, 成為Shader Model 4.0的組成部分。OpenGL只可通過它的一個外掛程式來使用幾何著色器。

Shader大體上可以分為兩類,簡單來說

  • 表面著色器(Surface Shader) - 為你做了大部分的工作,只需要簡單的技巧即可實現很多不錯的效果。類比卡片機,上手以後不太需要很多努力就能拍出不錯的效果。
  • 片段著色器(Fragment Shader) - 可以做的事情更多,但是也比較難寫。使用片段著色器的主要目的是可以在比較低的層級上進行更複雜(或者針對目標裝置更高效)的開發。

 

Shader程式結構

基本的表面著色器樣本:

Shader "Custom/Diffuse Texture" {  Properties {      _MainTex ("Base (RGB)", 2D) = "white" {}  }  SubShader {      Tags { "RenderType"="Opaque" }      LOD 200            CGPROGRAM      #pragma surface surf Lambert      sampler2D _MainTex;      struct Input {          float2 uv_MainTex;      };      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"}

基本的頂點片段著色器樣本:  

Shader "VertexInputSimple" {  SubShader {    Pass {      CGPROGRAM      #pragma vertex vert      #pragma fragment frag      #include "UnityCG.cginc"      struct v2f {          float4 pos : SV_POSITION;          fixed4 color : COLOR;      };      v2f vert (appdata_base v)      {          v2f o;          o.pos = mul (UNITY_MATRIX_MVP, v.vertex);          o.color.xyz = v.normal * 0.5 + 0.5;          o.color.w = 1.0;          return o;      }      fixed4 frag (v2f i) : SV_Target { return i.color; }      ENDCG    }  } }

下面主要介紹表面著色器

在Properties{}中定義著色器屬性,在這裡定義的屬性將被作為輸入提供給所有的子著色器。每一條屬性的定義的文法是這樣的:

_Name("Display Name", type) = defaultValue[{options}]

  • _Name - 屬性的名字,簡單說就是變數名,在之後整個Shader代碼中將使用這個名字來擷取該屬性的內容
  • Display Name - 這個字串將顯示在Unity的材質編輯器中作為Shader的使用者可讀的內容
  • type - 這個屬性的類型,可能的type所表示的內容有以下幾種:
關鍵字 類型 對應Cg類型 例
Float 浮點數 (任意一個浮點數) float _MyFloat (“My float”, Float) = 0.5
Range 浮點數 (在指定範圍內) (一個介於最小值和最大值之間的浮點數,一般用來當作調整Shader某些特性的參數(比如透明度渲染的截止值可以是從0至1的值等)) float _MyRange (“My Range”, Range(0.01, 0.5)) = 0.1
Color 浮點四元組 ( 一種顏色,由RGBA(紅綠藍和透明度)四個量來定義) float4 _MyColor (“Some Color”, Color) = (1,1,1,1)
Vector 浮點四元組 (一個四維數) float4 _MyVector(“Some Vector”,Vector) = (1,1,1,1)
2D 2的階數大小的貼圖 (一張2的階數大小(256,512之類)的貼圖。這張貼圖將在採樣後被轉為對應基於模型UV的每個像素的顏色,最終被顯示出來) sampler2D _MyTexture (“Texture”, 2D) = “white” {}
Rect 非2的階數大小的貼圖 (一個非2階數大小的貼圖) sampler2D _MyRect(“My Rect”, Rect) = “white” {}
CUBE CubeMap (即Cube map texture(立方體紋理),簡單說就是6張有聯絡的2D貼圖的組合,主要用來做反射效果(比如天空盒和動態反射),也會被轉換為對應點的採樣) samplerCUBE _MyCubemap (“Cubemap”, CUBE) = “” {}
  • defaultValue 定義了這個屬性的預設值,通過輸入一個符合格式的預設值來指定對應屬性的初始值(某些效果可能需要某些特定的參數值來達到需要的效果,雖然這些值可以在之後在進行調整,但是如果預設就指定為想要的值的話就省去了一個個調整的時間,方便很多)。
    • Color - 以0~1定義的rgba顏色,比如(1,1,1,1);
    • 2D/Rect/Cube - 對於貼圖來說,預設值可以為一個代表預設tint顏色的字串,可以是Null 字元串或者”white”,”black”,”gray”,”bump”中的一個
    • Float,Range - 某個指定的浮點數
    • Vector - 一個4維數,寫為 (x,y,z,w)
  • option它只對2D,Rect或者Cube貼圖有關,在寫輸入時我們最少要在貼圖之後寫一對什麼都不含的空白的{},當我們需要開啟特定選項時可以把其寫在這對花括弧內。如果需要同時開啟多個選項,可以使用空白分隔。可能的選擇有ObjectLinear, EyeLinear, SphereMap, CubeReflect, CubeNormal中的一個,這些都是OpenGL中TexGen的模式。

所以,一組屬性的申明看起來也許會是這個樣子的

//Define a color with a default value of semi-transparent blue_MainColor ("Main Color", Color) = (0,0,1,0.5)//Define a texture with a default of white_Texture ("Texture", 2D) = "white" {}
Tag

SubShader可以被若干的標籤(tags)所修飾,而硬體將通過判定這些標籤來決定什麼時候調用該著色器。 比如我們的例子中SubShader的第一句:

Tags { "RenderType"="Opaque" }

比較常見的標籤有:

  • Queue 
    這個標籤很重要,它定義了一個整數,決定了Shader的渲染的次序,數字越小就越早被渲染,早渲染就意味著可能被後面渲染的東西覆蓋掉看不見。 
    預定義的Queue有:
    名字 值 描述
    Background 1000 最早被調用的渲染,用來渲染天空盒或者背景
    Geometry 2000 這是預設值,用來渲染非透明物體(普通情況下,情境中的絕大多數物體應該是非透明的)
    AlphaTest 2450 用來渲染經過Alpha Test的像素,單獨為AlphaTest設定一個Queue是出於對效率的考慮
    Transparent 3000 以從後往前的順序渲染透明物體
    Overlay 4000 用來渲染疊加的效果,是渲染的最後階段(比如鏡頭光暈等特效)

  • RenderType 
    “Opaque”或”Transparent”是兩個常用的RenderType。如果輸出中都是非透明物體,那寫在Opaque裡;如果想渲染透明或者半透明的像素,那應該寫在Transparent中。這個Tag主要用ShaderReplacement,一般情況下這Tag好像也沒什麼作用。

另外比較有用的標籤還有"IgnoreProjector"="True"(不被Projectors影響),"ForceNoShadowCasting"="True"(從不產生陰影)以及"Queue"="xxx"(指定渲染順序隊列)。

LOD

LOD是 Level of Detail的簡寫,確切地說是Shader Level of Detail的簡寫,因為Unity中還有一個模型的LOD概念,這是兩個不同的東西。我們這裡只介紹Shader中LOD,模型的LOD請參考這裡。

Shader LOD 就是讓我們設定一個數值,這個數值決定了我們能用什麼樣的Shader。可以通過Shader.maximumLOD或者Shader.globalMaximumLOD 設定允許的最大LOD,當設定的LOD小於SubShader所指定的LOD時,這個SubShader將不可用。通過LOD,我們就可以為某個材質寫一組SubShader,指定不同的LOD,LOD越大則渲染效果越好,當然對硬體的要求也可能越高,然後根據不同的終端硬體設定來設定 globalMaximumLOD來達到兼顧效能的最佳顯示效果。

Unity內建Shader定義了一組LOD的數值,我們在實現自己的Shader的時候可以將其作為參考來設定自己的LOD數值

  • VertexLit及其系列 = 100
  • Decal, Reflective VertexLit = 150
  • Diffuse = 200
  • Diffuse Detail, Reflective Bumped Unlit, Reflective Bumped VertexLit = 250
  • Bumped, Specular = 300
  • Bumped Specular = 400
  • Parallax = 500
  • Parallax Specular = 600
Shader本體

前面雜項說完了,終於可以開始看看最主要的部分了,也就是將輸入轉變為輸出的代碼部分。為了方便看,請容許我把上面的SubShader的主題部分抄寫一遍

CGPROGRAM#pragma surface surf Lambertsampler2D _MainTex;struct Input {    float2 uv_MainTex;};void surf (Input IN, inout SurfaceOutput o) {    half4 c = tex2D (_MainTex, IN.uv_MainTex);    o.Albedo = c.rgb;    o.Alpha = c.a;}ENDCG

首先是CGPROGRAM。這是一個開始標記,表明從這裡開始是一段CG程式(我們在寫Unity的Shader時用的是Cg/HLSL語言)。最後一行的ENDCG與CGPROGRAM是對應的,表明CG程式到此結束。

接下來是是一個編譯指令:#pragma surface surf Lambert,它聲明了我們要寫一個表面Shader,並指定了光照模型。它的寫法是這樣的

#pragma surface surfaceFunction lightModel [optionalparams]

  • surface - 聲明的是一個表面著色器
  • surfaceFunction - 著色器代碼的方法的名字
  • lightModel - 使用的光照模型。

所以在我們的例子中,我們聲明了一個表面著色器,實際的代碼在surf函數中(在下面能找到該函數),使用Lambert(也就是普通的diffuse)作為光照模型。

接下來一句sampler2D _MainTex;,sampler2D是個啥?其實在CG中,sampler2D就是和texture所綁定的一個資料容器介面。等等..這個說法還是太複雜了,簡單理解的話,所謂載入以後的texture(貼圖)說白了不過是一塊記憶體儲存的,使用了RGB(也許還有A)通道,且每個通道8bits的資料。而具體地想知道像素與座標的對應關係,以及擷取這些資料,我們總不能一次一次去自己計算記憶體位址或者位移,因此可以通過sampler2D來對貼圖進行操作。更簡單地理解,sampler2D就是GLSL中的2D貼圖的類型,相應的,還有sampler1D,sampler3D,samplerCube等等格式。

解釋通了sampler2D是什麼之後,還需要解釋下為什麼在這裡需要一句對_MainTex的聲明,之前我們不是已經在Properties裡聲明過它是貼圖了麼。答案是我們用來執行個體的這個shader其實是由兩個相對獨立的塊組成的,外層的屬性聲明,復原等等是Unity可以直接使用和編譯的ShaderLab;而現在我們是在CGPROGRAM...ENDCG這樣一個代碼塊中,這是一段CG程式。對於這段CG程式,要想訪問在Properties中所定義的變數的話,必須使用和之前變數相同的名字進行聲明。於是其實sampler2D _MainTex;做的事情就是再次聲明並連結了_MainTex,使得接下來的CG程式能夠使用這個變數。

接下來是一個struct結構體。相信大家對於結構體已經很熟悉了,我們先跳過,直接看下面的的surf函數。上面的#pragma段已經指出了我們的著色器代碼的方法的名字叫做surf,就是這段代碼是我們的著色器的工作核心。我們已經說過不止一次,著色器就是給定了輸入,然後給出輸出進行著色的代碼。CG規定了聲明為表面著色器的方法(就是我們這裡的surf)的參數類型和名字,因此我們沒有權利決定surf的輸入輸出參數的類型,只能按照規定寫。這個規定就是第一個參數是一個Input結構,第二個參數是一個inout的SurfaceOutput結構。

Input其實是需要我們去定義的結構,這給我們提供了一個機會,可以把所需要參與計算的資料都放到這個Input結構中,傳入surf函數使用;SurfaceOutput是已經定義好了裡面類型輸出結構,但是一開始的時候內容暫時是空白的,我們需要向裡面填寫輸出,這樣就可以完成著色了。先仔細看看INPUT吧,現在可以跳回來看上面定義的INPUT結構體了:

struct Input {    float2 uv_MainTex;};

作為輸入的結構體必須命名為Input,這個結構體中定義了一個float2的變數,表示浮點數的float後面緊跟一個數字2,float和vec都可以在之後加入一個2到4的數字,來表示被打包在一起的2到4個同類型數。比如下面的這些定義:

//Define a 2d vector variablevec2 coordinate;//Define a color variablefloat4 color;//Multiply out a colorfloat3 multipliedColor = color.rgb * coordinate.x;

在訪問這些值時,我們即可以只使用名稱來獲得整組值,也可以使用下標的方式(比如.xyzw,.rgba或它們的部分比如.x等等)來獲得某個值。

在這個例子裡,我們聲明了一個叫做uv_MainTex的包含兩個浮點數的變數。

如果你對3D開發稍有耳聞的話,一定不會對uv這兩個字母感到陌生。UV mapping的作用是將一個2D貼圖上的點按照一定規則映射到3D模型上,是3D渲染中最常見的一種頂點處理手段。在CG程式中,我們有這樣的約定,在一個貼圖變數(在我們例子中是_MainTex)之前加上uv兩個字母,就代表提取它的uv值(其實就是兩個代表貼圖上點的二維座標 )。我們之後就可以在surf程式中直接通過訪問uv_MainTex來取得這張貼圖當前需要計算的點的座標值了。

回到surf函數,它的兩有參數,第一個是Input:在計算輸出時Shader會多次調用surf函數,每次給入一個貼圖上的點座標,來計算輸出。第二個參數是一個可寫的SurfaceOutput,SurfaceOutput是預定義的輸出結構,我們的surf函數的目標就是根據輸入把這個輸出結構填上。SurfaceOutput結構體的定義如下

struct SurfaceOutput {    half3 Albedo;     //像素的顏色    half3 Normal;     //像素的法向值    half3 Emission;   //像素的發散顏色    half Specular;    //像素的鏡面高光    half Gloss;       //像素的發光強度    half Alpha;       //像素的透明度};

這裡的half和我們常見float與double類似,都表示浮點數,只不過精度不一樣。也許你很熟悉單精確度浮點數(float或者single)和雙精確度浮點數(double),這裡的half指的是半精度浮點數,精度最低,運算效能相對比高精度浮點數高一些,因此被大量使用。

在例子中,我們做的事情非常簡單:

half4 c = tex2D (_MainTex, IN.uv_MainTex);o.Albedo = c.rgb;o.Alpha = c.a;

這裡用到了一個tex2d函數,這是CG程式中用來在一張貼圖中對一個點進行採樣的方法,返回一個float4。這裡對_MainTex在輸入焦點上進行了採樣,並將其顏色的rbg值賦予了輸出的像素顏色,將a值賦予透明度。於是,著色器就明白了應當怎樣工作:即找到貼圖上對應的uv點,直接使用顏色資訊來進行著色。

接下來…

我想現在你已經能讀懂一些最簡單的Shader了,接下來我推薦的是參考Unity的Surface Shader Examples多接觸一些各種各樣的基本Shader。

原文連結

Unity3D Shader 入門

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.