1. OpenGL坐標系統概述
?
OpenGL希望每次頂點著色后,我們的可見頂點都為標準化設備坐標(Normalized Device Coordinate,NDC)。也就是說每個頂點的x,y,z都應該在?1到1之間,超出這個范圍的頂點將是不可見的。
通常情況下我們會自己設定一個坐標范圍,之后再在頂點著色器中將這些坐標變換為表轉化設備坐標。然后這些標化設備坐標傳入光柵器(Rasterizer),將它們變換為屏幕上的二維坐標和像素。
將坐標變換為標準化設備坐標,接著再轉化為屏幕坐標的過程通常是分步進行的,也就是類似于流水線那樣子。在流水線中,物體的頂點在最終轉化為屏幕坐標之前還會被變換到多個坐標系統(Coordinate System)。將物體的坐標變換到幾個過渡坐標系(Intermediate Coordinate System)的優點在于,在這些特定的坐標系統中,一些操作或運算更加方便和容易,這一點很快就會變得很明顯。對我們來說比較重要的總共有5個不同的坐標系統
- 局部空間(Local Space,或者稱為物體空間(Object Space))
- 世界空間(World Space)
- 觀察空間(View Space,或者稱為視覺空間(Eye Space))
- 裁剪空間(Clip Space)
- 屏幕空間(Screen Space)
這就是一個頂點在最終被轉化為片段之前需要經歷的所有不同狀態。為了將坐標從一個坐標系變換到另一個坐標系,我們需要用到幾個變換矩陣,最重要的幾個分別是模型(Model)、觀察(View)、投影(Projection)三個矩陣。
物體頂點的起始坐標再局部空間(Local Space),這里稱它為局部坐標(Local Coordinate),它在之后會變成世界坐標(world Coordinate),觀測坐標(View Coordinate),裁剪坐標(Clip Coordinate),并最后以屏幕坐標(Screen Corrdinate)的形式結束。
3D場景轉換為最終的2D形式渲染到屏幕的大致流程:
局部坐標系使用模型變換轉換到世界坐標系,世界坐標系經過視變換轉換到照相機坐標系后,需要進行投影變換,將相機坐標系轉換到裁剪坐標系,再經過透視除法后,變換到規范化設備坐標系(NDC),最后進行視口變換后,3D坐標才變換到屏幕上的2D坐標。
有以下幾點需要注意:
- 局部坐標是對象相對于原點的坐標,也是物體的起始坐標。
- 下一步將局部坐標轉化為世界空間坐標,世界空間坐標是一個處于更大空間范圍內的。這些坐標相對于世界的全局原點,它們會和其他物體一起相對于世界原點進行擺放。
- 接下來將世界坐標轉化為觀測坐標,使得每個坐標都是從攝像機或者說觀察者角度進行觀察的。
- 坐標到達觀測空間后,我們需要將其投影到裁剪坐標。裁剪坐標會被處理從?1.0到1.0范圍內,并判斷哪些點將會出現在屏幕上。
- 最后,我們將裁剪坐標變換為屏幕坐標,我們將使用一個叫做視口變換(Viewport Transform)的過程。視口變換將位于?1.0到1.0范圍的坐標變換到由glViewport()函數所定義的坐標范圍內。最后變換出來的坐標將會送到光柵器,將其轉化為片段。
在OpenGL中,模型變換、視變換,投影變換,這些變換可以由用戶根據需要自行指定,這些內容在頂點著色器中完成。而透視除法、視口變換,這兩個步驟是OpenGL自動執行的,在頂點著色器處理后的階段完成。
你可能已經大致了解了每個坐標空間的作用。我們之所以將頂點變換到各個不同的空間的原因是有些操作在特定的坐標系統中才有意義且更方便。例如,當需要對物體進行修改的時候,在局部空間中來操作會更說得通;如果要對一個物體做出一個相對于其它物體位置的操作時,在世界坐標系中來做這個才更說得通,等等。
2. 各種坐標系
?
2D笛卡爾坐標
2D笛卡爾坐標是一個直角坐標系,它由一個X軸和一個Y軸組成,X軸Y軸在同一個平面上互相垂直且有公共原點。在二維繪圖中,最為常用的坐標系統是笛卡爾坐標系統。
通常,兩條數軸分別置于水平位置與垂直位置,取向右與向上的方向分別為兩條數軸的正方向。水平的數軸叫做x軸或橫軸,垂直的數軸叫做y軸或縱軸,x軸y軸統稱為坐標軸,它們的公共原點稱為坐標系的原點。關于正負方向問題,我們可以根據實際需求自己定義。
?
3D笛卡爾坐標系
三維笛卡爾坐標系是在二維笛卡爾坐標系的基礎上根據右手定則增加第三維坐標(即Z軸)而形成的。Z軸代表了深度分量,它同時垂直于X,Y軸,是一條從屏幕中心朝向讀者的直線。
為了更好的觀察,我們將Y軸向做旋轉,把X軸向下和后渲染.否則Z軸將直面我們,我們無法具體觀察到Z軸存在.我們可以用3個坐標(X,Y,Z)來指定三維空間中的任意一個位置.
?
右手系
右手系(right-hand system)是在空間中規定直角坐標系的方法之一。此坐標系中x軸,y軸和z軸的正方向是如下規定的:把右手放在原點的位置,使大姆指,食指和中指互成直角,把大姆指指向x軸的正方向,食指指向y軸的正方向時,中指所指的方向就是z軸的正方向。
也可以按如下方法確定右手(左手)坐標系:如果當右手(左手)的大拇指指向第一個坐標軸(x軸)的正向,而其余手指以第二個軸(y軸)繞第一軸轉動的方向握緊,就與第三個軸(z軸)重合,就稱此坐標系為右手(左手)坐標系。
- OpenGL坐標系(物體、世界、照相機坐標系)屬于右手坐標系
- 設備坐標系使用的是左手坐標系
世界坐標系
它是一個特殊的三維坐標系,它建立了描述其他坐標系所需要的參考系。也就是說,可以用世界坐標系去描述其他所有坐標系或者物體的位置。所以有很多人定義世界坐標系是“我們所關心的最大坐標系”,通過這個坐標系可以去描述和刻畫所有想刻畫的實體。世界坐標系又稱全局坐標系或者宇宙坐標系。
世界坐標系是系統的絕對坐標系,始終是固定不變的。如果我們將所有的物體導入到世界坐標系中,默認它們有可能會全擠在世界的原點(0, 0, 0)上,但是我們布置場景的時候一般希望物體分散到世界的各處,那么這時需要通過模型矩陣(Model Matrix)來實現。將物體變換到世界空間中,相當于就是建立物體的頂點相對于世界原點的關系。
?
物體坐標系
物體坐標系與特定的物體關聯,每個物體都有自己特定的坐標系。不同物體之間的坐標系相互獨立,可以相同,可以不同,沒有任何聯系。同時,物體坐標系與物體綁定,綁定的意思就是物體發生移動或者旋轉,物體坐標系發生相同的平移或者旋轉,物體坐標系和物體之間運動同步,相互綁定。
物體坐標系是以物體本身而言,比如,我先向你發指令,“向前走一步”,是向您的物體坐標體系指令。我并不知道你會往哪個絕對的方向移動。比如說,當你開車時,有人會說向左轉,有人說向東。但是,向左轉是物體坐標系的概念,而向東則是世界坐標系中的。
在某種情況下,我們可以理解物體坐標系為模型坐標系。因為模型頂點的坐標都是在模型坐標系中描述的。
?
攝像機/照相機坐標系
攝像機坐標系。攝像機坐標系是和觀察者密切相關的坐標系。攝像機坐標系和屏幕坐標系相似,只不過一個處在三維環境中,一個處在二維環境中。攝像機坐標系也是一種特殊的物體坐標系。一般的攝像機坐標系都是x軸向右,y軸向上,z軸向里。
觀察空間是將世界空間坐標轉化為用戶視野前方的坐標而產生的結果。因此觀察空間就是從攝像機的視角所觀察到的空間。而這通常是由一系列的位移和旋轉的組合來完成,平移/旋轉場景從而使得特定的對象被變換到攝像機的前方。這些組合在一起的變換通常存儲在一個觀察矩陣(View Matrix)里,它被用來將世界坐標變換到觀察空間。
?
慣性坐標系
這種坐標系是在世界坐標系和物體坐標系之間的一種坐標系。慣性坐標系的原點和物體坐標系的原點重合,但慣性坐標系的坐標軸是和世界坐標系的坐標軸平行的。為什么要引入慣性坐標系呢?三維系統在運行時,不可避免的引入外部工具如3DMax做得模型,每個模型又有很多三角面,點組成。這些三角面和點得坐標都是物體坐標。所以系統中會由大量的從物體坐標向世界坐標的轉換。有了慣性坐標系,可以簡化他們之間的轉換過程。從物體坐標系轉換到慣性坐標系只需要旋轉操作。從慣性坐標系到世界坐標系只需要平移操作。
?
齊次坐標系
齊次坐標就是將一個原本是n維的向量用一個n+1維向量來表示。在齊次坐標下,旋轉/平移/仿射變換/透視變換都可以用同一個矩陣實現,這在傳統笛卡爾坐標系下是不可能的。
4D向量是由3D坐標(x,y,z)和齊次坐標w組成,寫作(x,y,z,w)。
在3D世界中為什么需要3D的齊次坐標呢?簡單地說明一下,在一維空間中的一條線段上取一點x,然后我們想轉移x的位置,那我們應該是x'=x+k,但我們能使用一維的矩陣來表示這變換嗎?不能,因為此時一維的矩陣只能讓x點伸縮。但如果變成了一維的齊次空間[k 1]就很容易地做到。同樣地,在二維空間中,某一圖形如果不使用二維的齊次坐標,則只能旋轉和伸縮,確不能平移。
因此,我們在3D坐標中使用齊次坐標,是為了物體在矩陣變換中,除了伸縮旋轉,還能夠平移,如下運算:
齊次坐標w是什么意義: 設w=1,此時相當于我們把3D的坐標平移搬去了w=1的平面上,4D空間的點投影到w=1平面上,齊次坐標映射的3D坐標是(x/w,y/w,z/w),也就是(x,y,z)。(x,y,z)在齊次空間中有無數多個點與之對應。所有點的形式是(kx,ky,kz,k),其軌跡是通過齊次空間原點的“直線”(其實每個點相當于3D的坐標世界)。
當w=0時,有很大的意義,可解釋為無窮遠的“點”,其意義是描述方向。這也是平移變換的開關,當w=0時,
此時不能平移變換了。這個現象是非常有用的,因為有些向量代表“位置”,應當平移,而有些向量代表“方向”,如表面的法向量,不應該平移。從幾何意義上說,能將第一類數據當作"點",第二類數據當作"向量"。可以通過設置w的值來控制向量的意義。
?
3. 坐標系相關
?
裁剪空間過程
在一個頂點著色器運行的最后,OpenGL期望所有的坐標都能落在一個特定的范圍內,且任何在這個范圍之外的點都應該被裁剪掉(Clipped)。被裁剪掉的坐標就會被忽略,所以剩下的坐標就將變為屏幕上可見的片段。這也就是裁剪空間(Clip Space)名字的由來。因為將所有可見的坐標都指定在?1.0到1.0的范圍內不是很直觀,所以我們會指定自己的坐標集(Coordinate Set)并將它變換回標準化設備坐標系,就像OpenGL期望的那樣。
為了將頂點坐標從觀察變換到裁剪空間,我們需要定義一個投影矩陣(Projection Matrix),它指定了一個范圍的坐標,比如在每個維度上的?1000到1000。投影矩陣接著會將在這個指定的范圍內的坐標變換為標準化設備坐標的范圍(?1.0,1.0)。所有在范圍外的坐標不會被映射到在?1.0到1.0的范圍之間,所以會被裁剪掉。在上面這個投影矩陣所指定的范圍內,坐標(1250,500,750)將是不可見的,這是由于它的??坐標超出了范圍,它被轉化為一個大于1.0的標準化設備坐標,所以被裁剪掉了。
如果只是圖元(Primitive),例如三角形,的一部分超出了裁剪體積(Clipping Volume),則OpenGL會重新構建這個三角形為一個或多個三角形讓其能夠適合這個裁剪范圍。
由投影矩陣創建的觀察箱(Viewing Box)被稱為平截頭體(Frustum),每個出現在平截頭體范圍內的坐標都會最終出現在用戶的屏幕上。將特定范圍內的坐標轉化到標準化設備坐標系的過程(而且它很容易被映射到2D觀察空間坐標)被稱之為投影(Projection),因為使用投影矩陣能將3D坐標投影(Project)到很容易映射到2D的標準化設備坐標系中。
一旦所有頂點被變換到裁剪空間,最終的操作——透視除法(Perspective Division)將會執行,在這個過程中我們將位置向量的x,y,z分量分別除以向量的齊次w分量;透視除法是將4D裁剪空間坐標變換為3D標準化設備坐標的過程。這一步會在每一個頂點著色器運行的最后被自動執行。在這一階段之后,最終的坐標將會被映射到屏幕空間中(使用glViewport中的設定),并被變換成片段。
將觀察坐標變換為裁剪坐標的投影矩陣可以為兩種不同的形式,每種形式都定義了不同的平截頭體。我們可以選擇創建一個正射投影矩陣(Orthographic Projection Matrix)或一個透視投影矩陣(Perspective Projection Matrix)。
?
正射投影
正射投影矩陣定義了一個類似立方體的平截頭箱,它定義了一個裁剪空間,在這空間之外的頂點都會被裁剪掉。創建一個正射投影矩陣需要指定可見平截頭體的寬、高和長度。在使用正射投影矩陣變換至裁剪空間之后處于這個平截頭體內的所有坐標將不會被裁剪掉。它的平截頭體看起來像一個容器:
上面的平截頭體定義了可見的坐標,它由由寬、高、近(Near)平面和遠(Far)平面所指定。任何出現在近平面之前或遠平面之后的坐標都會被裁剪掉。正射平截頭體直接將平截頭體內部的所有坐標映射為標準化設備坐標,因為每個向量的??分量都沒有進行改變;如果??分量等于1.0,透視除法則不會改變這個坐標。
要創建一個正射投影矩陣,我們可以使用glOrtho函數:
glOrtho (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar);
前兩個參數指定了平截頭體的左右坐標,第三和第四參數指定了平截頭體的底部和頂部。通過這四個參數我們定義了近平面和遠平面的大小,然后第五和第六個參數則定義了近平面和遠平面的距離。這個投影矩陣會將處于這些??,??,??值范圍內的坐標變換為標準化設備坐標。
正射投影矩陣直接將坐標映射到2D平面中,即你的屏幕,但實際上一個直接的投影矩陣會產生不真實的結果,因為這個投影沒有將透視(Perspective)考慮進去,使用正投影所以實際大小相同的物體在屏幕上都具有相同的大小,所以從屏幕上看不出來觀測點到物體的距離有多遠。正投影比較適合平面圖形/2D圖形渲染時使用。
?
透視投影
如果需要渲染結果看起來真實,就需要透視投影矩陣來解決這個問題。
在生活中,我們會注意到離你越遠的東西看起來越小。這個奇怪的效果稱之為透視(Perspective)。透視的效果在我們看一條無限長的高速公路或鐵路時尤其明顯,正如下面圖片顯示的那樣:
正如你看到的那樣,由于透視,這兩條線在很遠的地方看起來會相交。這正是透視投影想要模仿的效果,它是使用透視投影矩陣來完成的。這個投影矩陣將給定的平截頭體范圍映射到裁剪空間,除此之外還修改了每個頂點坐標的w值,從而使得離觀察者越遠的頂點坐標w分量越大。被變換到裁剪空間的坐標都會在-w到w的范圍之間(任何大于這個范圍的坐標都會被裁剪掉)。OpenGL要求所有可見的坐標都落在-1.0到1.0范圍內,作為頂點著色器最后的輸出,因此,一旦坐標在裁剪空間內之后,透視除法就會被應用到裁剪空間坐標上,就是每個點的分量除以w進行轉換:
out = (x/w, y/w, x/w);
頂點坐標的每個分量都會除以它的??分量,距離觀察者越遠頂點坐標就會越小。這是也是??分量非常重要的另一個原因,它能夠幫助我們進行透視投影。最后的結果坐標就是處于標準化設備空間中的。
創建一個透視投影矩陣:
SetPerspective(float fFov, float fAspect, float fNear, float fFar)
同樣,SetPerspective所做的其實就是創建了一個定義了可視空間的大平截頭體,任何在這個平截頭體以外的東西最后都不會出現在裁剪空間體積內,并且將會受到裁剪。一個透視平截頭體可以被看作一個不均勻形狀的箱子,在這個箱子內部的每個坐標都會被映射到裁剪空間上的一個點。下面是一張透視平截頭體的圖片:
它的第一個參數定義了fFov的值,它表示的是視野(Field of View),其值通常是垂直方向上的視野角度大小,它設置了觀察空間的大小。如果想要一個真實的觀察效果,它的值通常設置為45.0f,但想要一個末日風格的結果你可以將其設置一個更大的值。第二個參數設置了寬高比,由視口的寬除以高所得。第三和第四個參數設置了平截頭體的近和遠平面。我們通常設置近距離為0.1f,而遠距離設為100.0f。所有在近平面和遠平面內且處于平截頭體內的頂點都會被渲染。
當使用正射投影時,每一個頂點坐標都會直接映射到裁剪空間中而不經過任何精細的透視除法(它仍然會進行透視除法,只是w分量沒有被改變(它保持為1),因此沒有起作用)。因為正射投影沒有使用透視,遠處的物體不會顯得更小,所以產生奇怪的視覺效果。由于這個原因,正射投影主要用于二維渲染以及一些建筑或工程的程序,在這些場景中我們更希望頂點不會被透視所干擾。某些如 Blender 等進行三維建模的軟件有時在建模時也會使用正射投影,因為它在各個維度下都更準確地描繪了每個物體。
使用透視投影的話,遠處的頂點看起來比較小,而在正射投影中每個頂點距離觀察者的距離都是一樣的。下面給出兩種投影方式的對比:
視口
窗口是以像素為單位度量。 在開始在窗口中繪制點,線,形狀之前,必須告訴OpenGL如何把指定坐標映射為屏幕坐標.
坐標系統必須從邏輯笛卡爾坐標映射到物理屏幕像素坐標. 這個映射是通過一種叫做視口(viewPort)的設置來指定.> 在我們代碼中,我們會通過glViewPort函數來實現視口的設計. 視口就是窗口內部用于繪制裁剪區域的客戶區域.
glViewport (GLint x, GLint y, GLsizei width, GLsizei height);
視口就是窗口中用來顯示圖形的一塊矩形區域,它可以和窗口等大,也可以比窗口大或者小。只有繪制在視口區域中的圖形才能被顯示,如果圖形有一部分超出了視口區域,那么那一部分是看不到的。
?
裁剪區域:就是視口矩形區域的最小最大x坐標(left, right)和最小最大y坐標(bottom, top),而不是窗口的最小最大x坐標和y坐標。通過glOrtho()函數設置,這個函數還需指定最近最遠z坐標,形成一個立體的裁剪區域。
glOrtho (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar);
?