OpenGL學(xué)習(xí)之路(3.0):OpenGL 深度測(cè)試

[TOC]

學(xué)習(xí)目標(biāo)

  • 渲染過程中可能產(chǎn)生的問題
  • 油畫渲染
  • 正面和背面剔除
  • 深度測(cè)試
  • 多邊形模型
  • 多邊形偏移
  • 裁剪
  • 顏色混合

一、在渲染過程中可能產(chǎn)生的問題

在繪制3D場(chǎng)景的時(shí)候,我們需要決定哪些部分是對(duì)觀察者可見的,或者哪些部分是對(duì)觀察者不可?的.對(duì)于不可見的部分,應(yīng)該及早丟棄.例例如在?個(gè)不透明的墻壁后,就不應(yīng)該渲染.這種情況叫做”隱藏?消除”(Hidden surface elimination).
比如以下的圖形:

image.png

1.1、解決方案:油畫法

  • 油畫算法
    • 先繪制場(chǎng)景中的離觀察者較遠(yuǎn)的物體,再繪制較近的物體。
    • 例如下面的圖例:
      • 先繪制紅色部分,再繪制黃色部分,最后再繪制灰色部分,即可解決隱藏面消除的問題


        image.png
      • 但是這樣就沒有弊端了嗎? 答案是NO!

1.2、解決方案:油畫弊端

  • 油畫算法
    • 使用油畫算法,只要將場(chǎng)景按照物理距離觀察者的距離遠(yuǎn)近排序,由遠(yuǎn)及近的繪制即可。那么會(huì)出現(xiàn)什么問題?如果三個(gè)三角形是疊加的情況下,油畫算法將無法處理。


      image.png

1.3、解決方案:正背面剔除(Face Culling)

  • 背景
    • 嘗試相信一個(gè)3D圖形,你從任何一個(gè)方向去觀察,最多可以看到幾個(gè)面?
      • 答案是最多3個(gè)面。從一個(gè)立方體的任意位置和方向上看,你不可能看到多于3個(gè)面。
      • 那么思考?
        • 我們能以某種方式去丟棄這部分?jǐn)?shù)據(jù),OpenGL在渲染的性能即可提高超過50%。
      • 解決問題
        • 如何知道某個(gè)面在觀察者的事業(yè)中出現(xiàn)呢?
        • 任何平面都有2個(gè)面,正面/背面。這意味著你一個(gè)時(shí)刻只能看到一面。
        • OpenGL 可以做到檢查所有正面朝向觀察者的面,并渲染它們。從而丟棄背面朝向的面。這樣可以節(jié)約片元著色器的性能。
        • 那么問題又來了?如何告訴OpenGL你繪制的圖形,哪個(gè)是正面,哪個(gè)面是背面?
          • 答案:通過分析頂點(diǎn)數(shù)據(jù)的順序來達(dá)到目的。

1.5、解決方案:分析頂點(diǎn)順序

  • 如圖可以看到頂點(diǎn)順序
    • image.png
    • 正/背面區(qū)分
      • 正面:按照逆時(shí)針頂點(diǎn)鏈接順序的三角形面
      • 背面:按照順時(shí)針頂點(diǎn)鏈接順序的三角形面

1.6、解決方案:分析立方體中的正背面

  • image.png
  • 分析
    • 左側(cè)三角形頂點(diǎn)順序?yàn)椋?->2->3; 右側(cè)三角形的頂點(diǎn)順序?yàn)椋?->2->3.
    • 當(dāng)觀察者在右側(cè)時(shí),則右邊的三角形方向逆時(shí)針方向則為正面,而左側(cè)的三角形為順時(shí)針則為背面。
    • 當(dāng)觀察者在左側(cè)時(shí),則左邊的三角形逆時(shí)針方向則為正面,而右則的三角形為順時(shí)針則為背面。
  • 得出結(jié)論
    • 正面和背面是有三角形的頂點(diǎn)定義順序和觀察者方向共同決定的,隨著觀察者的角度方向的改變,正面背面也會(huì)跟著改變

1.7、解決方案:正背面剔除的常用函數(shù)

  • 開啟表面剔除(默認(rèn)背面剔除)
    void glEnable(GL_CULL_FACE);    
  • 關(guān)閉表面剔除(默認(rèn)背面剔除)
    void glDisable(GL_CULL_FACE);
  • 用戶選擇剔除那個(gè)面(正面/背面)
    //mode參數(shù)為: GL_FRONT,GL_BACK,GL_FRONT_AND_BACK ,默認(rèn)GL_BACK(背面剔除)
     void glCullFace(GLenum mode);

  • 用戶指定繞序哪個(gè)為正面(根據(jù)自己自定義哪個(gè)面是正面),一般使用上面那個(gè)方法就可以了
    /**
    mode參數(shù)為: GL_CW,GL_CCW,默認(rèn)值:GL_CCW
    GL_CW 順時(shí)針為正面,GL_CCW 逆時(shí)針為正面
    */
    void glFrontFace(GLenum mode);

  • 例如,剔除正面實(shí)現(xiàn)(1)
    glCullFace(GL_BACK);
    glFrontFace(GL_CW);

  • 例如,剔除正面實(shí)現(xiàn)(2)一般用這種,上面那種代碼冗余

    glCullFace(GL_FRONT);

