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

個人 OpenGL 專題學習目錄,但願和你們一塊兒學習交流進步!編程


1、前言

如下我總結了一些最近學習 OpenGL 中經常使用的一些函數,添加了比較多的註釋,既是對本身學習的一個鞏固總結,也是防止之後遺忘能夠快速查看的記錄,同時但願也能幫助到更多在學習 OpenGL 的朋友們。緩存

Xcode 尚未搭建OpenGL環境的朋友能夠參照我這篇文章去搭建,OpenGL學習(二)-- Xcode 搭建 OpenGL 環境。 另外還有以前的我總結的一些 OpenGL 基礎文章,能夠從文章上方的目錄查看。安全

2、代碼+註釋總結

一、引入的頭文件:

#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
複製代碼

二、一些經常使用全局變量的聲明:

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 };// 定義一個顏色值,綠色

複製代碼

三、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;    
}
複製代碼

四、changeSize (int w, int h) 重塑函數:

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

/* 在窗口大小改變時,接收新的寬度&高度。 */
void changeSize(int w,int h) {
    /* 一、設置窗口座標 x,y 參數表明窗口中視圖的左下角座標,而寬度、高度是像素爲表示,一般x,y 都是爲0 */
    // 防止h變爲0
    if(h == 0){
        h = 1;
    }
    glViewport(0, 0, w, h);

    // 二、
    // 若是繪製的是立體圖形,還須要設置透視投影
    // 透視投影
    // 參數1:從頂點方向看去的視場角度(用角度值表示)
    // 參數2:寬和高的縱橫比
    // 參數3:fNear
    // 參數4:fFar
    viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 500.0f);
    
    // 三、
    // 上面只是設置了但沒有用,咱們須要把設置的轉化爲矩陣,把結果帶回去。
    // 往投影矩陣堆棧projectionMatrix里加載一個矩陣,從咱們的視景體viewFrustum裏獲取投影矩陣GetProjectionMatrix()
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    
    // 四、 
    // 模型視圖矩陣是用來旋轉、平移等操做的,這裏並無變化,因此只是加載一個單元矩陣。 
    // 往模型視圖矩陣堆棧modelViewMatrix里加載一個單元矩陣
    modelViewMatrix.LoadIdentity();
}
複製代碼

五、KeyPressFunc 點擊空格函數:

每次點擊空格,切換窗口的標題,並從新渲染圖形oop

//點擊空格,切換渲染圖形
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();
}
複製代碼

六、SpecialKeys 函數

點擊鍵盤的上下左右,讓世界座標系發生變化(並非移動物體自己)post

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

七、SetupRC() 函數:

進行必要的初始化,設置咱們的渲染環境,在 main 函數中調用。通常在這裏是去描述咱們要渲染的圖形長什麼樣子。真正去繪製的時候不在這裏,而是在 RenderScene()函數 中,後面會講解。學習

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

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

    // 三、繪製立體圖形的時候,須要開啓深度測試
    glEnable(GL_DEPTH_TEST);

    // 四、經過GLGeometryTransform管理矩陣堆棧
    // 使用transformPipeline 管道管理模型視圖矩陣堆棧 和 投影矩陣堆棧(把兩個矩陣做爲兩個參數放進去)
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);

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

// ----------------------GLTriangleBatch類型--------------------------

    // 利用「GLTriangleBatch」類型的三角形批次類構造圖形對象,GLTriangleBatch類型封裝了不少經常使用立體圖形。
    // 一、球
    /* 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);

    // 二、環面
    /* 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);
    
    // 三、圓柱
    /* 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.0f, 3.0f, 15, 2);
    
    // 四、錐
    /* 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,圓柱底部堆疊到頂部圓環的三角形數量 */
    //圓柱體,從0開始向Z軸正方向延伸。
    //圓錐體,是一端的半徑爲0,另外一端半徑可指定。
    gltMakeCylinder(coneBatch, 2.0f, 0.0f, 3.0f, 13, 2);
    
    // 五、磁盤
    /* 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.0f, 13, 3);


// ------------------------GLBatch類型--------------------------
// 用上面GLTriangleBatch的代碼的時候,把下面這段代碼先註釋掉再運行。兩塊兒代碼原本是分開的,爲了方便寫在了一塊兒。

//上面的是用的「GLTriangleBatch」類型的三角形批次類,下面咱們使用最基礎的「GLBatch」類型的三角形批次類繪製金字塔。
    // 首先建立三角形批次類,告訴它咱們須要多少個頂點,
    
    // 1.函數BeginMesh( GLuint nMaxVerts )// 指定有多少個頂點
    CC_Triangle.BeginMesh(300);// 指定有300個頂點
    
    // 2.建立一個頂點
    M3DVector3f m[] = {
        0.5, 0.0, 0.0,
        -0.5, 0.0, 0.0,
        0.0, 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();
// --------------------------------------------------------------
}
複製代碼

八、RenderScene()顯示函數:

在發生任何變化,調用 glutPostRedisplay() 方法時,會調用 RenderScene() 這個函數。 在 main 函數中註冊了 RenderScene 顯示函數 glutDisplayFunc(RenderScene);測試

下面我先着重解釋一下這幾段代碼:動畫

  • (1)壓棧 PushMatrix();

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

  • (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();
}
複製代碼

九、DrawWireFramedBatch 函數:

void DrawWireFramedBatch(GLTriangleBatch* pBatch) {
    // 平面着色器,繪製三角形
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), 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, transformPipeline.GetModelViewProjectionMatrix(), 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);
}
複製代碼

以上的總結參考了並部分摘抄瞭如下文章,很是感謝如下做者的分享!:

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

二、《OpenGL編程指南(第八版)》

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

相關文章
相關標籤/搜索