一疏叨、著色器的分類
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ù)->頂點著色器->頂點運(yùn)算,變換矩陣變換后的位置->返回
(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的特點?
運(yùn)算開銷較大士八,不如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陕见,會引入一個額外的矩陣乘法運(yùn)算,所以推薦使用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ù)余指〔度可用的可選參數(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)體相同敏储。”lightDir”參數(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" //降級著色器
}