Unity Shader技術(shù)入門(2)

一、著色器的分類

Unity中的著色器可以分為三大類

1.固定管線著色器(Fixed Pipeline Shader)->為了兼容老一代GPU而設(shè)計的最早的圖形學(xué)版本和最早的游戲都是基于這個著色器來編寫的。

特點是流水線作業(yè),固定指令,不可編程,比較簡單,無法自由的編寫GPU程序?qū)崿F(xiàn)不同的渲染。

2.頂點片元著色器(Fragment Shader)->比固定管線要新,功能強(qiáng)大。

特點是可編程著色器,允許開發(fā)者自由編寫GPU程序來實現(xiàn)不同的渲染功能。缺點是不支持光照。難易程度適中

3.表面著色器->Unity官方極力推薦的著色器。

最新的游戲都是基于這個著色器編寫的。

特點是可編程著色器,允許開發(fā)者自由編寫GPU程序來實現(xiàn)不同的渲染功能,而且支持光照,可以自定義光照模型,自由度最高。

固定管線是在老一代GPU能力比較有限時,對著色器的約束性比較高的一種形態(tài)。

前景:為了市場占有率,新的顯示支持部分功能特性,未來會逐漸被淘汰。

二、Shader中空間的概念

要游戲中的3D世界到屏幕上絢麗多彩的畫面需要進(jìn)行一系列的空間變換。

物體空間(本地空間)->世界空間->攝像機(jī)空間(視圖空間)->裁剪空間->標(biāo)準(zhǔn)屏幕空間->窗口空間

1.物體空間:所需要繪制的3D物體所在的原始坐標(biāo)系所代表的空間,也叫本地空間。

世界坐標(biāo)->本地坐標(biāo)方法:

Unity腳本中->transform.worldToLocalMatrix

Unity著色器中->左乘_World2Object矩陣

2.世界空間:物體最終顯示的3D場景中的擺放位置對應(yīng)的坐標(biāo)所屬的坐標(biāo)系所代表的空間。

本地坐標(biāo)->世界坐標(biāo)方法:

Unity腳本中->transform.localToWorldMatrix

Unity著色器中->左乘_Object2World矩陣

3.攝像機(jī)空間:物體經(jīng)過攝像機(jī)觀察后進(jìn)入攝像機(jī)空間,指的是以攝像機(jī)為原點的一個特定的坐標(biāo)系所代表的空間,在這個坐標(biāo)系中,攝像機(jī)位于原點,視線沿z軸負(fù)方向,y軸的方向與攝像機(jī)的UP向量方向一致。

本地坐標(biāo)->攝像機(jī)坐標(biāo)方法:

UNITY_MATRIX_MV矩陣

4.裁剪空間:

什么是視錐體?

對于正交投影來說是一個四邊平行于投影方向的四棱柱

對于透視投影來說是一個以近平面為上底,遠(yuǎn)平面為下底的棱臺(當(dāng)近平面為0時,則是一個以投影中心為頂點的四棱錐)。

什么是裁剪空間?

只有在攝像機(jī)空間中,并且位于視錐體內(nèi)的物體才能被觀察到。將攝像機(jī)空間內(nèi)視錐體內(nèi)的部分獨立出來經(jīng)過處理后形成的空間,就叫做裁剪空間。

物體坐標(biāo)->攝像機(jī)坐標(biāo)->屏幕空間的方法:

UNITY_MATRIX_MVP

5.標(biāo)準(zhǔn)設(shè)備空間:

對裁剪空間執(zhí)行透視除法后得到的空間。對于OpenGL來講,標(biāo)準(zhǔn)設(shè)備空間三個軸的坐標(biāo)范圍都是-1.0~1.0。

什么是透視除法?

將齊次坐標(biāo)[x, y, z, w]的4個分量都除以w,結(jié)果是[x/w, y/w, z/w, 1],本質(zhì)上就是對齊次坐標(biāo)進(jìn)行了規(guī)范化。

