OpenGL 學習 08 - 球體世界

執著丶執念發表於2018-06-03

OpenGL 學習 08 - 球體世界

學習書籍: OpenGL 超級寶典(中文第五版) 密碼:fu4w

書籍原始碼:OpenGL 超級寶典第五版原始碼 密碼:oyb4

環境搭建:OpenGL 學習 01 - Mac 搭建 OpenGL 環境

PS:因為以後 Demo 原始碼程式碼量會越來越多,不太可能全部複製在這裡了,我就解釋下 Demo 的核心原始碼,全部原始碼請前往我的 github/OpenGLDemo 下載,原始碼裡會有詳細註釋。

基本概念

三角形批次

GLTriangleBatch 就是專門為了繪製三角形的批次類,它將以更加高效的方式(索引頂點陣列)進行組織,並實際上將多邊形儲存在圖形卡(使用定點緩衝區物件)上,這裡只需要瞭解它的用法即可。

角色幀

在場景中移動的物體通常被稱為角色,為了方便我們計算角色在 3D 場景中進行移動、旋轉等操作的座標變換,每個角色都有自己的參考幀,即本地物件座標系,更簡潔的表示角色在空間中的座標和方向。

為了在 3D 場景中表示任何物件的位置和方向,GLTools 為我們實現了該參考幀的資料結構 GLFrame角色幀),包含了空間中的一個位置、一個指向前方的向量和一個指向上方的向量。

class GLFrame {
    M3DVector3f vOrigin;	// Where am I?
    M3DVector3f vForward;	// Where am I going?
    M3DVector3f vUp;		// Which way is up?
    ...
}
複製程式碼

可能會有疑問說,怎麼少了一個指向左邊或右邊的向量?因為這個指向左邊或右邊的向量可通過 vForwardvUp 向量叉乘得到。

有了角色幀的概念後,我們就知道照相機實際上就是一個角色幀,可以想象一下,一個人抬著照相機進行拍攝,它拍攝到的東西就是我們在視窗上看到的東西。

OpenGL 學習 08 - 球體世界

變換光線

對於幾何圖形變換來說,典型情況下我們會設定變換矩陣,將它們傳遞到著色器,然後讓硬體完成所有頂點的變換,但對於光源來說,會有一些不同,因為光源變換是獨立於幾何變換以外。

光源位置也需要轉化到視覺座標系,但傳遞到著色器的矩陣是幾何圖形,而不是光線。想象下,光源就像是一個燈光師,它總是跟隨著照相機的一起移動的。

得到光源矩陣:

// 先獲取到照相機矩陣
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);

// 根據光源位置,得到光源矩陣
M3DVector4f vLightPos = { 0.0f, 10.0f, 5.0f, 1.0f };
M3DVector4f vLightEyePos;
m3dTransformVector4(vLightEyePos, vLightPos, mCamera);
複製程式碼

使用點光源著色器,傳入光源矩陣:

shaderManager.UseStockShader(
    GLT_SHADER_POINT_LIGHT_DIFF,       
    transformPipeline.GetModelViewMatrix(),          
    transformPipeline.GetProjectionMatrix(), 
    vLightEyePos, 
    vColor
);
複製程式碼

原始碼解析

更多物件(球、花托、圓柱、圓錐、圓盤)

09-Objects 核心原始碼如下,全部原始碼下載:09-Objects

// 初始化球體的三角形批次,後面引數依次是,球半徑,片段數,堆疊數
gltMakeSphere(sphereBatch, 3.0, 10, 20);
複製程式碼

球

// 初始化花托的三角形批次,後面引數依次是,外半徑,內半徑,片段數,堆疊數
gltMakeTorus(torusBatch, 3.0f, 0.75f, 150, 15);
複製程式碼

花托

// 初始化圓柱的三角形批次,後面引數依次是,底部半徑,頂部半徑,高度,片段數,堆疊數
gltMakeCylinder(cylinderBatch, 2.0f, 2.0f, 3.0f, 13, 2);
複製程式碼

圓柱

