第三章 學習Shader所需的數學基礎(3)

@[TOC]編程

一、 頂點的座標空間變換過程

咱們知道,在渲染流水線中,一個頂點要通過多個座標空間的變換才能最終被畫在屏幕上。一個頂點最開始是在模型空間中定義的,它最後會被變換到屏幕空間中,獲得真正的屏幕像素座標。所以接下來咱們將解釋頂點要進行的各類空間變換的過程。 爲了幫助你們理解這個過程,咱們將創建在農場遊戲的實例背景下,每講到一種空間變換,咱們都會解釋如何應用到這個案例中。 在咱們的農場遊戲中,妞妞很好奇本身是如何渲染到屏幕上的。它只知道本身和一羣小夥伴快樂的在農場裏吃草,而前面一直有一個攝像機在觀察它們。妞妞特別喜歡本身的鼻子,它想知道本身的鼻子是怎麼被畫到屏幕上的。 在這裏插入圖片描述 在下面的內容中咱們將瞭解妞妞的鼻子是如何一步步畫到屏幕上的。緩存

2. 模型空間

模型空間(model space),如它的名字所暗示那樣,是和某個模型或者對象有關的。有時模型空間也被稱爲對象空間(object space)或局部空間(local space)。每一個模型都有本身獨立的座標空間,當它移動或旋轉的時候,模型空間會跟着它移動和旋轉。把咱們本身當成遊戲中的模型的話,當咱們在辦公室裏移動的時候,咱們的模型空間也跟着移動,當咱們轉身時,咱們自己的先後左右方向也在跟着改變。 在模型空間中,咱們常用一些方向概念,例如「前(forward)」「後(back)」「左(left)」「右(right)」「上(up)」「下(down)」。在咱們的文章中,咱們把這些方向稱爲天然方向。模型空間中的座標軸一般會使用這些天然方向。之前咱們說過,unity在模型空間中使用的是左手座標系,所以在模型空間中,+x軸、+y軸、+z軸分別對應的是模型的右、上和前向。須要注意的是模型空間模型座標空間中的x軸、y軸、z軸和天然方向上的對應不必定是上述這種關係,但因爲unity使用的是這樣的約定,所以本文將使用這種方式。 模型空間的原點和座標軸一般由美術人員在建模軟件裏肯定好的。當導入unity後,咱們能夠在頂點着色器中訪問到模型的頂點信息,其中包含了每一個頂點的座標。這些座標都是相對於模型空間中的原點(一般位於模型的重心)定義的。 當咱們把妞妞放到場景中時,就有一個模型座標空間時刻跟隨者它。妞妞鼻子的位置能夠經過訪問頂點屬性獲得。假設這個位置是(0,2,4),因爲頂點變換中每每包含了平移變換,因次須要把其擴展到齊次座標系下,獲得頂點座標是(0,2,4,1),以下圖所示。 在這裏插入圖片描述spa

3 世界空間

