1. 程式人生 > IOS開發 >OpenGL學習(七)-- 基礎變化綜合練習實踐總結

OpenGL學習(七)-- 基礎變化綜合練習實踐總結

我的 OpenGL 專題學習目錄,希望和大家一起學習交流進步!


一、前言

以下我總結了一些最近學習 OpenGL 中常用的一些函式,添加了比較多的註釋,既是對自己學習的一個鞏固總結,也是防止以後遺忘可以快速檢視的記錄,同時希望也能幫助到更多在學習 OpenGL 的朋友們。

Xcode 還沒有搭建OpenGL環境的朋友可以參照我這篇文章去搭建,OpenGL學習(二)-- Xcode 搭建 OpenGL 環境。 另外還有之前的我總結的一些 OpenGL 基礎文章,可以從文章上方的目錄檢視。

二、程式碼+註釋總結

1、引入的標頭檔案:

#include "GLTools.h"	// OpenGL toolkit
#include "GLMatrixStack.h"
#include "GLFrame.h"
#include "GLFrustum.h"
#include "GLBatch.h"
#include "GLGeometryTransform.h"
#include "StopWatch.h"

#include <math.h>
#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>
#endif 複製程式碼

2、一些常用全域性變數的宣告:

GLShaderManager		shaderManager;// 固定管線管理器

GLMatrixStack		modelViewMatrix;// 模型檢視矩陣堆疊

GLMatrixStack		projectionMatrix;// 投影檢視矩陣堆疊

GLFrame				cameraFrame;// 觀察者位置

GLFrame             objectFrame;// 世界座標位置

GLFrustum			viewFrustum;// 投影方式,學名:視景體。用來構造投影矩陣。

GLBatch             triangleBatch;// 簡單的批次容器,是GLTools的一個簡單的容器類。

GLTriangleBatch     CC_Triangle;// 三角形批次類

GLTriangleBatch     sphereBatch;// 球

GLTriangleBatch     torusBatch;// 環

GLTriangleBatch     cylinderBatch;// 圓柱

GLTriangleBatch     coneBatch;// 錐

GLTriangleBatch     diskBatch;// 磁碟

GLGeometryTransform	transformPipeline; // 變換管道,專門用來管理投影和模型矩陣的

GLfloat vGreen[] = {0.0f,1.0f,0.0f,1.0f };// 定義一個顏色值,綠色

複製程式碼

3、main 函式:程式入口中的設定

int main(int argc,char *argv[]) {
    //設定當前工作目錄,針對MAC OS X
    /*
     `GLTools`函式`glSetWorkingDrectory`用來設定當前工作目錄。實際上在Windows中是不必要的,因為工作目錄預設就是與程式可執行執行程式相同的目錄。但是在Mac OS X中,這個程式將當前工作資料夾改為應用程式捆綁包中的`/Resource`資料夾。`GLUT`的優先設定自動進行了這個中設定,但是這樣中方法更加安全。
     */
    gltSetWorkingDirectory(argv[0]);
    
   
    //初始化GLUT庫,這個函式只是傳輸命令引數並且初始化glut庫
    glutInit(&argc,argv);
    
    /*
     初始化雙緩衝視窗,其中標誌GLUT_DOUBLE、GLUT_RGBA、GLUT_DEPTH、GLUT_STENCIL分別指
     雙緩衝視窗、RGBA顏色模式、深度測試、模板緩衝區
     
     --GLUT_DOUBLE`:雙快取視窗,是指繪圖命令實際上是離屏快取區執行的,然後迅速轉換成視窗檢視,這種方式,經常用來生成動畫效果;
     --GLUT_DEPTH`:標誌將一個深度快取區分配為顯示的一部分,因此我們能夠執行深度測試;
     --GLUT_STENCIL`:確保我們也會有一個可用的模板快取區。
     深度、模板測試後面會細緻講到
     */
    glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH|GLUT_STENCIL);
    
    //GLUT視窗大小、視窗標題
    glutInitWindowSize(800,600);
    glutCreateWindow("Triangle");
    
    /*
     GLUT 內部執行一個本地訊息迴圈,攔截適當的訊息。然後呼叫我們不同時間註冊的回撥函式。我們一共註冊2個回撥函式:
     1)為視窗改變大小而設定的一個回撥函式
     2)包含OpenGL 渲染的回撥函式
     */
    //註冊重塑函式
    glutReshapeFunc(changeSize);
    //註冊顯示函式
    glutDisplayFunc(RenderScene);
    
    glutSpecialFunc(SpeacialKeys);
    
    /*
     初始化一個GLEW庫,確保OpenGL API對程式完全可用。
     在試圖做任何渲染之前,要檢查確定驅動程式的初始化過程中沒有任何問題
     */
    GLenum status = glewInit();
    if (GLEW_OK != status) {
        
        printf("GLEW Error:%s\n",glewGetErrorString(status));
        return 1;
        
    }
    
    //設定我們的渲染環境
    setupRC();
    
    glutMainLoop();

    return  0;    
}
複製程式碼

