學(xué)習(xí)目標:
- 1.渲染過程產(chǎn)生的問題(掌握)
- 油畫渲染(了解)
- 正面&背面剔除(掌握)
- 深度測試(掌握)
- ZFighting閃爍(了解)
- 窗口、視口呀闻、裁剪區(qū)域(掌握)
- 顏色混合(掌握)
1.渲染過程產(chǎn)生的問題
- 核心代碼
void setupRC(void){
glClearColor(0.5, 0.5, 0.7, 1);
shaderManager.InitializeStockShaders();
viewFrame.MoveForward(10.0f);
//創(chuàng)建“環(huán)”
gltMakeTorus(torusBatch, .5f, .3f, 60, 30);
}
void renderScene(void){
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
modelViewMatix.PushMatrix(viewFrame);
GLfloat vColor[] = { 0.2f, 0.4f, 0.6f, 1.0f };
//使用默認光源著色器
//通過光源袜瞬、陰影效果跟提現(xiàn)立體效果
//參數(shù)1:GLT_SHADER_DEFAULT_LIGHT 默認光源著色器
//參數(shù)2:模型視圖矩陣
//參數(shù)3:投影矩陣
//參數(shù)4:基本顏色值
shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT,transformPipeline.GetModelViewMatrix(),transformPipeline.GetProjectionMatrix(),vColor);
modelViewMatix.PopMatrix();
torusBatch.Draw();
glutSwapBuffers();
}
-
運行結(jié)果
- 問題分析:旋轉(zhuǎn)一定的角度后,甜甜圈中不可見的部分與可見的部分混在一起垄琐,導(dǎo)致了結(jié)果的混亂。在繪制3D場景的時候,我們需要決定哪些部分對觀察者是可見的,或者哪些部分對觀察者不可見的妇拯。對于不可見的部分,應(yīng)該及早丟棄。例如在一個不透明的墻壁后越锈,就不應(yīng)該渲染仗嗦。這種情況叫做“隱藏面消除”。
- 解決方法:油畫算法(不可取)甘凭;正背面剔除稀拐。
2.油畫渲染
-
油畫算法:先繪制場景中的離觀察者較遠的物體,在繪制較近的物體丹弱。例如下圖:先繪制紅色部分德撬,再繪制黃色部分,最后再繪制灰色部分躲胳,即可解決隱藏面消除的問題蜓洪。
-
弊端:無法處理圖形疊加一起的情況。
3.正面&背面剔除
- 背面剔除:在渲染的時候坯苹,背對觀察者的面丟棄隆檀,只將正面朝向觀察者進行計算。將看不到的面進行剔除北滥,節(jié)省CPU和GPU的資源刚操。
-
正/背面區(qū)分
- 正面:按照逆時針頂點順序連接
- 背面:按照順時針頂點順序連接
-
案例:分析立方體的正背面
分析:左側(cè)三角形頂點順序為:1->2->3;右側(cè)三角形頂點順序為:1->2->3;當觀察者在右側(cè)時再芋,則右邊的三角形為逆時針方向菊霜,則為正面,而左側(cè)的三角形為順時針济赎,則為背面鉴逞;當觀察者在左側(cè)時,則左邊的三角形為逆時針司训,則為正面构捡,而右側(cè)的三角形為順時針,則為背面壳猜。
總結(jié):正面和背面是由三角形的頂點定義順序和觀察者方向共同決定的勾徽。隨著觀察者的角度方向改變,正背面也會改變统扳。 - 相關(guān)代碼
- 開啟表面剔除(默認背面剔除)
void glEnable(GL_CULL_FACE);
- 關(guān)閉表面剔除(默認背面剔除)
void glDissable(GL_CULL_FACE);
- 用戶選擇剔除哪個面(正面/背面)
void glCullFace(GLenum mode); mode參數(shù)為:GL_FRONT,GL_BACK(默認),GL_FRONT_AND_BACK,
- 用戶指定順序哪個為正面
void glFrontFace(GLenum mode); mode參數(shù)為:GL_CW,GL_CCW,默認值為GL_CCW
- Demo中開啟背面剔除:
//召喚場景
void renderScene(void){
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
modelViewMatix.PushMatrix(viewFrame);
//開啟背面剔除
glEnable(GL_CULL_FACE);
GLfloat vColor[] = { 0.2f, 0.4f, 0.6f, 1.0f };
shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT,transformPipeline.GetModelViewMatrix(),transformPipeline.GetProjectionMatrix(),vColor);
modelViewMatix.PopMatrix();
torusBatch.Draw();
glutSwapBuffers();
}
運行結(jié)果:
4. 深度測試
- 深度:深度其實就是該像素點在3D世界中距離攝像機的距離Z值喘帚。
- 深度緩沖區(qū):深度緩沖區(qū)就是一塊內(nèi)存區(qū)域,專門存儲著每個像素點(繪制在屏幕上的)深度值咒钟。深度值(Z值)越大離攝像機就越遠吹由。在不使用深度測試的時候,如果我們先繪制一個距離比較近的物體朱嘴,在繪制距離比較遠的物體倾鲫,則距離遠的位圖因為后繪制,會把距離近的物體覆蓋掉。有了深度緩沖區(qū)后乌昔,繪制物體的順序就不那么重要了隙疚。實際上,只要存在深度緩沖區(qū)磕道,OpenGL都會把像素的深度值寫入到緩沖區(qū)甚淡。除非調(diào)用glDepthMask(GL_false)來禁止寫入。
- 深度測試:深度緩沖區(qū)(DepthBuffer)和顏色緩沖區(qū)(ColorBuffer)是對應(yīng)的捅厂。顏色緩沖區(qū)存儲像素的顏色信息,而深度緩沖區(qū)存儲像素的深度信息资柔。在決定是否繪制一個物體表面時焙贷,首先要將表面的當前像素的深度值與深度緩沖區(qū)的值進行比較。如果當前的深度值大于深度緩沖區(qū)的值贿堰,這丟棄這部分辙芍。否者利用這個像素對應(yīng)的深度值和顏色值,分別更新深度緩沖區(qū)和顏色緩沖區(qū)羹与。這個過程稱為”深度測試“故硅。
- 深度值計算:
- 深度值一般由16位、24位或者32位值表示纵搁,通常是24位吃衅。位數(shù)越高,深度的精確值越好腾誉。深度值的范圍在[0,1]之間徘层,值越小表示越靠近觀察者,值越大表示越遠離觀察者利职。
-
在OpenGL中趣效,屏幕空間坐標的Z值即是深度緩沖中的深度值。深度緩沖包含了一個介于0.0和1.0之間的深度值猪贪,它將會與觀察者視角所看見的場景中所有物體的z值進行比較跷敬。我們因此需要一些方法轉(zhuǎn)換這些視圖空間z值到[0,1]的范圍內(nèi),下面的線性方程把z值轉(zhuǎn)換為0.0-1.0之間的值:
- 使用深度測試:
- 深度緩沖區(qū)热押,一般由窗口管理系統(tǒng)西傀,GLFW創(chuàng)建。深度值一般由16位楞黄、24位和32位值表示池凄。位數(shù)越高,深度精確度越好鬼廓。
- 開啟深度測試:
glEnable(GL_DEPTH_TEST);
- 在繪制場景前肿仑,清除顏色緩存區(qū),深度緩沖區(qū)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- 指定深度測試判斷模式
void glDepthFunc(GLEnum mode);
- Demo中開啟深度測試:
glEnable(GL_DEPTH_TEST);
運行結(jié)果:
*5. ZFighting閃爍
-
ZFighting閃爍問題原因:開啟深度測試后,OpenGL就不會再去繪制模型被遮擋的部分尤慰。這樣實現(xiàn)的顯示更加真實馏锡。但是,由于深度緩沖區(qū)精度的限制伟端,對于深度非常小的情況下杯道,就可能導(dǎo)致判斷深度值不正確,導(dǎo)致深度測試的結(jié)果不可預(yù)測责蝠。顯示時交錯閃爍党巾。如下圖時而渲染出綠色,時而渲染出紅色霜医,無法確定,交錯閃爍)齿拂。
- 解決方案:
- 第一步:啟用Polygon Offet方式解決
//參數(shù)列表: //GL_POLYGON_OFFSET_POINT 對應(yīng)光柵化模式:GL_POINT //GL_POLYGON_OFFSET_LINE 對應(yīng)光柵化模式:GL_LINE //GL_POLYGON_OFFSET_FILL 對應(yīng)光柵化模式:GL_FILL glEnable(GL_POLYGON_OFFSET_FILL)
- 第二步:指定偏移量
void glPolygonOffset(Glfloat factor,Glfloat units); //應(yīng)?到片段上總偏移計算?方程式: Depth Offset = (DZ * factor) + (r * units); //DZ:深度值(Z值) //r:使得深度緩沖區(qū)產(chǎn)生變化的最小值負值,將使得z值距離我們更近肴敛,而正值署海,將使得z值距離我們更遠,我們 設(shè)置factor和units設(shè)置為-1医男,-1
- 第三步:關(guān)閉Polygon Offset
glDisable(GL_POLYGON_OFFSET_FILL)
- 問題預(yù)防:
- 不要將兩個物體靠的太近砸狞,避免渲染時三角形疊在一起。這種方式要求對場景中物體插入一個少量的偏移镀梭,那么就可能避免ZFighting現(xiàn)象刀森。例如立方體和平面問題中,將平面下移0.001f就可以解決這個問題丰辣。當然手動去插入這個小的偏移是要付出代價的撒强。
- 盡可能將近裁剪面設(shè)置的離觀察者遠一些,會使得整個裁剪范圍內(nèi)的精度變高一些笙什。但是這種方式會使離觀察者較近的物體被裁減掉飘哨,因此需要調(diào)試好裁剪面參數(shù)。
6. 窗口琐凭、視口芽隆、裁剪區(qū)域
- 窗口:就是顯示界面。
- 視口:就是窗口中用來顯示圖形的一塊矩形區(qū)域统屈,它可以和窗口等大胚吁,也可以比窗口大或小。只有繪制在視口區(qū)域中的圖形才能被顯示愁憔,如果圖形有一部分超出了視口區(qū)域腕扶,那么那一部分是看不到的。通過glViewport()函數(shù)設(shè)置吨掌。
- 裁剪區(qū)域(平行投影):就是視口矩形區(qū)域的最小最大x坐標(left,right)和最小最大y坐標(bottom,top),而不是窗口的最小最大x坐標和y坐標半抱。通過glOrtho()函數(shù)設(shè)置脓恕,這個函數(shù)還需指定最近最遠z坐標,形成一個立體的裁剪區(qū)域窿侈。
- 裁剪:是OpenGL提高渲染的一種方式炼幔,只刷新屏幕上發(fā)生變化的部分,OpenGL允許將要進行渲染的窗口只去指定一個裁剪框史简∧诵悖基本原理:用于渲染時限制繪制區(qū)域,通過此技術(shù)可以在屏幕(幀緩沖)指定一個矩形區(qū)域圆兵。啟用裁剪測試之后跺讯,不在此矩形區(qū)域內(nèi)的片元被丟棄,只有在此矩形區(qū)域內(nèi)的片元才有可能進入幀緩沖殉农。因此實際達到的效果就是在屏幕上開辟了一個小窗口抬吟,可以在其中進行指定內(nèi)容的繪制。
//1.開啟裁剪測試 glEnable(GL_SCISSOR_TEST); //2.關(guān)閉裁剪測試 gldisable(GL_SCISSOR_TEST)统抬; //3.指定裁剪窗口 x,y:指定裁剪框左下角位置 width,height:指定裁剪尺寸 void glScissor(GLint x, GLSize width,GLSize height);
裁剪核心代碼:
void renderScene(void){
glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
//裁剪成紅色分區(qū)
//1.設(shè)置裁剪區(qū)域的顏色
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
//2.設(shè)置裁剪尺寸
glScissor(100, 100, 600, 400);
//3.開啟裁剪測試
glEnable(GL_SCISSOR_TEST);
//4.開啟清屏,執(zhí)行裁剪
glClear(GL_COLOR_BUFFER_BIT);
//裁剪成綠色分區(qū)
//1.設(shè)置裁剪區(qū)域的顏色
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
//2.設(shè)置裁剪尺寸
glScissor(200, 200, 400, 200);
//3.開啟裁剪測試
glEnable(GL_SCISSOR_TEST);
//4.開啟清屏危队,執(zhí)行裁剪
glClear(GL_COLOR_BUFFER_BIT);
//關(guān)閉裁剪區(qū)域
glDisable(GL_SCISSOR_TEST);
glutSwapBuffers();
}
7. 顏色混合
- 混合:在OpenGL中聪建,物體透明技術(shù)通常被叫做混合(Blending)。OpenGL渲染時會把顏色值存在顏色緩沖區(qū)中茫陆,每個片段的深度值也是放在深度緩沖區(qū)中金麸。當深度緩沖區(qū)被關(guān)閉時,新的顏色將簡單的覆蓋原來顏色緩沖區(qū)的顏色值簿盅,當深度緩沖區(qū)再次打開時挥下,新的顏色片段比原來的值更鄰近的裁剪平面時,才會替換原來的顏色片段桨醋。
// 使用OpenGL的混合功能 glEnable(GL_BLEND); // 關(guān)閉OpenGL的混合功能 glDisable(GL_BLEND); // 設(shè)置混合因子棚瘟,需要用到glBlendFun函數(shù) // S:源混合因子 // D:目標混合因子 glBlendFunc(GL enum S,GLenum D);
- 組合顏色
OpenGL 會把源顏色和目標顏色各自取出,并乘以一個系數(shù)(源顏色乘以的系數(shù)稱為“源因子”喜最,目標顏色乘以的系數(shù)稱為“目標因子”)偎蘸,然后相加,這樣就得到了新的顏色瞬内。(也可以不是相加迷雪,新版本的OpenGL可以設(shè)置運算方式,包括加虫蝶、減章咧、取兩者中較大的、取兩者中較小的能真、邏輯運算等)赁严。- 目標顏色:已經(jīng)存儲在顏色緩沖區(qū)的顏色值(之前的顏色)扰柠。
- 源顏色:當前的要進入顏色緩存區(qū)的顏色值(后來要畫上去的顏色)。
也可以理解成源顏色和目標顏色是跟繪制的順序有關(guān)的误澳。假如先繪制了一個紅色的物體耻矮,再在其上繪制綠色的物體。則綠色是源顏色忆谓,紅色是目標顏色裆装。如果順序反過來,則 紅色就是源顏色倡缠,綠色才是目標顏色哨免。在繪制時,應(yīng)該注意順序昙沦,使得繪制的源顏色與設(shè)置的源因子對應(yīng)琢唾,目標顏色與設(shè)置的目標因子對應(yīng)。(聯(lián)想射箭 目標是靶心 箭是源)
用數(shù)學(xué)公式來表達一下這個運算方式:
盾饮。則混合產(chǎn)生的新顏色可以表示為:
//Cf:最終計算參數(shù)的顏色
//Cs:源顏色
//S:源混合因子
//D:目標混合因子
Cf = (Cs * S) + (Cd * d)
//假設(shè)源顏色的四個分量(指紅色采桃,綠色,藍色丘损,alpha值)是(Rs, Gs, Bs, As)
//目標顏色的四個分量是(Rd, Gd, Bd, Ad)普办,
//源因子為(Sr, Sg, Sb, Sa)
//目標因子為(Dr, Dg, Db, Da)
Cf = (Rs*Sr+Rd*Dr, Gs*Sg+Gd*Dg, Bs*Sb+Bd*Db, As*Sa+Ad*Da)
常用混合函數(shù)
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
- 混合的Demo
#include <stdio.h>
#include "GLShaderManager.h"
#include "GLTools.h"
#include <GLUT/GLUT.h>
GLBatch squareBatch;
GLBatch redBatch;
GLBatch blackBatch;
GLBatch blueBatch;
GLBatch greenBatcch;
GLShaderManager shaderManager;
GLfloat blockSize = 0.2f;
GLfloat vVerts[] = { -blockSize, -blockSize, 0.0f,
blockSize, -blockSize, 0.0f,
blockSize, blockSize, 0.0f,
-blockSize, blockSize, 0.0f};
//重塑函數(shù)
void changeSize(int w, int h){
glViewport(0, 0, w, h);
}
//召喚場景
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 };
shaderManager.UseStockShader(GLT_SHADER_IDENTITY,vRed);
redBatch.Draw();
shaderManager.UseStockShader(GLT_SHADER_IDENTITY,vGreen);
greenBatcch.Draw();
shaderManager.UseStockShader(GLT_SHADER_IDENTITY,vBlue);
blueBatch.Draw();
shaderManager.UseStockShader(GLT_SHADER_IDENTITY,vBlack);
blackBatch.Draw();
//固定矩形的顏色
shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vRed);
squareBatch.Draw();
//核心代碼 顏色混合
//1.開啟混合
glEnable(GL_BLEND);
//2.開啟組合函數(shù) 計算混合顏色因子
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glutSwapBuffers();
}
//設(shè)置渲染環(huán)境
void setupRC(void){
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
shaderManager.InitializeStockShaders();
//繪制移動的矩形
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
};
redBatch.Begin(GL_TRIANGLE_FAN,4);
redBatch.CopyVertexData3f(vBlock);
redBatch.End();
GLfloat vBlock1[] = {
-0.75f, 0.25f, 0.0f,
-0.25f, 0.25f, 0.0f,
-0.25f, 0.75f, 0.0f,
-0.75f, 0.75f, 0.0f
};
greenBatcch.Begin(GL_TRIANGLE_FAN,4);
greenBatcch.CopyVertexData3f(vBlock1);
greenBatcch.End();
GLfloat vBlock2[] = {
-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(vBlock2);
blueBatch.End();
GLfloat vBlock3[] = {
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(vBlock3);
blackBatch.End();
}
//特殊鍵位函數(shù)
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();
}
//根據(jù)空格次數(shù)。切換不同的“窗口名稱”
void keyPressFunc(unsigned char key, int x, int y){
}
//檢查OpenGL API是否安全可用
int checkOpenGLInit(void){
GLenum status = glewInit();
if(status != GLEW_OK){
printf("GLEW Error:%s\n",glewGetErrorString(status));
return 1;
}
return 0;
}
int main(int argc,char *argv[])
{
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
glutInitWindowSize(600, 600);
//申請一個雙緩存區(qū)徘钥、顏色緩存區(qū)衔蹲、深度緩存區(qū)、模板緩存區(qū)
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
//創(chuàng)建window的名稱
glutCreateWindow("移動矩形呈础,觀察顏色");
//注冊回調(diào)函數(shù)(改變尺寸)
glutReshapeFunc(changeSize);
//點擊空格時舆驶,調(diào)用的函數(shù)
glutKeyboardFunc(keyPressFunc);
//特殊鍵位函數(shù)(上下左右)
glutSpecialFunc(specialKeys);
//顯示函數(shù)
glutDisplayFunc(renderScene);
//判斷一下是否能初始化glew庫,確保項目能正常使用OpenGL 框架
checkOpenGLInit();
//繪制
setupRC();
//runloop運行循環(huán)
glutMainLoop();
return 0;
}