世界空間(world space)是一個特殊的座標系,由於它創建了咱們所關心的最大的空間。一些讀者可能會指出,空間能夠是無限大的,怎麼會有最大這一說呢?這裏的最大指的是一個宏觀的概念,也就是說它是咱們所關心的最外層的座標空間。以咱們的農場遊戲爲例,在這個遊戲裏世界空間指的就是農場,咱們不關心這個農場是在什麼地方,在這個虛擬的遊戲世界裏,農場就是最大的空間概念。 世界空間能夠被用於描述絕對位置(較真的讀者可能會再次提醒我,沒有絕對的位置。沒錯,但我相信讀者能夠明白這裏絕對的意思)。在本文中,絕對位置指的就是在世界座標系中的位置。一般,咱們會把世界空間的原點放置在遊戲空間的中心。 在unity中,世界空間一樣使用了左手座標系。但它的x軸、y軸、z軸是固定不變的。在unity中,咱們能夠經過調整Transform組件中的Position屬性來改變模型的位置,這裏的位置指的是相對於這個Transform的父節點(parent)的模型座標空間中的原點定義的。若是一個Transform沒有任何父節點,那麼這個位置就是在世界座標系中的位置,以下圖所示 在這裏插入圖片描述 咱們能夠想象還有一個虛擬的根模型,這個根模型的模型空間就是世界空間,全部的遊戲對象都屬於這個根模型。一樣,Transform中的Rotation和Scale也是一樣的道理。 頂點變換的第一步,就是將頂點座標從模型空間變換到世界空間中。這個變換一般叫作模型變換(model transform)。 如今咱們對妞妞的鼻子進行模型變換。爲此,咱們首先須要知道妞妞在世界座標系中進行了哪些變換,這能夠經過面板中的Transform組件來獲得相關的變換信息,以下圖所示: 在這裏插入圖片描述 根據Transform組件上的信息,咱們知道在世界空間中,妞妞進行了(2,2,2)的縮放,又進行了(0,150,0)的旋轉以及(5,0,25)的平移。注意這裏的變換順序是不能互換的,即先進行縮放,再進行旋轉,最後是平移。據此,咱們能夠構建出模型變換的變換矩陣: 在這裏插入圖片描述 如今咱們能夠用它來對妞妞的鼻子進行模型變換了: 在這裏插入圖片描述 也就是說,在世界空間下,妞妞的鼻子位置是(9,4,18.072)。注意,這裏的浮點數都是近似值,這裏近似到小數點後3位。實際的數值和unity採用的浮點值精度有關。3d

4. 觀察空間

觀察空間(view space)也被稱爲攝像機空間(camera space)。觀察空間能夠認爲是模型空間的一個特例——在全部的模型中有一個很是特殊的模型,即攝像機(雖然一般來講攝像機自己是不可見的),它的模型空間值得咱們單獨拿出來討論,也就是觀察空間。 攝像機決定了咱們渲染遊戲所使用的視角。在觀察空間中,攝像機位於原點,一樣其座標軸的選擇能夠是任意的,但因爲咱們是以unity爲主,而unity中觀察空間的座標軸選擇是:+x指向右方,+y指向上方,而正z軸指向攝像機的後方。在這裏,讀者可能會以爲奇怪,咱們以前討論的模型空間和世界空間中+z軸指的都是物體的前方,爲何這裏不同了呢?這是由於Unity在模型空間和世界空間選用的都是左手座標系,而觀察空間中使用的是右手座標系。這是複合OpenGL的傳統的,在這樣的觀察空間中,攝像機的正前方指向的是-z軸方向。 這種左右手座標系之間改變不多會對咱們在unity中的編程產生影響,由於unity爲咱們作了許多渲染底層的工做。可是若是讀者須要調用相似Camera.cameraToWorldMatrix、Camera.WorldToCameraMatrix等接口自行計算某模型在觀察空間中的位置上,就要當心這樣的差別。 最後提醒讀者的一點是,觀察空間和屏幕空間是不一樣的。觀察空間是一個三維空間,而屏幕空間是一個二維空間。從觀察空間到屏幕空間須要一個操做,那就是投影(projection)。咱們後面會講到。 頂點變換的第二步,就是將頂點座標從世界空間變換到觀察空間中。這個變換叫觀察變換(view transform) 回到咱們的農場遊戲。如今咱們須要把妞妞的鼻子從世界空間變換到觀察空間中。爲此咱們須要知道世界座標系下攝像機的變換信息。這一樣能夠經過攝像機面板中的Transform組件獲得,以下圖所示: 在這裏插入圖片描述 爲了獲得頂點在觀察空間中的位置,咱們能夠有兩種方法。一種方法是計算觀察空間的三個座標軸在世界空間下的表示,而後根據之前所講的方法,構建出從觀察空間變換到世界空間的變換矩陣,再對該矩陣求逆來獲得從世界空間變換到觀察空間的變換矩陣。咱們還可使用另外一種方法,即想象平移整個觀察空間,讓攝像機原點位於世界座標原點,座標軸與世界空間中的座標軸重合便可。這兩種方法獲得的變換矩陣都是同樣的,不一樣的是咱們的思考方式。 這裏咱們使用第二種方法。由Transform組件能夠知道,攝像機在世界空間中的變換是先按(30,0,0)進行旋轉,而後按(0,10,-10)進行了平移。那麼爲了把攝像機從新移回到初始狀態(這裏指攝像機原點位於世界座標原點、座標軸與世界空間中的座標軸重合),咱們須要進行逆向變換,即先按(0,-10,10)進行平移,以便攝像機回到原點,再按(-30,0,0)進行旋轉,以便讓座標軸重合。所以變換矩陣就是: 在這裏插入圖片描述 可是,因爲觀察空間使用的是右手座標系,所以須要對z份量進行取反操做。咱們能夠經過乘以另外一個特殊矩陣來獲得最終的觀察變換矩陣: 在這裏插入圖片描述 如今咱們能夠用它來對妞妞的鼻子進行頂點變換了: 在這裏插入圖片描述 這樣,咱們就獲得了觀察空間中妞妞鼻子的位置——(9,8.84,-27.31)。orm

