視口
窗口是以像素為單位度量. 在開始在窗口中繪制點,線,形狀之前,必須告訴OpenGL 如何把指定坐標映射為屏幕坐標.
坐標系統(tǒng)必須從邏輯笛卡爾坐標映射到物理屏幕像素坐標. 這個映射是通過一種叫做視口(viewPort)的設(shè)置來指定.
在顯示器的屏幕窗口上定義一個對齊的矩形的視口,OpenGL會自動建立世界窗口和視口的變換(包括縮放和平移)。當(dāng)世界窗口中所有對象都被繪制時,對象在世界窗口中的部分會被自動地映射到視口中————換句話說,被映射到屏幕坐標中,即像素在顯示器上的坐標。
視口就是窗口內(nèi)部用于繪制裁剪區(qū)域的客戶區(qū)域
// 視口的設(shè)定通過glViewport()函數(shù),它的原型是:
void glViewport(GLint x,GLint y,GLint width,GLint ehignt);
//它設(shè)置窗口的左下角,以及寬度和高度。
左右手坐標系

OpenGL坐標系中(物體、世界、照相機坐標系)都屬于右手坐標系
而規(guī)范化設(shè)備坐標系使用的是左手坐標系
坐標系
OpenGL希望在每次頂點著色器運行后,我們可見的所有頂點都為標準化設(shè)備坐標(Normalized Device Coordinate, NDC)。每個頂點的x,y,z坐標都應(yīng)該在?1.0到1.0之間,超出這個坐標范圍的頂點將是不可見的。
通常情況下我們會自己設(shè)定一個坐標范圍,之后再在頂點著色器中將這些坐標變換為標準化設(shè)備坐標。然后這些標準化設(shè)備坐標傳入光柵器(Rasterizer),將它們變換為屏幕上的二維坐標或像素。
將坐標變換為標準化設(shè)備坐標,接著再轉(zhuǎn)化為屏幕坐標的過程通常是分步進行的,也就是類似于流水線那樣子。在流水線中,物體的頂點在最終轉(zhuǎn)化為屏幕坐標之前還會被變換到多個坐標系統(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)
這就是一個頂點在最終被轉(zhuǎn)化為片段之前需要經(jīng)歷的所有不同狀態(tài)。
為了將坐標從一個坐標系變換到另一個坐標系,我們需要用到幾個變換矩陣,最重要的幾個分別是模型(Model)、觀察(View)、投影(Projection)三個矩陣。物體頂點坐標的起始局部空間(Local Space),這里稱它為局部坐標(Local Coordinate),它在之后會變成世界坐標(world Coordinate),觀測坐標(View Coordinate),裁剪坐標(Clip Coordinate),并最后以屏幕坐標(Screen Corrdinate)的形式結(jié)束。下面的這張圖展示了整個流程以及各個變換過程做了什么:

1.局部坐標是對象相對于局部原點的坐標,也是物體起始的坐標。
2.下一步是將局部坐標變換為世界空間坐標,世界空間坐標是處于一個更大的空間范圍的。這些坐標相對于世界的全局原點,它們會和其它物體一起相對于世界的原點進行擺放。
3.接下來我們將世界坐標變換為觀察空間坐標,使得每個坐標都是從攝像機或者說觀察者的角度進行觀察的。
4.坐標到達觀察空間之后,我們需要將其投影到裁剪坐標。裁剪坐標會被處理至-1.0到1.0的范圍內(nèi),并判斷哪些頂點將會出現(xiàn)在屏幕上。
5.最后,我們將裁剪坐標變換為屏幕坐標,我們將使用一個叫做視口變換(Viewport Transform)的過程。視口變換將位于-1.0到1.0范圍的坐標變換到由glViewport函數(shù)所定義的坐標范圍內(nèi)。最后變換出來的坐標將會送到光柵器,將其轉(zhuǎn)化為片段。
我們之所以將頂點變換到各個不同的空間的原因是有些操作在特定的坐標系統(tǒng)中才有意義且更方便。例如,當(dāng)需要對物體進行修改的時候,在局部空間中來操作會更說得通;如果要對一個物體做出一個相對于其它物體位置的操作時,在世界坐標系中來做這個才更說得通,等等。如果我們愿意,我們也可以定義一個直接從局部空間變換到裁剪空間的變換矩陣,但那樣會失去很多靈活性。
結(jié)合一個實際例子來理解一下
想想在現(xiàn)實世界中,我們怎樣能看到一樣?xùn)|西,比如說就是我們常用的電腦?首先,電腦會在工廠中叮叮當(dāng)當(dāng)?shù)匮b配起來,然后,通過飛機火車汽車之類的東西,將它送到我們手中。我們拿到之后,要把包裝拆掉,然后放到一個合適的地方,這樣我們才能看到。
讓我們一步一步拆解這個過程,看看我們要顯示一個3D的物體是有多么復(fù)雜!
局部空間(Local Space)/ 物體空間(Object Space)
局部空間是指物體所在的坐標空間,即對象最開始所在的地方。
電腦在工廠中制造的時候,工人需要去考慮這顆螺絲需要裝到東經(jīng)多少度,北緯多少度的位置上嗎?當(dāng)然不可能,他只需要考慮需要裝到電腦的什么位置!這個就是局部空間。在局部空間中,物體位于空間的原點,所有的調(diào)整都是基于物體的相對位置去調(diào)整的。
世界空間 (World Space)
如果我們將我們所有的物體導(dǎo)入到程序當(dāng)中,它們有可能會全擠在世界的原點(0, 0, 0)上,這并不是我們想要的結(jié)果。我們想為每一個物體定義一個位置,從而能在更大的世界當(dāng)中放置它們。世界空間中的坐標正如其名:是指頂點相對于(游戲)世界的坐標。
電腦裝配好之后,自然是要賣出去才有價值。這時候,就需要把它運到專賣店里,或者直接快遞到買家手里。于是,它被裝到火車上,從生產(chǎn)地(A)送到了銷售點(B),衛(wèi)星定位起始點位于東經(jīng)xx度,北緯xx度。這就是世界空間。物體首先要有一個初始位置,然后才能從一個位置移動到另一個位置。
在OpenGL中,我們使用模型矩陣(Model Matrix)將物體放到世界空間中的某個位置上。它能通過對物體進行位移、縮放、旋轉(zhuǎn)來將它置于它本應(yīng)該在的位置或朝向。
這時,我的手機響了,快遞員告訴我電腦已經(jīng)到了,讓我簽收。但是,我沒看到?。坑谑俏揖推鸫踩ツ每爝f了……到了樓下,環(huán)顧四周,發(fā)現(xiàn)快遞員正好在我左前方40米的距離,于是我轉(zhuǎn)個彎就直直地朝他走過去。我終于看到我的電腦了!
看上去非常流暢的過程,在OpenGL中就需要分成兩個操作:1、人眼的視線轉(zhuǎn)換到正對-z軸的方向,并將物體轉(zhuǎn)換到以人眼為原點的位置上。2、物體必須在人眼的視野之內(nèi)。這就是觀察空間和裁剪空間的概念??赡艿谝徊胶茈y察覺,我們從OpenGL的角度來解釋一下。
觀察空間 (View Space) / 視覺空間(Eye Space)
觀察空間經(jīng)常被人們稱之OpenGL的攝像機(Camera)(所以有時也稱為攝像機空間(Camera Space)或視覺空間(Eye Space))。觀察空間是以攝像機為位置為原點,將世界空間坐標轉(zhuǎn)化為用戶視野前方的坐標而產(chǎn)生的結(jié)果。因此觀察空間就是從攝像機的視角所觀察到的空間。而這通常是由一系列的位移和旋轉(zhuǎn)的組合來完成,平移/旋轉(zhuǎn)場景從而使得特定的對象被變換到攝像機的前方。這些組合在一起的變換通常存儲在一個觀察矩陣(View Matrix)里,
裁剪空間 (Clip Space)
攝像機有朝向,也有拍攝的視野范圍,所有在視野范圍之外的東西都看不到,都被剔除了。
在每個頂點著色器運行的最后,OpenGL希望所有的坐標都能落在一個特定的范圍內(nèi),所有超出范圍的點都應(yīng)該被裁剪掉,被裁剪掉的坐標就會被忽略,剩下的坐標才會進入片元著色階段然后顯示到你的屏幕上。這就是裁剪空間名字的由來。
因為將所有可見的坐標都指定在-1.0到1.0的范圍內(nèi)不是很直觀,所以我們會指定自己的坐標集(Coordinate Set)并將它變換回標準化設(shè)備坐標系,就像OpenGL期望的那樣。
將頂點坐標從觀察空間轉(zhuǎn)換到裁剪空間的操作叫做投影變換(perspective projection),我們需要定義一投影矩陣(Projection Matrix),它指定了一個范圍的坐標,比如在每個維度上的-1000到1000。投影矩陣接著會將在這個指定的范圍內(nèi)的坐標變換為標準化設(shè)備坐標的范圍(-1.0, 1.0)。所有在范圍外的坐標不會被映射到在-1.0到1.0的范圍之間,所以會被裁剪掉。在上面這個投影矩陣所指定的范圍內(nèi),坐標(1250, 500, 750)將是不可見的,這是由于它的x坐標超出了范圍,它被轉(zhuǎn)化為一個大于1.0的標準化設(shè)備坐標,所以被裁剪掉了。
還是從現(xiàn)實世界中看到東西的角度去分析。
人眼在看東西時,左右寬度上有一定的范圍,上下高度上也有一定的范圍。放到OpenGL中,就是攝像機的左右和上下方向上都有一定的視野角度(FOV),只有在這個角度范圍內(nèi)的東西,才可能被看到。