// 初始化圓錐的三角形批次,後面引數依次是,底部半徑,頂部半徑,高度,片段數,堆疊數
gltMakeCylinder(coneBatch, 2.0f, 0.0f, 3.0f, 13, 2);
複製程式碼

圓錐

// 初始化圓盤的三角形批次,後面引數依次是,內半徑,外半徑,片段數,堆疊數
gltMakeDisk(diskBatch, 1.5f, 3.0f, 13, 3);
複製程式碼

圓盤

球體世界 Lv1(繪製地板、旋轉的花托)

13-SphereWorld-Lv1 核心原始碼如下,全部原始碼下載:13-SphereWorld-Lv1

// 程式初始化
void SetupRC() {
    // 設定視窗背景顏色為黑色
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    // 初始化著色器
    shaderManager.InitializeStockShaders();
    // 開啟深度測試
    glEnable(GL_DEPTH_TEST);
    // 設定多邊形模式為前後面線段模式
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    // 得到花托批次資料
    gltMakeTorus(torusBatch, 0.4f, 0.15f, 30, 30);
    // 得到方格地板批次資料
    floorBatch.Begin(GL_LINES, 324);
    for(GLfloat x = -20.0; x <= 20.0f; x+= 0.5) {
        floorBatch.Vertex3f(x, -0.55f, 20.0f);
        floorBatch.Vertex3f(x, -0.55f, -20.0f);
        floorBatch.Vertex3f(20.0f, -0.55f, x);
        floorBatch.Vertex3f(-20.0f, -0.55f, x);
    }
    floorBatch.End();
}

// 視窗渲染回撥
void RenderScene(void) {
    // 獲取2次渲染之間的時間間隔
    static CStopWatch    rotTimer;
    float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
    // 清空緩衝區
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    // 壓棧,保持原始矩陣
    modelViewMatrix.PushMatrix();
    // 繪製地板
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vFloorColor);
    floorBatch.Draw();
   
    // 檢視矩陣進行平移、旋轉後進行繪製花托
    modelViewMatrix.Translate(0.0f, 0.0f, -2.5f);
    modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vTorusColor);
    torusBatch.Draw();
    
    // 出棧,恢復原始矩陣
    modelViewMatrix.PopMatrix();
    // 因為是雙緩衝區模式,後臺緩衝區替換到前臺快取區進行顯示
    glutSwapBuffers();
    // 自動觸發渲染,達到動畫效果
    glutPostRedisplay();
}
複製程式碼

OpenGL 學習 08 - 球體世界

球體世界 Lv2(加入角色移動和藍色小球旋轉)

14-SphereWorld-Lv2 核心原始碼如下,全部原始碼下載:14-SphereWorld-Lv2

// 程式初始化
void SetupRC() {
    // 設定視窗背景顏色為黑色
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    // 初始化著色器
    shaderManager.InitializeStockShaders();
    // 開啟深度測試
    glEnable(GL_DEPTH_TEST);
    // 設定多邊形模式為前後面線段模式
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    // 得到花托批次資料
    gltMakeTorus(torusBatch, 0.4f, 0.15f, 30, 30);
    // 得到旋轉小球批次資料【Lv2 增加的程式碼】
    gltMakeSphere(sphereBatch, 0.1f, 26, 13);
    
    // 得到方格地板批次資料
    floorBatch.Begin(GL_LINES, 324);
    for(GLfloat x = -20.0; x <= 20.0f; x+= 0.5) {
        floorBatch.Vertex3f(x, -0.55f, 20.0f);
        floorBatch.Vertex3f(x, -0.55f, -20.0f);
        floorBatch.Vertex3f(20.0f, -0.55f, x);
        floorBatch.Vertex3f(-20.0f, -0.55f, x);
    }
    floorBatch.End();
}

