OpenGL入門第三課--矩陣變換與座標系統

    

       在 OpenGL中,物體在被渲染到屏幕以前須要通過一系列的座標變換,聽起來有點嚇人;不過呢若是有必定的線性代數的基礎利用矩陣變換,其實也就沒那麼難了。即便沒學過線性代數,只須要了解一些基本的矩陣運算也基本能夠知足你們學習 OpenGL的要求了。下面咱們就來簡單學習一下有關矩陣的知識。動畫

    矩陣是一種很是有用的數學工具,雖然有點難度,可是一旦你理解了它們後,它們會變得很是有用。爲了深刻了解矩陣變換,咱們首先要在討論矩陣以前瞭解一下向量。ui

向量

       向量最基本的定義就是一個方向。或者更正式的說,向量有一個方向(Direction)和大小(Magnitude,也叫作強度或長度)。你能夠把向量想像成一個藏寶圖上的指示:「向左走10步,向北走3步,而後向右走5步」;「左」就是方向,「10步」就是向量的長度。因爲向量表示的是方向,起始於何處並不會改變它的值。以下圖向量v和向量w就是相等的. 

                           

        向量能夠在任意維度上,通常用到的都是2維和3維,一個三維向量在公式中一般是這樣表示的。入下圖:

  


                                         

      因爲向量是一個方向,因此有些時候會很難形象地將它們用位置(Position)表示出來。爲了讓其更爲直觀,咱們一般設定這個方向的原點爲(0, 0, 0),而後指向一個方向,對應一個點,使其變爲位置向量(Position Vector)(你也能夠把起點設置爲其餘的點,而後說:這個向量從這個點起始指向另外一個點)。好比說位置向量(3, 5)在圖像中的起點會是(0, 0),並會指向(3, 5)。

向量與標量的運算

       標量(Scalar)只是一個數字(或者說是僅有一個份量的向量)。當把一個向量加/減/乘/除一個標量,咱們能夠簡單的把向量的每一個份量分別進行該運算。對於加法來講會像這樣:

                                              

      其中的+能夠是+,-,·或÷,其中·是乘號。注意-和÷運算時不能顛倒(標量-/÷向量),由於顛倒的運算是沒有定義的。

向量取反

     對一個向量取反(Negate)會將其方向逆轉。一個指向東北的向量取反後就指向西南方向了。咱們在一個向量的每一個份量前加負號就能夠實現取反了(或者說用-1數乘該向量):

                                       

向量相減

     向量的加法能夠被定義爲是份量的相加,即將一個向量中的每個份量加上另外一個向量的對應份量;就像普通數字的加減同樣,向量的減法等於加上第二個向量的相反向量:

    

向量的長度

     咱們使用勾股定理來獲取向量的長度:

                                                         

      咱們也能夠加上Z的平方 把這個公式擴展到三維空間。另外有一個特殊類型的向量叫作單位向量(Unit Vector)。單位向量有一個特別的性質——它的長度是1。咱們能夠用任意向量的每一個份量除以向量的長度獲得它的單位向量。

向量相乘

     向量相乘分爲點乘和叉乘:

點乘:兩個向量的點乘等於它們的數乘結果乘以兩個向量之間夾角的餘弦值。可能聽起來有點費解,咱們來看一下公式。

                                   

      它們之間的夾角記做θ,這有什麼用,想象一下假如是兩個單位向量點乘會是什麼結果?如今點積只定義了兩個向量的夾角。是否還記得90度的餘弦值是0,0度的餘弦值是1。這樣使用點乘能夠很容易測試兩個向量是否正交(Orthogonal)或平行(正交意味着兩個向量互爲直角)。

    也能夠經過點乘的結果計算兩個非單位向量的夾角,點乘的結果除以兩個向量的長度之積,獲得的結果就是夾角的餘弦值,即 cosθ。因此,咱們該如何計算點乘呢?點乘是經過將對應份量逐個相乘,而後再把所得積相加來計算的。

叉乘:叉乘只在3D空間中有定義,它須要兩個不平行向量做爲輸入,生成一個正交於兩個輸入向量的第三個向量.若是輸入的兩個向量也是正交的,那麼叉乘以後將會產生3個互相正交的向量。下面的圖片展現了3D空間中叉乘的樣子:

                                

      不一樣於其餘運算,若是你沒有鑽研過線性代數,可能會以爲叉乘很反直覺,因此只記住公式就沒問題(記不住也沒問題)。下面你會看到兩個正交向量A和B叉積:

                