源碼實(shí)例一:

//工具類
#include "GLTools.h"
//矩陣堆棧
#include "GLMatrixStack.h"
//投影矩陣
#include "GLFrame.h"
//矩陣
#include "GLFrustum.h"
//幾何變換管道
#include "GLGeometryTransform.h"

#include <math.h>
#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>
#endif

// 觀察者照相機(jī)
GLFrame             viewFrame;
//使用GLFrustum類來設(shè)置透視投影
GLFrustum           viewFrustum;
//容器幫助類
GLTriangleBatch     torusBatch;
//模型視圖矩陣
GLMatrixStack       modelViewMatix;
//投影視圖矩陣
GLMatrixStack       projectionMatrix;
//幾何變換管道
GLGeometryTransform transformPipeline;
//著色器
GLShaderManager     shaderManager;

 // 標(biāo)記背面剔除、深度測(cè)試
 int iCull = 0;
 int iDepth = 0;

// 這個(gè)函數(shù)不需要初始化渲染
// context. 圖像上下文
void SetupRc() {
    
    //設(shè)置背景顏色
    glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
    
    //初始化著色器管理器
    shaderManager.InitializeStockShaders();
    
    //將照相機(jī)向后移動(dòng)7個(gè)單元,這是肉眼到物體的距離
    viewFrame.MoveForward(7.0f);
    
    //創(chuàng)建一個(gè)甜甜圈
    // void gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor);
    /**
     參數(shù)一:容器幫助類
     參數(shù)二:外邊緣半徑(主半徑)
     參數(shù)三:內(nèi)邊緣半徑(從半徑)
     參數(shù)四五:主半徑和從半徑的細(xì)分單元(三角形)數(shù)量
     */
    gltMakeTorus(torusBatch, 1.0f, 0.3f, 52, 26);
    
    //設(shè)置點(diǎn)的大小
    glPointSize(4.0f);
    
}
// 召喚場(chǎng)景
void RenderScene(void) {
    
    //清除窗口和深度緩沖區(qū)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    //根據(jù)設(shè)置iClull標(biāo)記來判斷是否開啟背面剔除
    if (iCull) {
        
        //開啟背面剔除
        glEnable(GL_CULL_FACE);
        //指定逆向針順序三角形為正面/指定順時(shí)針下三角形為正面
        glFrontFace(GL_CCW);
        //切除那個(gè)面
        glCullFace(GL_BACK);
    }
    
    /**
     模型視圖矩陣:圖形發(fā)生變化:平移/旋轉(zhuǎn)/縮放 放射變換,模型視圖矩陣就是為了記錄這些矩陣值
     投影矩陣:投影方式正投影/透視,通過投影矩陣來記錄這些矩陣值
     */
    //把攝像機(jī)矩陣壓入模型矩陣中-壓棧方式
    modelViewMatix.PushMatrix(viewFrame);
    
    GLfloat vRed[] = {1.0f, 0.0f, 0.0f, 1.0f};
    
    //使用平面著色器
    //參數(shù)1:平面著色器
    //參數(shù)2:模型視圖投影矩陣
    //有幾種方式:transformPipeline.GetModelViewMatrix():模型視圖矩陣,GetNormalMatrix()默認(rèn)視圖矩陣,GetProjectionMatrix()投影視圖矩陣,GetModelViewProjectionMatrix() 模型視圖投影矩陣
    //參數(shù)3:顏色
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vRed);
    
    
    //使用默認(rèn)光源著色器
    //通過光源、陰影效果跟提現(xiàn)立體效果
    //參數(shù)1:GLT_SHADER_DEFAULT_LIGHT 默認(rèn)光源著色器 - 著色器類型
    //參數(shù)2:模型視圖矩陣:
    //參數(shù)3:投影矩陣
    //參數(shù)4:基本顏色值
   // shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed);
    
    //繪制
    torusBatch.Draw();
    
    //出棧
    modelViewMatix.PopMatrix();
    
    //
    glutSwapBuffers();
}

//右鍵菜單欄選項(xiàng)
void ProcessMunu(int value) {
    
    switch (value) {
        case 1:
            //是否開啟正/背面剔除
            iCull = !iCull;
            break;
            
        default:
            break;
    }
    
    glutPostRedisplay();
}



