1. 程式人生 > >關於Opengl各種矩陣變換的理解

關於Opengl各種矩陣變換的理解

關於Opengl各種矩陣變換的理解

OpenGL的矩陣變換一直比較迷糊,貌似懂了又貌似不懂,今天貌似懂了過幾天又貌似忘記怎麼回事了,還是沒有理解透徹,因此痛下決心,一定要把這個事情弄懂。經過幾天的反覆思考檢視資料,我自己理解的結果就是:mv用來調整相機位置組成一個新座標系,p用來在這個座標系裡面切出一塊來變成-1,1之間的立方體顯示,w用來把這個-1,1的立方體轉到指定的視口上。如果這個不夠透徹,請看下面的詳細解釋。

一、相機模擬過程

首先是用相機模擬說明openGL的投影原理。下面這個圖應該是把原理解釋得比較透徹的一個了。



實際上,從三維空間到二維平面,就如同用相機拍照一樣,通常都要經歷以下幾個步驟 (括號內表示的是相應的圖形學概念):

第一步,將相機置於三角架上,讓它對準三維景物(視點變換,Viewing Transformation,對應openGLglLookAt,從哪裡看?)。

第二步,將三維物體放在適當的位置(模型變換,Modeling Transformation,對應openGL是各種transform/scal/rotate,比如物體移動旋轉縮放等。)。

第三步,選擇相機鏡頭並調焦,使三維物體投影在二維膠片上(投影變換,Projection Transformation,對應openGL就是gluPerspective/glOrtho)。

第四步,決定二維像片的大小(視口變換,Viewport Transformation

,對應openGL就是glViewport等)。

這樣,一個三維空間裡的物體就可以用相應的二維平面物體表示了,也就能在二維的電腦螢幕上正確顯示了。

二、數字顯示原理

我們建模是3D的,使用各種座標系,如世界座標系,使用者座標系等等。螢幕則是2D的,一般都是0-10240-768之類的範圍。為了使顯示的物體能以合適的位置、大小和方向顯示出來,必須要通過投影。投影的方法有兩種,即正射投影和透視投影。  有時為了突出圖形的一部分,只把圖形的某一部分顯示出來,這時可以定義一個三維視景體(Viewing Volume)。正射投影時一般是一個長方體的視景體,透視投影時一般是一個稜臺似的視景體。只有視景體內的物體能被投影在顯示平面上,其他部分則不能。在螢幕視窗內可以定義一個矩形,稱為視口(

Viewport),視景體投影后的圖形就在視口內顯示。  為了適應物理裝置座標和視口所在座標的差別,還要作一適應物理座標的變換。這個座標系稱為物理裝置座標系(比如Windows的螢幕座標原點是在螢幕左上方,我們使用的座標系一般是螢幕左下方為座標原點)。根據上面所述,三維圖形的顯示流程應如圖8-2所示。


下面的圖則是openGL實際上執行的過程。

 

通俗一點,世界座標系有一個100100100的點;用某個相機lookAt後,變成了一個5010050的點(相機座標系中);用了某個投影矩陣投影后,變成了一個0.50.70的點,這是投影到平面並且歸一化到-11區間的結果;設定了視口後,變成了一個500700的點,這是螢幕座標系了;和視口Y座標減一下以後變成了50068的點,這是螢幕畫素物理座標了(從上往下數68畫素)。

三、逐個步驟理解

OpenGL裡面,這些步驟總結下來,其實就是3個矩陣變換,合起來叫VPW矩陣。理解:V表示相機的觀察(view)矩陣;P是投影(Project)矩陣,W是視窗(Window)矩陣,其實叫MVPW矩陣更好,是modelview * projection * window三個矩陣的級聯Model矩陣其實就是使用者定義座標系轉成世界座標系才需要用。

1View

V表示觀察矩陣(view),對應openglglLookat,作用是把世界座標系轉成相機座標系。比如世界座標系100100的點,在某個相機觀察的時候,因為相機點要變成原點,所以可能就變成了5050glLootAt引數是一個相機點,一個目標點(這兩個點確定Z軸),一個向上的向量(確定X軸),這三個顯然就可以確定一個座標系,等於是把世界座標系轉成相機的UCS座標系,舉例來說,CAD裡面平移操作就是移動了相機,透視圖裡面走動也相當於移動了相機。這時候沒有進行任何裁剪。

2Project

P表示投影矩陣(project),有兩種:透視投影,和正交投影。主要用途就是從視覺座標系中裁剪一塊立體區域,變換到一個-11的正方體中方便裁剪(因為視錐體裁剪很不方便),最後去掉Z座標落到螢幕平面上。透視投影是裁剪一個錐臺區域,正交投影是裁剪一個長方體區域。這些投影函式的引數就需要注意了,不是世界座標系的引數哦。因為預設情況下lookat是從00點向下看,視覺座標情況和世界座標系相當,所以不用glulookat的時候設定投影函式是可以用世界座標系座標的;如果設定了視覺座標系,那麼就不一樣了。

透視投影矩陣相當於變換相機焦距,焦距小範圍就大,能看近物,類似廣角;焦距大範圍就小,但是遠處的能過來,類似長焦。對應opengl就是gluPerspectiv/glFrustum 函式,。兩個函式中,glFrustum更正規,功能更強,但是也更不好用一點(需要自己計算寬高比,通過剪切面寬高和近裁剪面距離的三角關係計算視角多少);

void glFrustum(GLdouble left,GLdouble Right,GLdouble bottom,GLdouble top,GLdouble near,GLdouble far),建議用gluPerspective

