函數(shù)原型:
?????? void glMatrixMode(GLenum mode)
參數(shù)說明:
?????? mode 指定哪一個矩陣堆棧是下一個矩陣操作的目標,可選值:
GL_MODELVIEW,對模型視圖矩陣堆棧應用隨后的矩陣操作??梢栽趫?zhí)行此命令后,輸出自己的物體圖形了?! L_PROJECTION,對投影矩陣堆棧應用隨后的矩陣操作??梢栽趫?zhí)行此命令后,為我們的場景增加透視?! L_TEXTURE,對紋理矩陣堆棧應用隨后的矩陣操作。可以在執(zhí)行此命令后,為我們的圖形增加紋理貼圖。
????? 在每個矩陣模式下都有一個矩陣對陣,在GL_MODELVIEW模式中,堆棧深度至少為32;在GL_PROJECTION和GL_TEXTURE模式中,堆棧深度至少為2;在任何模式中,當前矩陣總是該模式下矩陣堆棧中的最頂層矩陣。
函數(shù)說明:
?????? glMatrixMode()命令將當前矩陣設(shè)置成參數(shù)所指定的模式,以滿足不同繪圖所需執(zhí)行的矩陣變換。一般而言,在需要繪制出對象或要對所繪制對象進行幾何變換時,需要將變換矩陣設(shè)置成模型視圖模式;而當需要對繪制的對象設(shè)置某種投影方式時,則需要將變換矩陣設(shè)置成投影模式;只有在進行紋理映射時,才需要將變換矩陣設(shè)置成紋理模式。
????? 與glLoadIdentity()一同使用,glLoadIdentity()功能是重置當前指定的矩陣為單位矩陣。
? ? ? ?我們生活在一個三維的世界——如果要觀察一個物體,我們可以:
1、從不同的位置去觀察它。(視圖變換)
2、移動或者旋轉(zhuǎn)它,當然了,如果它只是計算機里面的物體,我們還可以放大或縮小它。(模型變換)
3、如果把物體畫下來,我們可以選擇:是否需要一種“近大遠小”的透視效果。另外,我們可能只希望看到物體的一部分,而不是全部(剪裁)。(投影變換)
4、我們可能希望把整個看到的圖形畫下來,但它只占據(jù)紙張的一部分,而不是全部。(視口變換)
這些,都可以在OpenGL中實現(xiàn)。
? ? ? ?OpenGL變換實際上是通過矩陣乘法來實現(xiàn)。無論是移動、旋轉(zhuǎn)還是縮放大小,都是通過在當前矩陣的基礎(chǔ)上乘以一個新的矩陣來達到目的。OpenGL可以在最底層直接操作矩陣,不過作為初學,這樣做的意義并不大。這里就不做介紹了。
一、模型變換和視圖變換
? ? ? ?從“相對移動”的觀點來看,改變觀察點的位置與方向和改變物體本身的位置與方向具有等效性。在OpenGL中,實現(xiàn)這兩種功能甚至使用的是同樣的函數(shù)。
? ? ? ?由于模型和視圖的變換都通過矩陣運算來實現(xiàn),在進行變換前,應先設(shè)置當前操作的矩陣為“模型視圖矩陣”。設(shè)置的方法是以GL_MODELVIEW為參數(shù)調(diào)用glMatrixMode函數(shù),像這樣:
glMatrixMode(GL_MODELVIEW);?//需要修改的是模型視圖矩陣、投影矩陣還是紋理矩陣。mode的值可以為:GL_MODELVIEW、GL_PROJECTION或GL_TEXTURE。
? ? ? ?通常,我們需要在進行變換前把當前矩陣設(shè)置為單位矩陣。這也只需要一行代碼:
glLoadIdentity();
? ? ? ?然后,就可以進行模型變換和視圖變換了。進行模型和視圖變換,主要涉及到三個函數(shù):
glTranslate*:把當前矩陣和一個表示移動物體的矩陣相乘。三個參數(shù)分別表示了在三個坐標上的位移值。 glRotate*:把當前矩陣和一個表示旋轉(zhuǎn)物體的矩陣相乘。物體將繞著(0,0,0)到(x,y,z)的直線以逆時針旋轉(zhuǎn),參數(shù)angle表示旋轉(zhuǎn)的角度。 glScale*:把當前矩陣和一個表示縮放物體的矩陣相乘。x,y,z分別表示在該方向上的縮放比例。
? ? ? ?注意我都是說“與XX相乘”,而不是直接說“這個函數(shù)就是旋轉(zhuǎn)”或者“這個函數(shù)就是移動”,這是有原因的,馬上就會講到。
? ? ? ?假設(shè)當前矩陣為單位矩陣,然后先乘以一個表示旋轉(zhuǎn)的矩陣R,再乘以一個表示移動的矩陣T,最后得到的矩陣再乘上每一個頂點的坐標矩陣v。所以,經(jīng)過變換得到的頂點坐標就是((RT)v)。由于矩陣乘法的結(jié)合率,((RT)v) = (R(Tv)),換句話說,實際上是先進行移動,然后進行旋轉(zhuǎn)。即:實際變換的順序與代碼中寫的順序是相反的。由于“先移動后旋轉(zhuǎn)”和“先旋轉(zhuǎn)后移動”得到的結(jié)果很可能不同,初學的時候需要特別注意這一點。
? ? ? ?OpenGL之所以這樣設(shè)計,是為了得到更高的效率。但在繪制復雜的三維圖形時,如果每次都去考慮如何把變換倒過來,也是很痛苦的事情。這里介紹另一種思路,可以讓代碼看起來更自然(寫出的代碼其實完全一樣,只是考慮問題時用的方法不同了)。
? ? ? ?讓我們想象,坐標并不是固定不變的。旋轉(zhuǎn)的時候,坐標系統(tǒng)隨著物體旋轉(zhuǎn)。移動的時候,坐標系統(tǒng)隨著物體移動。如此一來,就不需要考慮代碼的順序反轉(zhuǎn)的問題了。
? ? ? ?以上都是針對改變物體的位置和方向來介紹的。如果要改變觀察點的位置,除了配合使用glRotate*和glTranslate*函數(shù)以外,還可以使用這個函數(shù):gluLookAt。它的參數(shù)比較多,前三個參數(shù)表示了觀察點的位置,中間三個參數(shù)表示了觀察目標的位置,最后三個參數(shù)代表從(0,0,0)到 (x,y,z)的直線,它表示了觀察者認為的“上”方向。
二、投影變換
? ? ? ?投影變換就是定義一個可視空間,可視空間以外的物體不會被繪制到屏幕上。(注意,從現(xiàn)在起,坐標可以不再是-1.0到1.0了?。?br />? ? ? ?OpenGL支持兩種類型的投影變換,即透視投影和正交投影。投影也是使用矩陣來實現(xiàn)的。如果需要操作投影矩陣,需要以GL_PROJECTION為參數(shù)調(diào)用glMatrixMode函數(shù)。
glMatrixMode(GL_PROJECTION);
? ? ? ?通常,我們需要在進行變換前把當前矩陣設(shè)置為單位矩陣。
glLoadIdentity();
? ? ? ?透視投影所產(chǎn)生的結(jié)果類似于照片,有近大遠小的效果,比如在火車頭內(nèi)向前照一個鐵軌的照片,兩條鐵軌似乎在遠處相交了。
1.使用glFrustum函數(shù)可以將當前的可視空間設(shè)置為透視投影空間。其參數(shù)的意義如下圖:
? ? ? ?這個函數(shù)原型為:
void?glFrustum(GLdouble?left,?GLdouble?Right,?GLdouble?bottom,?GLdouble?top,?GLdouble?near,?GLdouble?far);
? ? ? ?創(chuàng)建一個透視型的視景體。其操作是創(chuàng)建一個透視投影的矩陣,并且用這個矩陣乘以當前矩陣。這個函數(shù)的參數(shù)只定義近裁剪平面的左下角點和右上角點的三維空間坐標,即(left,bottom,-near)和(right,top,-near);最后一個參數(shù)far是遠裁剪平面的離視點的距離值,其左下角點和右上角點空間坐標由函數(shù)根據(jù)透視投影原理自動生成。near和far表示離視點的遠近,它們總為正值(near/far 必須>0)。
void?mydisplay?(void) { ?????...... ????glMatrixMode?(GL_PROJECTION); ????LoadIdentity?(); ????Frustum?(left,?right,?bottom,?top,?near,?far); ????...... }
2.也可以使用更常用的gluPerspective函數(shù)
? ? ? ?這個函數(shù)原型為:
void?gluPerspective(GLdouble?fovy,GLdouble?aspect,GLdouble?zNear,?GLdouble?zFar);
創(chuàng)建一個對稱的透視型視景體,但它的參數(shù)定義于前面的不同,如圖。其操作是創(chuàng)建一個對稱的透視投影矩陣,并且用這個矩陣乘以當前矩陣。參數(shù)fovy定義視野在Y-Z平面的角度,范圍是[0.0, 180.0];參數(shù)aspect是投影平面寬度與高度的比率;參數(shù)Near和Far分別是近遠裁剪面到視點(沿Z負軸)的距離,它們總為正值。
以上兩個函數(shù)缺省時,視點都在原點,視線沿Z軸指向負方向。
3.正交投影相當于在無限遠處觀察得到的結(jié)果,它只是一種理想狀態(tài)。但對于計算機來說,使用正交投影有可能獲得更好的運行速度。
? ? ? ?使用glOrtho函數(shù)可以將當前的可視空間設(shè)置為正投影空間,如下圖所示:
? ? ? ?這個函數(shù)的原型為:
void?glOrtho(GLdouble?left,?GLdouble?right,?GLdouble?bottom,?GLdouble?top,?GLdouble?near,?GLdouble?far)
? ? ? ?六個參數(shù),前兩個是x軸最小坐標和最大坐標,中間兩個是y軸,最后兩個是z軸值。它創(chuàng)建一個平行視景體(就是一個長方體空間區(qū)域)。實際上這個函數(shù)的操作是創(chuàng)建一個正射投影矩陣,并且用這個矩陣乘以當前矩陣。其中近裁剪平面是一個矩形,矩形左下角點三維空間坐標是(left,bottom,-near),右上角點是(right,top,-near);遠裁剪平面也是一個矩形,左下角點空間坐標是(left,bottom,-far),右上角點是(right,top,-far)。
? ? ? ?注意,所有的near和far值同時為正或同時為負,值不能相同。如果沒有其他變換,正射投影的方向平行于Z軸,且視點朝向Z負軸。這意味著物體在視點前面時far和near都為負值,物體在視點后面時far和near都為正值。
? ? ? ?只有在視景體里的物體才能顯示出來。如果最后兩個值是(0,0),也就是near和far值相同了,視景體深度沒有了,整個視景體都被壓成個平面了,就會顯示不正確。
三、視口變換
? ? ? ?當一切工作已經(jīng)就緒,只需要把像素繪制到屏幕上了。這時候還剩最后一個問題:應該把像素繪制到窗口的哪個區(qū)域呢?通常情況下,默認是完整的填充整個窗口,但我們完全可以只填充一半。(即:把整個圖象填充到一半的窗口內(nèi))
運用相機模擬方式,我們很容易理解視口變換就是類似于照片的放大與縮小。在計算機圖形學中,它的定義是將經(jīng)過幾何變換、投影變換和裁剪變換后的物體顯示于屏幕窗口內(nèi)指定的區(qū)域內(nèi),這個區(qū)域通常為矩形,稱為視口。
? ? ? ?在實際中,視口的長寬比率總是等于視景體裁剪面的長寬比率。如果兩個比率不相等,那么投影后的圖像顯示于視口內(nèi)時會發(fā)生變形,如圖所示。
?
? ? ? ?使用glViewport來定義視口。其中前兩個參數(shù)定義了視口的左下腳(0,0表示最左下方),后兩個參數(shù)分別是寬度和高度。
void?glViewport(GLint?x,GLint?y,GLsizei?width,GLsizei?height);
四、操作矩陣堆棧
? ? ? ? ?介于是入門教程,先簡單介紹一下堆棧。你可以把堆棧想象成一疊盤子。開始的時候一個盤子也沒有,你可以一個一個往上放,也可以一個一個取下來。每次取下的,都是最后一次被放上去的盤子。通常,在計算機實現(xiàn)堆棧時,堆棧的容量是有限的,如果盤子過多,就會出錯。當然,如果沒有盤子了,再要求取一個盤子,也會出錯。
? ? ? ? 我們在進行矩陣操作時,有可能需要先保存某個矩陣,過一段時間再恢復它。當我們需要保存時,調(diào)用glPushMatrix函數(shù),它相當于把矩陣(相當于盤子)放到堆棧上。當需要恢復最近一次的保存時,調(diào)用glPopMatrix函數(shù),它相當于把矩陣從堆棧上取下。OpenGL規(guī)定堆棧的容量至少可以容納32個矩陣,某些OpenGL實現(xiàn)中,堆棧的容量實際上超過了32個。因此不必過于擔心矩陣的容量問題。通常,用這種先保存后恢復的措施,比先變換再逆變換要更方便,更快速。
? ? ? ?注意:模型視圖矩陣和投影矩陣都有相應的堆棧。使用glMatrixMode來指定當前操作的究竟是模型視圖矩陣還是投影矩陣。