OpenGL 學習 05 花托(剔除 深度測試 多邊形模式)

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

OpenGL 學習 05   花托(剔除 深度測試 多邊形模式)

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

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

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

之前我們都只是畫平面圖形,這節我們來畫一個 3D 圖形,對比下平面圖形和 3D 圖形繪製的一些區別和注意點。

基本概念

花托

一種環狀的像麵包圈一樣的物體,如果要我們自己去一個個去計算花托的每個點,是要花很多時間,GLTools 裡面已經為我們整合好了得到花托頂點資料的函式

//得到花托的頂點資料
void gltMakeTorus(
    GLTriangleBatch& torusBatch, // 返回的三角形批次,和普通批次功能類似,是專門畫三角形的批次
    GLfloat majorRadius, // 中心到外邊緣的半徑
    GLfloat minorRadius, // 外邊緣到內邊緣的距離
    GLint numMajor, // 片段數,即花托由多少個片段面組成
    GLint numMinor  // 堆疊數,即每個片段面由多少個頂點組成
); 
複製程式碼

OpenGL 學習 05   花托(剔除 深度測試 多邊形模式)

OpenGL 學習 05   花托(剔除 深度測試 多邊形模式)

油畫法渲染

在預設情況下,我們所渲染的點、線或三角形都會在螢幕上進行光柵化,並且會按照在組合批次時的指定順序進行排列,這樣就會出現一些問題,比如一些三角形在物體背面,我們應該是看不到的,或者前面繪製的三角形可能被後面繪製的三角形所覆蓋。

對於這些問題,可能的解決方法之一是,對這些三角形進行排序,先渲染較遠的三角形,再渲染較近的三角形,這種方式稱為“油畫法”。【油畫法渲染是非常低效的,重疊部分進行了多次寫操作,三角形排序開銷大】

OpenGL 學習 05   花托(剔除 深度測試 多邊形模式)

正面和背面剔除

對正面和背面三角形進行區分的原因之一就是為了進行剔除。背面剔除能極大提高效能,且能修正一些上面提出的問題。

剔除是在渲染的圖元裝配階段就整體拋棄掉一些三角形,並且沒有執行任何不恰當的光柵化操作。

OpenGL 學習 05   花托(剔除 深度測試 多邊形模式)

深度測試

深度測試是另外一種高效消除隱藏表面的技術。深度測試是在繪製每個畫素時,分配一個深度值z,表示該點與觀察者的距離**【注意,這裡是深度值越大,表示離觀察者越近】**,當另外一個畫素需要繪製在同一個位置,會先比較深度值,深度值大的說明在前面,深度值小的說明在後面。

深度測試是通過深度緩衝區實現的,深度快取區儲存了螢幕上每個畫素的深度值。要使用深度測試,一般需要配置渲染模式:

/*初始化渲染模式*/
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
複製程式碼

深度測試還有一個很強大的作用,就是處理重疊的獨立物件,這是剔除技術無法處理的一種情況。深度測試是先渲染離觀察者較近的物件,再渲染較遠的物件,和“油畫法”剛好相反。

開啟剔除,但未開啟深度測試

只開啟深度測試

多邊形模式

預設情況下,多邊形是作為實心圖形進行繪製,我們可以通過調整多邊形模式,讓多邊形繪製為只有點或輪廓。此外,我們也可以在多邊形的兩面分別應用這個多邊形模式

多邊形模式:

  • 實體(GL_FULL)
  • 輪廓(GL_LINE)
  • 點(GL_POINT)

多邊形模式應用面:

  • 正面(GL_FRONT)
  • 背面(GL_BACK)
  • 雙面(GL_FRONT_AND_BACK)

輪廓

點

原始碼解析

建立右鍵選單,便於切換各種模式

//建立選單,繫結一個響應方法
glutCreateMenu(ProcessMenu);
//為選單新增選項
glutAddMenuEntry("Toggle depth test", 1);
glutAddMenuEntry("Toggle cull backface", 2);
glutAddMenuEntry("Set Fill Mode", 3);
glutAddMenuEntry("Set Line Mode", 4);
glutAddMenuEntry("Set Point Mode", 5);
//右鍵彈出選單
glutAttachMenu(GLUT_RIGHT_BUTTON);
複製程式碼