void SpecailKeys(int key, int x, int y) {
    
    if (key == GLUT_KEY_UP) {
        viewFrame.RotateWorld(m3dDegToRad(-5.0), 1.0, 0.0, 0.0);
    }
    
    if (key == GLUT_KEY_DOWN) {
        viewFrame.RotateWorld(m3dDegToRad(5.0), 1.0, 0.0, 0.0);
    }
    
    if (key == GLUT_KEY_LEFT) {
        viewFrame.RotateWorld(m3dDegToRad(-5.0), 0.0, 1.0, 0.0);
    }
    
    if (key == GLUT_KEY_RIGHT) {
        viewFrame.RotateWorld(m3dDegToRad(5.0), 0.0, 1.0, 0.0);
    }
    
    //重新刷新window
    glutPostRedisplay();
    
}

void ChangeSize(int w, int h) {
    
 //防止h為0
    if (h == 0) {
        h = 1;
    }
    
    //設(shè)置窗口尺寸
    glViewport(0, 0, w, h);
    
    //創(chuàng)建透視投影,并將它載入到投影矩陣堆棧中
    /*SetPerspective
     參數(shù):
     1.垂直方向上的視場(chǎng)角度
     2.窗口的寬度與高度的縱橫比
     3.近裁剪面距離
     4.遠(yuǎn)裁剪面距離
     */
    //設(shè)置透視模式,初始化其透視矩陣
    viewFrustum.SetPerspective(35.0f, float(w)/float(h), 1.0f, 100.f);
    
    // 把透視矩陣加載到透視矩陣隊(duì)陣中
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    
    //初始化渲染管線
    transformPipeline.SetMatrixStacks(modelViewMatix, projectionMatrix);
    
}

int main(int argc, char* argv[])
{
    //設(shè)置工作路徑
    gltSetWorkingDirectory(argv[0]);
    //初始化
    glutInit(&argc, argv);
    //初始化渲染模型
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
    //設(shè)置窗口大小
    glutInitWindowSize(800, 600);
    //設(shè)置窗口標(biāo)題
    glutCreateWindow("Geometry Test Program");
    //注冊(cè)回調(diào)函數(shù)(渲染、尺寸)
    glutReshapeFunc(ChangeSize);
    //特殊鍵位函數(shù)(上下左右)
    glutSpecialFunc(SpecailKeys);
    // 顯示函數(shù)
    glutDisplayFunc(RenderScene);
    
    //創(chuàng)建右鍵菜單
    glutCreateMenu(ProcessMunu);
    
    glutAddMenuEntry("Toggle cull backFace", 1);
    glutAddMenuEntry("Toggle depth test", 2);
    glutAddMenuEntry("Set Line Mode", 3);
    glutAddMenuEntry("Set Line Mode", 4);
    glutAddMenuEntry("Set Point mode", 5);
    
    //設(shè)置右鍵
    glutAttachMenu(GLUT_RIGHT_BUTTON);
    
    GLenum err = glewInit();
    if (GLEW_OK != err) {
        fprintf(stderr, "GLEW Error: %s\n", glewGetString(err));
        return 1;
    }
    
    SetupRc();
    
    glutMainLoop();
    return 0;
    
}

  • 通過平面著色器運(yùn)行代碼后出現(xiàn)的效果
    • image.png
  • 通過默認(rèn)光源著色器運(yùn)行代碼效果如下:
    • image.png
      • 那么會(huì)發(fā)現(xiàn)文章開始時(shí)的圖片一樣,圖為未開啟正/背面剔除
    • 開啟正/背面剔除后的效果

      • image.png
      • 雖然那些黑色頁面已經(jīng)消除掉,但是又遺留下問題-凹槽,那么怎么解決呢?答案是深度測(cè)試。

1.8、了解深度

  • 什么是深度?
    • 深度其實(shí)就是該像素點(diǎn)在3D世界中距離攝像機(jī)的距離Z值
  • 什么是深度緩沖區(qū)?
    • 深度緩存區(qū),就是一塊內(nèi)存區(qū)域,專門儲(chǔ)存著每個(gè)像素點(diǎn)(繪制在屏幕上的)深度值。深度值(Z值)越大,則離攝像機(jī)越遠(yuǎn)。
  • 為什么需要緩沖區(qū)?
    • 在不使用深度測(cè)試的時(shí)候,如果我們先繪制一個(gè)距離比較近的物理,再繪制距離較遠(yuǎn)的物理,則距離遠(yuǎn)的位圖因?yàn)楹罄L制,會(huì)被距離近的物體覆蓋掉。有了深度緩沖區(qū)后,繪制 物體的順序就不那么重要的。實(shí)際上,只要存在深度緩沖區(qū),OpenGL都會(huì)把像素的深度值寫入到緩沖區(qū)中。除非調(diào)用glDepthMask(GL_FALSE)來禁止寫入。


      image.png

