OpenGL圖形管線和座標變換



1. OpenGL 渲染管線

OpenGL渲染管線分爲兩大部分,模型觀測變換(ModelView Transformation)投影變換(Projection Transformation)。做個比喻,計算機圖形開發就像我們照相一樣,目的就是把真實的場景在一張照相紙上表現出來。那麼觀測變換的過程就像是我們擺設相機的位置,選擇好要照的物體,擺好物體的造型。而投影變換就像相機把真實的三維場景顯示在相紙上一樣。下面就分別詳細的講一下這兩個過程。

1.1模型觀測變換

讓我們先來弄清楚OpenGL中的渲染管線。管線是一個抽象的概念,之所以稱之爲管線是因爲顯卡在處理數據的時候是按照一個固定的順序來的,而且嚴格按照這個順序。就像水從一根管子的一端流到另一端,這個順序是不能打破的。先來看看下面的圖1:


    圖1 OPENGL渲染管線                                 

圖中顯示了OpenGL圖形管線的主要部分,也是我們在進行圖形編程的時候常常要用到的部分。一個頂點數據從圖的左上角(MC)進入管線,最後從圖的右下角(DC)輸出MC是Model Coordinate的簡寫,表示模型座標DCDevice Coordinate的簡寫,表示設備座標。 當然DC有很多了,什麼顯示器,打印機等等。這裏DC我們就理解成常說的屏幕座標好了。MC當然就是3D座標了(注意:我說的3D座標,而不是世界坐 標),這個3D座標就是模型座標,也說成本地座標(相對於世界座標)。MC要經過模型變換(Modeling Transformation)才變換到世界座標,圖2:      

 
圖2 世界座標系和模型座標系

變換到世界座標WC(World Coordinate)說簡單點就是如何用世界座標系來表示本地座標系中的座標。爲了講得更清楚一些,這裏舉個2D的例子。如圖3:   


 圖3 世界座標系和模型座標系的計算

圖中紅色座標系是世界座標系WC,綠色的是模型座標系MC。現在有一個頂點,在模型座標系中的座標爲(1,1),現在要把這個模型座標轉換到世界座標中來表示。從圖中可以看出,點(1,1)在世界座標系中的座標爲(3,4),現在我們來通過計算得到我們希望的結果。首先我們要把模型座標系MC在世界座標系中表示出來,使用齊次座標(Homogeneous Coordinate )可以表示爲矩陣(注意,本教程中使用的矩陣都是以列向量組成):

其中,矩陣的第一列爲MC中x軸在WC中的向量表示第二列爲MC中y軸WC中的向量表示第三列爲MC中的原點在WC中的座標。對齊次座標系不瞭解的同學,請先學習遊戲數學方面的知識。有了這個模型變換矩陣後,用這個矩陣乘以在MC中表示的座標就可以得到該座標在世界座標系中的座標。所以該矩陣和MC中的座標(1,1)相乘有:

 這也正是我們需要的結果。現在讓我們把相機座標也加進去,相機座標也稱爲觀測座標(View Coordinate),如圖4和圖5。


圖4 ModelView變換的三個座標系


圖5 ModelView變換計算

來看看MC座標中的點(1,1)如何在相機座標中表示。從圖5中可以直接看出MC中的點(1,1)在相機座標系VC中爲(-2,-2)。和上面同樣的道理,我們可以寫出相機座標系VC在世界標系WC中可以表示爲:

那麼世界座標系中的點轉換爲相機座標系中的點我們就需求VC的逆矩陣:

那麼世界座標系WC中的點(3,4)在相機座標系VC中座標爲:

上面的變換過程,就是可以把模型座標變換爲相機座標。在OpenGL中,當我們申明頂點的時候,有時候說的是世界座標,這是因爲初始化的時候世界座標系、模型座標系和相機座標系是一樣的,重合在一起的。所以OpenGL中提供了模型觀測變換,它是把模型座標系直接轉換爲相機座標系,如圖4。現在我們已經計算得到了VC-1和MC,如果把VC-1和 MC相乘,就可以得到模型座標在相機座標中的表示。爲了得到模型座標系中的座標在相機座標系中的表示,這就是OpenGL中的ModelView變換矩 陣。這也是ModelView變換的名字的由來,它是通過了上面兩個步驟得到的。那麼這裏,ModelView變換矩陣M爲:

現在只要用上面的模型觀測矩陣M乘以模型座標系MC中的座標就可以得到相機座標系中的座標了。模型觀測變換的關鍵就是要得到相機座標系中的座標,因爲光照等計算都是在這個這個座標系中完成的。下面我們實際OpenGL程序中檢查一下。在程序中,爲了計算方便,我們使用圖6中的模型。


圖6 ModelView變換計算模型

根據圖中的數據,我們分別可以寫出對應MC和VC-1,從而求得觀測變換矩陣M

(VC的逆矩陣:【4,4】= 1, 而不是0)

現在程序中用glGetFloatv()這個函數來獲得當前矩陣數據來檢查一下。


如果在上面程序段中最後一個glGetFloatv(GL_MODELVIEW_MATRIX, m)處設定斷點的話,就可以看到圖7所顯示的數據。  


圖7 ModelView變換矩陣數據

到這裏,整個ModelView變換就完成了。通過ModelView變換後得到是相機座標系內的座標。在這個座標系內典型的計算就是法線了。現在再來看看後面一個階段。

1.2投影變換

先還是複習一下OpenGL的渲染管線。圖1中可以看到,在投影變換(Projection Transformation)中也分爲兩個部分,第一個部分是將上個階段得到的座標轉換爲平面座標,第二個部分是將轉換後的平面座標在進行歸一化並進行剪裁。一般地,將三維座標轉換爲平面座標有兩種投影方式:正交投影(Orthogonal Projection)和透視投影(Perspective Projection)

1.2.1 正交投影

正交投影很簡單,如圖8,對於三維空間中的座標點和一個二維平面,要在對應的平面上投影,只需將非該平面上的點的座標分量改爲該平面上的座標值,其餘座標不變。

 圖8 正交投影

比如將點(1,1,5)正交投影到z=0的平面上,那麼投影后的座標爲(1,1,0)。在openGL中,設置正交投影可以使用函數:

glOrtho (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar)  

該函數可以設置正交投影的投影空間,在該空間以外的座標點就不會被投影到投影平面上。函數中的六個參數分是投影空間六個平面,如圖9:


圖9 OpenGL正交投影空間和投影變換

在圖9中,大的投影空間是根據這六個參數設置的投影空間,OpenGL會自動將該空間歸一化,也就是將該空間或立方體轉化爲變長爲1的正六面體投影 空間,並且該證六面體的中心在相機座標系的原點。一旦設置使用glortho函數設置投影空間,OpenGL會生成投影矩陣。這個矩陣的作用就是將座標進 行正交投影並且將投影后的座標正規化(轉換到-1到1之間)。要注意的是,生成該矩陣的時候,OpenGL會把右手座標系轉換爲左手座標系。原因很簡單, 右手座標系的Z軸向平面外的,這樣不符合我們的習慣。該矩陣的矩陣推導這裏就不詳細說明了,不瞭解的同學可以參考遊戲數學方面資料,這裏只給出正交投影矩 陣。

這個矩陣看來很複雜,其實計算很簡單。舉個例子,現在設置了這樣的正交投影空間glOrtho(-10,10,-10,10,-10,10),這是個正六面體空間,變長爲10。把這些參數帶入上面的矩陣可以得到

現在還是在OpenGL程序中來檢查一下。在OpenGL程序中添加下面代碼段:

//投影設置  

glMatrixMode(GL_PROJECTION); 

glLoadIdentity();

glOrtho(-10,10,-10,10,-10,10);

glMatrixMode(GL_MODELVIEW);

glGetFloatv(GL_PROJECTION_MATRIX,m)

glGetFloatv(GL_PROJECTION_MATRIX,m)處設定斷點就可以看到圖10中所顯示的信息。

 
圖10 正交變換矩陣數據 

1.2.2透視投影

透視投影和正交投影最大的區別就是透視投影具有遠近感。

圖11 透視投影

透視投影採用了圖11中的模型,這樣的模型就是保證遠的物體看起來小,近的物體看起來大。 在OpenGL中設置透視投影可以使用函數:

void APIENTRY gluPerspective (GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar);

該函數也會根據給定的參數生成一個投影空間。如圖11中,該投影空間是一個截頭體。同樣地,OpenGL會自動生成透視投影矩陣,該矩陣也會讓3D座標投影在投影平面上,並且將投影后的座標也進行正規化。下面也直接給出OpenGL中使用的透視投影矩陣。

下面在OpenGL中添加下面代碼段:

//投影設置

glMatrixMode(GL_PROJECTION);

glLoadIdentity();

gluPerspective(45, 1.0, 1.0, 100);

glMatrixMode(GL_MODELVIEW);

glGetFloatv(GL_PROJECTION_MATRIX,m)

設置斷點後,我們可以看到圖12中顯示的數據。

  
圖12 透視變換矩陣數據

到此爲止,整個投影變換就完成了。透過投影變換後得到的是正規化的投影平面座標。這爲下一個階段的視口變換(View port Transformation)做好了準備。

1.3視口變換

現在到了最後一個階段了。這個階段叫做視口變換,它把上個階段得到的正規化的投影座標轉化爲windows 窗口座標。視口變換會將投影平面上的畫面映射到窗口上。在OpenGL中可以使用函數

GLAPI void GLAPIENTRY glViewport (GLint x, GLint y, GLsizei width, GLsizei height);  

來進行對窗口的映射,如圖13。


 圖13 視口變換glViewport(width/2, 0, width/2, height/2)

舉個例子說明,比如上個階段中得到了一個頂點的座標爲(0,0,0.5,1),根據這個座標,該頂點位於投影平面的正中間。如果將該點映射到大小爲 50*50的窗口上時,那麼它應該位於屏幕的中間,座標爲(25,25, 0.5,1)。當然這裏深度值0.5是不會改變的。有的同學肯定有疑問了,既然投影到了窗口上,那麼還要深度值0.5幹什麼?這裏要注意的是,雖然在窗口 上顯示時只需要x,y座標就夠了,但是要在2D窗口上顯示3D圖形時深度值是不可少的。這裏的深度值不是用於顯示,而是用於在光柵化的時候進行深度測試。

OpenGL也會根據glViewport函數提供的參數值生成一個視口變換矩陣

該矩陣把上個階段得到的正規化座標映射到窗口上,並且將正規化座標中的深度值在轉換到0到1之間。所以在深度緩衝中最大值爲1,最小值爲0。視口變換結束 後,OpenGL中主要的圖形管線階段就算完成了,後面就是光柵化等等操作。再來回顧一下圖1,現在相信大家對這個渲染管線有了一定的認識了,也明白了每 一個階段對應的變換矩陣以及如何進行座標之間的轉換的。

2. 屏幕座標轉換爲世界座標

通過前面的教程,以及現在大家對OpenGL整個渲染管線理解後,現在要將屏幕上一點座標轉換爲世界座標就比較容易了。從圖形管線的開始到結束,一 個模型座標系中的座標被轉化爲了屏幕座標,那麼現在把整個過程倒過來的話,屏幕上一點座標也可以轉爲爲世界座標。只要在對應的階段求得對應變換矩陣的逆矩 陣,就可以得到前一個階段的座標。這整個過程可以用圖14表示。


圖14屏幕座標轉換爲世界座標

圖中顯示的過程完全就是OpenGL渲染管線的逆過程,通過這個過程,屏幕上的點就可以轉化爲世界座標系中的點了。可能又有的同學要問,當鼠標點擊屏幕上一點的時候並沒有深度信息,轉換的時候要怎麼辦呢?這個時候可以使用OpenGL函數

void glReadPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels);  

該函數能夠獲得屏幕上一點對應像素的深度信息。有了這個深度信息,就可以利用上面過程把屏幕上一點轉換爲世界座標了。在OpenGL中,上面的過程其實已經有現成的函數可以使用,那就是

 

該函數直接將屏幕上一點轉換對應的世界座標,該函數的內部實現其實還是上面的那麼逆過程。下面給出利用該函數獲取世界座標的代碼段。


代碼中函數返回類型GVector是用戶定義的向量類,返回的是齊次座標。

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