6.實際窗口空間:

代表的是設(shè)備屏幕上的一塊矩形區(qū)域,坐標(biāo)以像素為單位。主要工作是將執(zhí)行透視除法后的x,y坐標(biāo)分量轉(zhuǎn)換為實際窗口的xy像素坐標(biāo),主要的思路是將標(biāo)準(zhǔn)設(shè)備空間的xy平面對應(yīng)到視口上,將-1.0~1.0內(nèi)的x,y坐標(biāo)折算為視口上的像素坐標(biāo)。

三、頂點片元著色器

頂點片元著色器是可編程著色器,相對于固定管線著色器可以給開發(fā)人員更大的發(fā)揮空間。但是缺點是不能直接和光照進(jìn)行交互。

頂點片元著色器的程序使用CG或HLSL來進(jìn)行編寫,嵌入在著色器的渲染通道塊中。

CG的代碼被編寫在CGPROGRAM和ENDCG之間。

1.編譯指令:

使用#pragma指令

pragma vertex <name> 將名稱為name的函數(shù)編譯為頂點著色器

pragma fragment <name> 將名稱為name的函數(shù)編譯為片元著色器

pragma fragmentoption <option> 添加option到已編譯的OpenGL片元程序

pragma target <name> 要編譯成哪個著色器目標(biāo)

A.頂點變換,決定頂點最終的位置,頂點函數(shù)

將(網(wǎng)格,模型)原始頂點數(shù)據(jù)在頂點函數(shù)中經(jīng)過特別的處理變?yōu)橐呀?jīng)經(jīng)過3D渲染管線變

換的可以投影到2D屏幕上的位置確定的頂點。

B.像素著色,決定每一個像素點最終的顏色,片元函數(shù)

將頂點函數(shù)變換后的頂點進(jìn)行著色,確定其最終顏色,對所有經(jīng)過頂點變換后的頂點進(jìn)

行光柵化(上色)

渲染管線的核心流程:

(1)把3D的頂點變成2D->頂點變換,算出頂點的最終位置->vertex頂點函數(shù)功能

(2)給2D屏幕上的頂點著色->頂點光柵化,算出頂點的最終顏色->fragment片元函數(shù)功能

片元著色器處理流水線,渲染管線

原始網(wǎng)格頂點數(shù)據(jù)->頂點函數(shù)->最終位置頂點數(shù)據(jù)->片元函數(shù)->頂點的顏色值

特別注意的:

每一個Shader代碼文件都必須包含一個頂點程序或一個片元程序或兩個都有。

必須使用#pragma vertex或#pragma fragment或兩個都使用。

2.頂點數(shù)據(jù)結(jié)構(gòu)體:

頂點著色器中頂點數(shù)據(jù)必須以一個結(jié)構(gòu)體的形式提交給CG/HLSL頂點程序,下面介紹一些常用的頂點數(shù)據(jù)結(jié)構(gòu)體:

(1)appdata_base:由頂點位置,法線以及一個紋理坐標(biāo)組成

vertex->頂點坐標(biāo)

normal->法線

texcoord->紋理坐標(biāo)

(2)appdata_tan:由頂點位置,切線,法線以及一個紋理坐標(biāo)組成

vertex->頂點坐標(biāo)

tangent->切線

normal->法線

texcoord->紋理坐標(biāo)

(3)appdata_full:由頂點位置,切線,法線,兩個紋理坐標(biāo)以及顏色組成

vertex->頂點坐標(biāo)

tangent->切線

normal->法線

texcoord->紋理坐標(biāo)1

texcoord1->紋理坐標(biāo)2

color->顏色

3.內(nèi)置變化矩陣

(1)頂點著色器的流程:傳入頂點數(shù)據(jù)->頂點著色器->頂點運算,變換矩陣變換后的位置->返回

(2)片元著色器的流程:頂點著色器返回的最終值->片元著色器->計算顏色->返回最終顏色

Unity著色器內(nèi)置的常用變換矩陣有如下幾個:

UNITY_MATRIX_MVP -> 基本變換矩陣x攝像機(jī)矩陣x投影矩陣

UNITY_MATRIX_MV -> 基本變換矩陣x攝像機(jī)矩陣

UNITY_MATRIX_V -> 攝像機(jī)矩陣

UNITY_MATRIX_P -> 投影矩陣

UNITY_MATRIX_VP -> 攝像機(jī)矩陣x投影矩陣

_Object2World -> 本地坐標(biāo)系轉(zhuǎn)換為世界坐標(biāo)系

_World2Object -> 世界坐標(biāo)系轉(zhuǎn)換為本地坐標(biāo)系

4、什么是GrabPass?

GrabPass是一種特殊的pass類型。當(dāng)物體將要被繪制時,它抓取屏幕內(nèi)容并繪制到一張texture里。

GrabPass的特點?

運算開銷較大,不如AlphaBlend等指令。能用AlphaBlend實現(xiàn)的就不用GrabPass。

GrabPass的使用方式

(1)GrabPass{} 抓取當(dāng)前屏幕內(nèi)容。

(2)GrabPass{"TextureName"}將抓取屏幕內(nèi)容并保存至一張texture里。每幀只為第一次使用這張紋理的物體做一次,這種更高效

UnityObjectToClipPos的作用?

其作用等同于UNITY_MATRIX_MVP,但是如果直接使用UNITY_MATRIX_MVP,會引入一個額外的矩陣乘法運算,所以推薦使用UnityObjectToClipPos / UnityObjectToViewPos函數(shù),它們會把這一次額外的矩陣乘法優(yōu)化為向量-矩陣乘法。

tex2D函數(shù)的作用?

tex2D是紋理采樣函數(shù),可以通過紋理圖像素信息與紋理坐標(biāo)計算該位置的頂點的紋理像素點顏色。

tex2D(texture,uv)第1個參數(shù)為紋理圖,第2個參數(shù)為紋理坐標(biāo),返回值為指定紋理坐標(biāo)位置的顏色值

四、****表面著色器:

頂點片元著色器最大的缺點是不能直接和光照交互。為了讓開發(fā)人員更方便快捷地處理光照,Unity提供了表面著色器。表面著色器代碼也是使用CG或HLSL語言編寫的。

著色器的三種形態(tài)中,表面著色器比固定管線著色器更加靈活,又比頂點片元著色器更加方便地處理光照,所以在游戲開發(fā)中最常用的是表面著色器。下面介紹表面著色器的基礎(chǔ)知識。

1.編譯指令

表面著色器與其他任何著色器一樣放置于CGPROGRAM….ENDCG塊中。區(qū)別是必須將其放置于子著色器塊中,而不能放在通道中,表面著色器自身會編譯為多個通道。它使用#pragma surface指令來表明它是個表面著色器。

pragma surface指令格式如下:

pragma surface<surfaceFunction><lightModel>[optionalparams]

這其中:

surfaceFunction為表面著色器函數(shù)名稱,通過該指令告訴編譯器cg代碼中surfaceFunction函數(shù)為表面著色器函數(shù)。

lightModel為光照模型。通過該指令告訴編譯器這個表面著色器使用哪個光照模型。Unity內(nèi)置的光照模型為Lambert(漫反射)和BlinnPhong(高光),也可以自定義光照模型。

Optionalparams為可選參數(shù)??捎玫目蛇x參數(shù)如下表所示:

2.輸入輸出參數(shù)結(jié)構(gòu)體

表面著色器函數(shù)可以有兩個參數(shù),其中一個參數(shù)為Input結(jié)構(gòu)體,用于為表面著色器函數(shù)輸入所需的紋理坐標(biāo)和其他數(shù)據(jù)。另一個參數(shù)為SurfaceOutput結(jié)構(gòu)體,需在表面著色器函數(shù)中寫入相應(yīng)的值,用于輸出數(shù)據(jù)。