1.9、解決?法: Z-buffer?法(深度緩沖區(qū)Depth-buffer)

  • 深度測(cè)試
    • 深度緩沖區(qū)(DepthBuffer)和顏色緩存區(qū)(ColorBuffer)是對(duì)應(yīng)的。顏色緩存區(qū)儲(chǔ)存像素的顏色信息,?深度緩沖區(qū)存儲(chǔ)像素的深度信息. 在決定是否繪制一個(gè)物體表?時(shí), ?先要將表?對(duì)應(yīng)的像素的深度值與當(dāng)前深度緩沖區(qū)中的值進(jìn)?比較. 如果大于深度緩沖區(qū)中的值,則丟棄這部分.否則 利用這個(gè)像素對(duì)應(yīng)的深度值和顏?值.分別更新深度緩沖區(qū)和顏?緩存區(qū). 這個(gè)過程稱為”深度測(cè)試”

二、使用深度測(cè)試

  • 深度緩沖區(qū),一般由窗口管理系統(tǒng),GLFW創(chuàng)建。深度一般由16位,24位,32位值來表示。通常是24位,位數(shù)越高,深度精度越好。
  • 開啟深度測(cè)試

        glClearColor(0.0f,0.0f,0.0f,1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  • 清除深度緩沖區(qū)默認(rèn)值為1.0,表示最大的深度值,深度值的范圍(0,1)之間。值越小表示越靠近觀察者,值越大表示越遠(yuǎn)表示越遠(yuǎn)離觀察者。

2.1、指定深度測(cè)試判斷式

        //指定深度測(cè)試判斷模式
        void glDepthFunc(GLEnum mode);
  • 默認(rèn)是GL_LESS


  • 如果想要打開或者阻止深度緩存的寫入?
    void glDepthMask(GLBool value);
    value : GL_TURE 開啟深度緩沖區(qū)寫入; //GL_FALSE 關(guān)閉深度緩沖區(qū)寫?

  • 深度測(cè)試代碼

//右鍵菜單欄選項(xiàng)
void ProcessMunu(int value) {
    
    switch (value) {
        case 1:
            //是否開啟正/背面剔除
            iCull = !iCull;
            break;
        case 2:
            // 是否開啟深度測(cè)試
            iDepth = !iDepth;
            break;
            
        case 3:
            // 填充方式-三角形
            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
            break;
            
        case 4:
            //填充方式-線
            glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
            break;
            
        case 5:
            //填充方式-點(diǎn)
            glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
            break;
    }
    
    //無論上面選擇了哪一個(gè)選項(xiàng),都修改了顯示效果,所以需要重新渲染
    glutPostRedisplay();
}

// 召喚場(chǎng)景
void RenderScene(void) {
    
    //清除窗口和深度緩沖區(qū)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    //根據(jù)設(shè)置iClull標(biāo)記來判斷是否開啟背面剔除
    if (iCull) {
        
        //開啟背面剔除
        glEnable(GL_CULL_FACE);
        //指定逆向針順序三角形為正面/指定順時(shí)針下三角形為正面
        glFrontFace(GL_CCW);
        //切除那個(gè)面
        glCullFace(GL_BACK);
        
    }else {
        
        glDisable(GL_CULL_FACE);
    }
    
    //根據(jù)設(shè)置iDepth標(biāo)記來判斷是否開啟深度測(cè)試
    if (iDepth) {
        glEnable(GL_DEPTH_TEST);
    }else {
        glDisable(GL_DEPTH_TEST);
    }
    
    /**
     模型視圖矩陣:圖形發(fā)生變化:平移/旋轉(zhuǎn)/縮放 放射變換,模型視圖矩陣就是為了記錄這些矩陣值
     投影矩陣:投影方式正投影/透視,通過投影矩陣來記錄這些矩陣值
     */
    //把攝像機(jī)矩陣壓入模型矩陣中-壓棧方式
    modelViewMatix.PushMatrix(viewFrame);
    
    GLfloat vRed[] = {1.0f, 0.0f, 0.0f, 1.0f};
    
    //使用平面著色器
    //參數(shù)1:平面著色器
    //參數(shù)2:模型視圖投影矩陣
    //有幾種方式:transformPipeline.GetModelViewMatrix():模型視圖矩陣,GetNormalMatrix()默認(rèn)視圖矩陣,GetProjectionMatrix()投影視圖矩陣,GetModelViewProjectionMatrix() 模型視圖投影矩陣
    //參數(shù)3:顏色
    //    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vRed);
    
    
    //使用默認(rèn)光源著色器
    //通過光源、陰影效果跟提現(xiàn)立體效果
    //參數(shù)1:GLT_SHADER_DEFAULT_LIGHT 默認(rèn)光源著色器 - 著色器類型
    //參數(shù)2:模型視圖矩陣:
    //參數(shù)3:投影矩陣
    //參數(shù)4:基本顏色值
    shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed);
    
    //繪制
    torusBatch.Draw();
    
    //出棧
    modelViewMatix.PopMatrix();
    
    //
    glutSwapBuffers();
}

  • 但是又會(huì)產(chǎn)生另外一個(gè)問題ZFighting閃爍,那么什么是ZFighting呢?為什么會(huì)導(dǎo)致呢?