矩陣

       矩陣就是一個矩形的數學表達式陣列,矩陣中每一項叫作矩陣的元素(Element)。下面是一個2×3矩陣的例子:

                                             

     矩陣能夠經過(i, j)進行索引,i是行,j是列,這就是上面的矩陣叫作2×3矩陣的緣由。

矩陣的加減法

     矩陣與標量之間的加減定義以下:


矩陣與標量的減法也類似:


    矩陣與矩陣之間的加減就是兩個矩陣對應元素的加減運算,因此整體的規則和與標量運算是差很少的,只不過在相同索引下的元素才能進行運算。這也就是說加法和減法只對同維度的矩陣纔是有定義的。一個3×2矩陣和一個2×3矩陣(或一個3×3矩陣與4×4矩陣)是不能進行加減的。咱們看看兩個2×2矩陣是怎樣相加的:


一樣的法則也適用於減法:


矩陣的數乘

     和矩陣與標量的加減同樣,矩陣與標量之間的乘法也是矩陣的每個元素分別乘以該標量。下面的例子展現了乘法的過程:

 

矩陣相乘

      矩陣之間的乘法不見得有多複雜,但的確很難讓人適應。矩陣乘法基本上意味着遵守規定好的法則進行相乘。固然,相乘還有一些限制:

  • 只有當左側矩陣的列數與右側矩陣的行數相等,兩個矩陣才能相乘;
  • 矩陣相乘不遵照交換律(Commutative),也就是說A⋅B≠B⋅A;

咱們先看一個兩個2×2矩陣相乘的例子:


    能夠看到,矩陣相乘很是繁瑣而容易出錯,不夠沒關係,在咱們OpenGL中是不會讓你們本身去計算這些的,都有相應API來完成,這裏只是但願你們瞭解一下矩陣相乘的原理。

矩陣與向量相乘

    如今咱們已經至關了解向量了。在OpenGL中能夠用向量來表示位置,表示顏色,甚至是紋理座標。與矩陣類比一下,它其實就是一個N×1矩陣,N表示向量份量的個數(也叫N維(N-dimensional)向量)。仔細想一下,其實向量和矩陣同樣都是一個數字序列,但它只有1列。那麼,這個新的定義對咱們有什麼幫助呢?若是咱們有一個M×N矩陣,咱們能夠用這個矩陣乘以咱們的N×1向量,由於這個矩陣的列數等於向量的行數,因此它們就能相乘。

可是爲何咱們會關心矩陣可否乘以一個向量?好吧,正巧,不少有趣的2D/3D變換均可以放在一個矩陣中,用這個矩陣乘以咱們的向量將變換(Transform)這個向量。若是你仍然有些困惑,咱們來看一些例子,你很快就能明白了。

單位矩陣

      在OpenGL中,因爲某些緣由咱們一般使用4×4的變換矩陣,而其中最重要的緣由就是大部分的向量都是4份量的。咱們能想到的最簡單的變換矩陣就是單位矩陣(Identity Matrix)。單位矩陣是一個除了對左角線是1之外其餘都是0的N×N矩陣。在下式中能夠看到,這種變換矩陣使一個向量徹底不變:

   

     你可能會奇怪一個沒變換的變換矩陣有什麼用?單位矩陣一般是生成其餘變換矩陣的起點,若是咱們深挖線性代數,這仍是一個對證實定理、解線性方程很是有用的矩陣。

縮放

    下面會構造一個變換矩陣來爲咱們提供縮放功能。咱們從單位矩陣瞭解到,每一個對角線元素會分別與向量的對應元素相乘。若是咱們把1變爲3會怎樣?這樣子的話,咱們就把向量的每一個元素乘以3了,這事實上就把向量縮放3倍。若是咱們把縮放變量表示爲(S1,S2,S3)咱們能夠爲任意向量(x,y,z)定義一個縮放矩陣:

          

     注意,第四個縮放向量仍然是1,由於在3D空間中縮放w份量是無心義的。w份量另有其餘用途。

位移

    位移(Translation)是在原始向量的基礎上加上另外一個向量從而得到一個在不一樣位置的新向量的過程,從而在位移向量基礎上移動了原始向量。咱們已經討論了向量加法,因此這應該不會太陌生。和縮放矩陣同樣,在4×4矩陣上有幾個特別的位置用來執行特定的操做,對於位移來講它們是第四列最上面的3個值。若是咱們把位移向量表示爲(Tx,Ty,Tz),咱們就能把位移矩陣定義爲:

        

    有了位移矩陣咱們就能夠在3個方向(x、y、z)上移動物體,它是咱們的變換工具箱中很是有用的一個變換矩陣。