//如果屬性中有紋理,則需要寫這個結(jié)構(gòu)體

struct Input {

// uv+變量名

float2 uv_MainTex;

};

//需要對Properties中的屬性重寫定義

//重新設(shè)置類型

//2D == sampler2D

//Range == half

//color == fixed4

sampler2D _MainTex;

half _Glossiness;

half _Metallic;

fixed4 _Color;

fixed4 _AlphaColor;

Input結(jié)構(gòu)體中的紋理坐標(biāo)必須在紋理名稱前面加上”uv”或”uv2”,帶”uv”的紋理坐標(biāo)為物體所帶的第一個紋理坐標(biāo),如果物體帶有第二個紋理坐標(biāo),則帶”uv2”的紋理坐標(biāo)為物體所帶的第二個紋理坐標(biāo)。其他可用的數(shù)據(jù)如下表所示:

Input結(jié)構(gòu)體不但可以包含上面所列的數(shù)據(jù),也可以包含自定義的數(shù)據(jù),用于從頂點函數(shù)傳遞數(shù)據(jù)給表面著色器。表面著色器的輸出結(jié)構(gòu)體SurfaceOutput是內(nèi)置定義好的,只需在表面著色器函數(shù)中為需要的變量賦值就可以了。標(biāo)準(zhǔn)的表面著色器輸出結(jié)構(gòu)體如下:

也可以自定義表面著色器的輸出結(jié)構(gòu)體,但自定義的結(jié)構(gòu)體必須包括SurfaceOutput結(jié)構(gòu)體的所有變量,然后可以添加自己需要的變量用于從自定義光照模型函數(shù)傳遞數(shù)據(jù)給表面著色器函數(shù)。

下面用一段自定義表面著色器材質(zhì)的基礎(chǔ)Shader代碼來解釋一下表面著色器的結(jié)構(gòu):

Shader "Custom/BaseForm2" {

Properties { //定義屬性塊

_Color ("Color", Color) = (1,1,1,1) //定義主顏色數(shù)值

_MainTex ("Albedo (RGB)", 2D) = "white" {}  //定義紋理數(shù)值

_Glossiness ("Smoothness", Range(0,1)) = 0.5  //定義高光系數(shù)數(shù)值

_Metallic ("Metallic", Range(0,1)) = 0.0  //定義金屬材質(zhì)系數(shù)數(shù)值

}

SubShader {

Tags { "RenderType"="Opaque" }  //標(biāo)簽

LOD 200  //LOD數(shù)值

CGPROGRAM

#pragma surface surf Standard fullforwardshadows  //表面著色器編譯指令

#pragma target 3.0  //著色器編譯目標(biāo)

sampler2D _MainTex;  //2D紋理屬性

struct Input {  //定義輸入?yún)?shù)結(jié)構(gòu)體

  float2 uv_MainTex;  //紋理UV坐標(biāo)

};

half _Glossiness;  //定義高光系數(shù)屬性

half _Metallic;  //定義金屬材質(zhì)系數(shù)屬性

fixed4 _Color;  //定義主顏色屬性

void surf (Input IN, inout SurfaceOutputStandard o) {  //表面著色器函數(shù)

  fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;  //根據(jù)UV坐標(biāo)從紋理提取顏色

  o.Albedo = c.rgb;  //設(shè)置顏色

  o.Metallic = _Metallic;  //設(shè)置金屬材質(zhì)系數(shù)

  o.Smoothness = _Glossiness;  //設(shè)置高光系數(shù)

  o.Alpha = c.a;  //設(shè)置透明度

}

ENDCG

}

FallBack "Diffuse" //降級著色器

}

這其中"RenderType"="Opaque"為子著色器標(biāo)簽的一組值,詳細(xì)的解釋如下圖所示:

3.自定義光照模型

編寫表面著色器就是描述一個表面的屬性(如反射率,顏色,法線等),并由光照模型完成光照交互的計算。系統(tǒng)內(nèi)置了Lambert(漫反射)和BlinnPhong(高光)兩個光照模型。有時也需要開發(fā)自定義光照模型。