三、ZFighting問題的原因

  • 為什么會(huì)出現(xiàn)ZFighting閃爍問題

    • 因?yàn)殚_啟深度測(cè)試后,OpenGL 就不會(huì)再去繪制模型被遮擋的部分. 這樣實(shí)現(xiàn)的顯示更加真實(shí).但是 由于深度緩沖區(qū)精度的限制對(duì)于深度相差?常?的情況下.(例如在同一平面上進(jìn)行2次制),OpenGL就可能出現(xiàn)不能正確判斷兩者的深度值,會(huì)導(dǎo)致深度測(cè)試的結(jié)果不可預(yù)測(cè).顯示出來的現(xiàn)象時(shí)交錯(cuò)閃爍的前面2個(gè)畫?交錯(cuò)出現(xiàn).


      image.png

      image.png
  • 如圖,因?yàn)槎鄠€(gè)畫面在同一深度緩沖區(qū)時(shí)導(dǎo)致深度測(cè)試的結(jié)果不可預(yù)測(cè),所以顯示現(xiàn)象交錯(cuò)閃爍的前面2個(gè)畫面交錯(cuò)出現(xiàn)的情況。

3.1、ZFighting閃爍問題問題解決

  • 第一步:?jiǎn)⒂肞olygon Offset方式解決
    • 解決方法: 讓深度值之間產(chǎn)生間隔.如果2個(gè)圖形之間有間隔,是不是意味著就不會(huì)產(chǎn)?干涉.可以理解為在執(zhí)?深度測(cè)試前將?方體的深度值做?些細(xì)微的增加.于是就能將重疊的2個(gè)圖形深度值有所區(qū)分.
    //啟?Polygon Offset?式: glEnable(GL_POLYGON_OFFSET_FILL)
    //參數(shù)列表: GL_POLYGON_OFFSET_POINT GL_POLYGON_OFFSET_LINE 

    GL_POLYGON_OFFSET_FILL

    //對(duì)應(yīng)光柵化模式: GL_POINT 對(duì)應(yīng)光柵化模式: GL_LINE
    //對(duì)應(yīng)光柵化模式: GL_FILL

  • 第二步:指定偏移量

    • 通過glPolygonOffset 來指定glPolygonOffset 需要2個(gè)參數(shù): factor , units
    • 每個(gè)Fragment 的深度值都會(huì)增加如下所示的偏移量:
              //m : 多邊形的深度的斜率的最?大值,理理解?一個(gè)多邊形越是與近裁剪?平行,m 就越接近于0.
              //r : 能產(chǎn)?于窗?坐標(biāo)系的深度值中可分辨的差異最?值.r 是由具體是由具體OpenGL 平臺(tái)指定的 ?個(gè)常量.
      
                  Offset = ( m * factor ) + ( r * units);
      
      
      • ?個(gè)大于0的Offset 會(huì)把模型推到離你(攝像機(jī))更遠(yuǎn)的位置,相應(yīng)的?個(gè)小于0的Offset會(huì)把模型拉近
      • ?般?言,只需要將-1.0和1.0 這樣簡(jiǎn)單賦值給glPolygonOffset 基本可以滿足需求.
          void glPolygonOffset(Glfloat factor,Glfloat units);
          //應(yīng)?到片段上總偏移計(jì)算方程式:
          Depth Offset = (DZ * factor) + (r * units); DZ:深度值(Z值)
          // r:使得深度緩沖區(qū)產(chǎn)?生變化的最?值
          //負(fù)值,將使得z值距離我們更近,而正值,將使得z值距離我們更遠(yuǎn)
      
      
  • 第三步:關(guān)閉Polygon Offset


    glDisable(GL_POLYGON_OFFSET_FILL)

3.2、ZFighting閃爍問題預(yù)防

  • 不要將兩個(gè)物體靠的太近,避免渲染時(shí)三?形疊在?起。這種方式要求對(duì)場(chǎng)景中物體插入一個(gè)少量的偏移,那么就可能避免ZFighting現(xiàn)象。例如上面的立方體和平面問題中,將平面下移0.001f就可以解決這個(gè)問題。當(dāng)然?動(dòng)去插?這個(gè)小的偏移是要付出代價(jià)的。
  • 盡可能將近裁剪面設(shè)置得離觀察者遠(yuǎn)?些。上?我們看到,在近裁剪平?附近,深度的精確度是很?高的,因此盡可能讓近裁剪面遠(yuǎn)一些的話,會(huì)使整個(gè)裁剪范圍內(nèi)的精確度變高?些。但是這種?式會(huì)使離觀察者較近的物體被裁減掉,因此需要調(diào)試好裁剪?參數(shù)。
  • 使?更高位數(shù)的深度緩沖區(qū),通常使?的深度緩沖區(qū)是24位的,現(xiàn)在有一些硬件使用32位的緩沖區(qū),使精確度得到提?。

四、裁剪

