OpenGL學習之路(3.0):OpenGL 深度測試

[TOC]

學習目標

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

一、在渲染過程中可能產生的問題

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

image.png

1.1、解決方案:油畫法

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


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

1.2、解決方案:油畫弊端

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


      image.png

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

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

1.5、解決方案:分析頂點順序

  • 如圖可以看到頂點順序
    • image.png
    • 正/背面區分
      • 正面:按照逆時針頂點鏈接順序的三角形面
      • 背面:按照順時針頂點鏈接順序的三角形面

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

  • image.png
  • 分析
    • 左側三角形頂點順序為:1->2->3; 右側三角形的頂點順序為:1->2->3.
    • 當觀察者在右側時,則右邊的三角形方向逆時針方向則為正面,而左側的三角形為順時針則為背面。
    • 當觀察者在左側時,則左邊的三角形逆時針方向則為正面,而右則的三角形為順時針則為背面。
  • 得出結論
    • 正面和背面是有三角形的頂點定義順序和觀察者方向共同決定的,隨著觀察者的角度方向的改變,正面背面也會跟著改變

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

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

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

  • 例如,剔除正面實現(1)
    glCullFace(GL_BACK);
    glFrontFace(GL_CW);

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

    glCullFace(GL_FRONT);

源碼實例一:

//工具類
#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

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

 // 標記背面剔除、深度測試
 int iCull = 0;
 int iDepth = 0;

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

//右鍵菜單欄選項
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;
    }
    
    //設置窗口尺寸
    glViewport(0, 0, w, h);
    
    //創建透視投影,并將它載入到投影矩陣堆棧中
    /*SetPerspective
     參數:
     1.垂直方向上的視場角度
     2.窗口的寬度與高度的縱橫比
     3.近裁剪面距離
     4.遠裁剪面距離
     */
    //設置透視模式,初始化其透視矩陣
    viewFrustum.SetPerspective(35.0f, float(w)/float(h), 1.0f, 100.f);
    
    // 把透視矩陣加載到透視矩陣隊陣中
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    
    //初始化渲染管線
    transformPipeline.SetMatrixStacks(modelViewMatix, projectionMatrix);
    
}

int main(int argc, char* argv[])
{
    //設置工作路徑
    gltSetWorkingDirectory(argv[0]);
    //初始化
    glutInit(&argc, argv);
    //初始化渲染模型
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
    //設置窗口大小
    glutInitWindowSize(800, 600);
    //設置窗口標題
    glutCreateWindow("Geometry Test Program");
    //注冊回調函數(渲染、尺寸)
    glutReshapeFunc(ChangeSize);
    //特殊鍵位函數(上下左右)
    glutSpecialFunc(SpecailKeys);
    // 顯示函數
    glutDisplayFunc(RenderScene);
    
    //創建右鍵菜單
    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);
    
    //設置右鍵
    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;
    
}

  • 通過平面著色器運行代碼后出現的效果
    • image.png
  • 通過默認光源著色器運行代碼效果如下:
    • image.png
      • 那么會發現文章開始時的圖片一樣,圖為未開啟正/背面剔除
    • 開啟正/背面剔除后的效果

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

1.8、了解深度

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


      image.png

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

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

二、使用深度測試

  • 深度緩沖區,一般由窗口管理系統,GLFW創建。深度一般由16位,24位,32位值來表示。通常是24位,位數越高,深度精度越好。
  • 開啟深度測試

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

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

2.1、指定深度測試判斷式

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


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

  • 深度測試代碼

//右鍵菜單欄選項
void ProcessMunu(int value) {
    
    switch (value) {
        case 1:
            //是否開啟正/背面剔除
            iCull = !iCull;
            break;
        case 2:
            // 是否開啟深度測試
            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:
            //填充方式-點
            glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
            break;
    }
    
    //無論上面選擇了哪一個選項,都修改了顯示效果,所以需要重新渲染
    glutPostRedisplay();
}

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

  • 但是又會產生另外一個問題ZFighting閃爍,那么什么是ZFighting呢?為什么會導致呢?

三、ZFighting問題的原因

  • 為什么會出現ZFighting閃爍問題

    • 因為開啟深度測試后,OpenGL 就不會再去繪制模型被遮擋的部分. 這樣實現的顯示更加真實.但是 由于深度緩沖區精度的限制對于深度相差?常?的情況下.(例如在同一平面上進行2次制),OpenGL就可能出現不能正確判斷兩者的深度值,會導致深度測試的結果不可預測.顯示出來的現象時交錯閃爍的前面2個畫?交錯出現.


      image.png

      image.png
  • 如圖,因為多個畫面在同一深度緩沖區時導致深度測試的結果不可預測,所以顯示現象交錯閃爍的前面2個畫面交錯出現的情況。