// 視窗渲染回撥
void RenderScene(void) {
    // 獲取2次渲染之間的時間間隔
    static CStopWatch    rotTimer;
    float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
    // 清空緩衝區
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    // 原來是沒法移動所以壓入原始矩陣,但現在角色可以移動通過角色幀獲取照相機矩陣,並壓入棧中【Lv2 修改的程式碼塊】
    M3DMatrix44f mCamera;
    cameraFrame.GetCameraMatrix(mCamera);
    modelViewMatrix.PushMatrix(mCamera);
    
    // 繪製地板
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vFloorColor);
    floorBatch.Draw();
    
    // 檢視矩陣進行平移
    modelViewMatrix.Translate(0.0f, 0.0f, -2.5f);
    
    // 保持旋轉前的檢視矩陣,因為如果不在旋轉前儲存,會導致旋轉小球也會帶上花托的旋轉【Lv2 增加的程式碼】
    modelViewMatrix.PushMatrix();
    
    // 繼續進行旋轉並進行繪製花托
    modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vTorusColor);
    torusBatch.Draw();
    
    // 恢復到旋轉前的檢視矩陣【Lv2 增加的程式碼】
    modelViewMatrix.PopMatrix();
    
    // 繪製旋轉小球【Lv2 增加的程式碼】
    modelViewMatrix.Rotate(yRot * -2.0f, 0.0f, 1.0f, 0.0f);
    modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vSphereColor);
    sphereBatch.Draw();
    
    // 出棧,恢復成原始矩陣
    modelViewMatrix.PopMatrix();
    // 因為是雙緩衝區模式,後臺緩衝區替換到前臺快取區進行顯示
    glutSwapBuffers();
    // 自動觸發渲染,達到動畫效果
    glutPostRedisplay();
}
// 特殊按鍵點選回撥【Lv2 增加的程式碼】
void SpecialKeys(int key, int x, int y) {
    float linear = 0.1f;
    float angular = float(m3dDegToRad(5.0f));

    // 控制角色向前後移動
    if(key == GLUT_KEY_UP)
        cameraFrame.MoveForward(linear);

    if(key == GLUT_KEY_DOWN)
        cameraFrame.MoveForward(-linear);

    // 控制角色向左右旋轉
    if(key == GLUT_KEY_LEFT)
        cameraFrame.RotateWorld(angular, 0.0f, 1.0f, 0.0f);

    if(key == GLUT_KEY_RIGHT)
        cameraFrame.RotateWorld(-angular, 0.0f, 1.0f, 0.0f);
}
複製程式碼

OpenGL 學習 08 - 球體世界

球體世界 Lv3(加入隨機小球分佈場景)

15-SphereWorld-Lv3 核心原始碼如下,全部原始碼下載:15-SphereWorld-Lv3