物體 3D 旋轉,便於檢視各種模式優劣

//上、下、左、右按鍵,3D 旋轉
/*
 * RotateWorld(float fAngle, float x, float y, float z)
 * fAngle: 旋轉弧度, x/y/z:以哪個座標軸旋轉
 * m3dDegToRad:角度 -> 弧度
 * viewFrame:物體世界座標
 */
switch (key) {
    case GLUT_KEY_UP:
        viewFrame.RotateWorld(m3dDegToRad(-5.0f), 1.0f, 0.0f, 0.0f);
        break;
    case GLUT_KEY_DOWN:
        viewFrame.RotateWorld(m3dDegToRad(5.0f), 1.0f, 0.0f, 0.0f);
        break;
    case GLUT_KEY_LEFT:
        viewFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0f, 1.0f, 0.0f);
        break;
    case GLUT_KEY_RIGHT:
        viewFrame.RotateWorld(m3dDegToRad(5.0f), 0.0f, 1.0f, 0.0f);
        break;
    default:
        break;
}
複製程式碼

開啟/關閉剔除

// 開啟剔除
glEnable(GL_CULL_FACE);
// 關閉剔除
glDisable(GL_CULL_FACE);
// 預設我們是剔除背面,也可以剔除正面,調以下函式進行控制
glCullFace(GL_FRONT); // 控制剔除的面為正面
glCullFace(GL_BACK); // 控制剔除的面為背面
glCullFace(GL_FRONT_AND_BACK); // 控制剔除的面為雙面
複製程式碼

開啟/關閉深度測試

//  開啟深度測試
glEnable(GL_DEPTH_TEST);
// 關閉深度測試
glDisable(GL_DEPTH_TEST);
複製程式碼

配置多邊形模式

//多邊形面模式為雙面實體
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
//多邊形面模式為雙面輪廓
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
//多邊形面模式為雙面點
glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
複製程式碼

全部原始碼

#include <GLTools.h>    // OpenGL toolkit
#include <GLMatrixStack.h>
#include <GLFrame.h>
#include <GLFrustum.h>
#include <GLGeometryTransform.h>
#include <math.h>
#include <glut/glut.h>

GLFrame             viewFrame;
GLFrustum           viewFrustum;
GLTriangleBatch     torusBatch;
GLMatrixStack       modelViewMatrix;
GLMatrixStack       projectionMatrix;
GLGeometryTransform transformPipeline;
GLShaderManager     shaderManager;

//是否開啟正反面剔除
int iCull = 0;
//是否開始深度測試
int iDepth = 0;

//點選選單選項
void ProcessMenu(int value) {
    switch(value) {
        case 1:
            //開關深度測試
            iDepth = !iDepth;
            break;
        case 2:
            //開關正反面剔除
            iCull = !iCull;
            break;
        case 3:
            //開關多邊形面模式
            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
            break;
        case 4:
            //開關多邊形線模式
            glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
            break;
        case 5:
            //開關多邊形點模式
            glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
            break;
    }
    
    //觸發渲染
    glutPostRedisplay();
}

//渲染畫面
void RenderScene(void) {
    //清除一個或一組特定的緩衝區
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    //判斷是否開啟正反面剔除
    if(iCull)
        glEnable(GL_CULL_FACE);
    else
        glDisable(GL_CULL_FACE);

    //判斷是否開啟深度測試
    if(iDepth)
        glEnable(GL_DEPTH_TEST);
    else
        glDisable(GL_DEPTH_TEST);
    
    //儲存當前的模型檢視矩陣
    modelViewMatrix.PushMatrix(viewFrame);
    
    //這裡使用預設光源進行著色,可以看到陰影
    GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 1.0f };
    shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed);
    
    //畫花托
    torusBatch.Draw();
    
    // 還原以前的模型檢視矩陣
    modelViewMatrix.PopMatrix();
    
    //將在後臺緩衝區進行渲染,然後在結束時交換到前臺
    glutSwapBuffers();
}

