OpenGL 座標系統(Perspective)

一、座標系統概述

本文類容見LearnOpenGL CN。直接copy過來留個存檔。

OpenGL希望每次頂點着色後,我們的可見頂點都爲標準化設備座標(Normalized Device Coordinate,NDC)。也就是說每個頂點的z,y,zz,y,z都應該在−1−1到11之間,超出這個範圍的頂點將是不可見的。通常情況下我們會自己設定一個座標範圍,之後再在頂點着色器中將這些座標變換爲錶轉化設備座標。然後這些標化設備座標傳入光柵器(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)的形式結束。下面這張圖闡釋了流程以及各個變換在做什麼

有以下幾點需要注意

  • 局部座標是對象相對於原點的座標,也是物體的起始座標。
  • 下一步將局部座標轉化爲世界空間座標,世界空間座標是一個處於更大空間範圍內的。這些座標相對於世界的全局原點,它們會和其他物體一起相對於世界原點進行擺放。
  • 接下來將世界座標轉化爲觀測座標,使得每個座標都是從攝像機或者說觀察者角度進行觀察的。
  • 座標到達觀測空間後,我們需要將其投影到裁剪座標。裁剪座標會被處理到−1.0−1.0到1.01.0範圍內,並判斷哪些點將會出現在屏幕上。
  • 最後,我們將裁剪座標變換爲屏幕座標,我們將使用一個叫做視口變換(Viewport Transform)的過程。視口變換將位於−1.0−1.0到1.01.0範圍的座標變換到由glViewport函數所定義的座標範圍內。最後變換出來的座標將會送到光柵器,將其轉化爲片段。

你可能已經大致瞭解了每個座標空間的作用。我們之所以將頂點變換到各個不同的空間的原因是有些操作在特定的座標系統中才有意義且更方便。例如,當需要對物體進行修改的時候,在局部空間中來操作會更說得通;如果要對一個物體做出一個相對於其它物體位置的操作時,在世界座標系中來做這個才更說得通,等等。如果我們願意,我們也可以定義一個直接從局部空間變換到裁剪空間的變換矩陣,但那樣會失去很多靈活性。

二、座標空間

2.1、局部空間

局部空間是指物體所在的座標空間,即對象最開始所在的地方。想象你在一個建模軟件(比如說Blender)中創建了一個立方體。你創建的立方體的原點有可能位於(0, 0, 0),即便它有可能最後在程序中處於完全不同的位置。甚至有可能你創建的所有模型都以(0, 0, 0)爲初始位置(譯註:然而它們會最終出現在世界的不同位置)。所以,你的模型的所有頂點都是在局部空間中:它們相對於你的物體來說都是局部的。

2.2、世界空間

如果我們將我們所有的物體導入到程序當中,它們有可能會全擠在世界的原點(0, 0, 0)上,這並不是我們想要的結果。我們想爲每一個物體定義一個位置,從而能在更大的世界當中放置它們。世界空間中的座標正如其名:是指頂點相對於世界的座標。如果你希望將物體分散在世界上擺放(特別是非常真實的那樣),這就是你希望物體變換到的空間。物體的座標將會從局部變換到世界空間;該變換是由模型矩陣(Model Matrix)實現的。

模型矩陣是一種變換矩陣,它能通過對物體進行位移、縮放、旋轉來將它置於它本應該在的位置或朝向。你可以將它想像爲變換一個房子,你需要先將它縮小(它在局部空間中太大了),並將其位移至郊區的一個小鎮,然後在y軸上往左旋轉一點以搭配附近的房子。你也可以把上一節將箱子到處擺放在場景中用的那個矩陣大致看作一個模型矩陣;我們將箱子的局部座標變換到場景/世界中的不同位置。

2.3 、觀測空間