gluPerspective(GLdouble fovy,GLdouble aspect,GLdouble zNear,GLdouble zFar)

fovy,這個最難理解,我的理解是,眼睛睜開的角度,,視角的大小,如果設定為0,相當你閉上眼睛了,所以什麼也看不到,如果為180,那麼可以認為你的視界很廣闊.這應該和焦距對應,視角小就是焦距大(長焦),視角大就是焦距小(廣角)。對應glFrustum,就是near設成0.01left/right設定成1之類,趨近於90度了。

aspect,這個好理解,就是實際視窗的縱橫比,x/y

zNear,這個呢,表示你近處,的裁面,

zFar表示遠處的裁面,正交投影則用於2D圖形,圖形不會像透視那樣有變形。

glOrtho是正交投影,引數為glOrtho(left, right, bottom, top, near, far), left表示視景體左面的座標,right表示右面的座標,bottom表示下面的,top表示上面的。這個函式簡單理解起來,就是一個物體擺在那裡,你怎麼去擷取他。如果擷取範圍大,物體在整個範圍內的比例就小,相當於縮小;擷取範圍小,物體佔據比例就大,相當於放大。然後擷取範圍偏,物體相當於在整個範圍內就偏了,相當於平移。這個擷取體需要和視窗範圍比例一致,否則就會變形。因為不管是透視投影還是正交投影,它們的結果都是把你指定的一定範圍內的圖形單位化成了-11之間的值。

當然這裡還需要再次強調,glOrthogluPerspect函式,使用的範圍,都是基於gluLookAt的,並不是世界座標裡面的範圍。

我們假設物體的繪製函式是

glBegin(GL_TRIANGLES);

glVertex3f(100,300,600);

glVertex3f(300,100,400);

glVertex3f(100,100,600);

glEnd();

究竟應該如何用這兩個函式設定投影?

最簡單的設定方法是glOrtho(-200,200,-200,200,-500,500);

gluLookAt(300,300,600,200,200,5000,1,0);  

可以看到,glOrtho的引數顯然不是絕對的世界座標,而是lookAt出來的座標系裡面的座標。

3Window

W表示視口矩陣(window),對應opengl就是glviewport。因為螢幕有大小,最後-11的之間裁剪結果總要映射回螢幕才行,glViewport就是告訴螢幕多大,怎麼映射回去。如果這個和投影時使用的比例不一致,就是變形。gluProject可以直接完成這幾個變換(直接把物體的世界座標系的點變成螢幕上的座標點,當然Y值還需要掉轉,因為OpenGL座標系和視窗座標系不一致)。

再回顧一下一下opengl四種座標變換的含義。模型變換:用於調整物體的大小、位置,模型邊換之後得到的物體座標是相對於全域性座標系統(世界座標系)的。檢視變換V:用於設定相機的位置、相機軸線的方向以及拍攝的方向。模型變換和檢視變換統稱為模型檢視變換(modelview),經過模型檢視變換之後的座標是視覺系統內的座標,其實是模型相對於相機的座標,因為這裡是模型和檢視的一種相對關係,因此模型變換和檢視變換具有一定的等價性和可轉換性。

接著是投影變換P,投影變換的效果是建立一個裁剪用的視景體(錐臺體或者長方體)投影位於其中物體,對視景體外的物體部分進行截斷,投影變換之後的座標反應的是物體在視景體內的位置屬性,比如同樣大小的面(在全域性座標系統中),透視投影時靠近視景體前端的佔其相對應的截面的比例更大,因此在變換後的面積也更大。正視投影(OrthoView)時就還是同樣大小的一個區域了。最後是視口變換,其作用是將確定最終影象的大小,並將幀緩衝區域內的資料轉變為能顯示在螢幕上的畫素。

由於變換是通過左乘矩陣完成的,因此實際座標變換的順序和程式碼中出現的順序是相反的。一般先設定模型檢視變換,再設定投影變換,最後是視口變換,並且由於改變視窗只對投影和視口變換有影響,因此常將這兩種變換放在reshape函式裡。

三、來點實際的

說一千道一萬,不實際嘗試總不能確定的。主要是弄清楚這幾個矩陣和變換之間的情況。下面就是一些實際操作的結果(使用osg)。

首先是第一個,初始情況:

弄了一個-5000,-5000開始的座標網,及一個0,0到10000,10000的矩形。我直接用了一個投影矩陣,其他什麼都沒有(視覺矩陣為單位陣,視窗矩陣視口設定就不變了):

camera->setProjectionMatrixAsOrtho2D(-15000,15000,-15000*scale,15000*scale);


下面這個設定是這樣的,沒有對投影進行和視窗寬高比的處理,顯然就變形了:camera->setProjectionMatrixAsOrtho2D(-15000,15000,-15000,15000);//變形?


下面這個呢,是設定了下面的引數(縮小了10倍),可以看到,只是裁剪了一小塊區域了。

camera->setProjectionMatrixAsOrtho2D(-1500,1500,-1500*scale,1500*scale);//放大了?


下面這個增加了一個lookAt函式,等於以前的圖都是視覺座標系和世界座標系重合,這個呢,視覺座標系原點移到了5000,5000的位置,整個圖形就顯得偏了。偏下來了。證明投影時用的是視覺座標系的點。

camera->setViewMatrixAsLookAt(osg::Vec3(5000,5000,1),osg::Vec3(5000,5000,0),osg::Vec3(0,1,0));

camera->setProjectionMatrixAsOrtho2D(-15000,15000,-15000*scale,15000*scale);//移位