OpenGL希望在每次頂點著色器運行后,我們可見的所有頂點都為標準化設備坐標(Normalized Device Coordinate, NDC)。也就是說,每個頂點的x,y,z坐標都應該在-1.0到1.0之間,超出這個坐標范圍的頂點都將不可見。我們通常會自己設定一個坐標的范圍,之后再在頂點著色器中將這些坐標變換為標準化設備坐標。然后將這些標準化設備坐標傳入光柵器(Rasterizer),將它們變換為屏幕上的二維坐標或像素。
將坐標變換為標準化設備坐標,接著再轉化為屏幕坐標的過程通常是分步進行的,也就是類似于流水線那樣子。在流水線中,物體的頂點在最終轉化為屏幕坐標之前還會被變換到多個坐標系統(tǒng)(Coordinate System)。將物體的坐標變換到幾個過渡坐標系(Intermediate Coordinate System)的優(yōu)點在于,在這些特定的坐標系統(tǒng)中,一些操作或運算更加方便和容易,這一點很快就會變得很明顯。對我們來說比較重要的總共有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)槭澜缱鴺?World Coordinate),觀察坐標(View Coordinate),裁剪坐標(Clip Coordinate),并最后以屏幕坐標(Screen Coordinate)的形式結束。下面的這張圖展示了整個流程以及各個變換過程做了什么:

1. 局部坐標是對象相對于局部原點的坐標,也是物體起始的坐標。
2. 下一步是將局部坐標變換為世界空間坐標,世界空間坐標是處于一個更大的空間范圍的。這些坐標相對于世界的全局原點,它們會和其它物體一起相對于世界的原點進行擺放。
3. 接下來我們將世界坐標變換為觀察空間坐標,使得每個坐標都是從攝像機或者說觀察者的角度進行觀察的。
4. 坐標到達觀察空間之后,我們需要將其投影到裁剪坐標。裁剪坐標會被處理至-1.0到1.0的范圍內,并判斷哪些頂點將會出現(xiàn)在屏幕上。
5. 最后,我們將裁剪坐標變換為屏幕坐標,我們將使用一個叫做視口變換(Viewport Transform)的過程。視口變換將位于-1.0到1.0范圍的坐標變換到由glViewport函數(shù)所定義的坐標范圍內。最后變換出來的坐標將會送到光柵器,將其轉化為片段。
你可能已經大致了解了每個坐標空間的作用。我們之所以將頂點變換到各個不同的空間的原因是有些操作在特定的坐標系統(tǒng)中才有意義且更方便。例如,當需要對物體進行修改的時候,在局部空間中來操作會更說得通;如果要對一個物體做出一個相對于其它物體位置的操作時,在世界坐標系中來做這個才更說得通,等等。如果我們愿意,我們也可以定義一個直接從局部空間變換到裁剪空間的變換矩陣,但那樣會失去很多靈活性。
接下來我們將要更仔細地討論各個坐標系統(tǒng)。
局部空間
局部空間是指物體所在的坐標空間,即對象最開始所在的地方。想象你在一個建模軟件(比如說Blender)中創(chuàng)建了一個立方體。你創(chuàng)建的立方體的原點有可能位于(0, 0, 0),即便它有可能最后在程序中處于完全不同的位置。甚至有可能你創(chuàng)建的所有模型都以(0, 0, 0)為初始位置(譯注:然而它們會最終出現(xiàn)在世界的不同位置)。所以,你的模型的所有頂點都是在局部空間中:它們相對于你的物體來說都是局部的。
我們一直使用的那個箱子的頂點是被設定在-0.5到0.5的坐標范圍中,(0, 0)是它的原點。這些都是局部坐標。
世界空間
如果我們將我們所有的物體導入到程序當中,它們有可能會全擠在世界的原點(0, 0, 0)上,這并不是我們想要的結果。我們想為每一個物體定義一個位置,從而能在更大的世界當中放置它們。世界空間中的坐標正如其名:是指頂點相對于(游戲)世界的坐標。如果你希望將物體分散在世界上擺放(特別是非常真實的那樣),這就是你希望物體變換到的空間。物體的坐標將會從局部變換到世界空間;該變換是由模型矩陣(Model Matrix)實現(xiàn)的。
模型矩陣是一種變換矩陣,它能通過對物體進行位移、縮放、旋轉來將它置于它本應該在的位置或朝向。你可以將它想像為變換一個房子,你需要先將它縮?。ㄋ诰植靠臻g中太大了),并將其位移至郊區(qū)的一個小鎮(zhèn),然后在y軸上往左旋轉一點以搭配附近的房子。你也可以把上一節(jié)將箱子到處擺放在場景中用的那個矩陣大致看作一個模型矩陣;我們將箱子的局部坐標變換到場景/世界中的不同位置。
觀察空間
觀察空間經常被人們稱之OpenGL的攝像機(Camera)(所以有時也稱為攝像機空間(Camera Space)或視覺空間(Eye Space))。觀察空間是將世界空間坐標轉化為用戶視野前方的坐標而產生的結果。因此觀察空間就是從攝像機的視角所觀察到的空間。而這通常是由一系列的位移和旋轉的組合來完成,平移/旋轉場景從而使得特定的對象被變換到攝像機的前方。這些組合在一起的變換通常存儲在一個觀察矩陣(View Matrix)里,它被用來將世界坐標變換到觀察空間。在下一節(jié)中我們將深入討論如何創(chuàng)建一個這樣的觀察矩陣來模擬一個攝像機。
裁剪空間
在一個頂點著色器運行的最后,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),因為使用投影矩陣能將3D坐標投影(Project)到很容易映射到2D的標準化設備坐標系中。
一旦所有頂點被變換到裁剪空間,最終的操作——透視除法(Perspective Division)將會執(zhí)行,在這個過程中我們將位置向量的x,y,z分量分別除以向量的齊次w分量;透視除法是將4D裁剪空間坐標變換為3D標準化設備坐標的過程。這一步會在每一個頂點著色器運行的最后被自動執(zhí)行。
在這一階段之后,最終的坐標將會被映射到屏幕空間中(使用glViewport中的設定),并被變換成片段。
將觀察坐標變換為裁剪坐標的投影矩陣可以為兩種不同的形式,每種形式都定義了不同的平截頭體。我們可以選擇創(chuàng)建一個正射投影矩陣(Orthographic Projection Matrix)或一個透視投影矩陣(Perspective Projection Matrix)。
資料來源:https://learnopengl-cn.github.io/01%20Getting%20started/08%20Coordinate%20Systems/