觀察空間經常被人們稱之OpenGL的攝像機(Camera)(所以有時也稱爲攝像機空間(Camera Space)或視覺空間(Eye Space))。觀察空間是將世界空間座標轉化爲用戶視野前方的座標而產生的結果。因此觀察空間就是從攝像機的視角所觀察到的空間。而這通常是由一系列的位移和旋轉的組合來完成,平移/旋轉場景從而使得特定的對象被變換到攝像機的前方。這些組合在一起的變換通常存儲在一個觀察矩陣(View Matrix)裏,它被用來將世界座標變換到觀察空間。在下一節中我們將深入討論如何創建一個這樣的觀察矩陣來模擬一個攝像機。

2.4 、裁剪空間

2.4.1 過程

在一個頂點着色器運行的最後,OpenGL期望所有的座標都能落在一個特定的範圍內,且任何在這個範圍之外的點都應該被裁剪掉(Clipped)。被裁剪掉的座標就會被忽略,所以剩下的座標就將變爲屏幕上可見的片段。這也就是裁剪空間(Clip Space)名字的由來。

因爲將所有可見的座標都指定在−1.0−1.0到1.01.0的範圍內不是很直觀,所以我們會指定自己的座標集(Coordinate Set)並將它變換回標準化設備座標系,就像OpenGL期望的那樣。

爲了將頂點座標從觀察變換到裁剪空間,我們需要定義一個投影矩陣(Projection Matrix),它指定了一個範圍的座標,比如在每個維度上的−1000−1000到10001000。投影矩陣接着會將在這個指定的範圍內的座標變換爲標準化設備座標的範圍(−1.0,1.0)(−1.0,1.0)。所有在範圍外的座標不會被映射到在−1.0−1.0到1.01.0的範圍之間,所以會被裁剪掉。在上面這個投影矩陣所指定的範圍內,座標(1250,500,750)(1250,500,750)將是不可見的,這是由於它的xx座標超出了範圍,它被轉化爲一個大於1.01.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)。

2.4.2 正射投影

正射投影矩陣定義了一個類似立方體的平截頭箱,它定義了一個裁剪空間,在這空間之外的頂點都會被裁剪掉。創建一個正射投影矩陣需要指定可見平截頭體的寬、高和長度。在使用正射投影矩陣變換至裁剪空間之後處於這個平截頭體內的所有座標將不會被裁剪掉。它的平截頭體看起來像一個容器:![屏幕快照 

上面的平截頭體定義了可見的座標,它由由寬、高、近(Near)平面和遠(Far)平面所指定。任何出現在近平面之前或遠平面之後的座標都會被裁剪掉。正射平截頭體直接將平截頭體內部的所有座標映射爲標準化設備座標,因爲每個向量的ww分量都沒有進行改變;如果ww分量等於1.01.0,透視除法則不會改變這個座標。

要創建一個正射投影矩陣,我們可以使用GLM的內置函數glm::ortho

glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f);

前兩個參數指定了平截頭體的左右座標,第三和第四參數指定了平截頭體的底部和頂部。通過這四個參數我們定義了近平面和遠平面的大小,然後第五和第六個參數則定義了近平面和遠平面的距離。這個投影矩陣會將處於這些x,y,zx,y,z值範圍內的座標變換爲標準化設備座標。

正射投影矩陣直接將座標映射到2D平面中,即你的屏幕,但實際上一個直接的投影矩陣會產生不真實的結果,因爲這個投影沒有將透視(Perspective)考慮進去。所以我們需要透視投影矩陣來解決這個問題。

2.4.3 透視投影

如果你曾經體驗過實際生活給你帶來的景象,你就會注意到離你越遠的東西看起來更小。這個奇怪的效果稱之爲透視(Perspective)。透視的效果在我們看一條無限長的高速公路或鐵路時尤其明顯,正如下面圖片顯示的那樣:

正如你看到的那樣,由於透視,這兩條線在很遠的地方看起來會相交。這正是透視投影想要模仿的效果,它是使用透視投影矩陣來完成的。這個投影矩陣將給定的平截頭體範圍映射到裁剪空間,除此之外還修改了每個頂點座標的w值,從而使得離觀察者越遠的頂點座標w分量越大。被變換到裁剪空間的座標都會在-w到w的範圍之間(任何大於這個範圍的座標都會被裁剪掉)。OpenGL要求所有可見的座標都落在-1.0到1.0範圍內,作爲頂點着色器最後的輸出,因此,一旦座標在裁剪空間內之後,透視除法就會被應用到裁剪空間座標上:

 

out=⎛⎝⎜x/wy/wz/w⎞⎠⎟out=(x/wy/wz/w)

 

頂點座標的每個分量都會除以它的ww分量,距離觀察者越遠頂點座標就會越小。這是也是ww分量非常重要的另一個原因,它能夠幫助我們進行透視投影。最後的結果座標就是處於標準化設備空間中的。在GLM中可以這樣創建一個透視投影矩陣:

glm::mat4 proj = glm::perspective(glm::radians(45.0f), (float)width/(float)height, 0.1f, 100.0f);

同樣,glm::perspective所做的其實就是創建了一個定義了可視空間的大平截頭體,任何在這個平截頭體以外的東西最後都不會出現在裁剪空間體積內,並且將會受到裁剪。一個透視平截頭體可以被看作一個不均勻形狀的箱子,在這個箱子內部的每個座標都會被映射到裁剪空間上的一個點。下面是一張透視平截頭體的圖片

它的第一個參數定義了fov的值,它表示的是視野(Field of View),並且設置了觀察空間的大小。如果想要一個真實的觀察效果,它的值通常設置爲45.0f,但想要一個末日風格的結果你可以將其設置一個更大的值。第二個參數設置了寬高比,由視口的寬除以高所得。第三和第四個參數設置了平截頭體的平面。我們通常設置近距離爲0.1f,而遠距離設爲100.0f。所有在近平面和遠平面內且處於平截頭體內的頂點都會被渲染。

當使用正射投影時,每一個頂點座標都會直接映射到裁剪空間中而不經過任何精細的透視除法(它仍然會進行透視除法,只是w分量沒有被改變(它保持爲1),因此沒有起作用)。因爲正射投影沒有使用透視,遠處的物體不會顯得更小,所以產生奇怪的視覺效果。由於這個原因,正射投影主要用於二維渲染以及一些建築或工程的程序,在這些場景中我們更希望頂點不會被透視所幹擾。某些如 Blender 等進行三維建模的軟件有時在建模時也會使用正射投影,因爲它在各個維度下都更準確地描繪了每個物體。下面你能夠看到在Blender裏面使用兩種投影方式的對比:

你可以看到,使用透視投影的話,遠處的頂點看起來比較小,而在正射投影中每個頂點距離觀察者的距離都是一樣的。

三、將座標系統組合在一起

我們爲上述的每一個步驟都創建了一個變換矩陣:模型矩陣觀察矩陣投影矩陣。一個頂點座標將會根據以下過程被變換到裁剪座標:

 

Vclip=Mpro⋅Mview⋅Mmodel⋅VlocalVclip=Mpro⋅Mview⋅Mmodel⋅Vlocal

 

這一系列的矩陣變換需要從右往左讀。最後的頂點應該被賦值到頂點着色器中的gl_Position,OpenGL將會自動進行透視除法和裁剪。

然後呢?

頂點着色器的輸出要求所有的頂點都在裁剪空間內,這正是我們剛纔使用變換矩陣所做的。OpenGL然後對裁剪座標執行透視除法從而將它們變換到標準化設備座標。OpenGL會使用glViewPort內部的參數來將標準化設備座標映射到屏幕座標,每個座標都關聯了一個屏幕上的點(在我們的例子中是一個800x600的屏幕)。這個過程稱爲視口變換。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章