旋轉

    在3D空間中旋轉須要定義一個角和一個旋轉軸(Rotation Axis)。物體會沿着給定的旋轉軸旋轉特定角度。當2D向量在3D空間中旋轉時,咱們把旋轉軸設爲z軸。

     使用三角學,給定一個角度,能夠把一個向量變換爲一個通過旋轉的新向量。這一般是使用一系列正弦和餘弦函數(通常簡稱sin和cos)各類巧妙的組合獲得的。

       旋轉矩陣在3D空間中每一個單位軸都有不一樣定義,旋轉角度用θ表示:

沿x軸旋轉:


沿y軸旋轉:


沿z軸旋轉:


    利用旋轉矩陣咱們能夠把任意位置向量沿一個單位旋轉軸進行旋轉。也能夠將多個矩陣複合,好比先沿着x軸旋轉再沿着y軸旋轉。

    是否是感受旋轉看起來好複雜,這些旋轉矩陣都是怎麼計算出來的,其實這些都不須要太過在乎,只有理解旋轉也是經過一個特定的矩陣相乘完成的就能夠了。

矩陣的組合

    使用矩陣進行變換的真正力量在於,根據矩陣之間的乘法,咱們能夠把多個變換組合到一個矩陣中。讓咱們看看咱們是否能生成一個變換矩陣,讓它組合多個變換。假設咱們有一個頂點(x, y, z),咱們但願將其縮放2倍,而後位移(1, 2, 3)個單位。咱們須要一個位移和縮放矩陣來完成這些變換。結果的變換矩陣看起來像這樣:


    注意,當矩陣相乘時咱們先寫位移再寫縮放變換的。矩陣乘法是不遵照交換律的,這意味着它們的順序很重要。當矩陣相乘時,在最右邊的矩陣是第一個與向量相乘的,因此你應該從右向左讀這個乘法。建議您在組合矩陣時,先進行縮放操做,而後是旋轉,最後纔是位移,不然它們會(消極地)互相影響。好比,若是你先位移再縮放,位移的向量也會一樣被縮放(譯註:好比向某方向移動2米,2米也許會被縮放成1米)!

用最終的變換矩陣左乘咱們的向量會獲得如下結果:

           

不錯!向量先縮放2倍,而後位移了(1, 2, 3)個單位。

OpenGL中的座標系

       OpenGL中頂點着色後,咱們的可見頂點都爲標準化設備座標(Normailzed Device Coordinate,NDC)。也就是每一個頂點的x,y,z都應該在-1到1直接,不然對咱們都是不可見的。

      一個頂點在被轉化爲片斷以前須要依次經歷一下幾個重要的座標系:

  1. 局部空間(Local Space 或者稱爲 物體空間 Object Space)
  2. 世界空間(World Space)
  3. 觀察空間 (View Space 或者稱爲 視覺空間 Eye Space)
  4. 裁剪空間(Clip Space)
  5. 屏幕空間 (Screen Space)

   從一個座標系變到另一個座標系須要利用變換矩陣,最重要的幾個分別是模型(Model)、觀察(View)、投影(Projection)三個矩陣.物體頂點的起始座標再局部空間(Local Space),這裏稱它爲局部座標(Local Coordinate),它在以後會變成世界座標(world Coordinate),觀測座標(View Coordinate),裁剪座標(Clip Coordinate),並最後以屏幕座標(Screen Corrdinate)的形式結束.

       想要把3D圖形最終渲染到2D設備屏幕上,除了使用模型變換和視變換將物體座標轉換到照相機座標系外,還須要進行投影變換將座標變爲裁剪座標系,而後通過透視除法變換到規範化設備座標系(NDC),最後進行視口變換渲染到2D屏幕上,以下圖:


     在上面的圖中,OpenGL只定義了裁剪座標系、規範化設備座標系和屏幕座標系,而局部座標系(物體座標系)、世界座標系和照相機座標系都是爲了方便用戶設計而自定義的座標系。也就是說,模型變換、視變換、投影變換,這些變換能夠根據開發者的需求自行定義,這些內容在頂點着色器中完成。另外的兩個透視除法和視口變換,這兩個步驟是OpenGL自動執行的,在頂點着色器處理後的階段完成。

     上面的每個變換都建立了一個變換矩陣,模型矩陣、觀察矩陣和投影矩陣。講這些矩陣組合起來,一個頂點座標將會根據如下過程被變換到裁剪座標:


     注意矩陣運算的順序是相反的(記住咱們須要從右往左閱讀矩陣的乘法)。最後的頂點應該被賦值到頂點着色器中的gl_Position,OpenGL將會自動進行透視除法和裁剪。

    說了那麼多理論,好像都不知道怎麼用,下面咱們來寫一個簡單的案例,看看如何利用矩陣變換講3d圖像到渲染到2d屏幕上。

