1. 程式人生 > >OpenGL系列教程之四:OpenGL 變換

OpenGL系列教程之四:OpenGL 變換



概述 在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值的範圍。視窗座標是經過下面這兩個函式變換得到的
glViewport(x,y,w,h); glDepthRange(n,f);
視口變換公式是一個從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)
我們可以使用角度或觀察點直接構造GL_MODELVIEW矩陣而不使用OpenGL的變換函式。下面是一些構造GL_MODELVIEW矩陣的有用的程式碼:
  • 角度到座標軸
  • 觀察點到座標軸
  • 矩陣類
注意如果多個變換應用到頂點中時OpenGL是按照相反的順序執行的。例如,如果一個頂點首先經過矩陣MA變換,然後經過矩陣MB變換,然後OpenGL在將矩陣與頂點相乘之前會首先執行MBxMA操作。因此,最後的變換會首先被應用,最早的變換會最後被應用。
// 注意物件首先被平移,然後被旋轉
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矩陣可以參考這兒。