在OpenGL 中提?渲染的?種?式.只刷新屏幕上發(fā)?變化的部分OpenGL 允許將要進(jìn)行渲染的窗?只 去指定?個(gè)裁剪框.
基本原理:?于渲染時(shí)限制繪制區(qū)域,通過此技術(shù)可以再屏幕(幀緩沖)指定?個(gè)矩形區(qū)域。啟用剪裁測(cè)試之后,不在此矩形區(qū)域內(nèi)的片元被丟棄,只有在此矩形區(qū)域內(nèi)的?元才有可能進(jìn)入幀緩沖。因此實(shí)際達(dá)到的效果就是在屏幕上開辟了了?個(gè)?窗口,可以再其中進(jìn)行指定內(nèi)容的繪制。


    //1 開啟裁剪測(cè)試 glEnable(GL_SCISSOR_TEST);
    //2.關(guān)閉裁剪測(cè)試 glDisable(GL_SCISSOR_TEST);
    //3.指定裁剪窗?
    void glScissor(Glint x,Glint y,GLSize width,GLSize height);
    x,y:指定裁剪框左下?位置; width , height:指定裁剪尺?

4.1、理解窗口,視口,裁剪區(qū)域

  • 窗?: 就是顯示界?
  • 視?: 就是窗口中用來顯示圖形的?塊矩形區(qū)域,它可以和窗口等大,也可以?窗??或者?。只有繪制在視口區(qū)域中的圖形才能被顯示,如果圖形有?部分超出了視口區(qū)域,那么那?部分是看不到的。
  • 通過glViewport()函數(shù)設(shè)置。裁剪區(qū)域(平行投影):就是視?矩形區(qū)域的最小最大x坐標(biāo)(left,right)和最?最?y坐標(biāo) (bottom,top),?不是窗口的最小最大x坐標(biāo)和y坐標(biāo)。通過glOrtho()函數(shù)設(shè)置,這個(gè)函數(shù)還需指定最近最遠(yuǎn)z坐標(biāo),形成一個(gè)?體的裁剪區(qū)域。
image.png

image.png

代碼實(shí)例



//demo OpenGL 裁剪
#include "GLTools.h"
#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>
#endif