5 裁剪空間

頂點接下來要從觀察空間轉換到裁剪空間(clip space,也被稱爲齊次裁剪空間)中,這個用於變換的矩陣叫作裁剪矩陣(clip matrix),也被稱爲投影矩陣(projection matrix)。 裁剪矩陣的目標是可以方便的對渲染圖元進行裁剪:徹底位於這塊空間內部的圖元會被保留,徹底位於這塊空間外部的圖元將會被剔除,而與這塊空間相交的圖元將會被裁剪。那麼這塊空間是如何決定的呢?答案是由視椎體(view frustum)來決定。 視椎體是指空間中的一片區域,這塊區域決定了攝像機能夠看到的空間。視椎體由6個平面包圍而成,這些平面也被稱爲裁剪平面(clip planes)。視椎體有兩種類型,這涉及到兩種投影類型:一種是正交投影(orthographic projection),一種是透視投影(perspective projection)。下圖顯示了從同一位置、同一角度渲染同一個場景的兩種攝像機的渲染結果。 在這裏插入圖片描述 從圖中能夠發現,在透視投影中,地板上的平行線不會保持平行,離攝像機越近網格越大,離攝像機越遠網格越小。而在正交投影中,全部的網格大小都同樣,並且平行線會一直保持平行。能夠注意到,透視投影模擬了人眼看世界的方式,而正交投影則徹底保留了物體的距離和角度。所以,在追求真實感的3D遊戲中咱們每每會使用透視投影,而在一些2D遊戲或渲染小地圖等其餘HUD元素時,咱們會使用正交投影。 在視椎體的6塊裁剪平面中,有兩塊裁剪平面比較特殊,它們分別被稱爲近裁剪平面(near clip plane)和遠裁剪平面(far clip plane)。它們決定了攝像機能夠看到的深度的範圍。正交投影和透視投影的視椎體以下圖所示。 在這裏插入圖片描述 從上圖能夠看出,透視投影的視椎體是一個金字塔形,側面的4個裁剪平面會在攝像機處相交。它更符合視椎體這個詞語。正交投影的視椎體是一個長方體。前面講到,咱們但願根據視椎體圍成的區域對圖元進行裁剪,可是若是直接使用視椎體定義的空間來進行裁剪,那麼不一樣的視椎體就須要不一樣的處理過程,並且對於透視投影的視椎體來講,想要判斷一個頂點是否處於一個金字塔內部是比較麻煩的。所以咱們想要一種更加通用、方便和整潔的方式來進行裁剪的工做,這種方式就是經過一個投影矩陣把頂點轉換到一個裁剪空間中。 投影矩陣有兩個目的: (1)首先爲投影作準備。這是個迷惑點,雖然投影矩陣的名稱包含了投影2字,但它並無進行真正的投影工做,而是在爲投影作準備。真正的投影發生在後面的齊次除法(homogeneous division)過程當中。而經歷過投影矩陣的變換後,頂點w的份量會具備特殊的意義。 讀者:投影究竟是什麼意思呢? 咱們:能夠理解成是一個空間的降維,例如從四維空間投影到三維空間中。而投影矩陣實際上並不會真的進行這個步驟,它會爲真正的投影作準備工做。真正的投影會在屏幕映射時發生,經過齊次除法來獲得二維座標。 (2)其次是對x、y、z份量進行縮放。咱們上面講過直接使用視椎體的6個裁剪平面進行裁剪會比較麻煩。而通過投影矩陣的縮放後,咱們能夠直接使用w份量做爲一個範圍值,若是x、y、z份量都位於這個範圍內,就說明該頂點位於裁剪空間內。 在裁剪空間以前,雖然咱們使用了齊次座標來表示點和矢量,但它們的第四個份量都是固定的:點的w份量是1,方向矢量的w份量是0。通過投影矩陣變換後,咱們會賦予齊次座標的第四個座標更加豐富的含義。下面,咱們來看一下兩種投影類型使用的投影矩陣具體是什麼。對象

