OpenGL希望在每次頂點著色器運行后笼沥,我們可見的所有頂點都為標準化設備坐標(Normalized Device Coordinate, NDC)贿讹。
我們通常會自己設定一個坐標的范圍姐叁,之后再在頂點著色器中將這些坐標變換為標準化設備坐標孵延。然后將這些標準化設備坐標傳入光柵器(Rasterizer)贷腕,將它們變換為屏幕上的二維坐標或像素惹苗。
在流水線中,物體的頂點在最終轉化為屏幕坐標之前還會被變換到多個坐標系統(tǒng)(Coordinate System)规婆。
對我們來說比較重要的總共有5個不同的坐標系統(tǒng):
- 局部空間(Local Space澜躺,或者稱為物體空間(Object Space))
- 世界空間(World Space)
- 觀察空間(View Space,或者稱為視覺空間(Eye Space))
- 裁剪空間(Clip Space)
- 屏幕空間(Screen Space)
這就是一個頂點在最終被轉化為片段之前需要經歷的所有不同狀態(tài)抒蚜。
概述
為了將坐標從一個坐標系變換到另一個坐標系掘鄙,我們需要用到幾個變換矩陣。
最重要的幾個分別是模型(Model)嗡髓、觀察(View)操漠、投影(Projection)三個矩陣。
我們的頂點坐標起始于局部空間(Local Space)器贩,在這里它稱為局部坐標(Local Coordinate)颅夺,它在之后會變?yōu)?strong>世界坐標(World Coordinate)朋截,觀察坐標(View Coordinate)蛹稍,裁剪坐標(Clip Coordinate),并最后以屏幕坐標(Screen Coordinate)的形式結束部服。下面的這張圖展示了整個流程以及各個變換過程做了什么:
- 局部坐標是對象相對于局部原點的坐標唆姐,也是物體起始的坐標。
- 下一步是將局部坐標變換為世界空間坐標廓八,世界空間坐標是處于一個更大的空間范圍的奉芦。這些坐標相對于世界的全局原點,它們會和其它物體一起相對于世界的原點進行擺放剧蹂。
- 接下來我們將世界坐標變換為觀察空間坐標声功,使得每個坐標都是從攝像機或者說觀察者的角度進行觀察的。
- 坐標到達觀察空間之后宠叼,我們需要將其投影到裁剪坐標先巴。裁剪坐標會被處理至-1.0到1.0的范圍內,并判斷哪些頂點將會出現(xiàn)在屏幕上冒冬。
- 最后伸蚯,我們將裁剪坐標變換為屏幕坐標,我們將使用一個叫做視口變換(Viewport Transform)的過程简烤。視口變換將位于-1.0到1.0范圍的坐標變換到由
glViewport
函數(shù)所定義的坐標范圍內剂邮。最后變換出來的坐標將會送到光柵器,將其轉化為片段横侦。
我們之所以將頂點變換到各個不同的空間的原因是有些操作在特定的坐標系統(tǒng)中才有意義且更方便挥萌。例如绰姻,當需要對物體進行修改的時候,在局部空間中來操作會更說得通引瀑;如果要對一個物體做出一個相對于其它物體位置的操作時龙宏,在世界坐標系中來做這個才更說得通,等等伤疙。如果我們愿意银酗,我們也可以定義一個直接從局部空間變換到裁剪空間的變換矩陣,但那樣會失去很多靈活性徒像。
局部空間
局部空間是指物體所在的坐標空間黍特,即對象最開始所在的地方。
世界空間
世界空間中的坐標正如其名:是指頂點相對于(游戲)世界的坐標锯蛀。
如果你希望將物體分散在世界上擺放(特別是非常真實的那樣)灭衷,這就是你希望物體變換到的空間。物體的坐標將會從局部變換到世界空間旁涤;該變換是由模型矩陣(Model Matrix)實現(xiàn)的翔曲。
模型矩陣是一種變換矩陣,它能通過對物體進行位移劈愚、縮放瞳遍、旋轉來將它置于它本應該在的位置或朝向。
觀察空間
觀察空間經常被人們稱之OpenGL的攝像機(Camera)(所以有時也稱為攝像機空間(Camera Space)或視覺空間(Eye Space))菌羽。
觀察空間是將世界空間坐標轉化為用戶視野前方的坐標而產生的結果掠械。因此觀察空間就是從攝像機的視角所觀察到的空間。
這通常是由一系列的位移和旋轉的組合來完成注祖,平移/旋轉場景從而使得特定的對象被變換到攝像機的前方猾蒂。
這些組合在一起的變換通常存儲在一個觀察矩陣(View Matrix)里,它被用來將世界坐標變換到觀察空間是晨。
裁剪空間
在一個頂點著色器運行的最后肚菠,OpenGL期望所有的坐標都能落在一個特定的范圍內,且任何在這個范圍之外的點都應該被裁剪掉(Clipped)罩缴。被裁剪掉的坐標就會被忽略蚊逢,所以剩下的坐標就將變?yōu)槠聊簧峡梢姷钠巍_@也就是裁剪空間(Clip Space)名字的由來靴庆。
因為將所有可見的坐標都指定在-1.0到1.0的范圍內不是很直觀时捌,所以我們會指定自己的坐標集(Coordinate Set)并將它變換回標準化設備坐標系,就像OpenGL期望的那樣炉抒。
為了將頂點坐標從觀察變換到裁剪空間奢讨,我們需要定義一個投影矩陣(Projection Matrix),它指定了一個范圍的坐標,比如在每個維度上的-1000到1000拿诸。投影矩陣接著會將在這個指定的范圍內的坐標變換為標準化設備坐標的范圍(-1.0, 1.0)扒袖。
所有在范圍外的坐標不會被映射到在-1.0到1.0的范圍之間,所以會被裁剪掉亩码。在上面這個投影矩陣所指定的范圍內季率,坐標(1250, 500, 750)將是不可見的,這是由于它的x坐標超出了范圍描沟,它被轉化為一個大于1.0的標準化設備坐標飒泻,所以被裁剪掉了。
如果只是圖元(Primitive)吏廉,例如三角形泞遗,的一部分超出了裁剪體積(Clipping Volume),則OpenGL會重新構建這個三角形為一個或多個三角形讓其能夠適合這個裁剪范圍席覆。
由投影矩陣創(chuàng)建的觀察箱(Viewing Box)被稱為平截頭體(Frustum)史辙,每個出現(xiàn)在平截頭體范圍內的坐標都會最終出現(xiàn)在用戶的屏幕上。
將特定范圍內的坐標轉化到標準化設備坐標系的過程(而且它很容易被映射到2D觀察空間坐標)被稱之為投影(Projection)
一旦所有頂點被變換到裁剪空間佩伤,最終的操作——透視除法(Perspective Division)將會執(zhí)行聊倔,在這個過程中我們將位置向量的x
,y
生巡,z
分量分別除以向量的齊次w
分量耙蔑;透視除法是將4D裁剪空間坐標變換為3D標準化設備坐標的過程。這一步會在每一個頂點著色器運行的最后被自動執(zhí)行障斋。
在這一階段之后纵潦,最終的坐標將會被映射到屏幕空間中(使用glViewport
中的設定)徐鹤,并被變換成片段垃环。
將觀察坐標變換為裁剪坐標的投影矩陣可以為兩種不同的形式,每種形式都定義了不同的平截頭體返敬。
我們可以選擇創(chuàng)建一個正射投影矩陣(Orthographic Projection Matrix)或一個透視投影矩陣(Perspective Projection Matrix)遂庄。
正射投影
正射投影矩陣定義了一個類似立方體的平截頭箱,它定義了一個裁剪空間劲赠,在這空間之外的頂點都會被裁剪掉涛目。創(chuàng)建一個正射投影矩陣需要指定可見平截頭體的寬、高和長度凛澎。在使用正射投影矩陣變換至裁剪空間之后處于這個平截頭體內的所有坐標將不會被裁剪掉霹肝。它的平截頭體看起來像一個容器:
上面的平截頭體定義了可見的坐標,它由由寬塑煎、高沫换、近(Near)平面和遠(Far)平面所指定。任何出現(xiàn)在近平面之前或遠平面之后的坐標都會被裁剪掉最铁。正射平截頭體直接將平截頭體內部的所有坐標映射為標準化設備坐標讯赏,因為每個向量的w分量都沒有進行改變垮兑;如果w分量等于1.0,透視除法則不會改變這個坐標漱挎。
要創(chuàng)建一個正射投影矩陣系枪,我們可以使用GLM的內置函數(shù)glm::ortho
:
glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f);
前兩個參數(shù)指定了平截頭體的左右坐標,第三和第四參數(shù)指定了平截頭體的底部和頂部磕谅。通過這四個參數(shù)我們定義了近平面和遠平面的大小私爷,然后第五和第六個參數(shù)則定義了近平面和遠平面的距離。這個投影矩陣會將處于這些x膊夹,y当犯,z值范圍內的坐標變換為標準化設備坐標。
正射投影矩陣直接將坐標映射到2D平面中割疾,即你的屏幕嚎卫,但實際上一個直接的投影矩陣會產生不真實的結果,因為這個投影沒有將透視(Perspective)考慮進去宏榕。所以我們需要透視投影矩陣來解決這個問題拓诸。
透視投影
如果你曾經體驗過實際生活給你帶來的景象,你就會注意到離你越遠的東西看起來更小麻昼。這個奇怪的效果稱之為透視(Perspective)奠支。
透視的效果在我們看一條無限長的高速公路或鐵路時尤其明顯,正如下面圖片顯示的那樣:
正如你看到的那樣抚芦,由于透視倍谜,這兩條線在很遠的地方看起來會相交。這正是透視投影想要模仿的效果叉抡,它是使用透視投影矩陣來完成的尔崔。
這個投影矩陣將給定的平截頭體范圍映射到裁剪空間,除此之外還修改了每個頂點坐標的w值褥民,從而使得離觀察者越遠的頂點坐標w分量越大季春。
被變換到裁剪空間的坐標都會在-w到w的范圍之間(任何大于這個范圍的坐標都會被裁剪掉)。OpenGL要求所有可見的坐標都落在-1.0到1.0范圍內消返,作為頂點著色器最后的輸出载弄,因此,一旦坐標在裁剪空間內之后撵颊,透視除法就會被應用到裁剪空間坐標上:
頂點坐標的每個分量都會除以它的w分量宇攻,距離觀察者越遠頂點坐標就會越小。這是也是w分量非常重要的另一個原因倡勇,它能夠幫助我們進行透視投影逞刷。最后的結果坐標就是處于標準化設備空間中的。
在GLM中可以這樣創(chuàng)建一個透視投影矩陣:
glm::mat4 proj = glm::perspective(glm::radians(45.0f), (float)width/(float)height, 0.1f, 100.0f);
同樣,glm::perspective所做的其實就是創(chuàng)建了一個定義了可視空間的大平截頭體亲桥,任何在這個平截頭體以外的東西最后都不會出現(xiàn)在裁剪空間體積內洛心,并且將會受到裁剪。一個透視平截頭體可以被看作一個不均勻形狀的箱子题篷,在這個箱子內部的每個坐標都會被映射到裁剪空間上的一個點词身。下面是一張透視平截頭體的圖片:
它的第一個參數(shù)定義了fov的值,它表示的是視野(Field of View)番枚,并且設置了觀察空間的大小法严。如果想要一個真實的觀察效果,它的值通常設置為45.0f葫笼,但想要一個末日風格的結果你可以將其設置一個更大的值深啤。第二個參數(shù)設置了寬高比,由視口的寬除以高所得路星。第三和第四個參數(shù)設置了平截頭體的近和遠平面溯街。我們通常設置近距離為0.1f,而遠距離設為100.0f洋丐。所有在近平面和遠平面內且處于平截頭體內的頂點都會被渲染呈昔。
把它們都組合到一起
我們?yōu)樯鲜龅拿恳粋€步驟都創(chuàng)建了一個變換矩陣:模型矩陣、觀察矩陣和投影矩陣友绝。
一個頂點坐標將會根據以下過程被變換到裁剪坐標:
注意矩陣運算的順序是相反的(記住我們需要從右往左閱讀矩陣的乘法)堤尾。最后的頂點應該被賦值到頂點著色器中的gl_Position,OpenGL將會自動進行透視除法和裁剪迁客。
然后呢郭宝?
頂點著色器的輸出要求所有的頂點都在裁剪空間內,這正是我們剛才使用變換矩陣所做的掷漱。OpenGL然后對裁剪坐標執(zhí)行透視除法從而將它們變換到標準化設備坐標粘室。OpenGL會使用glViewPort
內部的參數(shù)來將標準化設備坐標映射到屏幕坐標,每個坐標都關聯(lián)了一個屏幕上的點(在我們的例子中是一個800x600的屏幕)切威。這個過程稱為視口變換育特。