// 程式初始化
void SetupRC() {
    // 設定視窗背景顏色為黑色
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    // 初始化著色器
    shaderManager.InitializeStockShaders();
    // 開啟深度測試
    glEnable(GL_DEPTH_TEST);
    // 設定多邊形模式為前後面線段模式
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    // 得到花托批次資料
    gltMakeTorus(torusBatch, 0.4f, 0.15f, 30, 30);
    // 得到旋轉小球批次資料【Lv2 增加的程式碼】
    gltMakeSphere(sphereBatch, 0.1f, 26, 13);
    // 得到方格地板批次資料
    floorBatch.Begin(GL_LINES, 324);
    for(GLfloat x = -20.0; x <= 20.0f; x+= 0.5) {
        floorBatch.Vertex3f(x, -0.55f, 20.0f);
        floorBatch.Vertex3f(x, -0.55f, -20.0f);
        floorBatch.Vertex3f(20.0f, -0.55f, x);
        floorBatch.Vertex3f(-20.0f, -0.55f, x);
    }
    floorBatch.End();
    
    // 隨機小球群位置資料生成【Lv3 增加的程式碼】
    for(int i = 0; i < NUM_SPHERES; i++) {
        GLfloat x = ((GLfloat)((rand() % 400) - 200) * 0.1f);
        GLfloat z = ((GLfloat)((rand() % 400) - 200) * 0.1f);
        spheres[i].SetOrigin(x, 0.0f, z);
    }
}
// 視窗渲染回撥
void RenderScene(void) {
    // 獲取2次渲染之間的時間間隔
    static CStopWatch    rotTimer;
    float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
    // 清空緩衝區
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    // 原來是沒法移動所以壓入原始矩陣,但現在角色可以移動通過角色幀獲取照相機矩陣,並壓入棧中【Lv2 修改的程式碼塊】
    M3DMatrix44f mCamera;
    cameraFrame.GetCameraMatrix(mCamera);
    modelViewMatrix.PushMatrix(mCamera);
    // 繪製地板
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vFloorColor);
    floorBatch.Draw();
    
    // 繪製隨機小球群,這裡的做法是瞬間切換當前角色位置並開始繪製小球,然後切換回原來角色位置【Lv3 增加的程式碼】
    for(int i = 0; i < NUM_SPHERES; i++) {
        // 儲存當前矩陣狀態,為了能切換回原來的角色位置
        modelViewMatrix.PushMatrix();
        // 調整當前角色位置為隨機生成小球的位置
        modelViewMatrix.MultMatrix(spheres[I]);
        // 繪製藍色小球
        shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vSphereColor);
        sphereBatch.Draw();
        // 還原矩陣狀態,切換為原來角色位置
        modelViewMatrix.PopMatrix();
    }
    
    // 檢視矩陣進行平移
    modelViewMatrix.Translate(0.0f, 0.0f, -2.5f);
    // 保持旋轉前的檢視矩陣,因為如果不在旋轉前儲存,會導致旋轉小球也會帶上花托的旋轉【Lv2 增加的程式碼】
    modelViewMatrix.PushMatrix();
    // 繼續進行旋轉並進行繪製花托
    modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vTorusColor);
    torusBatch.Draw();
    // 恢復到旋轉前的檢視矩陣【Lv2 增加的程式碼】
    modelViewMatrix.PopMatrix();
    // 繪製旋轉小球【Lv2 增加的程式碼】
    modelViewMatrix.Rotate(yRot * -2.0f, 0.0f, 1.0f, 0.0f);
    modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vSphereColor);
    sphereBatch.Draw();
    // 出棧,恢復成原始矩陣
    modelViewMatrix.PopMatrix();
    // 因為是雙緩衝區模式,後臺緩衝區替換到前臺快取區進行顯示
    glutSwapBuffers();
    // 自動觸發渲染,達到動畫效果
    glutPostRedisplay();
}
複製程式碼

OpenGL 學習 08 - 球體世界

球體世界 Lv4(加入點光源著色)

16-SphereWorld-Lv4 核心原始碼如下,全部原始碼下載:16-SphereWorld-Lv4