5.1 透視投影

視椎體的意義在於定義了場景中的一塊三維空間。全部位於這塊空間內的物體都會被渲染,不然就會被剔除或裁減。咱們已經知道這塊區域由6個裁剪平面定義,那麼這6個裁剪平面又是怎麼決定的呢?在Unity中,它們由Camera組件中的參數和Game視圖的橫縱比共同決定,如圖所示。 在這裏插入圖片描述 由圖能夠看出,咱們能夠經過Camera組件的Field of View(簡稱FOV)屬性來改變視椎體豎直方向的張開角度,而Clipping Planes中的Near和Far參數能夠控制視椎體的近裁剪平面和遠裁剪平面距離攝像機的遠近。這樣咱們能夠求出視椎體近裁剪平面和遠裁剪平面的高度,也就是: 在這裏插入圖片描述 如今咱們還缺少橫向信息。這能夠經過攝像機的橫縱比獲得。在Unity中,一個攝像機的橫縱比由Game視圖的橫縱比和Viewport Rect中的W和H屬性共同決定(實際上,Unity容許咱們在腳本里經過Camera.aspect進行更改,但這裏不作討論)。假設,當前攝像機的橫縱比爲Aspect,咱們定義: 在這裏插入圖片描述 如今,咱們能夠根據已知的Near、Far、FOV和Aspect的值來決定透視投影的投影矩陣。以下: 在這裏插入圖片描述 上面公式的推導部分能夠參見本章的擴展閱讀部分。須要注意的是,這裏的投影矩陣是創建在Unity對座標系的假定上面,也就是說,咱們針對的是觀察空間爲右手座標系,使用列矩陣在矩陣右側進行相乘,且變換z份量範圍將在[-w,w]之間的狀況。而在相似DirectX這樣的圖形接口中,它們但願變換後z份量範圍將在[0,w]之間,所以就須要對上面的透視矩陣進行更改。這不在本書的討論範圍內。 而一個頂點和上述矩陣相乘後,能夠由觀察空間變換到裁剪空間中,結果以下: 在這裏插入圖片描述 從結果能夠看出,這個投影矩陣本質就是對x、y和z份量進行了不一樣程度的縮放(固然,z份量還作了一個平移),縮放的目的是爲了方便裁剪。咱們能夠注意到,此時頂點的w份量再也不是1,而是原先z份量的取反結果。如今,咱們就能夠按以下不等式來判斷一個變換後的頂點會否位於視錐體內,若是一個頂點在視錐體內,那麼它變換後的座標必須知足: 在這裏插入圖片描述 任何不知足上述條件的圖元都須要被剔除或裁減。下圖顯示了通過上述投影矩陣後,視椎體的變化: 在這裏插入圖片描述 從上圖還能夠注意到,裁剪矩陣會改變空間的旋向性:空間從右手座標系變換到了座標系,這意味着離攝像機越遠,z值將越大。blog