3.1、ZFighting閃爍問題問題解決

  • 第一步:啟用Polygon Offset方式解決
    • 解決方法: 讓深度值之間產生間隔.如果2個圖形之間有間隔,是不是意味著就不會產?干涉.可以理解為在執?深度測試前將?方體的深度值做?些細微的增加.于是就能將重疊的2個圖形深度值有所區分.
    //啟?Polygon Offset?式: glEnable(GL_POLYGON_OFFSET_FILL)
    //參數列表: GL_POLYGON_OFFSET_POINT GL_POLYGON_OFFSET_LINE 

    GL_POLYGON_OFFSET_FILL

    //對應光柵化模式: GL_POINT 對應光柵化模式: GL_LINE
    //對應光柵化模式: GL_FILL

  • 第二步:指定偏移量

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


    glDisable(GL_POLYGON_OFFSET_FILL)

3.2、ZFighting閃爍問題預防

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

四、裁剪

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


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

4.1、理解窗口,視口,裁剪區域

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

image.png

代碼實例



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

//召喚場景
void RenderScene(void)
{
    //設置清屏顏色為藍色
    glClearColor(0.0f, 0.0f, 1.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    
    //1.現在剪成小紅色分區
    //(1)設置裁剪區顏色為紅色
    glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
    //(2)設置裁剪尺寸
    glScissor(100, 100, 600, 400);
    //(3)開啟裁剪測試
    glEnable(GL_SCISSOR_TEST);
    //(4)開啟清屏,執行裁剪
    glClear(GL_COLOR_BUFFER_BIT);
    
    // 2.裁剪一個綠色的小矩形
    //(1).設置清屏顏色為綠色
    glClearColor(0.0f, 1.0f, 0.0f, 0.0f);
    //(2).設置裁剪尺寸
    glScissor(200, 200, 400, 200);
    //(3).開始清屏執行裁剪
    glClear(GL_COLOR_BUFFER_BIT);
    
    //關閉裁剪測試
    glDisable(GL_SCISSOR_TEST);
    
    //強制執行緩存區
    glutSwapBuffers();
}

void ChangeSize(int w, int h)
{
    //保證高度不能為0
    if(h == 0)
        h = 1;
    
    // 將視口設置為窗口尺寸
    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 渲染時會把顏?值存在顏色緩存區中,每個?片段的深度值也是放在深度緩沖區。當深度 緩沖區被關閉時,新的顏色將簡單的覆蓋原來顏色緩存區存在的顏?值,當深度緩沖區再次打開時,新的顏?片段只是當它們?原來的值更接近鄰近的裁剪平?才會替換原來的顏?片段。


    glEnable(GL_BlEND);

  • 5.1、組合顏色

    • ?標顏色:已經存儲在顏色緩存區的顏?值
    • 源顏色:作為當前渲染命令結果進入顏?緩存區的顏?值 當混合功能被啟動時,源顏色和?標顏色的組合方式是混合方程式控制的。在默認情況下,
    • 混合?程式如下所示:
        Cf = (Cs * S) + (Cd * D)
        Cf: 最終計算參數的顏色 
        Cs: 源顏色
        Cd: ?標顏色 
        S : 源合因子 
        D : 目標混合因子
    
    
  • 5.2、設置混合因子

    • 設置混合因子,需要用到glBlendFund函數

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

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

5.3 課堂案例

下?通過?個常見的混合函數組合來說明問題:

    
    glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

如果顏?緩存區已經有?種顏?紅色(1.0f,0.0f,0.0f,0.0f),這個?標顏色Cd,如果在這上?用?種alpha為0.6的藍?(0.0f,0.0f,1.0f,0.6f)
Cd (?標顏色) = (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)
等價于 = (Blue * 0.6f) + (Red * 0.4f)

5.4、總結

最終顏色是以原先的紅色(?標顏色)與 后來的藍?(源顏色)進?組合。源顏?的alpha值 越?,添加的藍色顏色成分越高,?標顏色所保留的成分就會越少。混合函數經常?于實現在其他一些不透明的物體前?繪制?個透明物體的效果。

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個移動矩形
    squareBatch.Begin(GL_TRIANGLE_FAN, 4);
    squareBatch.CopyVertexData3f(vVerts);
    squareBatch.End();
    
    //繪制4個固定矩形
    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();
}

//上下左右鍵位控制移動
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();
}

//召喚場景
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 };
    
    //召喚場景的時候,將4個固定矩形繪制好
    //使用 單位著色器
    //參數1:簡單的使用默認笛卡爾坐標系(-1,1),所有片段都應用一種顏色。GLT_SHADER_IDENTITY
    //參數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.開啟組合函數 計算混合顏色因子
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    //3.使用著色器管理器
    //*使用 單位著色器
    //參數1:簡單的使用默認笛卡爾坐標系(-1,1),所有片段都應用一種顏色。GLT_SHADER_IDENTITY
    //參數2:著色器顏色
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vRed);
    //4.容器類開始繪制
    squareBatch.Draw();
    //5.關閉混合功能
    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("移動矩形,觀察顏色");
    
    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 函數

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

5.7 glBlendFuncSeparate 注意

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

5.8、常量混合顏色

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

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,923評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,740評論 3 420
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,856評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,175評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,931評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,321評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,383評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,533評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,082評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,891評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,618評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,319評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,732評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,987評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,794評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,076評論 2 375

推薦閱讀更多精彩內容