案例

    咱們首先來看一下案例效果再來講說如何實現:

                                                            

    這這個案例中咱們先經過先經過平移矩陣與旋轉矩陣叉乘獲得模型視圖矩陣,而後經過投影矩陣叉乘模型視圖矩陣獲得模型視圖投影矩陣也就是咱們常說的mvp,而後經過平面着色器畫出圖形。具體代碼以下:

main函數一些初始化操做和回調函數的註冊:

int main(int argc, char* argv[]){   
 gltSetWorkingDirectory(argv[0]);   
 glutInit(&argc, argv);   
 glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_SINGLE);   
 glutInitWindowSize(800, 600);   
 glutCreateWindow("矩陣變換Demo");   
 glutReshapeFunc(ChangeSize);   
 glutDisplayFunc(RenderScene);   
 GLenum error = glewInit();    
 if(GLEW_OK != error) {        
   fprintf(stderr,"GLEW Error: %s\n",glewGetErrorString(error));      
   return 1;  
  }   
 setupRC();   
 glutMainLoop();   
 return 0;
}複製代碼

這裏有三個方法比較重要 第一個 setupRC,主要完成一些繪圖前的準備工做:

void setupRC () {
    glClearColor(0.8, 0.8, 0.8, 1.0f); //設置清屏顏色  
    shaderManager.InitializeStockShaders();//初始化固定管線
    glEnable(GL_DEPTH_TEST);//開啓深度測試   
    gltMakeSphere(torusBatch, 0.4, 10, 20);//造成一個球    
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);//設置多邊形填充模式
}複製代碼

    changeSize這個函數在初次窗口顯示 或者其餘任什麼時候候窗口改變的時候將會被調用。主要完成視口和投影矩陣的設置,具體以下:

void ChangeSize(int w, int h){
    if(h == 0) h = 1;
    glViewport(0, 0, w, h);   
     viewFrustum.SetPerspective(35, float(w)/float(h), 1, 1000); 
 }複製代碼

RenderScene函數就是具體完成繪製的函數,具體代碼以下:

void RenderScene(void){    
    //清除屏幕、深度緩存區
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    //1.創建基於時間變化的動畫
    static CStopWatch rotTimer;
    //當前時間 * 60s
    float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
    //2.矩陣變量
    /*
     mTranslate: 平移
     mRotate: 旋轉
     mModelview: 模型視圖
     mModelViewProjection: 模型視圖投影MVP
     */
    M3DMatrix44f mTranslate, mRotate, mModelview, mModelViewProjection;
    //建立一個4*4矩陣變量,將花托沿着Z軸負方向移動2.5個單位長度
    m3dTranslationMatrix44(mTranslate, 0.0f, 0.0f, -2.5f);
    //建立一個4*4矩陣變量,將花托在Y軸上渲染yRot度,yRot根據通過時間設置動畫幀率
     m3dRotationMatrix44(mRotate, m3dDegToRad(yRot), 0.0f, 1.0f, 0.0f);
    //爲mModerView 經過矩陣旋轉矩陣、移動矩陣相乘,將結果添加到mModerView上
    m3dMatrixMultiply44(mModelview, mTranslate, mRotate);
    // 將投影矩陣乘以模型視圖矩陣,將變化結果經過矩陣乘法應用到mModelViewProjection矩陣上
    //注意順序: 投影 * 模型 != 模型 * 投影
     m3dMatrixMultiply44(mModelViewProjection, viewFrustum.GetProjectionMatrix(),mModelview);
    //繪圖顏色
    GLfloat vBlack[] = { 0.0f, 0.0f, 0.0f, 1.0f };
    //經過平面着色器提交矩陣,和顏色。
    shaderManager.UseStockShader(GLT_SHADER_FLAT, mModelViewProjection, vBlack);
    //開始繪圖
    torusBatch.Draw();
    // 交換緩衝區,並當即刷新
    glutSwapBuffers();
    glutPostRedisplay();
} 複製代碼

   這裏咱們先建立了平移和旋轉矩陣,經過叉乘 :平移矩陣 X 旋轉矩陣 = 模型視圖矩陣。從右往左度實際是旋轉後平移,爲何要先旋轉後平移在上文矩陣的組合中已經說過了。

而後經過投影矩陣叉乘模型視圖矩陣獲得模型視圖投影矩陣(mvp),這樣就經過固定管線的平面着色器須要的參數就有了,而後調用相關OpenGL API 就能順利完成繪製。

相關文章
相關標籤/搜索