//召喚場(chǎng)景
void RenderScene(void)
{
    //設(shè)置清屏顏色為藍(lán)色
    glClearColor(0.0f, 0.0f, 1.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    
    //1.現(xiàn)在剪成小紅色分區(qū)
    //(1)設(shè)置裁剪區(qū)顏色為紅色
    glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
    //(2)設(shè)置裁剪尺寸
    glScissor(100, 100, 600, 400);
    //(3)開啟裁剪測(cè)試
    glEnable(GL_SCISSOR_TEST);
    //(4)開啟清屏,執(zhí)行裁剪
    glClear(GL_COLOR_BUFFER_BIT);
    
    // 2.裁剪一個(gè)綠色的小矩形
    //(1).設(shè)置清屏顏色為綠色
    glClearColor(0.0f, 1.0f, 0.0f, 0.0f);
    //(2).設(shè)置裁剪尺寸
    glScissor(200, 200, 400, 200);
    //(3).開始清屏執(zhí)行裁剪
    glClear(GL_COLOR_BUFFER_BIT);
    
    //關(guān)閉裁剪測(cè)試
    glDisable(GL_SCISSOR_TEST);
    
    //強(qiáng)制執(zhí)行緩存區(qū)
    glutSwapBuffers();
}

void ChangeSize(int w, int h)
{
    //保證高度不能為0
    if(h == 0)
        h = 1;
    
    // 將視口設(shè)置為窗口尺寸
    glViewport(0, 0, w, h);
}

//程序入口
int main(int argc, char* argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
    glutInitWindowSize(800,600);
    glutCreateWindow("OpenGL Scissor");
    glutReshapeFunc(ChangeSize);
    glutDisplayFunc(RenderScene);
    glutMainLoop();
    
    return 0;
}

五、混合

我們把OpenGL 渲染時(shí)會(huì)把顏?值存在顏色緩存區(qū)中,每個(gè)?片段的深度值也是放在深度緩沖區(qū)。當(dāng)深度 緩沖區(qū)被關(guān)閉時(shí),新的顏色將簡(jiǎn)單的覆蓋原來顏色緩存區(qū)存在的顏?值,當(dāng)深度緩沖區(qū)再次打開時(shí),新的顏?片段只是當(dāng)它們?原來的值更接近鄰近的裁剪平?才會(huì)替換原來的顏?片段。


    glEnable(GL_BlEND);

  • 5.1、組合顏色

    • ?標(biāo)顏色:已經(jīng)存儲(chǔ)在顏色緩存區(qū)的顏?值
    • 源顏色:作為當(dāng)前渲染命令結(jié)果進(jìn)入顏?緩存區(qū)的顏?值 當(dāng)混合功能被啟動(dòng)時(shí),源顏色和?標(biāo)顏色的組合方式是混合方程式控制的。在默認(rèn)情況下,
    • 混合?程式如下所示:
        Cf = (Cs * S) + (Cd * D)
        Cf: 最終計(jì)算參數(shù)的顏色 
        Cs: 源顏色
        Cd: ?標(biāo)顏色 
        S : 源合因子 
        D : 目標(biāo)混合因子
    
    
  • 5.2、設(shè)置混合因子

    • 設(shè)置混合因子,需要用到glBlendFund函數(shù)

      glBlendFunc(GLenum S,GLenum D);
      S:源合因?
      D:?標(biāo)混合因?
      
      
      image.png

      表中R、G、B、A 分別代表 紅、綠、藍(lán)、alpha。
      表中下標(biāo)S、D,分別代表源、目標(biāo)
      表中C 代表常量顏?(默認(rèn)?色)

5.3 課堂案例

下?通過?個(gè)常見的混合函數(shù)組合來說明問題:

    
    glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

如果顏?緩存區(qū)已經(jīng)有?種顏?紅色(1.0f,0.0f,0.0f,0.0f),這個(gè)?標(biāo)顏色Cd,如果在這上?用?種alpha為0.6的藍(lán)?(0.0f,0.0f,1.0f,0.6f)
Cd (?標(biāo)顏色) = (1.0f,0.0f,0.0f,0.0f);
Cs (源顏色) = (0.0f,0.0f,1.0f,0.6f);
S = 源alpha值 = 0.6f
D = 1 - 源alpha值 = 1-0.6f = 0.4f
?程式 Cf = (Cs * S) + (Cd * D)
等價(jià)于 = (Blue * 0.6f) + (Red * 0.4f)

5.4、總結(jié)

最終顏色是以原先的紅色(?標(biāo)顏色)與 后來的藍(lán)?(源顏色)進(jìn)?組合。源顏?的alpha值 越?,添加的藍(lán)色顏色成分越高,?標(biāo)顏色所保留的成分就會(huì)越少。混合函數(shù)經(jīng)常?于實(shí)現(xiàn)在其他一些不透明的物體前?繪制?個(gè)透明物體的效果。

5.5、案例

  • 代碼如下:

//顏色組合
#include "GLTools.h"
#include "GLShaderManager.h"

#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>
#endif

GLBatch squareBatch;
GLBatch greenBatch;
GLBatch redBatch;
GLBatch blueBatch;
GLBatch blackBatch;

GLShaderManager shaderManager;


GLfloat blockSize = 0.2f;
GLfloat vVerts[] = { -blockSize, -blockSize, 0.0f,
    blockSize, -blockSize, 0.0f,
    blockSize,  blockSize, 0.0f,
    -blockSize,  blockSize, 0.0f};


void SetupRC()
{
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f );
    shaderManager.InitializeStockShaders();

    //繪制1個(gè)移動(dòng)矩形
    squareBatch.Begin(GL_TRIANGLE_FAN, 4);
    squareBatch.CopyVertexData3f(vVerts);
    squareBatch.End();
    
    //繪制4個(gè)固定矩形
    GLfloat vBlock[] = { 0.25f, 0.25f, 0.0f,
        0.75f, 0.25f, 0.0f,
        0.75f, 0.75f, 0.0f,
        0.25f, 0.75f, 0.0f};
    
    greenBatch.Begin(GL_TRIANGLE_FAN, 4);
    greenBatch.CopyVertexData3f(vBlock);
    greenBatch.End();
    
    
    GLfloat vBlock2[] = { -0.75f, 0.25f, 0.0f,
        -0.25f, 0.25f, 0.0f,
        -0.25f, 0.75f, 0.0f,
        -0.75f, 0.75f, 0.0f};
    
    redBatch.Begin(GL_TRIANGLE_FAN, 4);
    redBatch.CopyVertexData3f(vBlock2);
    redBatch.End();
    
    
    GLfloat vBlock3[] = { -0.75f, -0.75f, 0.0f,
        -0.25f, -0.75f, 0.0f,
        -0.25f, -0.25f, 0.0f,
        -0.75f, -0.25f, 0.0f};
    
    blueBatch.Begin(GL_TRIANGLE_FAN, 4);
    blueBatch.CopyVertexData3f(vBlock3);
    blueBatch.End();
    
    
    GLfloat vBlock4[] = { 0.25f, -0.75f, 0.0f,
        0.75f, -0.75f, 0.0f,
        0.75f, -0.25f, 0.0f,
        0.25f, -0.25f, 0.0f};
    
    blackBatch.Begin(GL_TRIANGLE_FAN, 4);
    blackBatch.CopyVertexData3f(vBlock4);
    blackBatch.End();
}

