[TOC]
學習目標
- 渲染過程中可能產生的問題
- 油畫渲染
- 正面和背面剔除
- 深度測試
- 多邊形模型
- 多邊形偏移
- 裁剪
- 顏色混合
一、在渲染過程中可能產生的問題
在繪制3D場景的時候,我們需要決定哪些部分是對觀察者可見的,或者哪些部分是對觀察者不可?的.對于不可見的部分,應該及早丟棄.例例如在?個不透明的墻壁后,就不應該渲染.這種情況叫做”隱藏?消除”(Hidden surface elimination)
.
比如以下的圖形:
1.1、解決方案:油畫法
- 油畫算法
- 先繪制場景中的離觀察者較遠的物體,再繪制較近的物體。
- 例如下面的圖例:
-
先繪制紅色部分,再繪制黃色部分,最后再繪制灰色部分,即可解決隱藏面消除的問題
image.png 但是這樣就沒有弊端了嗎?
答案是NO!
-
1.2、解決方案:油畫弊端
- 油畫算法
-
使用油畫算法,只要將場景按照物理距離觀察者的距離遠近排序,由遠及近的繪制即可。那么會出現什么問題?如果三個三角形是疊加的情況下,油畫算法將無法處理。
image.png
-
1.3、解決方案:正背面剔除(Face Culling)
- 背景
- 嘗試相信一個3D圖形,你從任何一個方向去觀察,最多可以看到幾個面?
- 答案是最多3個面。從一個立方體的任意位置和方向上看,你不可能看到多于3個面。
- 那么思考?
- 我們能以某種方式去丟棄這部分數據,OpenGL在渲染的性能即可提高超過50%。
- 解決問題
- 如何知道某個面在觀察者的事業中出現呢?
- 任何平面都有2個面,正面/背面。這意味著你一個時刻只能看到一面。
- OpenGL 可以做到檢查所有正面朝向觀察者的面,并渲染它們。從而丟棄背面朝向的面。這樣可以節約片元著色器的性能。
- 那么問題又來了?如何告訴OpenGL你繪制的圖形,哪個是正面,哪個面是背面?
- 答案:通過分析
頂點數據的順序
來達到目的。
- 答案:通過分析
- 嘗試相信一個3D圖形,你從任何一個方向去觀察,最多可以看到幾個面?
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)是對應的。顏色緩存區儲存像素的顏色信息,?深度緩沖區存儲像素的深度信息. 在決定是否繪制一個物體表?時,
?先要將表?對應的像素的深度值與當前深度緩沖區中的值進?比較. 如果大于深度緩沖區中的值,則丟棄這部分.否則 利用這個像素對應的深度值和顏?值.分別更新深度緩沖區和顏?緩存區.
這個過程稱為”深度測試”
- 深度緩沖區(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坐標,形成一個?體的裁剪區域。
代碼實例
//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);