// 程式初始化
void SetupRC() {
    // 設定視窗背景顏色為黑色
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    // 初始化著色器
    shaderManager.InitializeStockShaders();
    // 開啟深度測試
    glEnable(GL_DEPTH_TEST);
    
    // 設定多邊形模式為前後面線段模式【Lv4 刪除的程式碼】
//    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    
    // 得到花托批次資料
    gltMakeTorus(torusBatch, 0.4f, 0.15f, 30, 30);
    // 得到旋轉小球批次資料【Lv2 增加的程式碼】
    gltMakeSphere(sphereBatch, 0.1f, 26, 13);
    // 得到方格地板批次資料
    floorBatch.Begin(GL_LINES, 324);
    for(GLfloat x = -20.0; x <= 20.0f; x+= 0.5) {
        floorBatch.Vertex3f(x, -0.55f, 20.0f);
        floorBatch.Vertex3f(x, -0.55f, -20.0f);
        floorBatch.Vertex3f(20.0f, -0.55f, x);
        floorBatch.Vertex3f(-20.0f, -0.55f, x);
    }
    floorBatch.End();
    // 隨機小球群位置資料生成【Lv3 增加的程式碼】
    for(int i = 0; i < NUM_SPHERES; i++) {
        GLfloat x = ((GLfloat)((rand() % 400) - 200) * 0.1f);
        GLfloat z = ((GLfloat)((rand() % 400) - 200) * 0.1f);
        spheres[i].SetOrigin(x, 0.0f, z);
    }
}
// 視窗渲染回撥
void RenderScene(void) {
    // 獲取2次渲染之間的時間間隔
    static CStopWatch    rotTimer;
    float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
    // 清空緩衝區
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    // 原來是沒法移動所以壓入原始矩陣,但現在角色可以移動通過角色幀獲取照相機矩陣,並壓入棧中【Lv2 修改的程式碼塊】
    M3DMatrix44f mCamera;
    cameraFrame.GetCameraMatrix(mCamera);
    modelViewMatrix.PushMatrix(mCamera);
    
    // 得到點光源位置 【Lv4 增加的程式碼】
    M3DVector4f vLightPos = { 0.0f, 10.0f, 5.0f, 1.0f };
    M3DVector4f vLightEyePos;
    m3dTransformVector4(vLightEyePos, vLightPos, mCamera);
    
    // 繪製地板
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vFloorColor);
    floorBatch.Draw();
    // 繪製隨機小球群,這裡的做法是瞬間切換當前角色位置並開始繪製小球,然後切換回原來角色位置【Lv3 增加的程式碼】
    for(int i = 0; i < NUM_SPHERES; i++) {
        // 儲存當前矩陣狀態,為了能切換回原來的角色位置
        modelViewMatrix.PushMatrix();
        // 調整當前角色位置為隨機生成小球的位置
        modelViewMatrix.MultMatrix(spheres[I]);
        // 繪製藍色小球
        // 設定著色器為點光源著色器【Lv4 調整的程式碼】
        shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF, 
                                     transformPipeline.GetModelViewMatrix(),
                                     transformPipeline.GetProjectionMatrix(), 
                                     vLightEyePos, 
                                     vSphereColor);
        sphereBatch.Draw();
        // 還原矩陣狀態,切換為原來角色位置
        modelViewMatrix.PopMatrix();
    }
    // 檢視矩陣進行平移
    modelViewMatrix.Translate(0.0f, 0.0f, -2.5f);
    // 保持旋轉前的檢視矩陣,因為如果不在旋轉前儲存,會導致旋轉小球也會帶上花托的旋轉【Lv2 增加的程式碼】
    modelViewMatrix.PushMatrix();
    // 繼續進行旋轉並進行繪製花托
    modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);

    // 設定著色器為點光源著色器【Lv4 調整的程式碼】
    shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF, 
                                 transformPipeline.GetModelViewMatrix(),
                                 transformPipeline.GetProjectionMatrix(), 
                                 vLightEyePos, 
                                 vTorusColor);

    torusBatch.Draw();
    // 恢復到旋轉前的檢視矩陣【Lv2 增加的程式碼】
    modelViewMatrix.PopMatrix();
    // 繪製旋轉小球【Lv2 增加的程式碼】
    modelViewMatrix.Rotate(yRot * -2.0f, 0.0f, 1.0f, 0.0f);
    modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);

    // 設定著色器為點光源著色器【Lv4 調整的程式碼】
    shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF, 
                                 transformPipeline.GetModelViewMatrix(),
                                 transformPipeline.GetProjectionMatrix(), 
                                 vLightEyePos, 
                                 vSphereColor);

    sphereBatch.Draw();
    // 出棧,恢復成原始矩陣
    modelViewMatrix.PopMatrix();
    // 因為是雙緩衝區模式,後臺緩衝區替換到前臺快取區進行顯示
    glutSwapBuffers();
    // 自動觸發渲染,達到動畫效果
    glutPostRedisplay();
}
複製程式碼

OpenGL 學習 08 - 球體世界

上面的 Demo 原始碼全部都放在我的 github/OpenGLDemo 上,大家可以去下載和除錯。

有什麼問題可以在下方評論區提出,寫得不好可以提出你的意見,我會合理採納的,O(∩_∩)O哈哈~,求關注求贊

相關文章