開始自學Shader,前一兩個月前讀的進度經過一點時間就完全忘記了
用自己的大腦去解讀Unity Shader就好像付費解鎖冰原DLC一樣,完全不同的世界。
姻緣下找到不錯的學習教材,開始有系統的學習,順便幫自己做個筆記,也許能幫到你。
先聲明自己的用詞都極為不專業,也許會用錯字或觀念誤導什麼的,還請包涵和指點。
(建議用網頁版看,手機格式會跑掉)
unlit的基本架構
Shader "Unlit/RE0Practice"
{
Properties{}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex v
#pragma fragment f
fixed4 v():SV_POSITION{
return 0;
}
fixed4 f():SV_TARGET{
return fixed4(1,1,1,1);
}
ENDCG
}
}
}
- Shader "shader名稱"
- Properties :宣告可以從外部輸入的變數的地方
- SubShader: 一個shader可以有多個Subshader,用處大概就是A平臺主機只支援Subshader A,而B平臺主機只支援Subshader B,兩個SubShader就可以寫在同一個腳本裡,主機會自動去偵測支援哪個管道。
- Pass: 寫我們要對這Shader做哪些處理的方法,一個Subshader可以有多個pass,但太多會降低效能
上面程式碼還有一些藍藍的字沒講解,別擔心!! 我第一次看也很怕,但其實很單純,我先一層一層講下來:
Properties:
變數型態有
- Int
- Float
- Range
- Color
- Vector
- Cube
- 2D
- 3D
宣告方式:
變數名稱 ("inspector顯示名稱", 變數型態) =初始值
亂打的範例:
Properties{
myInt ("MYInt",int)=2
v ("v",Vector)=(1,2,3,1)
c ("c",Cube)=""{}
d ("d",2D)="white"{}
r ("myRange",Range(0,5))=2.5
}
*注意: Vector型態一定是四維的
面板長這樣:
Subshader:
除了上面講的功能,他還有一些東西可以設定:
Cull Back | Front | Off 剔除選擇的面
ZTest 沒用過
ZWrite On | Off 要不要寫深度
Blend 混和模式
Tags有好幾個可以查,但以新手來說,我只用過來設定透明材質
亂打得範例:
Tags { "RenderType"="Transparent" "Queue"="Transparent" }
Blend SrcAlpha OneMinusSrcAlpha
Cull off
ZWrite On
LOD 100
Pass:
先用CGPROGRAM 和 ENDCG包起來,宣告這是CG語法。
接下來一個pass基本要輸出2個東西! 就是 vertex和fragment 才能正常顯示在畫面上。
要用#pragma 來宣告接下來用來輸出的函數名稱,
以這個程式碼為例:
CGPROGRAM
#pragma vertex v
#pragma fragment f
fixed4 v():SV_POSITION{
return 0;
}
fixed4 f():SV_TARGET{
return fixed4(1,1,1,1);
}
ENDCG
格式:
#pragma vertex 函數名稱
#pragma fragment 函數名稱
然後實做 v()和 f(),輸出值的型態可以改,目前先以最簡單的fixed4為例子。
" : "右邊是輸出的目標,SV_POSITION跟POSITION在大部分的平臺都是相同的,但保守起見,輸出的目標都是SV開頭的那個比較好。
所以整段程式碼的意思是:
有個函數v負責放置vertex的位置,有個函數f負責給面上白色 (1,1,1,1)
因為點都放在0,所以看不到任何東西,但恭喜! 寫出了第一個可執行的shader
再改一下:
fixed4 v(float4 vp:POSITION):SV_POSITION{
return UnityObjectToClipPos(vp);
}
fixed4 f():SV_TARGET{
return fixed4(1,1,1,1);
}
給v函數 輸入物件的位置資訊vp,用UnityObjectToClipPos轉成世界座標的位置顯示。
一開始我很困惑到底是從哪裡輸入資料進來的,但好像是Unity 的MeshRender會負責這部分。
結果:
自訂型態:
剛開一個shader檔案裡面預設有a2v和v2f的資料型態,一開始真的會嚇到不知道怎麼改才好,但其實就算不用那些也能操作,但就是比較整齊的感覺吧
如果我程式碼改成:
struct myData{
float4 v:POSITION; //模型頂點座標
float3 normal: NORMAL; //法線
float4 texcoord:TEXCOORD0; //貼圖座標
};
struct myOutPut{
float4 pos :SV_POSITION;
fixed3 col : COLOR0;
};
myOutPut v(myData vp){
myOutPut o;
//vp.v+=0.5; 修改傳入的座標
o.pos= UnityObjectToClipPos(vp.v);
//o.pos+=20; 影響在物件在世界座標的位置
o.col=vp.normal*0.5+0.2;
return o;
}
fixed4 f(myOutPut d):SV_TARGET{
return fixed4(d.col,1);
}
結果:
補充:
有內建的資料結構可用,以下是用appdata_full
會輕鬆很多
//片段著色資料
struct v2f{
float4 pos : SV_POSITION;
fixed4 color : COLOR0;
};
//讓頂點著色器與片段著色器互動
v2f vert(appdata_full v){
v2f o;
o.pos= UnityObjectToClipPos(v.vertex);
o.color=fixed4(v.texcoord.xy ,0.0 ,1);
o.color=fixed4(v.texcoord1.xy ,0.0 ,1);
return o;
}
fixed4 frag(v2f i):SV_Target{
return i.color;
}
接下來是胡亂實驗:改成這樣但結果與上面相同
struct myData{
float4 v:POSITION; //模型頂點座標
float3 normal: NORMAL; //法線
float4 texcoord:TEXCOORD0; //貼圖座標
fixed3 col : COLOR0;
};
myData v(myData vp){
myData o;
o.v= UnityObjectToClipPos(vp.v);
o.col=vp.normal*0.5+0.5;
return o;
}
fixed4 f(myData d):SV_TARGET{
d.v=0;//無效
//d.col=d.normal*0.5+0.5; //無效,無法取得normal
return fixed4(d.col,1);
}
但我發現在片段著色器 f() 裡面執行跟頂點有關的程式碼是無效的,或會取不到值。
所以我想會制定出一個偏向給fragment函數用的資料類別可能一來是頂點與片段間資料傳遞,二來是用來區分跟片段著色較無關係的變數吧??
先這樣,我直接跳過數學理論那段就來看程式碼了....在想下一個該繼續看實作的,還是回去看數學的基礎呢
然後我的書是跟老師借的,非常推薦!! 對新手超友善。