Unity Shader入门精要笔记(四):矩阵与空间变换
本系列文章由Aimar_Johnny編寫,歡迎轉(zhuǎn)載,轉(zhuǎn)載請標(biāo)明出處,謝謝。
http://blog.csdn.net/lzhq1982/article/details/73612170
上一篇我們學(xué)習(xí)了一些數(shù)學(xué)知識,包括矩陣,這一篇我們重點(diǎn)講矩陣的幾何意義:空間變換。
1、變換
變換(transform),指的是我們把一些數(shù)據(jù),如點(diǎn)、方向矢量、甚至是顏色等,通過某種方式進(jìn)行轉(zhuǎn)換的過程。
1)線性變換和仿射變換
能滿足下面公式的變換就是線性變換:
縮放和旋轉(zhuǎn)都是線性變換,對于線性變換,一個3x3的矩陣就可以表示對一個三維矢量的線性變換。
平移不是線性變換,比如 f(x) = x + (1, 2, 3),如果我們令x = (1, 1, 1),那么
f(x) + f(x) = (4, 6, 8) ? ? ?f(x + x) = (3, 4, 5)
顯然不符合上面的公式,所以不能用3x3的矩陣表示一個平移變換,那怎么辦,所以有了仿射變換。仿射變換就是合并線性變換和平移變換的變換類型。仿射變換用一個4x4的矩陣來表示,為此,需要把矢量擴(kuò)展到四維空間,這就是齊次坐標(biāo)空間。
2)齊次坐標(biāo)
3x3的矩陣不能表示平移操作,我們擴(kuò)展到了4x4的矩陣。為此,我們還需要把原來的三維矢量換成四維矢量,這個四維矢量就是齊次坐標(biāo)。那如何把三維矢量轉(zhuǎn)換成齊次坐標(biāo)呢,很簡單,對于點(diǎn),把w分量也就是第四維設(shè)為1,對于方向矢量,w分量設(shè)為0。也就是對于一個點(diǎn),我們用4x4矩陣對其平移、旋轉(zhuǎn)、縮放都可以。但對于方向矢量,平移效果沒用,因?yàn)榉较蚴噶课覀冎魂P(guān)心其方向和大小,位置沒有意義。
3)幾種基礎(chǔ)變換矩陣
a、平移矩陣
從上面可以看出我們用的是齊次坐標(biāo)表示法,把點(diǎn)(x, y, z)在空間中平移了個單位。
平移矩陣的逆矩陣就是反向平移得到的矩陣,即:
可以看出,平移矩陣不是正交矩陣。
b、縮放矩陣
如果三個縮放系數(shù)k都相等,我們稱為統(tǒng)一縮放,否則是非統(tǒng)一縮放,因?yàn)榉墙y(tǒng)一縮放會拉伸或擠壓模型,所以會改變與模型相關(guān)的角度和比例,這在法線變換時很重要,如果非統(tǒng)一變換,直接使用變換頂點(diǎn)的變換矩陣就會出錯。
縮放矩陣的逆矩陣很容易求解:
可以看出不是正交矩陣。
c、旋轉(zhuǎn)矩陣
這里的旋轉(zhuǎn)是圍繞空間的x,y,z軸旋轉(zhuǎn)。
繞x,y,z軸旋轉(zhuǎn)的矩陣分別是:
??? ?
旋轉(zhuǎn)矩陣的逆矩陣是旋轉(zhuǎn)相反角度得到的變換矩陣,你可以試試把上面的用-代替求出的矩陣就是其逆矩陣,然后你會驚奇的發(fā)現(xiàn)和其轉(zhuǎn)置矩陣一樣,所以旋轉(zhuǎn)矩陣是正交矩陣。
d、復(fù)合變換
我們可以把平移、旋轉(zhuǎn)和縮放組合起來,形成一個復(fù)雜的變換過程。這個過程可以用下面公式計(jì)算:
還記得上一篇最后我們說過我們會采取列矩陣的方式,從右往左逐一變換,即按照縮放,旋轉(zhuǎn),平移的順序。注意矩陣乘法是不滿足交換律的,所以變換順序很重要,不同的順序結(jié)果會不一樣。在大多數(shù)情況下,我們采取上面的順序。原因自行看書吧,這里不解釋。
還有個要注意的是旋轉(zhuǎn)的變換順序。如果要同時繞三個軸進(jìn)行旋轉(zhuǎn),在Unity中,這個旋轉(zhuǎn)順序是zxy,這意味著組合旋轉(zhuǎn)變換矩陣是:
這里要注意一點(diǎn),這里的旋轉(zhuǎn)過程中我們的坐標(biāo)軸不變,不是那種旋轉(zhuǎn)一個軸后由于自身方向的改變再按新的坐標(biāo)軸的旋轉(zhuǎn)的順序。
2、坐標(biāo)空間
通過上一篇和上面有關(guān)矩陣變換的相關(guān)知識的學(xué)習(xí),到現(xiàn)在我們終于進(jìn)入我們的核心主題了,所有的這一切都是為了坐標(biāo)空間。
1)坐標(biāo)空間的變換
在渲染流水線中,我們往往需要把一個點(diǎn)或方向矢量從一個坐標(biāo)空間轉(zhuǎn)換到另一個坐標(biāo)空間。而定義一個坐標(biāo)空間,需要指明原點(diǎn)和3個坐標(biāo)軸方向。而這些數(shù)值實(shí)際上是相對另一個坐標(biāo)空間的。舉個例子,我現(xiàn)在坐在屋子的東南角,這是以屋子中心為原點(diǎn),方位朝向?yàn)樽鴺?biāo)軸的。但我的屋子在大樓的四層西北方向,那如何以大樓為空間描述我的位置呢。所以坐標(biāo)空間會有一個層次結(jié)構(gòu),每個空間都有一個父空間。對坐標(biāo)空間的變換實(shí)際上就是在父空間和子空間之間對點(diǎn)和矢量進(jìn)行變換。
假設(shè)有父空間P和子空間C,我們往往需要把子空間下的點(diǎn)或矢量轉(zhuǎn)換到父空間下表示的,或者反過來,把父空間的點(diǎn)或矢量轉(zhuǎn)換到子空間下的,可以使用下面公式表示這兩種需求:
其中表示從子空間變換到父空間的變換矩陣,是其逆矩陣。那么如何求解這些變換矩陣呢,當(dāng)然,只要求解其一就可,另一個為它的逆矩陣。我們來求。
假如我們已知子空間C的3個坐標(biāo)軸在父空間P下表示為,以及原點(diǎn),當(dāng)給定一個子空間下的坐標(biāo)?= (a, b, c),我們求其在父空間的坐標(biāo),我們可以得到公式:
原理很好解釋,我們從出發(fā),沿x走了a,沿y走了b,沿z走了c,就得到了其在父空間的坐標(biāo)。
下面就是見證奇跡的時刻了:
最后分別代表他們所在的列,這個公式還存在著加法表達(dá)式,即平移變換,還記得平移變換的矩陣表達(dá)式吧,3x3的矩陣無法表示平移變換,我們先把他們轉(zhuǎn)換到齊次坐標(biāo)空間。
第一行到第二行的轉(zhuǎn)變就是平移矩陣的表示方法,所以我們得出了最終結(jié)論:
好了,從這個里面我們可以得到相當(dāng)大的信息量。
首先有了,就是它的逆矩陣;
其次,如果我們知道了這個轉(zhuǎn)換矩陣,那么提取它的前三列就是子空間在父空間下的坐標(biāo)軸,第四列就是原點(diǎn)。
再次,在對方向矢量的空間變換中,我們不關(guān)心其位置所在,所以原點(diǎn)沒有意義,三維表達(dá)式就夠了,所以方向矢量的變換矩陣為:
最后,如果這個轉(zhuǎn)換矩陣是正交矩陣的話,那么其逆就是其轉(zhuǎn)置,那么它的行列分別可以表示其在父子空間的坐標(biāo)軸了,非常方便。
2)頂點(diǎn)坐標(biāo)空間變換過程
下面我們要說說,在渲染流水線中,頂點(diǎn)在各個空間的變換過程。
a、模型空間 (model space)
也叫對象空間(object space)或局部空間(local space)。每個模型都有自己獨(dú)立的坐標(biāo)空間,當(dāng)它移動或旋轉(zhuǎn)時,模型空間也會跟著移動和旋轉(zhuǎn)。在模型空間中,我們常使用一些方向概念,例如“前(forward)”、“后(back)”、“左(left)”、“右(right)”、“上(up)”、“下(down)”。Unity在模型空間使用的是左手坐標(biāo)系,所以+x、+y、+z軸分別對應(yīng)模型的右、上、前。而模型的右上前是由我們的美工人員制作模型時定好的。
b、世界空間(world space)
相對于模型空間,世界空間是模型所在的最外層的父空間。Unity中,世界空間同樣是左手坐標(biāo)系,原點(diǎn)是游戲空間的中心,x、y、z軸固定不變。頂點(diǎn)變換的第一步,是將頂點(diǎn)從模型空間變換到世界空間。這個變換叫模型變換(model transform)。在Unity中,我們看到模型的Transform組件里的位置旋轉(zhuǎn)和縮放,都是基于它的父節(jié)點(diǎn)(parent)的,當(dāng)模型沒有父節(jié)點(diǎn)時,那這個Transform就是基于世界空間的。變換過程用下面的公式就可:
有關(guān)平移旋轉(zhuǎn)縮放的矩陣上面都介紹過了,把Transform組件的信息代入進(jìn)去就可求得,這里不多介紹。
c、觀察空間(view space)
觀察空間也被稱為攝像機(jī)空間,在觀察空間中,攝像機(jī)位于原點(diǎn),它決定了我們渲染游戲所使用的視角。前面說過,觀察空間采用的是右手坐標(biāo)系,所以+z軸指的是攝像機(jī)后方。頂點(diǎn)變化的第二步,就是將頂點(diǎn)坐標(biāo)從世界空間變換到觀察空間中。這個變換叫觀察變換(view transform)。
為了得到頂點(diǎn)在觀察空間的位置,我們可以有兩種方法。
一種方法是計(jì)算觀察空間的三個坐標(biāo)軸在世界空間的表示,然后按照上面“1)坐標(biāo)空間的變換”的方法算出觀察空間到世界空間的變換矩陣,再求逆得住世界空間到觀察空間的變換矩陣。
第二種方法是平移整個觀察空間,讓攝像機(jī)原點(diǎn)位于世界空間原點(diǎn),坐標(biāo)軸與世界空間坐標(biāo)軸重合。兩種方法得到的變換矩陣是一樣的。
這里我們用第二種方法,有一點(diǎn)很重要,我們上面說過世界空間的變換順序公式是先縮放,再旋轉(zhuǎn),再平移,而這里我們?yōu)榱税褦z像機(jī)移回世界坐標(biāo)原點(diǎn),我們需要逆向變換,所以是先平移,再旋轉(zhuǎn),再縮放。
注意我們是從右往左,矩陣乘法滿足結(jié)合不滿足交換,所以乘法順序可以從左往右。這里可以把攝像機(jī)的Transform組件數(shù)據(jù)逐一代入。
還有一點(diǎn)要注意,因?yàn)橛^察空間是右手坐標(biāo)系,與世界空間的左手坐標(biāo)系z軸相反,所以z分量要取反操作:
最后乘以經(jīng)過上一步世界變換的位置就可以了
書中有關(guān)于一個農(nóng)場的例子,有詳細(xì)的計(jì)算過程,有興趣的可以按著算一遍。
d、裁剪空間(clip space)
幾種空間轉(zhuǎn)換中最復(fù)雜的一個空間。裁剪空間,也被稱為齊次裁剪空間,用于變換的矩陣叫裁剪矩陣,也叫投影矩陣。在渲染流水線中說過裁剪,完全位于裁剪空間的被保留,完全位于外面的被剔除,與這個空間相交的會被裁剪。那么這個空間是如何決定的,答案是視錐體(view frustum)。
視錐體決定了攝像機(jī)可以看到的空間。視錐體由六個面組成,這些面也叫裁剪平面。視錐體有兩種類型,涉及兩種投影,正交投影(orthographic projection)和透視投影(perspective projection)。這里用一下書上的圖:
可以看出,透視投影模擬了人眼看世界的方式,適合3D游戲,而正交投影則完全保留了物體的距離和角度,適合2D游戲。
在上圖中我們可以看到兩個特殊的面,是近裁剪平面和遠(yuǎn)裁剪平面。他們決定了攝像機(jī)可以看到的深度范圍。和側(cè)面的四個面決定了裁剪空間。如果直接用視錐體定義的空間來進(jìn)行裁剪,那么不同的視錐體要不同的處理,而且透視投影的視錐體判斷起來更麻煩。所以我們需要一種更通用的方式,通過一個投影矩陣把頂點(diǎn)轉(zhuǎn)移到一個裁剪空間中。
投影矩陣有兩個目的:
第一,為投影做準(zhǔn)備
投影矩陣并沒有進(jìn)行真正的投影工作,投影是個降維的過程,從三維降到二維,真正的投影發(fā)生在后面的屏幕映射中,通過齊次除法獲得二維坐標(biāo)。經(jīng)過矩陣變換后,頂點(diǎn)的w分量會有特殊的意義。
第二,對x、y、z分量進(jìn)行縮放。
直接用視錐體的6個裁剪平面進(jìn)行裁剪比較麻煩。經(jīng)過投影矩陣縮放后,w分量會成為一個范圍值,如果x、y、z都在這個范圍內(nèi),就說明頂點(diǎn)位于裁剪空間中。
下面,我們分別看看兩種投影類型的投影矩陣:
一、透視投影
我們先看看透視投影的6個裁剪平面怎么決定的。在Unity中,它們由Camera組件中的參數(shù)和Game視圖的縱橫比共同決定。如圖所示:
上圖中Camera的Field of View(FOV)決定視錐體豎直方向的張開角度,Clipping Planes中的Near和Far決定視錐體的近裁剪平面和遠(yuǎn)裁剪平面距離攝像機(jī)的遠(yuǎn)近,這樣就可以求出近和遠(yuǎn)裁剪平面的高度:
而橫向信息由攝像機(jī)的縱橫比決定。這個縱橫比由Game視圖的縱橫比和Viewport Rect中的W和H屬性共同決定(Unity中可以通過Camera.aspect獲得)。假設(shè)縱橫比為Aspect,則:
這樣可以確定透視投影的投影矩陣:
推導(dǎo)過程看書上的擴(kuò)展閱讀部分。這個投影矩陣是建立在Unity坐標(biāo)系上,觀察空間是右手坐標(biāo)系,使用列矩陣右側(cè)相乘,且變換后z分量在[-w, w]之間。但在DirectX中,z分量在[0, w]之間,上面的透視矩陣就要更改了。這里不討論。
用上一步觀察空間得到的坐標(biāo)和投影矩陣相乘,就可以變換到裁剪空間中:
就如上面說過的,本質(zhì)就是對x、y、z做了不同的縮放(z還有個平移)。w也不再是1,而是z取反。最后通過x、y、z是否在[-w, w]中判斷是否位于視錐體內(nèi)。不在其內(nèi)的會被剔除或裁剪,這樣通過投影矩陣后,視錐體變化如下:
讀者可以把左邊的各個頂點(diǎn)帶入上面的公式,就會得到右邊頂點(diǎn),而且我們發(fā)現(xiàn),裁剪矩陣使空間從右手坐標(biāo)系換到了左手坐標(biāo)系,z從向外為正變成了向里為正。
二、正交投影
和透視投影類似,我們看看其Camera組件的屬性:
視錐體是個長方體,因此不需要FOV了,用Size代替了,Size是高度的一半。因此,我們得到公式:
Aspect是橫縱比。這樣,可以得到正交投影的裁剪矩陣,如下:
然后觀察空間的頂點(diǎn)與矩陣相乘,如下:
可以看出,w分量依然為1。判斷是否位于裁剪空間內(nèi)與透視投影一樣,x、y、z是否在[-w, w]之間。通過投影矩陣后,視錐體變化如下:
可以看出,變換后空間從長方體變成正方體了,范圍是[-1, 1]。
e、屏幕空間(screen space)
終于說完裁剪空間了,經(jīng)過投影矩陣變換后,我們完成了裁剪工作,開始正式投影了,把視錐體投影到屏幕空間。屏幕空間是個二維空間,投影的過程分為兩步:
首先,要進(jìn)行齊次除法,也被稱為透視除法。就是用x、y、z分量除以w分量。在OpenGL中,這一步得到的坐標(biāo)叫歸一化的設(shè)備坐標(biāo)(NDC,Normalized Device Coordinates)。經(jīng)過這一步,我們把坐標(biāo)從齊次裁剪空間轉(zhuǎn)換到NDC中,你會驚奇的發(fā)現(xiàn),這樣會使透視投影的類似金字塔形狀的空間變成正方體,并且和正交投影的一樣:
推導(dǎo)過程很容易,回頭看裁剪空間那里的公式,透視投影坐標(biāo)經(jīng)裁剪矩陣變換后w是-z,所以坐標(biāo)x、y、z都除以-z就得到了右邊的樣子。而正交投影變換后w是1,所以除以1沒變化,這樣兩種投影方式就都是一樣的正方體了。
現(xiàn)在,我們開始屏幕映射了。Unity左下角坐標(biāo)是(0, 0),右上角是(pixelWidth, pixelHeight),現(xiàn)在經(jīng)過齊次除法后x、y的范圍是[-1, 1],所以這個過程就是個縮放的過程。首先把x、y變到[0, 1],很簡單,比如x = (x + 1) / 2,然后再乘以pixelWidth就是映射后的x了,當(dāng)然這里的x,y都是裁剪空間的坐標(biāo)除以w,總公式如下:
x、y被用作投影了,z分量會被用于深度緩沖,傳統(tǒng)方式是z/w直接存進(jìn)深度緩沖,但這不是必須的,驅(qū)動生產(chǎn)商會根據(jù)硬件來選擇最好的存儲格式。
f、總結(jié)
以上就是一個頂點(diǎn)如何從模型空間變換到屏幕空間的過程。頂點(diǎn)著色器的最基本的任務(wù)就是把頂點(diǎn)坐標(biāo)從模型空間轉(zhuǎn)換到裁剪空間中。也就是前三個頂點(diǎn)變換過程。后面一系列變換是自動完成的。然后在片元著色器中,我們就可以得到該片元在屏幕空間的像素位置了。最后上一張圖總結(jié)一下:
總結(jié)
以上是生活随笔為你收集整理的Unity Shader入门精要笔记(四):矩阵与空间变换的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python有链表和指针吗_了解如何更改
- 下一篇: 品茗安全帮助html,品茗安全计算软件操