本系列為作者學(xué)習(xí)UnityShader入門(mén)精要而作的筆記,內(nèi)容將包括:
- 書(shū)本中句子照抄 + 個(gè)人批注
- 項(xiàng)目源碼
- 一堆新手會(huì)犯的錯(cuò)誤
- 潛在的太監(jiān)斷更,有始無(wú)終
總之適用于同樣開(kāi)始學(xué)習(xí)Shader的同學(xué)們進(jìn)行有取舍的參考。
(該系列筆記中大多數(shù)都會(huì)復(fù)習(xí)前文的知識(shí),特別是前文知識(shí)非常重要的時(shí)候,這是為了鞏固記憶,諸位可以直接通過(guò)目錄跳轉(zhuǎn))
復(fù)習(xí)
知識(shí)點(diǎn)復(fù)習(xí)
坐標(biāo)空間的轉(zhuǎn)換
上節(jié)課中,我們介紹了從頂點(diǎn)到屏幕的渲染流水線的幾何階段中發(fā)生的坐標(biāo)空間變換。從模型空間到世界空間再到齊次裁剪空間,然后轉(zhuǎn)換到NDC,再轉(zhuǎn)換到觀察空間,最后轉(zhuǎn)為屏幕空間。我們之所以使用如此之多的空間變換,是由于某些概念和描述只有在其特定空間之下才有意義。例如Unity場(chǎng)景中的坐標(biāo)描述只能相對(duì)于世界空間,而片元裁剪則是只能在齊次裁剪空間進(jìn)行等等。
一個(gè)坐標(biāo)空間中必須包含的就是其原點(diǎn)位置和坐標(biāo)系。但是不同空間的原點(diǎn)位置也是不一樣的,因此想要描述從一個(gè)空間轉(zhuǎn)換到另一個(gè)空間,常常需要以其中一個(gè)空間為坐標(biāo)參考。因此,坐標(biāo)空間之間也就存在了層級(jí)關(guān)系,我們將作為參考系的坐標(biāo)空間稱為父空間 ,以父空間 為參考的稱為子空間 。
因此,每個(gè)坐標(biāo)空間都可以描述為另一個(gè)坐標(biāo)空間(父空間)的子空間。對(duì)坐標(biāo)空間的變換實(shí)質(zhì)上就是在父空間和子空間之間對(duì)點(diǎn)和向量進(jìn)行變換 。由于不同空間的點(diǎn)和向量在坐標(biāo)上的描述一定是唯一對(duì)應(yīng)的,我們也將其稱之為空間映射關(guān)系。
那么空間到空間的這種變換函數(shù)關(guān)系,我們可以用上節(jié)學(xué)習(xí)的四階矩陣來(lái)進(jìn)行描述。設(shè) 父空間為 P ,子空間為 C 父空間為P,子空間為C 父空間為P,子空間為C,那么 P → C P \to C P→C的映射矩陣就是 T P → C T_{P\to C} TP→C?,反之 C → P C \to P C→P的映射矩陣就是 T C → P T_{C \to P} TC→P? :
有
A
P
=
T
P
→
C
A
C
A_P = T_{P\to C}A_C
AP?=TP→C?AC?
B
C
=
T
C
→
P
B
P
B_C = T_{C \to P}B_P
BC?=TC→P?BP?
空間映射關(guān)系其實(shí)就是應(yīng)用了一次齊次變換矩陣。
頂點(diǎn)的坐標(biāo)空間變換
頂點(diǎn)著色器中,一個(gè)頂點(diǎn)究竟是如何變換到最終的屏幕空間的:它要經(jīng)歷以下幾個(gè)空間變換
上述空間轉(zhuǎn)換過(guò)程被我們稱為一個(gè)MVP變換,M代表model,V代表view,P代表projection投影。
頂點(diǎn)著色器最基本的任務(wù)就是將頂點(diǎn)從模型空間轉(zhuǎn)換到裁剪空間,也就是需要在頂點(diǎn)著色器中實(shí)現(xiàn)MVP變換。
模型空間
模型空間(model space) 代表了模型自身所處的坐標(biāo)空間,或者我們也可以稱其為對(duì)象空間(object space)或者局部空間(local space) ,每個(gè)模型都有屬于自己的獨(dú)立的模型空間,當(dāng)模型發(fā)生變換時(shí),其模型空間也隨之發(fā)生變換。
模型中的頂點(diǎn)坐標(biāo)是四維的,需要被拓展到齊次坐標(biāo)系下。不過(guò)由于我們?cè)赨nity中描述某點(diǎn)都是以世界空間為基準(zhǔn),因此模型空間我們不大關(guān)心。
世界空間
世界空間(world space) 用于描述模型的絕對(duì)位置,即在Unity場(chǎng)景中的世界坐標(biāo)系位置,世界空間的原點(diǎn)就是游戲場(chǎng)景的中心。
在Unity中,物體有Transfrom組件的Position屬性來(lái)表示它的坐標(biāo)。這個(gè)Position表示的是物體相對(duì)于父物體(parent)為原點(diǎn)的坐標(biāo),如果一個(gè)GameObject沒(méi)有任何父節(jié)點(diǎn),則Transfrom代表了物體在世界空間中的坐標(biāo)。Transfrom的Rotation和Scale也同理。
頂點(diǎn)變換的第一步就是從模型空間變換到世界空間
對(duì)于空間的變換,我們的應(yīng)用是先縮放,在旋轉(zhuǎn),再平移,因此,模型空間到世界空間的變換矩陣為:
M m o d e l → w o r l d = M t r a n s M r o t a t e M s c a l e P w o r l d = M m o d e l → w o r l d P m o d e l M_{model \to world} = M_{trans}M_{rotate}M_{scale} \newline P_{world}=M_{model \to world} P_{model} Mmodel→world?=Mtrans?Mrotate?Mscale?Pworld?=Mmodel→world?Pmodel?
模型空間轉(zhuǎn)換到世界空間可以視為MVP轉(zhuǎn)換中由 M → V M \to V M→V的準(zhǔn)備工作,因?yàn)槲覀円允澜缈臻g為父空間描述其他空間的坐標(biāo)。
觀察空間
世界空間到觀察空間代表了 M → V M \to V M→V的流程,
觀察空間(view space)或者稱視圖空間是攝像頭視角下的空間坐標(biāo),也稱為攝像機(jī)空間(camera space)。
觀察空間決定了攝像機(jī)渲染游戲使用的視角。攝像機(jī)的位置代表了原點(diǎn),而我們?cè)谥爸v過(guò),觀察空間使用的是右手坐標(biāo)系,也就是屏幕到人眼的方向?yàn)檎鴺?biāo)。
現(xiàn)在我們想要將世界空間的坐標(biāo)變換到觀察空間中,這個(gè)變換稱為觀察變換(view transfrom)
想要實(shí)現(xiàn)世界空間到觀察空間的變換,只需獲得場(chǎng)景下攝像機(jī)的Tramsform信息,也就是世界空間坐標(biāo)信息,我們知道這個(gè)坐標(biāo)是觀察空間乘以變換矩陣得到的世界空間。因此找到世界空間到觀察空間的逆矩陣,就可以將模型坐標(biāo)從世界空間變換到觀察空間。
以上圖為例,不難發(fā)現(xiàn)觀察空間到世界空間的逆變換就是縮放1/1倍,繞x軸旋轉(zhuǎn)-30°,最后平移(0,-10,10)。
并且注意,世界空間轉(zhuǎn)換到觀察空間,左手坐標(biāo)系變?yōu)橛沂肿鴺?biāo)系,則z軸方向翻轉(zhuǎn),所以z軸坐標(biāo)取反:
M
w
o
r
l
d
→
v
i
e
w
=
M
n
e
g
a
t
e
?
z
M
v
i
e
w
→
w
o
r
l
d
?
1
P
v
i
e
w
=
M
w
o
r
l
d
→
v
i
e
w
P
w
o
r
l
d
M_{world \to view} =M_{negate\space z}M_{view \to world}^{-1} \newline P_{view}=M_{world \to view} P_{world}
Mworld→view?=Mnegate?z?Mview→world?1?Pview?=Mworld→view?Pworld?
裁剪空間
接下來(lái)我們要將觀察空間轉(zhuǎn)換到裁剪空間(clip space ,也被稱為齊次裁剪空間)。這個(gè)用于變換的矩陣叫做裁剪矩陣clip matrix,也叫投影矩陣(projection matrix)。
裁剪空間的目的是確定那些圖元需要被渲染,圖元如果完全在裁剪空間內(nèi)則保留;如果部分在裁剪空間內(nèi),就根據(jù)邊緣對(duì)圖元裁剪,保留空間內(nèi)的部分進(jìn)行渲染;如果圖元完全不在空間內(nèi)則被舍棄。而裁剪空間的大小則是由視錐體(view frustum) 決定。
視錐體指的是空間中的一塊區(qū)域,這塊區(qū)域決定了攝像機(jī)可以看到的空間。視錐體決定了攝像機(jī)能看到的視野范圍,視錐體是一個(gè)六面體,它的每個(gè)面被稱為裁剪平面clip planes。
攝像機(jī)有兩者投影模式,一種是透視投影(perspective projection) ,另一種是正交投影(orthographic projection)。
透視投影模擬了正常人眼視角下的隨距離產(chǎn)生的透視效果,而正交投影則是不帶透視效果的渲染。
在視錐體的所有裁剪平面中,有兩個(gè)裁剪平面比較特殊,分別被稱為近裁剪平面(near clip plane) 和 遠(yuǎn)裁剪平面(far clip plane) 。他們決定了攝像機(jī)可以看到的深度范圍。
由于透視投影的視錐是一個(gè)金字塔形,因此如果通過(guò)點(diǎn)在空間上的位置判斷是否在視錐內(nèi)從而實(shí)現(xiàn)裁剪是相當(dāng)麻煩的。因此,我們想用一種更通用的方式來(lái)進(jìn)行裁剪,這種方式就是通過(guò)一個(gè)投影矩陣把頂點(diǎn)轉(zhuǎn)換到一個(gè)裁剪空間中。
投影矩陣的目的有兩個(gè):
- 為投影做準(zhǔn)備,雖然投影矩陣名稱中包含了投影,但沒(méi)有真正進(jìn)行投影的工作,真正的投影發(fā)生在齊次除法過(guò)程中
- 其次是對(duì)xyz分量進(jìn)行縮放,通過(guò)投影矩陣可以將w分量作為一個(gè)范圍值,若xyz分量都位于這個(gè)范圍中則說(shuō)明該頂點(diǎn)位于裁剪空間內(nèi)。
在之前講齊次坐標(biāo)的時(shí)候,我們使用了w分量,不過(guò)通常w分量是固定值,點(diǎn)的w分量為1,而方向向量的w分量為0,經(jīng)過(guò)投影矩陣變換后w分量可以被我們用于判斷頂點(diǎn)是否在裁剪空間內(nèi)。
投影矩陣
想要將頂點(diǎn)從觀察空間轉(zhuǎn)換到裁剪空間,則需要乘以投影矩陣,最后用齊次除法除以裁剪分量。
投影矩陣的原理其實(shí)很簡(jiǎn)單, 本質(zhì)上來(lái)講就是將視錐轉(zhuǎn)換為NDC,就是計(jì)算出從正交立方體到NDC的這個(gè)矩陣(透視視錐的原理也相同,需要先將透視視錐的frustum映射為正交立方體再使用上述步驟)
則有:
M
p
e
r
s
p
=
M
o
r
t
h
o
M
p
e
r
s
p
→
o
r
t
h
o
正交投影:
P
c
l
i
p
=
M
o
r
t
h
o
P
v
i
e
w
透視投影:
P
c
l
i
p
=
M
p
e
r
s
p
P
v
i
e
w
M_{persp} = M_{ortho} M_{persp \to ortho}\newline 正交投影: P_{clip}=M_{ortho} P_{view}\newline 透視投影:P_{clip}=M_{persp} P_{view}
Mpersp?=Mortho?Mpersp→ortho?正交投影:Pclip?=Mortho?Pview?透視投影:Pclip?=Mpersp?Pview?
這只是將頂點(diǎn)從觀察空間變換到四維的齊次裁剪空間,在齊次裁剪空間中進(jìn)行裁剪工作:
如果一個(gè)頂點(diǎn)在視錐內(nèi),那么滿足:
?
w
<
=
x
<
=
w
?
w
<
=
y
<
=
w
?
w
<
=
z
<
=
w
-w<=x<=w \newline -w<=y<=w \newline -w<=z<=w
?w<=x<=w?w<=y<=w?w<=z<=w
不滿足以上條件的頂點(diǎn)將被舍棄以完成對(duì)片元的裁剪工作。
屏幕空間
我們現(xiàn)在知道了那些頂點(diǎn)保留,那些舍棄。這樣我們就可以投影到屏幕空間了。
屏幕空間是二維空間,在此之前我們還需要將頂點(diǎn)從四維的齊次裁剪空間投射到三維的NDC空間再投射到屏幕空間。
于是從齊次裁剪空間到屏幕空間過(guò)程分為兩步:
- 進(jìn)行標(biāo)準(zhǔn)齊次除法(homogeneous division),也稱為透視除法(perspective division)。這一步很簡(jiǎn)單,其實(shí)就是對(duì)齊次坐標(biāo)系的xyzw分量分別除以w分量,使得w分量變?yōu)?即可將其投射回三維空間,這個(gè)新得到的坐標(biāo)空間被我們稱為歸一化的設(shè)備坐標(biāo)NDC。
- 現(xiàn)在從齊次裁剪空間轉(zhuǎn)換到了NDC,我們會(huì)得到分量在[-1,1]的單位正方體。然后我們?cè)阡秩竟芫€的幾何化階段使用這個(gè)NDC的坐標(biāo)來(lái)計(jì)算屏幕空間上的坐標(biāo)即可,只需簡(jiǎn)單縮放就可以完成。
從齊次裁剪空間投射到NDC空間(這一步代表了MVP變換中從觀察視圖轉(zhuǎn)換到投影視圖的過(guò)程) :
( x ′ , y ′ , z ′ ) = ( x w , y w , z w ) (x',y',z')=(\frac{x}{w},\frac{y}{w},\frac{z}{w}) (x′,y′,z′)=(wx?,wy?,wz?)
NDC再做視口映射到屏幕坐標(biāo):
s
c
r
e
e
n
x
=
x
′
?
p
i
x
e
l
W
i
d
t
h
2
+
p
i
x
e
l
W
i
d
t
h
2
s
c
r
e
e
n
y
=
y
′
?
p
i
x
e
l
H
e
i
g
h
t
2
+
p
i
x
e
l
H
e
i
g
h
t
2
screen_x=\frac{x'·pixelWidth}{2}+\frac{pixelWidth}{2}\newline screen_y=\frac{y'·pixelHeight}{2}+\frac{pixelHeight}{2}
screenx?=2x′?pixelWidth?+2pixelWidth?screeny?=2y′?pixelHeight?+2pixelHeight?
由于Unity中,從裁剪空間到屏幕空間的轉(zhuǎn)換是底層完成的,因此我們的頂點(diǎn)著色器只需要將頂點(diǎn)轉(zhuǎn)換到齊次裁剪空間即可。
法線變換
法線變換是一種特殊的變換。**法線(normal)**也被稱為法向量,猶原記得初中物理中學(xué)習(xí)光的反射中的關(guān)于法線的概念。法線是一種需要特殊處理的向量,在游戲中,模型的一個(gè)頂點(diǎn)往往會(huì)攜帶許多額外的信息,而頂點(diǎn)法線就是其中一種。當(dāng)我們變換一個(gè)模型的時(shí)候,不僅需要變換它的頂點(diǎn),還需要變換頂點(diǎn)法線,以便于在后續(xù)處理中(例如片元著色器)計(jì)算光照信息。
之前我們將向量變換的時(shí)候,總能用一個(gè)3x3或者4x4的矩陣實(shí)現(xiàn)從坐標(biāo)空間A到坐標(biāo)空間B的變換,然而在對(duì)法線進(jìn)行變換的時(shí)候,如果使用同一個(gè)變換矩陣,可能就無(wú)法保持法線的垂直性。
在初中學(xué)習(xí)光的反射的時(shí)候,會(huì)用到這樣的圖片。垂直于反射平面的那條線就是法線,然而在模型中,法線是每一個(gè)頂點(diǎn)都擁有的屬性。
關(guān)于切線空間計(jì)算的文章
在模型中,我們的切線空間一般而言是根據(jù)模型的UV方向來(lái)決定的,UV方向確定xy軸,一般U方向代表切線T,V方向代表副切線B,法線N就是正交的z軸方向(沿著面片的正面為正方向)。
當(dāng)然,由于法線是頂點(diǎn)的信息之一,法線是可以自定義方向的,這也會(huì)導(dǎo)致切線方向隨之改變。
由于切線與UV坐標(biāo)相關(guān),因此我們對(duì)其可以直接應(yīng)用3階線性變換 M A → B M_{A\to B} MA→B?,將切線從原坐標(biāo)空間A變換到新坐標(biāo)空間B:
T B = M A → B T A T_B=M_{A\to B}T_A TB?=MA→B?TA?
其實(shí)我認(rèn)為,由于法線方向可以自定義,嚴(yán)格來(lái)說(shuō)不垂直于平面的法線不算錯(cuò)誤結(jié)果,或許就是有這種需求呢?法線只需要滿足垂直于切線平面即可。
法線與切線垂直關(guān)系可以表示為內(nèi)積為0,即: T A ? N A = 0 T_A·N_A=0 TA??NA?=0,給定變換矩陣 M A ? B M_{A-B} MA?B?,已知 T B = M A ? B T A T_B=M_{A-B}T_A TB?=MA?B?TA??,F(xiàn)在想要一個(gè)變換矩陣 G G G來(lái)變換法線 N A N_A NA?,使得變換后的法線依然保持與切線垂直,即內(nèi)積為0:
T B ? N B = ( M A ? B T A ) ? ( G N A ) = 0 ( M A ? B T A ) ? ( G N A ) = ( M A ? B T A ) T ( G N A ) = T A T ( M A → B T G ) N A = 0 T_B·N_B =(M_{A-B}T_{A})·(GN_A)=0\newline (M_{A-B}T_{A})·(GN_A)=(M_{A-B}T_{A})^T(GN_A)=T^T_A(M^T_{A\to B}G)N_A=0 TB??NB?=(MA?B?TA?)?(GNA?)=0(MA?B?TA?)?(GNA?)=(MA?B?TA?)T(GNA?)=TAT?(MA→BT?G)NA?=0
由于 T A ? N A = 0 T_A·N_A=0 TA??NA?=0,因此如果 M A → B T G = E M^T_{A\to B}G=E MA→BT?G=E上式成立
所以 G = ( M A → B T ) ? 1 = ( M A → B ? 1 ) T G=(M^T_{A\to B})^{-1}=(M^{-1}_{A\to B})^T G=(MA→BT?)?1=(MA→B?1?)T
因此只需求出 M A → B M_{A\to B} MA→B?的逆矩陣的轉(zhuǎn)置即可對(duì)法線進(jìn)行正確的線性變換。且若 M A → B M_{A\to B} MA→B?為正交矩陣,則逆矩陣的轉(zhuǎn)置就是它本身。
如果基礎(chǔ)變換的縮放系數(shù)是統(tǒng)一的,那么可以直接除以統(tǒng)一縮放系數(shù)k, ( M A → B T ) ? 1 = 1 k M A → B (M^{T}_{A\to B})^{-1}=\frac{1}{k}M_{A\to B} (MA→BT?)?1=k1?MA→B?
(吐槽一下,書(shū)中 ( M A → B T ) ? 1 (M^{T}_{A\to B})^{-1} (MA→BT?)?1非得叫做 M A → B M_{A\to B} MA→B?的逆轉(zhuǎn)置矩陣嗎?不覺(jué)得直接叫轉(zhuǎn)置矩陣的逆矩陣好一點(diǎn)嗎?逆轉(zhuǎn)置矩陣不就搞不清楚是轉(zhuǎn)置矩陣的逆矩陣還是逆矩陣的轉(zhuǎn)置矩陣了嗎?)
Unity Shader的內(nèi)置變量(數(shù)學(xué)篇)
變換矩陣變量
Unity中給出了所有內(nèi)置的可用于坐標(biāo)空間變換的矩陣:
變量名 | 描述 |
---|---|
UNITY_MATRIX_MVP | 當(dāng)前模型視圖投影矩陣,通常用于把頂點(diǎn)/方向矢量從模型空間轉(zhuǎn)換到裁剪空間(從名字可以看出是MVP復(fù)合矩陣變換) |
UNITY_MATRIX_MV | 當(dāng)前模型視圖矩陣,通常用于把頂點(diǎn)/方向矢量從模型空間轉(zhuǎn)換到視角(觀察)空間 |
UNITY_MATRIX_V | 當(dāng)前視圖矩陣,通常用于把頂點(diǎn)/方向矢量從世界空間轉(zhuǎn)換到視角(觀察)空間 |
UNITY_MATRIX_P | 當(dāng)前的投影矩陣,通常用于把頂點(diǎn)/方向矢量從視角(觀察)空間轉(zhuǎn)換到裁剪空間 |
UNITY_MATRIX_VP | 當(dāng)前視圖投影矩陣,通常用于把頂點(diǎn)/方向矢量從世界空間轉(zhuǎn)換到裁剪空間 |
UNITY_MATRIX_T_MV | UNITY_MATRIX_MV矩陣的轉(zhuǎn)置 |
UNITY_MATRIX_IT_MV | 模型視圖矩陣UNITY_MATRIX_MV的轉(zhuǎn)置矩陣的逆矩陣,通常用于把法線從模型空間轉(zhuǎn)換到視角(觀察)空間 ,也可用于得到UNITY_MATRIX_MV的逆矩陣 |
unity_ObjectToWorld | 當(dāng)前模型轉(zhuǎn)空間矩陣,通常用于把頂點(diǎn)/方向向量從模型空間轉(zhuǎn)換到世界空間 |
unity_WorldToObject | 當(dāng)前世界轉(zhuǎn)模型矩陣,通常用于把頂點(diǎn)/方向向量從世界空間轉(zhuǎn)換到模型空間 |
矩陣變換變量中給出了轉(zhuǎn)置矩陣和逆矩陣的變量,由此可見(jiàn)正交矩陣的性質(zhì)對(duì)于我們的計(jì)算是非常重要的。而一般而言,我們?cè)谶@里求解的方陣 M M M( M M M指四階的齊次變換矩陣的左上的三階矩陣,也就是只包含旋轉(zhuǎn)和縮放的部分),如果只包含旋轉(zhuǎn)和統(tǒng)一縮放(不平移),那么大概率就是正交的,為了滿足正交條件,還需要除以統(tǒng)一縮放倍率k,即 M = 1 k M ′ M = \frac{1}{k}M' M=k1?M′,其中 M ′ M' M′是正交矩陣。
上表也沒(méi)有給出 ( M V ) ? 1 (MV)^{-1} (MV)?1,實(shí)際上只需要對(duì)UNITY_MATRIX_IT_MV變量求轉(zhuǎn)置矩陣即可求出該逆矩陣了。
攝像機(jī)和屏幕參數(shù)
變量名 | 類型 | 描述 |
---|---|---|
_WorldSpaceCameraPos | float3 | 相機(jī)在世界空間的位置 |
_ProjectionParams | float4 | x = 1.0(如果當(dāng)前使用翻轉(zhuǎn)投影矩陣渲染則為-1.0),y是相機(jī)的近平面y=Near,z是相機(jī)的遠(yuǎn)平面,z=Far,w是1.0 / Far +1.0(參考NDC映射公式) |
_ScreenParams | float4 | x=width,y=height,z=1.0/width+1.0,w=1.0+1.0/height,其中width和height分別是該攝像機(jī)的渲染目標(biāo)(render target)的像素寬高(參考視口映射公式) |
_ZBufferParams | float4 | 用于線性化Z緩沖區(qū)的值。x = (1 - Far /Near),y = (Far/Near)、z = (x /Far)和w = (y /Far) |
unity_OrthoParams | float4 | x是正交相機(jī)的寬度width,y是正交的相機(jī)的高度height,z是未使用的,為正交的相機(jī)時(shí)w為1.0,透視相機(jī)時(shí)w為0.0 |
unity_CameraProjection | float4x4 | 該攝像機(jī)的投影矩陣 |
unity_CameraInvProjection | float4x4 | 該攝像機(jī)投影矩陣的逆矩陣 |
unity_CameraWorldClipPlanes[6] | float4 | 該攝像機(jī)的6個(gè)裁剪平面在世界空間下的等式,按如下順序:左、右、下、上、近、遠(yuǎn)裁剪平面 |
答疑
使用3x3還是4x4的變換矩陣
對(duì)于線性變換來(lái)說(shuō),3x3的矩陣就可以表示旋轉(zhuǎn)和縮放關(guān)系了,之所以要使用4x4的矩陣,是為了表示包含了平移的仿射變換,放在是在四維齊次坐標(biāo)空間則是線性變換,而只需將w分量設(shè)為1就不會(huì)對(duì)向量在三維的映射有影響。當(dāng)然,不設(shè)為1也可以通過(guò)齊次除法映射回三維。
Cg中的矢量和矩陣類型
UnityShader中使用Cg作為著色器編程語(yǔ)言,Cg中變量類型有很多,本書(shū)只介紹如何進(jìn)行數(shù)學(xué)運(yùn)算
float
float類型包括常見(jiàn)的float3,float4,float3x3,float4x4 ,從命名不難看出,前二者代表了向量(也可以用于表示1xn的行矩陣和nx1的列矩陣,這取決于運(yùn)算的種類和在運(yùn)算中的位置),后二者代表矩陣,而數(shù)字代表了維度。
m
u
l
(
M
,
v
)
=
=
m
u
l
(
v
,
t
r
a
n
p
o
s
e
(
M
)
)
mul(M,v)==mul(v,tranpose(M))
mul(M,v)==mul(v,tranpose(M))實(shí)際上就是公式
M
V
=
V
T
M
T
MV=V^TM^T
MV=VTMT(之所以沒(méi)對(duì)V進(jìn)行轉(zhuǎn)置是因?yàn)閒loat4類型既可以當(dāng)作行矩陣,也可以當(dāng)作列矩陣)
通常情況下,我們對(duì)于向量float3和float4都是直接右乘的。
需要注意Cg對(duì)矩陣類型的元素的初始化和訪問(wèn)順序。在Cg中,對(duì)float4x4等類型的變量是按照行優(yōu)先的方式進(jìn)行填充的。如果我們直接使用 ( 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ) (1,2,3,4,5,6,7,8,9) (1,2,3,4,5,6,7,8,9)去填充一個(gè)3x3的矩陣,那么則會(huì)按照次序逐行進(jìn)行填充,得到:
[ 1 2 3 4 5 6 7 8 9 ] \begin{bmatrix}1&2&3 \\ 4&5&6 \\ 7&8&9 \end{bmatrix} ?147?258?369? ?
同樣的,當(dāng)訪問(wèn)Cg中矩陣元素時(shí),也是按行來(lái)進(jìn)行索引的。
在Unity的API中提供了一種矩陣類型Matrix4x4,這種矩陣是按列填充的
Unity中的屏幕坐標(biāo):ComputeScreenPos/VPOS/WPOS
在編寫(xiě)Shader的時(shí)候,我們有時(shí)候希望能夠獲得片元在屏幕上的像素位置。
在頂點(diǎn)/片元著色器中,有兩種方式來(lái)獲得片元的屏幕坐標(biāo):
- 一種是對(duì)片元著色器的輸入聲明VPOS或者WPOS語(yǔ)義。VPOS是HLSL中對(duì)屏幕坐標(biāo)的語(yǔ)義,而WPOS是Cg中對(duì)屏幕坐標(biāo)的語(yǔ)義(那我還是推薦VPOS吧,比較Cg已經(jīng)g了),二者在Unity Shader中是等價(jià)的。我們可以在HLSL/Cg中通過(guò)語(yǔ)義的方式來(lái)定義頂點(diǎn)/片元著色器的默認(rèn)輸入,而不需要自己定義輸入輸出的數(shù)據(jù)結(jié)構(gòu)。
我們可以這樣寫(xiě):
VPOS/WPOS的語(yǔ)義定義的輸入是一個(gè)float4類型的變量。我們知道這個(gè)聲明為VPOS的變量sp回?fù)碛衳y值,代表了屏幕空間的像素坐標(biāo)。例如屏幕分辨率為400*300,則x的范圍為[0.5,400.5],y為[0.5,300.5] ,之所以像素坐標(biāo)不是整數(shù),是因?yàn)閳D形API們認(rèn)為起點(diǎn)像素的中心值是0.5,0是邊緣值。
那么,它的z分量0和w分量1代表了什么?z=0代表了在攝像機(jī)的近裁剪平面Near處,反之z=1代表了在遠(yuǎn)裁剪平面Far處。
如果使用的是正交投影,那么w恒為1,若使用透視投影則w分量的范圍是 [ 1 N e a r , 1 F a r ] ( 根據(jù)投影矩陣變換再除以變換矩陣的 w 分量后得到 ) [\frac{1}{Near},\frac{1}{Far}](根據(jù)投影矩陣變換再除以變換矩陣的w分量后得到) [Near1?,Far1?](根據(jù)投影矩陣變換再除以變換矩陣的w分量后得到)
我們最后要把屏幕空間除以屏幕分辨率來(lái)得到視口空間(viewport space) 的坐標(biāo),視口坐標(biāo)很簡(jiǎn)單,只需要把屏幕坐標(biāo)歸一化即可,這樣屏幕左下角是(0,0),右上角是(1,1)
如果已知屏幕坐標(biāo),只需把xy值除以屏幕分辨率即可。
- 另一種方法就是使用Unity提供的ComputeScreenPos 函數(shù)。該函數(shù)再UnityCG.cginc中被定義(有這么好的東西不早說(shuō)),通常用法是:首先再頂點(diǎn)著色器中將ComputeScreenPos的結(jié)果保存在輸出結(jié)構(gòu)體中,然后再片元著色器中進(jìn)行一個(gè)齊次除法運(yùn)算后得到視口空間下的坐標(biāo),例如:
上面說(shuō)的兩種方法效果一樣,觀察上述代碼,不難發(fā)現(xiàn)其實(shí)就是先對(duì)頂點(diǎn)應(yīng)用了MVP變換得到齊次裁剪坐標(biāo),然后應(yīng)用ComputeScreenPos方法得到屏幕坐標(biāo),最后將屏幕坐標(biāo)進(jìn)行齊次除法得到視口空間坐標(biāo)。(ComputeScreenPos的方法名聽(tīng)起來(lái)是直接獲取屏幕坐標(biāo),但不是這樣,我們依舊需要進(jìn)行齊次除法)
上圖就是我們這段代碼的結(jié)果,將歸一化的坐標(biāo)值映射為RGB值,x對(duì)應(yīng)R,y對(duì)應(yīng)G
Unity之所以依舊需要我們對(duì)ComputeScreenPos進(jìn)行齊次除法,而不是自己完成,是由于ComputeScreenPos是在頂點(diǎn)著色器中使用的,如果在頂點(diǎn)著色器中直接使用齊次除法會(huì)影響頂點(diǎn)信息的插值,插值結(jié)果就會(huì)不準(zhǔn)確,原因在于插值必須是線性的,而只要在齊次空間下插值才是線性的,若在投影空間中則是非線性的。
雖然我們視口坐標(biāo)是二維的,只包含了xy,但是上述代碼中,頂點(diǎn)著色器的輸出結(jié)果是自定義的vertOut結(jié)構(gòu)體,存儲(chǔ)了四維向量。因此依舊保留了z和w值。
使用該方法,同樣的,x,y的范圍在 [ 0 , 1 ] [0,1] [0,1]。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-792870.html
如果使用的是正交投影,z值范圍為
[
?
1
,
1
]
[-1,1]
[?1,1] ,那么w恒為1;
若使用透視投影則z值范圍為
[
?
N
e
a
r
,
F
a
r
]
[-Near,Far]
[?Near,Far] , w分量的范圍是
[
1
N
e
a
r
,
1
F
a
r
]
(
根據(jù)投影矩陣變換再除以變換矩陣的
w
分量后得到
)
[\frac{1}{Near},\frac{1}{Far}](根據(jù)投影矩陣變換再除以變換矩陣的w分量后得到)
[Near1?,Far1?](根據(jù)投影矩陣變換再除以變換矩陣的w分量后得到)文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-792870.html
到了這里,關(guān)于【UnityShader入門(mén)精要學(xué)習(xí)筆記】第四章(6)法線變換、內(nèi)置變量以及本章答疑的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!