5.2 正交投影

首先,咱們仍是看一下正交投影中的6個裁剪平面是如何定義的。和透視投影相似,在Unity中,它們也是由Camera組件中的參數和Game視圖的縱橫比共同決定,以下圖所示: 在這裏插入圖片描述 正交投影的視椎體是一個長方體,所以計算上相比透視投影來講更簡單。由圖能夠看出,咱們能夠經過Camera組件的Size屬性來改變視椎體豎直方向高度的一半,而Clipping Planes中的Near和Far參數能夠控制視椎體的近裁剪平面和遠裁剪平面距離攝像機的遠近。這樣,咱們能夠求出視椎體近裁剪平面和遠裁剪平面的高度,也就是: 在這裏插入圖片描述 如今咱們還缺少橫向的信息。一樣咱們能夠經過攝像機的縱橫比獲得。假設,當前攝像機的縱橫比爲Aspect,那麼: 在這裏插入圖片描述 如今,咱們能夠根據已知的Near、Far、Size和Aspect的值來肯定正交投影的裁剪矩陣。以下: 在這裏插入圖片描述 一樣,這裏的投影矩陣是創建在Unity對座標系的假定上面的。 一個頂點和上述投影矩陣相乘後的結果以下: 在這裏插入圖片描述 注意到,和透視投影不一樣的是,使用正交投影的投影矩陣對頂點進行變換後,其w份量仍然爲1。本質是由於投影矩陣的最後一行不一樣,透視投影的投影矩陣的最後一行是[0,0,-1,0],而正交投影的投影矩陣的最後一行是[0,0,0,1]。這樣的選擇是有緣由的,是爲了齊次除法作準備,在後面咱們會講到。 判斷一個變換後的頂點是否位於視椎體內使用的等式和透視投影中同樣,這種通用性也是爲何要使用投影矩陣的緣由之一。下圖顯示了通過上述投影矩陣後,正交投影的視椎體變化。 在這裏插入圖片描述 一樣,裁剪矩陣改變了空間的旋向性。能夠注意到,通過正交投影變換後的頂點實際已經位於一個立方體內了。 但願看到這裏讀者的腦殼尚未爆炸,如今咱們繼續來看咱們的農場遊戲。在上面,咱們已經幫妞妞肯定了它的鼻子在觀察空間中的位置——(9,8。84,-27.31)。如今,咱們要計算它在裁剪空間中的位置。 首先,咱們須要知道農場遊戲中使用的攝像機類型。因爲農場遊戲是一個3D遊戲,所以這裏咱們使用了透視攝像機。攝像機參數和Game視圖的縱橫好比圖所示: 在這裏插入圖片描述 據此,咱們能夠知道透視投影的參數:FOV爲60度,Near爲5,Far爲40,Aspect爲4/3=1.33。那麼對應的投影矩陣是: 在這裏插入圖片描述 而後,咱們用這個投影矩陣來吧妞妞的鼻子從觀察空間轉換到裁剪空間中。以下 在這裏插入圖片描述 這樣,咱們就求出了妞妞的鼻子在裁剪空間中的位置——(11.691,15.311,23.692,27.31)。接下來Unity會判斷妞妞的鼻子是否須要裁剪。經過比較獲得,妞妞的鼻子知足下面的不等式: 在這裏插入圖片描述 由此,咱們能夠判斷,妞妞的鼻子位於視椎體內,不須要被裁減。接口

6 屏幕空間