4、changeSize (int w,int h) 重塑函式:

重塑函式,為視窗改變大小而設定的一個回撥函式,視窗大小改變時,接收新的寬度&高度。 在 main 函式中使用 glutReshapeFunc 函式進行了註冊: glutReshapeFunc(changeSize);//註冊重塑函式

/*
 在視窗大小改變時,接收新的寬度&高度。
 */
void changeSize(int w,int h) {
    /*
      1、設定視窗座標
      x,y 引數代表視窗中檢視的左下角座標,而寬度、高度是畫素為表示,通常x,y 都是為0
     */
    // 防止h變為0
    if(h == 0){
        h = 1;
    }
    glViewport(0,0,w,h);

    // 2、
    // 如果繪製的是立體圖形,還需要設定透視投影
    // 透視投影
    // 引數1:從頂點方向看去的視場角度(用角度值表示)
    // 引數2:寬和高的縱橫比
    // 引數3:fNear
    // 引數4:fFar
    viewFrustum.SetPerspective(35.0f,float(w) / float(h),500.0f);
    
    // 3、
    // 上面只是設定了但沒有用,我們需要把設定的轉化為矩陣,把結果帶回去。
    // 往投影矩陣堆疊projectionMatrix里加載一個矩陣,從我們的視景體viewFrustum裡獲取投影矩陣GetProjectionMatrix()
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    
    // 4、    
    // 模型檢視矩陣是用來旋轉、平移等操作的,這裡並沒有變化,所以只是載入一個單元矩陣。   
    // 往模型檢視矩陣堆疊modelViewMatrix里加載一個單元矩陣
    modelViewMatrix.LoadIdentity();
}
複製程式碼

5、KeyPressFunc 點選空格函式:

每次點選空格,切換視窗的標題,並重新渲染圖形

//點選空格,切換渲染圖形
void KeyPressFunc(unsigned char key,int x,int y) {
    if(key == 32) {
        nStep++;
        if(nStep > 4)
            nStep = 0;
    }
    
    switch(nStep) {
        case 0:
            glutSetWindowTitle("Sphere");
            break;
        case 1:
            glutSetWindowTitle("Torus");
            break;
        case 2:
            glutSetWindowTitle("Cylinder");
            break;
        case 3:
            glutSetWindowTitle("Cone");
            break;
        case 4:
            glutSetWindowTitle("Disk");
            break;
    }
    
    glutPostRedisplay();
}
複製程式碼

6、SpecialKeys 函式

點選鍵盤的上下左右,讓世界座標系發生變化(並不是移動物體本身)

//上下左右,移動世界座標系
void SpecialKeys(int key,int y) {
    if(key == GLUT_KEY_UP)
        // 移動世界座標系,而不是去移動物體。
        // 將世界座標系在X方向移動-5.0
        objectFrame.RotateWorld(m3dDegToRad(-5.0f),0.0f);
    
    if (key == GLUT_KEY_DOWN)
        objectFrame.RotateWorld(m3dDegToRad(5.0f),0.0f);
    
    if (key == GLUT_KEY_LEFT)
        objectFrame.RotateWorld(m3dDegToRad(-5.0f),0.0f);
    
    if (key == GLUT_KEY_RIGHT)
        objectFrame.RotateWorld(m3dDegToRad(5.0f),0.0f);
    
    glutPostRedisplay();
}
複製程式碼

