坐標系統(tǒng)
想要弄懂幾何變換,一定要搞清楚OpenGL中的坐標系統(tǒng)。
從我們構(gòu)造模型的局部坐標系(Local/Object Space)經(jīng)過一系列處理最終渲染到屏幕坐標(Screen Space)下,這過程中有6種坐標系。
World Coordinates(世界坐標系)Object Coordinates(對象坐標系、模型坐標系、局部坐標系或當前繪圖坐標系)Eye Coordinates(眼坐標系或照相機坐標系)Clip Coordinates(裁剪坐標系)Normalized Device Coordinates (NDC) (歸一化設(shè)備坐標系)Window Coordinates (Screen Coordinates)(屏幕坐標)
實際上,并不存在單獨的模型變換(Model)和視點變換(View),通常將這兩種變換合稱為ModelView變換。則OpenGL的頂點變換過程如圖所示:
世界坐標系
世界坐標系始終是固定不變的。OpenGL使用右手坐標,這里有一個形象的方法:使用右手定則
X 是你的拇指
Y 是你的食指
Z 是你的中指
如果你把你的拇指指向右邊,食指指向天空,那么中指將指向你的背后。我們的觀察方向是Z軸負半軸的方向。
進行旋轉(zhuǎn)操作時需要指定的角度θ的方向則由右手法則來決定,即右手握拳,大拇指直向某個坐標軸的正方向,那么其余四指指向的方向即為該坐標軸上的θ角的正方向(即θ角增加的方向),圖中用圓弧形箭頭標出:
對象坐標系
這是對象在被應(yīng)用任何變換之前的初始位置和方向所在的坐標系,也就是當前繪圖坐標系。該坐標系不是固定的,且僅對該對象適用。在默認情況下,該坐標系與世界坐標系重合。這里能用到的函數(shù)有g(shù)lTranslatef(),glScalef(), glRotatef(),當用這些函數(shù)對當前繪圖坐標系進行平移、伸縮、旋轉(zhuǎn)變換之后, 世界坐標系和當前繪圖坐標系不再重合。改變以后,再用glVertex3f()等繪圖函數(shù)繪圖時,都是在當前繪圖坐標系進行繪圖,所有的函數(shù)參數(shù)也都是相對當前繪圖坐標系來講的。如圖則是對物體進行變換后,對象坐標系與世界坐標系的相對位置。對象坐標系也叫本地(局部)坐標系。
眼坐標系
模型變換:對象坐標系->世界坐標系
視變換:世界坐標系->眼坐標系
GL_MODELVIEW矩陣是模型變換矩陣和視點變換矩陣的組合(Mview*Mmodel),前面已經(jīng)說了,并不存在單獨的模型變換(Model)和視點變換(View)。所以使用GL_MODELVIEW矩陣就可以使對象從對象坐標系轉(zhuǎn)換到眼坐標系。
為啥要轉(zhuǎn)換到眼坐標系呢?
可以這樣理解,通過前面的MODEVIEW變換,這個世界坐標系中的場景已經(jīng)繪制好了。這時候我們還不能看到場景哦,因為我們的觀察位置還沒定呢,而且如果我們眼睛(照相機)的位置不同,那么觀察物體的角度則不同,那看到的場景的樣子肯定也不同,所以要有這一步,把場景與我們的觀察位置對應(yīng)起來。
默認情況下,眼坐標系與世界坐標系也是重合的。使用函數(shù) gluLookAt()則可以指定眼睛(相機)的位置和眼睛看向的方向。該函數(shù)的原型如下:
void?gluLookAt(GLdouble?eyex,?GLdouble?eyey,?GLdouble?eyez,? ????????????????????????GLdouble?centerx,?GLdouble?centery,?GLdouble?centerz, ????????????????????????GLdouble?upx,?GLdouble?upy,?GLdouble?upz)
函數(shù)參數(shù)中:點(eyex, eyey, eyez)代表眼睛所在位置;
? ? ? ? ? ? ? ? ? ?點(centerx, centery,centerz)代表眼睛看向的位置;
? ? ? ? ? ? ? ? ? ?向量(upx, upy, upz)代表視線向上方向,其中視點和參考點的連線與視線向上方向要保持垂直關(guān)系。
只需控制這三個量,便可定義新的視點。
tips
使用glTranslatef(),glScalef(), glRotatef()這些函數(shù)是對對象坐標系進行變動;使用void gluLookAt()是對眼坐標系進行變動,兩者可以達到相同的變換效果。相當于對象不動移動相機,和相機不動移動對象。比如場景向x軸正方向移動1個單位(相機不動),相當于相機向x軸負方向移動一個單位(對象不動),glTranslatef(1.0, 0.0, 0.0) ?
裁剪坐標系
眼坐標到裁剪坐標是通過投影完成的。眼坐標通過乘以GL_PROJECTION矩陣變成了裁剪坐標。
這個GL_PROJECTION矩陣定義了視景體( viewing volume),即確定哪些物體位于視野之內(nèi),位于視景體外的對象會被剪裁掉。除了視景體,投影變換還定義了頂點是如何投影到屏幕上的,是透視投影(perspective projection)還是正交投影(orthographic projection)。透視投影似于日常生活看到的場景,遠處物體看起來小,近處看起來大。使用透視投影函數(shù)glFrustum()和gluPerspective().
void?glFrustum(GLdouble?left,?GLdouble?right, ?GLdouble?bottom,?GLdouble?top,? ?GLdouble?near,?GLdouble?far)
far, near是指近裁剪面,遠剪裁面離視點的距離(>0);對角坐標,(left, bottom, -near)和(right, top, -near)定義了近裁剪面的左下角和右上角的(x, y, z)坐標。
void?gluPerspective(GLdouble?fovy,??GLdouble?aspect, ???GLdouble?near,?GLdouble?far)
fovy視角,aspect = w/h
正交投影把物體直接映射到屏幕上,不影響它們的相對大小。也就是圖像反映物體的實際大小。函數(shù)glOrtho()創(chuàng)建一個用于正交投影的平行視景體, 將其與當前矩陣相乘。
void?glOrtho(GLdouble?left,?GLdouble?right, ???????GLdouble?bottom,?GLdouble?top,? ???????GLdouble?near,?GLdouble?far);
關(guān)于透視投影和正交投影詳見:OpenGL之glMatrixMode函數(shù)的用法
歸一化設(shè)備坐標系
?既然要規(guī)范化,那么就得先有一個規(guī)范。前面在投影部分也已經(jīng)看到,每種投影,都有一個剪裁空間,稱之為觀察體,對正交投影來說是一個立方體,對透視投影來說是一個棱臺。如果一個觀察體是一個x、y、z坐標范圍都是 [-1, 1] 的立方體,則稱之為規(guī)范化立方體,這個就是所謂的規(guī)范。那么,將原來的觀察體,映射到規(guī)范化立方體的過程,就是規(guī)范化。
一個格外需要注意的地方是,由于后面的屏幕坐標系通常是左手坐標系,所以這里的規(guī)范化觀察體也使用左手坐標系,意味著 x 軸和 y 軸沒有改變,但是 z 軸的正方向轉(zhuǎn)了個。這帶來的結(jié)果是,在這樣的坐標系下,z 的坐標值越小,距離觀察者(也就是你)越近。實際上,在opengl中,進行規(guī)范化之后,近裁剪平面的z軸坐標是 -1,遠裁剪平面的z軸坐標是1。
由裁剪坐標系下通過除以W分量得到。這個操作稱為透視除法。NDC坐標很像屏幕坐標,但是還沒有經(jīng)過平移和縮放到屏幕像素?,F(xiàn)在3個軸上的值范圍均為[-1,1]。屏幕坐標系
通常將屏幕上的設(shè)備坐標稱為屏幕坐標。設(shè)備坐標又稱為物理坐標,是指輸出設(shè)備上的坐標。設(shè)備坐標用對象距離窗口左上角的水平距離和垂直距離來指定對象的位置,是以像素為單位來表示的,設(shè)備坐標的 X 軸向右為正,Y 軸向下為正,坐標原點位于窗口的左上角。
從NDC坐標到屏幕坐標基本上是一個線性映射關(guān)系。通過對NDC坐標進行視口變換得到。這時候就要用到函數(shù)glViewport(),該函數(shù)用來定義渲染區(qū)域的矩形,也就是最終圖像映射到的區(qū)域。