OpenGL系列教程之四:OpenGL 變換
阿新 • • 發佈:2019-01-25
概述 在OpenGL渲染管線中幾何資料(頂點位置和法向量)在光柵化處理之前會先經過頂點操作和圖元裝配。
OpenGL 頂點變換
物體座標系(Object Coordinates) 這是物體的區域性座標系,並且表示物體未經過任何變換前的初始位置和朝向。為了變換物體,使用glRotatef(),glTranslatef(),glScalef()等函式。
人眼座標系(Eye Coordinates) 這是將GL_MODELVIEW(模型檢視矩陣)與物體座標相乘產生的。在OpenGL中物體從物體自身的座標變換到人眼座標使用GL_MODELVIEW矩陣。GL_MODELVIEW矩陣是將模型矩陣和檢視矩陣相乘的結果(
注意在OpenGL中沒有單獨的照相機(檢視)矩陣。因此,為了模仿檢視變換,場景(三維物體和光照)必須使用檢視變換的反轉進行變換。換句話說,OpenGL定義的照相機在人眼座標系中一直在(0,0,0)位置並且指向Z軸的負半軸,並且不能進行變換。模型檢視矩陣的更多詳細細節。 法向量為了後續的光照計算也會從物體座標系變換到人眼座標系。注意法向量和頂點變換的不同方式。它是將GL_MODELVIEW的逆矩陣的轉置矩陣乘以法向量。法向量變換的更多細節。
裁剪座標(Clip Coordinates) 將GL_PROJECTTION與人眼座標相乘就得到了裁剪座標。GL_PROJECTION(投影矩陣)定義了視景體;幾何資料怎麼被投影到螢幕中(透視投影或正投影)。它被稱為裁剪座標是因為變換後的頂點座標(x,y,z)經過了裁剪(通過與-w和+w的比較)。投影矩陣的更多細節。
歸一化的裝置座標(Normalized Device Coordinates(NDC)) 將裁剪座標與w相除就得到歸一化的裝置座標。它被稱為透視除法。它更像視窗座標,但是還沒有被平移和縮放到匹配視窗。現在3個座標軸上變數的取值被歸一化到-1到1之間。
視窗座標(Window Coordinates(Screen Coordinates)) 這是將歸一化的裝置座標應用到視口變換而產生的。歸一化的裝置座標經過縮放了平移以匹配視窗。視窗座標最後被傳遞給OpenGL渲染管線中光柵化程式生成片元。glViewport()命令被用來定義最後渲染出來的影象將要對映到的長方形區域。glDepthRange()命令被用來定義視窗座標的z值的範圍。視窗座標是經過下面這兩個函式變換得到的
視口變換公式是一個從NDC到視窗座標的簡單的線性變換。
OpenGL 變換矩陣 OpenGL使用一個4*4的矩陣來表示變換。注意矩陣中的16個元素是按列的順序儲存在一個一維陣列中。如果你需要將他轉換成標準型,即按行儲存,則需要將這個矩陣轉置。 OpenGL有4中不同型別的矩陣:GL_MODELVIEW(模型檢視矩陣),GL_PROJCETION(投影矩陣),GL_TEXTURE(紋理矩陣),GL_COLOR(顏色矩陣)。你可以通過使用glMatrixMode()函式改變當前的矩陣型別。例如,為了選中GL_MODELVIEW矩陣,使用glMatrixMode(GL_MODELVIEW).
模型-檢視 矩陣(GL_MODELVIEW) GL_MODEVIEW矩陣聯合和檢視矩陣和模型矩陣到一個矩陣中。為了變換檢視(相機),你需要將整個場景按照檢視矩陣的逆進行移動。gluLookAt()函式是用來設定檢視變換的。
GL_MODELVIEW矩陣的4列 上圖中(m12,m13,m14)表示glTranslatef()函式進行的平移變換。m15是齊次座標,主要用來進行投影變換。 3個元素集,(m0,m1,m2), (m4,m5,m6)和(m8,m9,m10)是用來表示歐氏變化,如旋轉(glRoattef()),和縮放(glScalef())。注意這3個集合實際上表示的是3個正交的軸。
- (m0,m1,m2):+X軸,指向右邊的向量,預設為(1,0,0)
- (m4,m5,m6):+Y軸,指向上邊的向量,預設為(0,1,0)
- (m8,m9,m10):+Z軸,指向前邊的向量,預設為(0,0,1)
- 角度到座標軸
- 觀察點到座標軸
- 矩陣類
// 注意物件首先被平移,然後被旋轉
glRotatef(angle, 1, 0, 0); // 將物件繞x軸旋轉angle角度
glTranslatef(x, y, z); // 移動物件到(x,y,z)處
drawObject();
投影矩陣(GL_PROJECTION) GL_PROJECTION矩陣被用來定義截頭錐。這個截頭錐決定了哪些物體或物體的哪些部分會被裁剪掉。它也定義了三維場景將會如何被對映到螢幕上。(更多細節參考如果構造投影矩陣) OpenGL為GL_PROJCETION變換提供了兩個函式。glFrustum()用來產生一個透視投影,glOrtho()用來產生一個正投影。兩個函式都需要6個引數來指定6個裁剪面:左面,右面,上面,近面,遠面。截頭錐的8個頂點如下圖:
OpenGL透視投影體 近(遠)面的座標計算可以通過相似三角形計算得到,如左面的計算方法:
對正投影而言,相似三角形的比例是1,因此遠平面的左,右,上,下點的座標和近平面是一樣的。如下圖:
OpenGL正投影體 你也可以傳遞更少的引數給gluPerspective()函式和gluOrtho2D()函式。gluPerspective()函式只需要4個引數;視線的垂直夾角,面的寬高比,視點距近裁剪面和遠裁剪面的距離。4個引數與6個引數之間的相互轉換的程式碼如下:
// 這建立了一個對稱的截頭錐
// 它將給定的4個引數(fovy, aspect, near, far)轉換
// 成glFrustum()所需的6個引數:(l, r, b, t, n, f)
void makeFrustum(double fovY, double aspectRatio, double front, double back)
{
const double DEG2RAD = 3.14159265 / 180;
double tangent = tan(fovY/2 * DEG2RAD); // fovY的一半的正切,fovY是視線的夾角
double height = front * tangent; // 近裁剪面的高度的一半
double width = height * aspectRatio; // 近裁剪面的寬度的一半
// params: left, right, bottom, top, near, far
glFrustum(-width, width, -height, height, front, back);
}
然而,如果你需要建立一個不對稱的視景體那麼必須直接使用glFrustum()函式。例如,如果你需要渲染一個寬屏場景到兩個鄰接的螢幕中,你可以將視景體劃分成兩個不對稱的視景體(左邊和右邊),然後將場景分別渲染到兩個視景體中,如下圖:
非對稱視景體的例子
紋理矩陣(GL_TEXTURE) 紋理座標(s,t,r,q)在紋理對映之前會和GL_TEXTURE(紋理矩陣)相乘。預設情況下它是歸一化的,所以紋理將會準確對映物體上你指定的紋理座標處。通過修改GL_TEXTURE,你可以移動,旋轉,拉伸,收縮紋理。
// 將紋理繞x軸旋轉
glMatrixMode(GL_TEXTURE);
glRotatef(angle, 1, 0, 0);
顏色矩陣(GL_COLOR) 顏色值(r,g,b,a)會和矩陣GL_COLOR相乘。它可以被用來交換顏色空間和顏色值。GL_COLOR矩陣不會被普遍使用因為它需要GL_ARB_imaging擴充套件支援。
其他一些矩陣操作
glPushMatrix():將當前矩陣壓入當然矩陣棧中 glPopMatrix():從當前矩陣棧中彈出一個元素 glLoadIdentify():將當前矩陣設定為單位矩陣(歸一化) glLoadMatrix{fd}(m):將當前矩陣替換成矩陣m glLoadTransposeMatrix{fd}(m):將當前矩陣替換成以行為主的矩陣m glMultMatrix{fd}(m):m乘以當前矩陣,並將結果設定為當前矩陣 glMultTransposeMatrix{fd}(m):將以行為主的矩陣乘以當前矩陣,並將結果設定為當前矩陣 glGetFloatv(GL_MODELVIEW_MATRIX,m):返回模型檢視矩陣到矩陣m
這個例子程式顯示了怎麼使用glTranslatef()函式和glRotatef()函式來操縱模型檢視矩陣。
注意無論在Mac下還是在Windows下所有的OpenGL函式呼叫都是在ModelGL.h和ModelGL.cpp中實現的,在兩個包中所有檔案都是相同的。
這個例子程式使用一個自定義的4*4矩陣類和OpenGL預設的矩陣模式來指定模型變換和檢視變換。ModelGL.cpp中定義了3個矩陣物件:matrxModel,matrixView和matrixModelView。每個矩陣儲存了前乘的變換並使用glLoadMatrix()函式將矩陣中的元素傳遞到OpenGL中。實際的繪圖模式如下:
...
glPushMatrix();
// 為檢視變換設定檢視矩陣
glLoadMatrixf(matrixView.getTranspose());
// 在模型變換之前繪製最原始的網格
drawGrid();
// 為模型變換和檢視變換設定模型檢視矩陣
// 從物體座標系變換到人眼座標系
glLoadMatrixf(matrixModelView.getTranspose());
// 模型檢視變換之後繪製一個茶壺
drawTeapot();
glPopMatrix();
...
相同的功能使用OpenGL預設的矩陣操作函式如下:
...
glPushMatrix();
// 將模型檢視矩陣歸一化
glLoadIdentity();
// 首先,將檢視從物體座標系變換到人眼座標系
// 注意所有的值都是負數,因為我們是使用檢視矩陣的逆來移動整個場景
glRotatef(-cameraAngle[2], 0, 0, 1); // roll
glRotatef(-cameraAngle[1], 0, 1, 0); // heading
glRotatef(-cameraAngle[0], 1, 0, 0); // pitch
glTranslatef(-cameraPosition[0], -cameraPosition[1], -cameraPosition[2]);
// 在模型變換之前繪製最原始的網格
drawGrid();
// 模型變換
// GL_MODELVIEW矩陣的結果如下:
// ModelView_M = View_M * Model_M
glTranslatef(modelPosition[0], modelPosition[1], modelPosition[2]);
glRotatef(modelAngle[0], 1, 0, 0);
glRotatef(modelAngle[1], 0, 1, 0);
glRotatef(modelAngle[2], 0, 0, 1);
// 模型檢視變換之後繪製一個茶壺
drawTeapot();
glPopMatrix();
...
這個例子程式顯示瞭如何使用glFrustum()函式和glOrtho()函式來操作投影變換。
再次說明,ModelGL.h和ModelGL.cpp在Window和Mac下是一樣的,並且所有的OpenGL函式呼叫都在這兩個檔案中。
ModelGL類有一個自定義的矩陣物件,matrixProjection,和兩個成員函式,setFrustum()和setOrthoFrustum(),這兩個函式等價於glFrustum()和glOrtho()。
///////////////////////////////////////////////////////////////////////////////
// 使用6個引數像glFrustum()函式一樣設定一個透視的視景體
// (left, right, bottom, top, near, far)
// 注意:這是以行為主的標記方式,OpenGL需要先將其轉置
///////////////////////////////////////////////////////////////////////////////
void ModelGL::setFrustum(float l, float r, float b, float t, float n, float f)
{
matrixProjection.identity();
matrixProjection[0] = 2 * n / (r - l);
matrixProjection[2] = (r + l) / (r - l);
matrixProjection[5] = 2 * n / (t - b);
matrixProjection[6] = (t + b) / (t - b);
matrixProjection[10] = -(f + n) / (f - n);
matrixProjection[11] = -(2 * f * n) / (f - n);
matrixProjection[14] = -1;
matrixProjection[15] = 0;
}
///////////////////////////////////////////////////////////////////////////////
// 使用6個引數像glOrtho()函式一樣設定一個正投影的視景體
// (left, right, bottom, top, near, far)
// 注意:這是以行為主的標記方式,OpenGL需要先將其轉置
///////////////////////////////////////////////////////////////////////////////
void ModelGL::setOrthoFrustum(float l, float r, float b, float t, float n,
float f)
{
matrixProjection.identity();
matrixProjection[0] = 2 / (r - l);
matrixProjection[3] = -(r + l) / (r - l);
matrixProjection[5] = 2 / (t - b);
matrixProjection[7] = -(t + b) / (t - b);
matrixProjection[10] = -2 / (f - n);
matrixProjection[11] = -(f + n) / (f - n);
}
...
// 將投影矩陣傳遞給OpenGL
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(matrixProjection.getTranspose());
...
構造一個16個元素的GL_PROJECTION矩陣可以參考這兒。