//為程式作一次性的設定
void SetupRC() {
    //設定視窗背景顏色
    glClearColor(0.3f, 0.3f, 0.3f, 1.0f );
    
    //初始化著色器管理器
    shaderManager.InitializeStockShaders();
    
    //設定變換管線以使用兩個矩陣堆疊
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
    
    //移動物體的位置,值越大物體越遠,值越小物體越近
    viewFrame.MoveForward(6.0f);
    
    //建立一個花托批次,引數依次是:批次,外半徑,內半徑,片段數,堆疊數(在計算機中圓是正多邊形,頂點數越多越像真正的圓)
    gltMakeTorus(torusBatch, 1.0f, 0.3f, 52, 26);
    
    //設定預設點大小
    glPointSize(4.0f);
}

//特殊按鍵(功能鍵或者方向鍵)監聽
void SpecialKeys(int key, int x, int y) {
    //上、下、左、右按鍵,3D 旋轉
    /*
     * RotateWorld(float fAngle, float x, float y, float z)
     * fAngle: 旋轉弧度, x/y/z:以哪個座標軸旋轉
     * m3dDegToRad:角度 -> 弧度
     */
    switch (key) {
        case GLUT_KEY_UP:
            viewFrame.RotateWorld(m3dDegToRad(-5.0f), 1.0f, 0.0f, 0.0f);
            break;
        case GLUT_KEY_DOWN:
            viewFrame.RotateWorld(m3dDegToRad(5.0f), 1.0f, 0.0f, 0.0f);
            break;
        case GLUT_KEY_LEFT:
            viewFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0f, 1.0f, 0.0f);
            break;
        case GLUT_KEY_RIGHT:
            viewFrame.RotateWorld(m3dDegToRad(5.0f), 0.0f, 1.0f, 0.0f);
            break;
        default:
            break;
    }
    
    //觸發渲染
    glutPostRedisplay();
}

//視窗大小改變時接受新的寬度和高度,其中0,0代表視窗中視口的左下角座標,w,h代表畫素
void ChangeSize(int width, int height) {
    // 防止下面除法的除數為0導致的閃退
    if(height == 0) height = 1;
    
    //設定檢視視窗位置
    glViewport(0, 0, width, height);
    
    // 建立投影矩陣,並將它載入到投影矩陣堆疊中
    viewFrustum.SetPerspective(35.0f, float(width) / float(height), 1.0f, 500.0f);
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
}

//程式入口
int main(int argc, char* argv[]) {
    //設定當前工作目錄,針對MAC OS X
    gltSetWorkingDirectory(argv[0]);
    
    //初始化GLUT庫
    glutInit(&argc, argv);
    
    /*初始化渲染模式,其中標誌GLUT_DOUBLE、GLUT_RGBA、GLUT_DEPTH、GLUT_STENCIL分別指
     雙緩衝視窗、RGBA顏色模式、深度測試、模板緩衝區*/
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
    
    //初始化視窗大小
    glutInitWindowSize(800, 600);
    //建立視窗
    glutCreateWindow("Geometry Test Program");
    
    //註冊回撥函式
    glutReshapeFunc(ChangeSize);
    glutDisplayFunc(RenderScene);
    glutSpecialFunc(SpecialKeys);
    
    //建立選單,繫結一個響應方法
    glutCreateMenu(ProcessMenu);
    
    //為選單新增選項
    glutAddMenuEntry("Toggle depth test",1);
    glutAddMenuEntry("Toggle cull backface",2);
    glutAddMenuEntry("Set Fill Mode", 3);
    glutAddMenuEntry("Set Line Mode", 4);
    glutAddMenuEntry("Set Point Mode", 5);
    
    //右鍵彈出選單
    glutAttachMenu(GLUT_RIGHT_BUTTON);
    
    //確保驅動程式的初始化中沒有出現任何問題。
    GLenum err = glewInit();
    if(GLEW_OK != err) {
        fprintf(stderr, "glew error:%s\n", glewGetErrorString(err));
        return 1;
    }
    
    //初始化設定
    SetupRC();
    
    //進入呼叫迴圈
    glutMainLoop();
    return 0;
}
複製程式碼

Demo 原始碼在這裡:github->openGLDemo->04-GeoTest

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

相關文章