//上下左右鍵位控制移動(dòng)
void SpecialKeys(int key, int x, int y)
{
    GLfloat stepSize = 0.025f;
    
    GLfloat blockX = vVerts[0];
    GLfloat blockY = vVerts[7];
    
    if(key == GLUT_KEY_UP)
        blockY += stepSize;
    
    if(key == GLUT_KEY_DOWN)
        blockY -= stepSize;
    
    if(key == GLUT_KEY_LEFT)
        blockX -= stepSize;
    
    if(key == GLUT_KEY_RIGHT)
        blockX += stepSize;
    
    
    if(blockX < -1.0f) blockX = -1.0f;
    if(blockX > (1.0f - blockSize * 2)) blockX = 1.0f - blockSize * 2;;
    if(blockY < -1.0f + blockSize * 2)  blockY = -1.0f + blockSize * 2;
    if(blockY > 1.0f) blockY = 1.0f;
    
    
    vVerts[0] = blockX;
    vVerts[1] = blockY - blockSize*2;
    
    vVerts[3] = blockX + blockSize*2;
    vVerts[4] = blockY - blockSize*2;
    
    vVerts[6] = blockX + blockSize*2;
    vVerts[7] = blockY;
    
    vVerts[9] = blockX;
    vVerts[10] = blockY;
    
    squareBatch.CopyVertexData3f(vVerts);
    
    glutPostRedisplay();
}

//召喚場(chǎng)景
void RenderScene(void)
{
    
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    
    //定義4種顏色
    GLfloat vRed[]   = { 1.0f, 0.0f, 0.0f, 0.5f };
    GLfloat vGreen[] = { 0.0f, 1.0f, 0.0f, 1.0f };
    GLfloat vBlue[]  = { 0.0f, 0.0f, 1.0f, 1.0f };
    GLfloat vBlack[] = { 0.0f, 0.0f, 0.0f, 1.0f };
    
    //召喚場(chǎng)景的時(shí)候,將4個(gè)固定矩形繪制好
    //使用 單位著色器
    //參數(shù)1:簡(jiǎn)單的使用默認(rèn)笛卡爾坐標(biāo)系(-1,1),所有片段都應(yīng)用一種顏色。GLT_SHADER_IDENTITY
    //參數(shù)2:著色器顏色
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vGreen);
    greenBatch.Draw();
    
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vRed);
    redBatch.Draw();
    
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vBlue);
    blueBatch.Draw();
    
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vBlack);
    blackBatch.Draw();
    
    
    //組合核心代碼
    //1.開啟混合
    glEnable(GL_BLEND);
    //2.開啟組合函數(shù) 計(jì)算混合顏色因子
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    //3.使用著色器管理器
    //*使用 單位著色器
    //參數(shù)1:簡(jiǎn)單的使用默認(rèn)笛卡爾坐標(biāo)系(-1,1),所有片段都應(yīng)用一種顏色。GLT_SHADER_IDENTITY
    //參數(shù)2:著色器顏色
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vRed);
    //4.容器類開始繪制
    squareBatch.Draw();
    //5.關(guān)閉混合功能
    glDisable(GL_BLEND);
    
    
    //同步繪制命令
    glutSwapBuffers();
}


void ChangeSize(int w, int h)
{
    glViewport(0, 0, w, h);
}

int main(int argc, char* argv[])
{
    gltSetWorkingDirectory(argv[0]);
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
    glutInitWindowSize(800, 600);
    glutCreateWindow("移動(dòng)矩形,觀察顏色");
    
    GLenum err = glewInit();
    if (GLEW_OK != err)
    {
        fprintf(stderr, "Error: %s\n", glewGetErrorString(err));
        return 1;
    }
    
    glutReshapeFunc(ChangeSize);
    glutDisplayFunc(RenderScene);
    glutSpecialFunc(SpecialKeys);
    
    SetupRC();
    
    glutMainLoop();
    return 0;
}


5.6、glBlendFuncSeparate 函數(shù)

除了能使?glBlendFunc 來設(shè)置混合因子,還可以有更靈活的選擇。
void glBlendFuncSeparate(GLenum strRGB,GLenum dstRGB ,GLenum strAlpha,GLenum dstAlpha);
strRGB: 源顏色的混合因? dstRGB: ?標(biāo)顏色的混合因子 strAlpha: 源顏?的Alpha因子 dstAlpha: ?標(biāo)顏?的Alpha因子

5.7 glBlendFuncSeparate 注意

  • glBlendFunc 指定源和目標(biāo) RGBA值的混合函數(shù);但是glBlendFuncSeparate函數(shù)則允許為RGB 和 Alpha 成分單獨(dú)指定混合函數(shù)。
  • GL_CONSTANT_COLOR,GL_ONE_MINUS_CONSTANT_COLOR,GL_CONSTANT_ALPHA,GL_ONE_MINUS_CONSTANT值允許混合?程式中引??個(gè)常量混合顏?。

5.8、常量混合顏色

常量混合顏色,默認(rèn)初始化為?色(0.0f,0.0f,0.0f,0.0f),但是還是可以修改這個(gè)常量混合顏色。
void glBlendColor(GLclampf red ,GLclampf green ,GLclampf blue ,GLclampf alpha);

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容