通過投影矩陣的變換後,咱們能夠進行裁剪操做。當完成了全部的裁剪操做後,就須要進行真正的投影了,也就是說咱們須要把視椎體投影到屏幕空間(screen space)中。通過這一步變換,咱們會獲得真正的像素位置,而不是虛擬的三維座標。 屏幕空間是一個二維空間,所以咱們必須把頂點從裁剪空間投影到屏幕空間中,來生成對應的2D座標。這個過程能夠理解成有兩個步驟。 首先,咱們須要進行齊次除法(homogeneous division),也被稱爲透視除法(perspective division)。雖然這個步驟聽起來很陌生,但實際上它很是簡單,就是用齊次座標的w份量去除以x,y,z份量。在OpenGL中,咱們把這一步獲得的座標叫作歸一化的設備座標(Normalized Device Coordinates,NDC)。通過這一步,咱們能夠把座標從齊次裁剪座標空間轉換到NDC中。通過透視投影變換後的裁剪空間,通過齊次除法後會變到一個立方體內。按照OpenGl傳統,這個立方體的x,y,z份量的範圍都是[-1,1]。可是在DirectX這樣的API中,z的份量範圍會是[0,1]。而Unity選擇了OpenGL這樣的裁剪空間,以下圖所示: 在這裏插入圖片描述 而對於正交投影來講,它的裁剪空間實際上已是一個立方體了,並且因爲通過正交投影矩陣變換後的頂點的w份量是1,所以齊次除法並不會對頂點的x,y,z座標產生影響,以下圖所示: 在這裏插入圖片描述 通過齊次除法後,透視投影和正交投影的視椎體都變換到相同的立方體內,如今咱們能夠根據變換後的x和y座標來映射輸出窗口的對應像素座標。 在Unity中,屏幕空間左下角的像素座標是(0,0),右上角的像素座標是(pixelWidth,pixelHeight),因爲當前x和y座標都是[-1,1],所以,這個映射的過程就是一個縮放的過程。 齊次除法和屏幕映射的過程可使用下面的公式來總結: 在這裏插入圖片描述 上面的式子對x和y份量都進行了處理,那麼z份量呢?一般,z份量會被用於深度緩衝。一個傳統的方式是把clipz/clipx的值直接存進深度緩存中,但這並非必須的。一般驅動產商會根據硬件來選擇最好的存儲格式。此時clipw也並不會被拋棄,雖然它完成了它的主要工做——在齊次除法中做爲分母來獲得NDC,但它仍然會在後續的一些工做中起到重要做用,例如進行透校訂插值。 在Unity中,從裁剪空間到屏幕空間的轉換是由底層幫咱們完成的,咱們的頂點着色器只須要把頂點轉換到裁剪空間便可。 在上一步中,咱們知道了裁剪空間中妞妞鼻子的位置——(11.691,15.311,23.692,27.31)。如今咱們終於能夠肯定妞妞鼻子在屏幕上像素的位置。假設,當前屏幕的寬度爲400,高度爲300。首先咱們要進行齊次除法,把裁剪的座標投影到NDC中,而後再映射到屏幕空間中。這個過程以下: 在這裏插入圖片描述 由此,咱們知道了妞妞鼻子在屏幕上的位置——(285.617,234.096)遊戲

7. 總結

以上就是一個頂點如何從模型空間變換到屏幕座標的過程,下圖總結了這些空間和用於變換的矩陣: 在這裏插入圖片描述 頂點着色器最基本的任務就是把頂點座標從模型空間轉換到裁剪空間中。這對應了圖中前三個頂點變換過程。而在片元着色器中,咱們一般也能夠獲得該片元在屏幕空間的像素位置。咱們會在之後的講解中看到如何獲得這些像素的位置。 在Unity中,座標系的旋向性也隨着變換髮生了改變。下圖總結了Unity各個空間使用的座標系旋向性。 在這裏插入圖片描述 從圖中能夠發現,只有在觀察空間中Unity使用了右手座標系。 須要注意的是,這裏給出的僅僅是一些重要的座標空間。還有一些空間在實際開發中也會遇到,例如切線空間(tangent space)。切線空間一般用於法線映射,在後面咱們會說到。圖片

相關文章
相關標籤/搜索