每一個游戲可以呈現炫麗效果的背后,需要進行一系列的復雜計算,同時也伴隨著各種各樣的頂點空間變換,渲染游戲的程序可以理解成是把一個個頂點經過層層處理最終轉化到螢屏上的程序,本文就旨在說明,頂點是經過了哪些坐標空間后,最終被畫在了我們的螢屏上,
空間變換的原理
首先,我們來看一個簡單的問題:當給定一個坐標空間以及其中一點(a, b, c)時,我們是如何知道該點的位置的呢?
- 從坐標空間的原點開始
- 向x軸方向移動a個單位
- 向y軸方向移動b個單位
- 向z軸方向移動c個單位
坐標空間的變換就蘊含在上面的4個步驟中,現在,我們已知坐標空間C的3個坐標軸在坐標空間P下的表示Xc, Yc, Zc,以及其原點位置Oc,當給定坐標空間C中的一點Ac = (a, b, c),我們同樣可以依照上面4個步驟來確定其在坐標空間P下的位置Ap
- 從坐標空間的原點開始,即 Oc
- 向x軸方向移動a個單位,即 Oc + aXc
- 向y軸方向移動b個單位,即 Oc + aXc + bYc
- 向z軸方向移動c個單位,即 Oc + aXc + bYc + cZc
對得到的運算式做如下變換,其中“|”符號表示按列展開

繼續對其中的加法運算式做變換,即擴展到齊次坐標空間做平移變換

現在,我們得到了坐標空間C到坐標空間P的變換矩陣Mc->p

可以看出Mc->p實際上是通過坐標空間C在坐標空間P中的原點和坐標軸的矢量表示構建出來的:把3個坐標軸依次放入矩陣的前3列,把原點矢量放到最后一列,再用0和1填充最后一行即可,
我們可以利用反向思維,從這個變換矩陣中提取出坐標空間C的原點和坐標軸在坐標空間P的表示,例如,當我們已知從模型空間到世界空間的4×4變換矩陣,我們可以提取出它的第一列,再進行歸一化(為了消除縮放的影響)來得到模型空間的x軸在世界空間下的單位矢量表示,同樣的方法可以提取y軸和z軸,
當對方向矢量進行坐標空間變換時,由于矢量是沒有位置的,因此坐標空間的原點變換是可以忽略的,那么對方向矢量的坐標空間變換就可以使用3×3的矩陣來表示,即

在Shader中,我們常常看到截取變換矩陣的前3行前3列來對法線方向,光照方向進行空間變化,這正是原因所在,
一旦求出來Mc->p,Mp->c就可以通過求逆矩陣的方式求出來,因為從坐標空間C變換到坐標空間P與從坐標空間P變換到坐標空間C是互逆的兩個程序,當Mc->p是一個正交矩陣時,Mc->p的逆矩陣就等于它的轉置矩陣,即

此時,我們還可以通過Mc->p反推出坐標空間P的坐標軸在坐標空間C中的表示Xp, Yp, Zp,這些坐標軸對應的就是Mc->p的每一行,
模型空間
模型空間,是和某個模型或者說是物件有關的,模型空間也被稱為物件空間或區域空間,
每個模型都有自己獨立的坐標空間,當它移動或旋轉的時候,模型空間也會跟著移動和旋轉,
Unity在模型空間中使用的是左手坐標系,因此在模型空間中,+x軸,+y軸,+z軸分別對應的是模型的右,上,前向,
模型空間的原點和坐標軸通常是由美術人員在建模軟體里確定好的,當匯入到Unity中后,我們可以在頂點著色器中訪問到模型的頂點資訊,其中就包含了每個頂點的坐標,這些坐標都是相對于模型空間中的原點(通常位于模型的重心)定義的,
世界空間
世界空間是一個特殊的坐標系,因為它建立了我們所關心的最大空間,即整個游戲空間
在Unity中,世界空間同樣使用了左手坐標系,它的x軸,y軸,z軸是固定不變的,
頂點變換的第一步,就是將頂點坐標從模型空間轉換到世界空間中,這個變換通常叫做模型變換
在Unity中,我們可以通過Transform組件中的值得知模型做了哪些變換,這個值是根據Transform的父節點的模型坐標空間中的原點定義的,如果這個Transform沒有任何父節點,那么這個值就是相對于世界坐標空間定義的,
要將模型空間中的一點轉換到其父空間中,需要獲取M子->父,這個矩陣可以通過模型的Transform值得到,Transform中包含了旋轉,縮放和平移值,則M子->父 = Mtranslation Mrotate Mscale,而從模型空間轉換到世界空間的變換矩陣M模型->世界可以通過子空間到父空間變換矩陣,父空間到爺爺空間變換矩陣,連乘,直到世界空間為止得到,
觀察空間
觀察空間也被稱為攝像機空間,可以認為是模型空間的一個特例,即攝像機的模型空間,
在Unity中,觀察空間使用的是右手坐標系,即+x軸指向右方,+y軸指向上方,+z軸指向攝像機后方
頂點變換的第二步就是將頂點坐標從世界空間變換到觀察空間中,這個變換通常叫做觀察變換,
從觀察空間到世界空間的變換矩陣我們同樣可以通過Transform中的值得到,再對該矩陣求逆得到從世界空間到觀察空間的變換矩陣,我們還可以使用另一種方法,對Transform組件中的值直接取反(做逆向變換),然后得到從世界空間到觀察空間的變換矩陣,注意,由于觀察空間使用的是右手坐標系,因此還需要對變換矩陣的z分量進行取反操作,
裁剪空間
頂點接下來要從觀察空間轉換到裁剪空間中,這個變換可以被稱為投影變換,這個用于變換的矩陣叫做裁剪矩陣或是投影矩陣
裁剪空間的目的是能夠方便地對渲染圖元進行裁剪:完全位于這塊空間內部的圖元將會被保留,完全位于這塊空間外部的圖元將會被剔除,與這塊空間邊界相交的圖元就會被裁剪,而這塊空間就是由視椎體來決定的,
視椎體有兩種型別,分別對應兩種投影型別:透視投影(下圖左)和正交投影(下圖右),透視投影模擬了人眼看世界的方式,而正交投影則完全保留了物體的距離和角度,