自定義的光照模型是由名稱為”Lighting”開頭的函數(shù)實現(xiàn)的。自定義光照模型函數(shù)的聲明有以下幾種形式,用于不同的需求。

(1).half4 Lighting<Name>(SurfaceOutput s, half3 lightDir, half atten)

其在正向渲染路徑中用于非與視線方向相關(guān)的光照模型(例如,漫反射)。

(2).half4 Lighting<Name>(SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)

其在正向渲染路徑中用于與視線方向相關(guān)的光照模型。

(3).half4 Lighting<Name> _PrePass(SurfaceOutput s, half4 light)

其用于延時光照路徑中的光照模型。

這其中,SurfaceOutput結(jié)構(gòu)體用于和表面著色器函數(shù)傳輸數(shù)據(jù)。這個結(jié)構(gòu)體也可以自己定義,但必須與表面著色器函數(shù)的輸出結(jié)構(gòu)體相同?!眑ightDir”參數(shù)為點到光源的單位向量,”viewDir”參數(shù)為點到攝像機(jī)的單位向量,”atten”參數(shù)為光源的衰減系數(shù)。

光照模型函數(shù)的返回值為經(jīng)過光照計算的顏色值。下面通過一個帶自定義光照模型的表面著色器來詳細(xì)介紹自定義光照模型:

Shader "Custom/BaseForm3" {

Properties {

_Color ("Color", Color) = (1,1,1,1)  //主顏色數(shù)值

_MainTex ("Albedo (RGB)", 2D) = "white" {}  //2D紋理數(shù)值

_Shininess ("Shininess ", Range(0,10)) = 10  //鏡面反射系數(shù)

}

SubShader {

CGPROGRAM

#pragma surface surf Phong  //表面著色器編譯指令

sampler2D _MainTex;  //2D紋理屬性

fixed4 _Color;  //主顏色屬性

float _Shininess;  //鏡面反射系數(shù)屬性

struct Input {

  float2 uv_MainTex;  //uv紋理坐標(biāo)

};

//光照模型函數(shù)

float4 LightingPhong(SurfaceOutput s, float3 lightDir,half3 viewDir, half atten){

  float4 c;

  float diffuseF = max(0,dot(s.Normal,lightDir));  //計算漫反射強(qiáng)度

  float specF;

  float3 H = normalize(lightDir+viewDir);  //計算視線與光線的半向量

  float specBase = max(0,dot(s.Normal,H));  //計算法線與半向量的點積

  specF = pow(specBase,_Shininess);  //計算鏡面反射強(qiáng)度

  c.rgb = s.Albedo * _LightColor0 * diffuseF *atten + _LightColor0*specF;

  //結(jié)合漫反射光與鏡面反射光計算最終光照顏色

  c.a = s.Alpha;

  return c;  //返回最終光照顏色

}

void surf (Input IN, inout SurfaceOutput o) {  //表面著色器函數(shù)

  fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;//根據(jù)UV坐標(biāo)從紋理提取顏色

  o.Albedo = c.rgb;  //設(shè)置顏色

  o.Alpha = c.a;  //設(shè)置透明度

}

ENDCG

}

FallBack "Diffuse" //降級著色器

}

4.頂點變換函數(shù)

頂點變化函數(shù)可以修改頂點著色器中的輸入頂點數(shù)據(jù)以及為表面著色器函數(shù)傳遞頂點數(shù)據(jù)。這可用于程序性動畫,沿法線的擠壓等效果。使用表面著色器編譯指令vertex:<Name>,其中”Name”為頂點函數(shù)的名稱。頂點函數(shù)的聲明有以下幾種形式,用于不同的需求。

void <Name> (inout appdata_full v)。

其用于只修改頂點著色器中的輸入頂點數(shù)據(jù)。

half4 <Name>(inout appdata_full v, out Input o)。