7、SetupRC() 函式:

進行必要的初始化,設定我們的渲染環境,在 main 函式中呼叫。一般在這裡是去描述我們要渲染的圖形長什麼樣子。真正去繪製的時候不在這裡,而是在 RenderScene()函式 中,後面會講解。

// 將上下文中,進行必要的初始化
void SetupRC() {
    // 1、設定背景顏色
    glClearColor(0.7f,0.7f,1.0f );

    // 2、初始化固定著色器管理器
    shaderManager.InitializeStockShaders();

    // 3、繪製立體圖形的時候,需要開啟深度測試
    glEnable(GL_DEPTH_TEST);

    // 4、通過GLGeometryTransform管理矩陣堆疊
    // 使用transformPipeline 管道管理模型檢視矩陣堆疊 和 投影矩陣堆疊(把兩個矩陣作為兩個引數放進去)
    transformPipeline.SetMatrixStacks(modelViewMatrix,projectionMatrix);

    // 5、為了讓效果明顯,將觀察者座標位置Z移動往螢幕裡移動15個單位位置
    // 引數:表示離螢幕之間的距離。 負數,是往屏幕後面移動;正數,往螢幕前面移動
    cameraFrame.MoveForward(-15.0f);

// ----------------------GLTriangleBatch型別--------------------------

    // 利用“GLTriangleBatch”型別的三角形批次類構造圖形物件,GLTriangleBatch型別封裝了很多常用立體圖形。
    // 1、球
    /*
      gltMakeSphere(GLTriangleBatch& sphereBatch,GLfloat fRadius,GLint iSlices,GLint iStacks);
     引數1:sphereBatch,三角形批次類物件
     引數2:fRadius,球體半徑
     引數3:iSlices,從球體底部堆疊到頂部的三角形帶的數量;其實球體是一圈一圈三角形帶組成
     引數4:iStacks,圍繞球體一圈排列的三角形對數
     建議:一個對稱性較好的球體的片段數量是堆疊數量的2倍,就是iStacks = 2 * iSlices,引數4是引數3的2倍。
     繪製球體都是圍繞Z軸,這樣+z就是球體的頂點,-z就是球體的底部。
     */
    gltMakeSphere(sphereBatch,3.0,10,20);

    // 2、環面
    /*
     gltMakeTorus(GLTriangleBatch& torusBatch,GLfloat majorRadius,GLfloat minorRadius,GLint numMajor,GLint numMinor);
     引數1:torusBatch,三角形批次類物件
     引數2:majorRadius,甜甜圈中心到外邊緣的半徑
     引數3:minorRadius,甜甜圈中心到內邊緣的半徑
     引數4:numMajor,沿著主半徑的三角形數量
     引數5:numMinor,沿著內部較小半徑的三角形數量
     */
    gltMakeTorus(torusBatch,3.0f,0.75f,15,15);
    
    // 3、圓柱
    /*
     void gltMakeCylinder(GLTriangleBatch& cylinderBatch,GLfloat baseRadius,GLfloat topRadius,GLfloat fLength,GLint numSlices,GLint numStacks);
     引數1:cylinderBatch,三角形批次類物件
     引數2:baseRadius,底部半徑
     引數3:topRadius,頭部半徑
     引數4:fLength,圓形長度
     引數5:numSlices,圍繞Z軸的三角形對的數量
     引數6:numStacks,圓柱底部堆疊到頂部圓環的三角形數量
     */
    gltMakeCylinder(cylinderBatch,2.0f,2);
    
    // 4、錐
    /*
     void gltMakeCylinder(GLTriangleBatch& cylinderBatch,圓柱底部堆疊到頂部圓環的三角形數量
     */
    //圓柱體,從0開始向Z軸正方向延伸。
    //圓錐體,是一端的半徑為0,另一端半徑可指定。
    gltMakeCylinder(coneBatch,13,2);
    
    // 5、磁碟
    /*
    void gltMakeDisk(GLTriangleBatch& diskBatch,GLfloat innerRadius,GLfloat outerRadius,GLint nSlices,GLint nStacks);
     引數1:diskBatch,三角形批次類物件
     引數2:innerRadius,內圓半徑
     引數3:outerRadius,外圓半徑
     引數4:nSlices,圓盤圍繞Z軸的三角形對的數量
     引數5:nStacks,圓盤外網到內圍的三角形數量
     */
    gltMakeDisk(diskBatch,1.5f,3);


// ------------------------GLBatch型別--------------------------
// 用上面GLTriangleBatch的程式碼的時候,把下面這段程式碼先註釋掉再執行。兩塊兒程式碼本來是分開的,為了方便寫在了一起。

//上面的是用的“GLTriangleBatch”型別的三角形批次類,下面我們使用最基礎的“GLBatch”型別的三角形批次類繪製金字塔。
    // 首先建立三角形批次類,告訴它我們需要多少個頂點,
    
    // 1.函式BeginMesh( GLuint nMaxVerts )// 指定有多少個頂點
    CC_Triangle.BeginMesh(300);// 指定有300個頂點
    
    // 2.建立一個頂點
    M3DVector3f m[] = {
        0.5,0.0,-0.5,0.5,0.0
    };
    
    // 3.將頂點複製進去,可以使用copy函式,也可以使用add函式新增
    // 引數1:表示頂點座標值
    // 引數2:表示法線座標值,沒有就寫NULL
    // 引數3:表示紋理座標值,沒有就寫NULL
    CC_Triangle.AddTriangle(m,NULL,NULL);
    //CC_Triangle.CopyVertexData3f
    
    CC_Triangle.End();
    
    CC_Triangle.Draw();
// --------------------------------------------------------------
}
複製程式碼