從上面的圖中可以看出,h和w就確定了攝像機上下左右可以看到的范圍大小。通常我們會設(shè)置上下左右的視野都是90度。為了方便計算(這點非常重要!想把所有的物體都渲染出來,世界上所有的計算機的運算能力加起來都不夠,所以,不要顯示的就堅決剔除。),我們也會設(shè)置一個近裁剪面和一個遠裁剪面。比近裁剪面更近的物體被剔除,比遠裁剪面更遠的物體被剔除。我們需要把兩個裁剪面之間的所有物體都映射到投影平面上(投影平面可以在裁剪面和遠裁剪面之間,圖上只是一種情況),其他的物體都被剔除!
由投影矩陣創(chuàng)建的觀察箱(Viewing Box)被稱為平截頭體(Frustum),每個出現(xiàn)在平截頭體范圍內(nèi)的坐標都會最終出現(xiàn)在用戶的屏幕上。將特定范圍內(nèi)的坐標轉(zhuǎn)化到標準化設(shè)備坐標系的過程(而且它很容易被映射到2D觀察空間坐標)被稱之為投影(Projection),因為使用投影矩陣能將3D坐標投影(Project)到很容易映射到2D的標準化設(shè)備坐標系中。
一旦所有頂點被變換到裁剪空間,最終的操作——透視除法(Perspective Division)將會執(zhí)行,在這個過程中我們將位置向量的x,y,z分量分別除以向量的齊次w分量;透視除法是將4D裁剪空間坐標變換為3D標準化設(shè)備坐標的過程。這一步會在每一個頂點著色器運行的最后被自動執(zhí)行。
在這一階段之后,最終的坐標將會被映射到屏幕空間中(使用glViewport中的設(shè)定),并被變換成片段。
將觀察坐標變換為裁剪坐標的投影矩陣可以為兩種不同的形式,每種形式都定義了不同的平截頭體。我們可以選擇創(chuàng)建一個正射投影矩陣(Orthographic Projection Matrix)或一個透視投影矩陣(Perspective Projection Matrix)。
視口空間 (Screen Space)
視口空間,可以簡單地理解成應(yīng)用窗口。投影平面上的東西和窗口上的像素通過一一對應(yīng)的方式映射到窗口,在窗口上顯示!
這一步由OpenGL完成,我們不管。
投影方式
不管我們覺得自己的眼睛看到的三維立體圖像多么真實.屏幕上像素實際上只有二維的.
那么OpenGL 是如何將笛卡爾坐標系映射成可以在屏幕上顯示的二維坐標的?
在這里需要用到投影.我們需要指定投影空間,指定在窗口顯示的視景體(Viewing Volume).并指定如何對它進行變換.
2D+透視 = 3D
正投影(Orthographics Projection)或平行投影
正射投影矩陣定義了一個類似立方體的平截頭箱,它定義了一個裁剪空間,在這空間之外的頂點都會被裁剪掉。創(chuàng)建一個正射投影矩陣需要指定可見平截頭體的寬、高和長度。在使用正射投影矩陣變換至裁剪空間之后處于這個平截頭體內(nèi)的所有坐標將不會被裁剪掉。它的平截頭體看起來像一個容器:

上面的平截頭體定義了可見的坐標,它由由寬、高、近(Near)平面和遠(Far)平面所指定。任何出現(xiàn)在近平面之前或遠平面之后的坐標都會被裁剪掉。正射平截頭體直接將平截頭體內(nèi)部的所有坐標映射為標準化設(shè)備坐標,因為每個向量的w分量都沒有進行改變;如果w分量等于1.0,透視除法則不會改變這個坐標。
要創(chuàng)建一個正射投影矩陣,我們可以使用GLM的內(nèi)置函數(shù)glm::ortho:
glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f);
前兩個參數(shù)指定了平截頭體的左右坐標,第三和第四參數(shù)指定了平截頭體的底部和頂部。通過這四個參數(shù)我們定義了近平面和遠平面的大小,然后第五和第六個參數(shù)則定義了近平面和遠平面的距離。這個投影矩陣會將處于這些x,y,z值范圍內(nèi)的坐標變換為標準化設(shè)備坐標。
正射投影矩陣直接將坐標映射到2D平面中,即你的屏幕,但實際上一個直接的投影矩陣會產(chǎn)生不真實的結(jié)果,因為這個投影沒有將透視(Perspective)考慮進去。所以我們需要透視投影矩陣來解決這個問題。
透視投影(Perspective Projection).
透視投影矩陣將給定的平截頭體范圍映射到裁剪空間,除此之外還修改了每個頂點坐標的w值,從而使得離觀察者越遠的頂點坐標w分量越大。被變換到裁剪空間的坐標都會在-w到w的范圍之間(任何大于這個范圍的坐標都會被裁剪掉)。OpenGL要求所有可見的坐標都落在-1.0到1.0范圍內(nèi),作為頂點著色器最后的輸出,因此,一旦坐標在裁剪空間內(nèi)之后,透視除法就會被應(yīng)用到裁剪空間坐標上:

頂點坐標的每個分量都會除以它的w分量,距離觀察者越遠頂點坐標就會越小。這是也是w分量非常重要的另一個原因,它能夠幫助我們進行透視投影。最后的結(jié)果坐標就是處于標準化設(shè)備空間中的。
在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)在裁剪空間體積內(nèi),并且將會受到裁剪。一個透視平截頭體可以被看作一個不均勻形狀的箱子,在這個箱子內(nèi)部的每個坐標都會被映射到裁剪空間上的一個點。下面是一張透視平截頭體的圖片:

它的第一個參數(shù)定義了fov的值,它表示的是視野(Field of View),并且設(shè)置了觀察空間的大小。如果想要一個真實的觀察效果,它的值通常設(shè)置為45.0f,但想要一個末日風(fēng)格的結(jié)果你可以將其設(shè)置一個更大的值。第二個參數(shù)設(shè)置了寬高比,由視口的寬除以高所得。第三和第四個參數(shù)設(shè)置了平截頭體的近和遠平面。我們通常設(shè)置近距離為0.1f,而遠距離設(shè)為100.0f。所有在近平面和遠平面內(nèi)且處于平截頭體內(nèi)的頂點都會被渲染。
當(dāng)你把透視矩陣的 near 值設(shè)置太大時(如10.0f),OpenGL會將靠近攝像機的坐標(在0.0f和10.0f之間)都裁剪掉,這會導(dǎo)致一個你在游戲中很熟悉的視覺效果:在太過靠近一個物體的時候你的視線會直接穿過去。
當(dāng)使用正射投影時,每一個頂點坐標都會直接映射到裁剪空間中而不經(jīng)過任何精細的透視除法(它仍然會進行透視除法,只是w分量沒有被改變(它保持為1),因此沒有起作用)。因為正射投影沒有使用透視,遠處的物體不會顯得更小,所以產(chǎn)生奇怪的視覺效果。由于這個原因,正射投影主要用于二維渲染以及一些建筑或工程的程序,在這些場景中我們更希望頂點不會被透視所干擾。某些如 Blender 等進行三維建模的軟件有時在建模時也會使用正射投影,因為它在各個維度下都更準確地描繪了每個物體。

將坐標系統(tǒng)組合在一起
我們?yōu)樯鲜龅拿恳粋€步驟都創(chuàng)建了一個變換矩陣:模型矩陣、觀察矩陣和投影矩陣。一個頂點坐標將會根據(jù)以下過程被變換到裁剪坐標:

注意矩陣運算的順序是相反的(記住我們需要從右往左閱讀矩陣的乘法)。最后的頂點應(yīng)該被賦值到頂點著色器中的gl_Position,OpenGL將會自動進行透視除法和裁剪。
然后呢?
頂點著色器的輸出要求所有的頂點都在裁剪空間內(nèi),這正是我們剛才使用變換矩陣所做的。OpenGL然后對裁剪坐標執(zhí)行透視除法從而將它們變換到標準化設(shè)備坐標。OpenGL會使用glViewPort內(nèi)部的參數(shù)來將標準化設(shè)備坐標映射到屏幕坐標,每個坐標都關(guān)聯(lián)了一個屏幕上的點(在我們的例子中是一個800x600的屏幕)。這個過程稱為視口變換。
3D圖形學(xué)中的常見的坐標系
1.世界坐標系
世界坐標系是系統(tǒng)的絕對坐標系,在沒有建立用戶坐標系之前畫面上所有的點的坐標都可以在該坐標系的原點來確定各自的位置.世界坐標系始終是固定不變的。
它是一個特殊的坐標系,它建立了描述其他坐標系所需要的參考系。也就是說,可以用世界坐標系去描述其他所有坐標系或者物體的位置。所以有很多人定義世界坐標系是“我們所關(guān)心的最大坐標系”,通過這個坐標系可以去描述和刻畫所有想刻畫的實體。
世界坐標系又稱全局坐標系或者宇宙坐標系。
2.物體坐標系
物體坐標系與特定的物體關(guān)聯(lián),每個物體都有自己特定的坐標系。不同物體之間的坐標系相互獨立,可以相同,可以不同,沒有任何聯(lián)系。同時,物體坐標系與物體綁定,綁定的意思就是物體發(fā)生移動或者旋轉(zhuǎn),物體坐標系發(fā)生相同的平移或者旋轉(zhuǎn),物體坐標系和物體之間運動同步,相互綁定。

這個立方體的物體坐標系如圖所示,不論該立方體位于世界坐標系的任何位置,處于什么角度,物體坐標系與物體都是綁定在一起。
物體坐標系又稱模型坐標系
3.攝像機(照相機)坐標系
在坐標系的范疇里,攝像機坐標系和照相機坐標系都是一樣的意義。照相機坐標系是和觀察者密切相關(guān)的坐標系。照相機坐標系和屏幕坐標系相似,差別在于照相機坐標系處于3D空間中,而屏幕坐標系在2D平面里。

4.慣性坐標系
慣性坐標系是為了簡化世界坐標系到慣性坐標系的轉(zhuǎn)化而產(chǎn)生的。慣性坐標系的原點與物體坐標系的原點重合,慣性坐標系的軸平行于世界坐標系的軸。引入了慣性坐標系之后,物體坐標系轉(zhuǎn)換到慣性坐標系只需旋轉(zhuǎn),從慣性坐標系轉(zhuǎn)換到世界坐標系只需平移。

坐標轉(zhuǎn)換
OpenGL最終的渲染設(shè)備是2D的,我們需要將3D表示的場景轉(zhuǎn)換為最終的2D形式,前面使用模型變換和視變換將物體坐標轉(zhuǎn)換到照相機坐標系后,需要進行投影變換,將坐標從相機—>裁剪坐標系,經(jīng)過透視除法后,變換到規(guī)范化設(shè)備坐標系(NDC),最后進行視口變換后,3D坐標才變換到屏幕上的2D坐標,這個過程如下圖所示

在上面的圖中,注意,OpenGL只定義了裁剪坐標系、規(guī)范化設(shè)備坐標系和屏幕坐標系,而局部坐標系(模型坐標系)、世界坐標系和照相機坐標系都是為了方便用戶設(shè)計而自定義的坐標系,它們的關(guān)系如下圖所示

圖中左邊的過程包括模型變換、視變換,投影變換,這些變換可以由用戶根據(jù)需要自行指定,這些內(nèi)容在頂點著色器中完成;
圖中右邊的兩個步驟,包括透視除法、視口變換,這兩個步驟是OpenGL自動執(zhí)行的,在頂點著色器處理后的階段完成。