其用于修改頂點著色器中的輸入頂點數(shù)據(jù)以及為表面著色器函數(shù)傳遞數(shù)據(jù)。

其中inout類型的結(jié)構(gòu)體使用了頂點數(shù)據(jù)結(jié)構(gòu)體,用于給頂點函數(shù)輸入頂點數(shù)據(jù)。out類型的結(jié)構(gòu)體為表面著色器中使用的輸入結(jié)構(gòu)體,用于頂點變換函數(shù)為表面著色器函數(shù)傳遞數(shù)據(jù)。下面我們通過一個Surface Shader案例來實現(xiàn)頂點變換函數(shù)實現(xiàn)吹氣膨脹效果從而來詳細(xì)的了解一下頂點函數(shù)。

Shader "Custom/BaseForm4" {

Properties {

_MainTex ("Texture", 2D) = "white" {}  //2D紋理數(shù)值

_Amount ("Extrusion Amount", Range(0,0.1)) = 0.05  //膨脹系數(shù)數(shù)值

}

SubShader {

CGPROGRAM

#pragma surface surf Lambert vertex:vert  //表面著色器編譯指令

struct Input {  //Input結(jié)構(gòu)體

  float2 uv_MainTex;  //uv紋理坐標(biāo)

};

float _Amount;  //定義膨脹系數(shù)屬性

sampler2D _MainTex;  //定義2D紋理

void vert (inout appdata_base v) {  //頂點變換函數(shù)

  v.vertex.xyz += v.normal * _Amount;  //通過法線擠壓實現(xiàn)充氣的效果

}

void surf (Input IN, inout SurfaceOutput o) {  //表面著色器函數(shù)

  o.Albedo=tex2D (_MainTex, IN.uv_MainTex).rgb;  //從紋理提取顏色為漫反射顏色賦值

}

ENDCG

}

Fallback "Diffuse" //降級著色器

}

5.最終顏色修改函數(shù)

最終顏色修改函數(shù)用于修改表面著色器的最終顏色。這可用于繪制物體表面的最終調(diào)色。使用表面著色器編譯指令finalcolor:<Name>,其中”Name”為最終顏色修改函數(shù)的名稱。最終顏色修改函數(shù)的聲明形式如下。

void <Name> (Input IN, SurfaceOutput o, inout fixed4 color)

其中,Input結(jié)構(gòu)體用于頂點變換函數(shù)為最終顏色修改函數(shù)傳遞數(shù)據(jù),SurfaceOutput結(jié)構(gòu)體用于為最終顏色修改函數(shù)傳輸數(shù)據(jù),inout類型的”color”參數(shù)為最終顏色修改函數(shù)輸出最終顏色。下面通過最終顏色修改函數(shù)實現(xiàn)調(diào)色的表面著色器來詳細(xì)介紹最終顏色修改函數(shù)。

Shader "Custom/BaseForm5" {

Properties {

_MainTex ("Texture", 2D) = "white" {}  //2D紋理數(shù)值

_ColorTint ("Tint", Color) = (1.0, 0.6, 0.6, 1.0)  //調(diào)色數(shù)值

}

SubShader {

Tags { "RenderType" = "Opaque" }

CGPROGRAM

#pragma surface surf Lambert finalcolor:mycolor  //表面著色器編譯指令

struct Input {  //Input結(jié)構(gòu)體

  float2 uv_MainTex;  //uv紋理坐標(biāo)

};

fixed4 _ColorTint;  //調(diào)色數(shù)值屬性

sampler2D _MainTex;  // 2D紋理屬性

void mycolor(Input IN, SurfaceOutput o, inout fixed4 color){  //最終顏色修改函數(shù)

  color *= _ColorTint;  //通過調(diào)色數(shù)值修改最終顏色

}

void surf (Input IN, inout SurfaceOutput o) {  //表面著色器函數(shù)

  o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;  //從紋理提取顏色為漫反射顏色賦值

}

ENDCG

}

Fallback "Diffuse" //降級著色器

}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容