8、RenderScene()顯示函式:

在發生任何變化,呼叫 glutPostRedisplay() 方法時,會呼叫 RenderScene() 這個函式。 在 main 函式中註冊了 RenderScene 顯示函式 glutDisplayFunc(RenderScene);

下面我先著重解釋一下這幾段程式碼:

  • (1)壓棧 PushMatrix();

modelViewMatrix.PushMatrix(); 這句程式碼的意思是壓棧,如果 PushMatix() 括號裡是空的,就代表是把棧頂的矩陣複製一份,再壓棧到它的頂部。如果不是空的,比如是括號裡是單元矩陣,那麼就代表壓入一個單元矩陣到棧頂了。

  • (2)矩陣相乘 MultMatrix(mObjectFrame)

modelViewMatrix 的堆疊中的矩陣 與 mOjbectFrame 矩陣相乘,儲存到 modelViewMatrix 矩陣堆疊中。 modelViewMatrix.MultMatrix(mObjectFrame); 這句程式碼的意思是把 模型檢視矩陣堆疊棧頂 的矩陣 copy 出一份來和新矩陣進行矩陣相乘,然後再將相乘的結果賦值給棧頂的矩陣。

MultMatrix.png

  • (3)出棧PopMatrix();

modelViewMatrix.PopMatrix(); 把棧頂的矩陣出棧,恢復為原始的矩陣堆疊,這樣就不會影響後續的操作了。

下面是我自己總結的一個《矩陣入棧、相乘、出棧》的流程圖:

矩陣入棧相乘出棧.png

  • (4)cameraFrame.GetCameraMatrix(mCamera) :

cameraFrame.GetCameraMatrix(mCamera); 這裡和 OC 的語法有些不一樣,它的意思是從 cameraFrame 這個 觀察者座標系 中獲取矩陣,然後賦值給 mCamera。同理的還有獲取 世界座標位置 的矩陣 objectFrame.GetMatrix(mObjectFrame);cameraFramemCamera 都是 GLFrame 型別的結構體。

接下來看一下 RenderScene() 程式碼:

//召喚場景
void RenderScene(void) {
    //1.清除一個或者一組特定的快取區
    /*
     緩衝區是一塊存在影象資訊的儲存空間,紅色、綠色、藍色和alpha分量通常一起分量通常一起作為顏色快取區或畫素快取區引用。
     OpenGL 中不止一種緩衝區(顏色快取區、深度快取區和模板快取區)
      清除快取區對數值進行預置
     引數:指定將要清除的快取的
     GL_COLOR_BUFFER_BIT :指示當前啟用的用來進行顏色寫入緩衝區
     GL_DEPTH_BUFFER_BIT :指示深度快取區
     GL_STENCIL_BUFFER_BIT:指示模板緩衝區
     */
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    
    //模型檢視矩陣棧堆,壓棧
    modelViewMatrix.PushMatrix();
    //獲取攝像頭矩陣
    M3DMatrix44f mCamera;
    //從camereaFrame中獲取矩陣到mCamera
    cameraFrame.GetCameraMatrix(mCamera);
    //模型檢視堆疊的 矩陣與mCamera矩陣 相乘之後,儲存到modelViewMatrix矩陣堆疊中
    modelViewMatrix.MultMatrix(mCamera);
    
    //建立矩陣mObjectFrame
    M3DMatrix44f mObjectFrame;
    //從ObjectFrame 獲取矩陣到mOjectFrame中
    objectFrame.GetMatrix(mObjectFrame);
    //將modelViewMatrix 的堆疊中的矩陣 與 mOjbectFrame 矩陣相乘,儲存到modelViewMatrix矩陣堆疊中
    modelViewMatrix.MultMatrix(mObjectFrame);
    
    //使用平面著色器
    //引數1:型別
    //引數2:通過transformPipeline獲取模型檢視矩陣
    //引數3:顏色
    shaderManager.UseStockShader(GLT_SHADER_FLAT,transformPipeline.GetModelViewProjectionMatrix(),vBlack);
    
    //判斷你目前是繪製第幾個圖形
// 注意這裡傳遞的是地址 &sphereBatch
    switch(nStep) {
        case 0:
            DrawWireFramedBatch(&sphereBatch);// 球
            break;
        case 1:
            DrawWireFramedBatch(&torusBatch);// 環面
            break;
        case 2:
            DrawWireFramedBatch(&cylinderBatch);// 圓柱
            break;
        case 3:
            DrawWireFramedBatch(&coneBatch);// 錐
            break;
        case 4:
            DrawWireFramedBatch(&diskBatch);// 磁碟
            break;
    }
    
    // 繪製完畢了,需要把棧頂的矩陣pop出去,不要影響我下一次繪圖
    modelViewMatrix.PopMatrix();
    
    // Flush drawing commands
    glutSwapBuffers();
}
複製程式碼

9、DrawWireFramedBatch 函式:

void DrawWireFramedBatch(GLTriangleBatch* pBatch) {
    // 平面著色器,繪製三角形
    shaderManager.UseStockShader(GLT_SHADER_FLAT,vGreen);
   
    // 傳過來的引數,對應不同的圖形Batch
    pBatch->Draw();
    
    // 畫出黑色輪廓
    glPolygonOffset(-1.0f,-1.0f);
    
    // 開啟線段圓滑處理
    glEnable(GL_LINE_SMOOTH);
    
    // 開啟混合功能
    glEnable(GL_BLEND);
    
    // 顏色混合
    // 表示源顏色乘以自身的alpha 值,目標顏色乘以1.0減去源顏色的alpha值,這樣一來,源顏色的alpha值越大,則產生的新顏色中源顏色所佔比例就越大,而目標顏色所佔比例則減 小。這種情況下,我們可以簡單的將源顏色的alpha值理解為“不透明度”。這也是混合時最常用的方式。
    glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
    
    // 通過程式點大小模式來設定點的大小
    glEnable(GL_POLYGON_OFFSET_LINE);
    
    // 多邊形模型(背面、線) 將多邊形背面設為線框模式
    glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
    
    // 線條寬度
    glLineWidth(2.5f);
    
    // 平面著色器繪製線條
    shaderManager.UseStockShader(GLT_SHADER_FLAT,vBlack);
    
    pBatch->Draw();
    
    // 恢復多邊形模式和深度測試
    glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
    glDisable(GL_POLYGON_OFFSET_LINE);
    glLineWidth(1.0f);
    glDisable(GL_BLEND);
    glDisable(GL_LINE_SMOOTH);
}
複製程式碼

以上的總結參考了並部分摘抄了以下文章,非常感謝以下作者的分享!:

1、《OpenGL超級寶典 第5版》

2、《OpenGL程式設計指南(第八版)》

轉載請備註原文出處,不得用於商業傳播——凡幾多