投影矩陣雖然叫做投影矩陣,但并沒有真正進行投影,而是為投影做準備,目的是對x,y,z分量進行縮放,經過投影矩陣的縮放后,我們可以直接使用w分量作為范圍值,只有x,y,z分量都位于這個范圍內的頂點才認為是在裁剪空間內,并且w分量在真正的投影時也會用到,
透視投影和正交投影分別對應了不同的投影矩陣,還需要注意的是投影矩陣會改變空間的旋向性:空間從右手坐標系變換到了左手坐標系
透視投影

其中FOV表示視椎體垂直方向的張開角度,而Near和Far分別控制了近裁剪平面和遠裁剪平面距離攝像機的遠近,這樣我們可以求出近裁剪平面的高度,如下所示,遠裁剪平面類似,

而根據攝像機的橫縱比資訊,我么就可以得到近裁剪平面的寬度

一個頂點和透視投影的投影矩陣相乘后得到的結果如下

視椎體的變化如下所示

此時我們就可以按如下不等式來判斷一個變換后的頂點是否位于視椎體內

正交投影

其中Size表示視椎體豎直方向上高度的一半,而Near和Far同樣分別控制了近裁剪平面和遠裁剪距離攝像機的遠近,則近裁剪平面的高度如下所示,遠裁剪平面類似,

近裁剪平面的高度同樣可以通過攝像機的縱橫比得到

一個頂點和正交投影的投影投影矩陣相乘后得到的結果如下

視椎體的變化如下所示

判斷一個變換后的頂點是否位于視椎體內使用的不等式和透視投影中的一樣,這種通用性也是為什么要使用投影矩陣的原因之一,
螢屏空間
當完成了所有的裁剪作業后,就需要進行真正的投影了,即把視椎體投影到螢屏空間中,這個程序可以被稱為螢屏映射,經過這一步變換,我們會得到真正的像素位置,對應的2D坐標,而不是虛擬的三維坐標,這個程序可以理解成有兩步:
-
進行標準齊次除法,也被稱為透視除法,就是把齊次坐標系的x,y,z分量都除以w分量,在OpenGL中把這一步得到的坐標叫做歸一化的設備坐標(NDC),經過透視投影變換后的裁剪空間會變換到一個立方體內,而正交投影的裁剪空間本身就是一個立方體(它的w分量是1,齊次除法不會對它產生影響),在Unity中這個立方體的x,y,z分量的范圍都是[-1, 1],和OpenGL保持一致,

-
經過齊次除法后,透視投影和正交投影的視椎體都變換到一個相同的立方體內,現在,我們可以根據變換后的x,y坐標來映射輸出視窗對應的像素坐標,
在Unity中,螢屏空間左下角的像素是(0, 0),右上角的像素坐標是(pixelWidth, pixelHeight),齊次除法和螢屏映射的程序可以使用下面的公式來表示
\[screen_x = \frac{clip_x * pixelWidth}{2 * clip_w} + \frac{pixelWidth}{2} \]
\[screen_y = \frac{clip_y * pixelHeight}{2 * clip_w} + \frac{pixelHeight}{2} \]
在Unity中,從裁剪空間到螢屏空間的轉換是由底層幫我們完成的,我們的頂點著色器只需要把頂點轉換到裁剪空間即可(模型空間-世界空間-觀察空間-裁剪空間,對應的矩陣通常會串聯成一個MVP矩陣),
法線變換
最后,我們再來看一種特殊的變換:法線變換,在游戲中,模型的一個頂點往往會攜帶額外的資訊,而頂點法線和切線就是其中的兩種資訊,切線和法線是互相垂直的,
由于切線是由兩個頂點之間的差值計算得到的,因此我們可以直接使用變換頂點的矩陣MA->B來變換切線,但如果直接使用MA->B來變換法線,得到的新法線可能就不會和切線垂直了,例如下圖所示.

那么應該使用哪個矩陣來變換法線呢?我們可以通過數學約束條件推出這個矩陣,由于頂點法線NA和切線TA垂直,則TANA = 0,給定變換矩陣MA->B,我們已知TB = MA->BTA,現在我們要找到一個矩陣G來變換法線NA,使得變換后的法線仍然與切線垂直,即

通過一些推導可得

由于TANA = 0,因此可得

即

這說明使用原變換矩陣的逆轉置矩陣來變換法線就可以得到正確的結果,
值得注意的是,如果矩陣MA->B是正交矩陣,則我們可以直接使用原變換矩陣作為法線的變換矩陣,如果變換只包含旋轉和統一縮放,我們可以利用統一縮放系數k來得到變換矩陣MA->B的逆轉置矩陣,這樣可以避免計算逆矩陣的程序,

視口空間
視口空間中的坐標被稱為視口坐標,就是把螢屏歸一化,這樣螢屏左下角就是(0, 0),右上角就是(1, 1),如果已知螢屏坐標的話,我們只需要把x,y分量除以螢屏解析度即可得到視口坐標,如果已知裁剪空間中的坐標,可以通過以下公式得到視口坐標

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/3900.html
標籤:其他
上一篇:版本管理
