一蔓纠、繪制甜甜圈
上篇文中已經(jīng)實(shí)現(xiàn)由OpenGL圖元繪制一些簡(jiǎn)單的圖形,今天我們來(lái)嘗試?yán)L制一個(gè)甜甜圈并且看下會(huì)不會(huì)有新的問(wèn)題出現(xiàn):
具體代碼和上篇基本一樣惫皱,只是修改了SetupRC
和main函數(shù)中部分代碼安疗。
1.SetupRC
函數(shù)
void SetupRC()
{
//1.設(shè)置背景顏色
glClearColor(0.3f, 0.3f, 0.3f, 1.0f );
//2.初始化著色器管理器
shaderManager.InitializeStockShaders();
//3.將相機(jī)向后移動(dòng)7個(gè)單元:肉眼到物體之間的距離
viewFrame.MoveForward(7.0);
//4.創(chuàng)建一個(gè)甜甜圈
gltMakeTorus(torusBatch, 1.0f, 0.3f, 52, 26);
//5.點(diǎn)的大小(方便點(diǎn)填充時(shí),肉眼觀察)
glPointSize(4.0f);
}
gltMakeTorus
是用來(lái)創(chuàng)建甜甜圈的函數(shù):
//GLTriangleBatch& torusBatch:GLTriangleBatch 容器幫助類
//majorRadius:外邊緣半徑
//minorRadius:內(nèi)邊緣半徑
//numMajor、numMinor:主半徑和從半徑的細(xì)分單元數(shù)量
void gltMakeTorus(GLTriangleBatch& torusBatch,
GLfloat majorRadius,
GLfloat minorRadius,
GLint numMajor,
GLint numMinor);
2.main
函數(shù)
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(SpecialKeys);
glutDisplayFunc(RenderScene);
//添加右擊菜單欄
// Create the Menu
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;
}
其中主要是下面幾句代碼:
//添加右擊菜單欄
// Create the Menu
glutCreateMenu(ProcessMenu);
//是否開啟深度測(cè)試
glutAddMenuEntry("Toggle depth test",1);
//是否消除隱藏面
glutAddMenuEntry("Toggle cull backface",2);
//填充方式--充滿
glutAddMenuEntry("Set Fill Mode", 3);
//填充方式--線段
glutAddMenuEntry("Set Line Mode", 4);
//填充方式--點(diǎn)
glutAddMenuEntry("Set Point Mode", 5);
glutAttachMenu(GLUT_RIGHT_BUTTON);
這幾行代碼實(shí)現(xiàn)了注冊(cè)鼠標(biāo)右鍵5個(gè)點(diǎn)擊事件以及處理這5個(gè)點(diǎn)擊事件的ProcessMenu
函數(shù)裆熙。
-
ProcessMenu
函數(shù)
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();
}
ok,接下來(lái)運(yùn)行工程,看下繪制的結(jié)果:
二萤厅、隱藏面消除
看起來(lái)是不是很完美啊。成功繪制的喜悅讓我十分激動(dòng)靴迫,然后想看下甜甜圈的全貌惕味,于是我按了下上下左右,結(jié)果問(wèn)題就出現(xiàn)了:
這是個(gè)什么??笆妇ⅰ赦拘?
原來(lái)是因?yàn)槲覀冊(cè)诶L制3D場(chǎng)景的時(shí)候,我們需要決定哪些部分是對(duì)觀察者可見(jiàn)的,或者哪些部分是對(duì)觀察者不可見(jiàn)的.對(duì)于不可見(jiàn)的部分即隱藏面,應(yīng)該及早丟棄。
例如在?個(gè)不透明的墻壁后面的圖像芬沉,就不應(yīng)該渲染.這種情況叫做”隱藏面消除”(Hidden surface elimination)躺同。
剛剛我們繪制的甜甜圈實(shí)際上是由很多個(gè)三角形拼起來(lái)的,在旋轉(zhuǎn)的過(guò)程中
OpenGL
并不知道該顯示哪些視圖丸逸,哪些本不應(yīng)該被我們看見(jiàn)的三角形的背面也被我們看見(jiàn)了蹋艺,所以出現(xiàn)了上面圖片中的情況。接下來(lái)我們就來(lái)探討下如何進(jìn)行隱藏面消除:
解決方案一:油畫算法
即像畫油畫一樣黄刚,先畫畫中最下面的部分捎谨,再畫上面的部分,知道全部完成憔维。在OpenGL
中可以理解為:
-
先繪制場(chǎng)景中的離觀察者較遠(yuǎn)的物體,再繪制較近的物體涛救。
但是油畫算法使用場(chǎng)景有限,如果多個(gè)圖形出現(xiàn)疊加的情況业扒,油畫算法也將無(wú)法處理检吆。
解決方案二:正背面剔除(Face Culling
)
試想一下,當(dāng)你觀察一個(gè)3D的正方形或者三角形圖形程储,你從任何的角度看過(guò)去蹭沛,最多可以看到幾個(gè)面? 答案是3個(gè),還有一部分面是你看不到的章鲤,那這些看不到的面摊灭,我們?yōu)楹我ダL制?豈不是白白浪費(fèi)OpenGL
性能败徊?
- 在平面圖形中帚呼,任何平?都有2個(gè)?,正面/背面.意味著你一個(gè)時(shí)刻只能看到一面集嵌。
OpenGL
可以做到檢查所有正面朝向觀察者的面,并渲染它們.從?丟棄背?朝向的面. 這樣可以節(jié)約?元著色器的性能萝挤。 - 如何告
OpenGL
你繪制的圖形,哪個(gè)?是正面,哪個(gè)面是背?? 答案: 通過(guò)分析頂點(diǎn)數(shù)據(jù)的順序御毅。
1.平面圖形的正背面:
GLfloat vertices[] = {
//順時(shí)針
vertices[0], // vertex 1
vertices[1], // vertex 2
vertices[2], // vertex 3
// 逆時(shí)針
vertices[0], // vertex 1
vertices[2], // vertex 3
vertices[1] // vertex 2
};
在OpenGL中,針對(duì)平面圖形的正背面區(qū)分:
正?: 按照逆時(shí)針頂點(diǎn)連接順序的三?形?
背面: 按照順時(shí)針頂點(diǎn)連接順序的三角形面
2.立體圖形的正背面:
- 圖中左側(cè)三?形頂點(diǎn)順序?yàn)? 1—> 2—> 3 ; 右側(cè)三角形的頂點(diǎn)順序?yàn)? 1—> 2—> 3 怜珍。
- 當(dāng)觀察者在右側(cè)時(shí),則右邊的三角形?向?yàn)槟鏁r(shí)針?lè)较騽t為正面,?左側(cè)的三?形為順時(shí)針則為背面
- 當(dāng)觀察者在左側(cè)時(shí),則左邊的三角形為逆時(shí)針?向判定為正面,?右側(cè)的三角形為順時(shí)針判定為背面.
正?和背面是有三?形的頂點(diǎn)定義順序和觀察者?向共同決定的.隨著觀察者的?度?向的改變,正面背?也會(huì)跟著改變端蛆。
3.OpenGL中正背面剔除的代碼
開啟表?剔除(默認(rèn)背面剔除)
void glEnable(GL_CULL_FACE);
關(guān)閉表面剔除(默認(rèn)背面剔除)
void glDisable(GL_CULL_FACE);
?戶選擇剔除那個(gè)面(正?/背面) void glCullFace(GLenum mode);
mode參數(shù)為: GL_FRONT,GL_BACK,GL_FRONT_AND_BACK ,默認(rèn)GL_BACK
?戶指定繞序那個(gè)為正面
void glFrontFace(GLenum mode);
mode參數(shù)為: GL_CW,GL_CCW,默認(rèn)值:GL_CCW
例如:剔除正面實(shí)現(xiàn)(1)
glCullFace(GL_BACK);
glFrontFace(GL_CW);
例如:剔除正面實(shí)現(xiàn)(2)
glCullFace(GL_FRONT);
經(jīng)過(guò)上面的了解,可以知道正背面剔除更適合用于OpenGL
中的隱藏面消除酥泛。
接下來(lái)我們就加上正背面的消除的代碼再看看效果:
在RenderScene
中添加以下代碼:
//開啟/關(guān)閉正背面剔除功能
if (iCull) {
glEnable(GL_CULL_FACE);
glFrontFace(GL_CCW);
glCullFace(GL_BACK);
}else
{
glDisable(GL_CULL_FACE);
}
運(yùn)行功能并點(diǎn)擊鼠標(biāo)右鍵菜單今豆,點(diǎn)擊第二個(gè)選項(xiàng):
可以看到隱藏面已經(jīng)消除,之前旋轉(zhuǎn)式顯示的現(xiàn)象已經(jīng)不存在了柔袁。但是細(xì)心地同學(xué)肯定發(fā)現(xiàn)了呆躲,我們又出現(xiàn)了新的問(wèn)題:
了解和解決這個(gè)問(wèn)題就要用到OpenGL中很重要的一個(gè)知識(shí)點(diǎn)了--深度測(cè)試。
三捶索、深度測(cè)試
相關(guān)名詞解釋:
- 深度:深度其實(shí)就是該像素點(diǎn)在3D世界中距離攝像機(jī)的距離,即Z坐標(biāo)值插掂;
- 深度緩沖區(qū):深度緩存區(qū),就是一塊內(nèi)存區(qū)域,專?存儲(chǔ)著每個(gè)像素點(diǎn)(繪制在屏幕上的)深度值。
如果觀察者在Z軸的正方向腥例,Z越大則越靠近觀察者辅甥;
如果觀察者在Z軸的負(fù)方向,Z越小則越靠近觀察者燎竖。
我們?yōu)槭裁葱枰疃染彌_區(qū)呢璃弄?
在不使用深度測(cè)試的時(shí)候,如果我們先繪制?個(gè)距離比較近的物體,再繪制距離較遠(yuǎn)的物體,則距離遠(yuǎn)的位圖因?yàn)楹罄L制,會(huì)把距離近的物體覆蓋掉. 有了深度緩沖區(qū)后,繪制物體的順序就不那么重要了。實(shí)際上,只要存在深度緩沖區(qū),OpenGL
都會(huì)把像素的深度值寫?到緩沖區(qū)中. 除非調(diào)用glDepthMask(GL_FALSE)
來(lái)禁?寫入构回。
-
深度測(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è)過(guò)程稱為深度測(cè)試垦梆。 -
深度值計(jì)算:
- 深度值?般由16位、24位或者32位值表示仅孩,通常是24位托猩。位數(shù)越高的話,深度的精確度越好辽慕。深度值的范圍在[0,1]之間京腥,值越小表示越靠近觀察者,值越大表示遠(yuǎn)離觀察者溅蛉。
-
深度緩沖主要是通過(guò)計(jì)算深度值來(lái)?較?小公浪,在深度緩沖區(qū)中包含深度值介于0.0和1.0之間他宛, 從觀察者看到其內(nèi)容與場(chǎng)景中的所有對(duì)象的 z 值進(jìn)行了?較。這些視圖空間中的 z 值可以在投影平頭截體的近平面和遠(yuǎn)平面之間的任何值欠气。我們因此需要一些?法來(lái)轉(zhuǎn)換這些視圖空間 z 值 到 [0厅各,1] 的范圍內(nèi),下面的 (線性) ?程把 z 值轉(zhuǎn)換為 0.0 和 1.0 之間的值:
far和near是提供到投影矩陣設(shè)置可見(jiàn)視圖截錐的遠(yuǎn)近值预柒。
使用深度測(cè)試:
-
深度緩沖區(qū)一般由窗口管理系統(tǒng)队塘,
GLFW
創(chuàng)建。深度值?般由16位宜鸯、24位憔古、32位值表示,通常是24位淋袖。位數(shù)越高鸿市,深度精確度更好。 - 開始深度測(cè)試:
glEnable(GL_DEPTH_TEST);
- 在繪制場(chǎng)景前即碗,清除顏色緩存區(qū)焰情、深度緩沖區(qū)。
glClearColor(0.0f,0.0f,0.0f,1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- 打開/關(guān)閉 深度緩存區(qū)寫入:
void glDepthMask(GLBool value);
value : GL_TURE 開啟深度緩沖區(qū)寫?入; GL_FALSE 關(guān)閉深度緩沖區(qū)寫?入
在案例中使用深度測(cè)試:
同樣的在RenderSence
中加入代碼:
if (iDepth) {
glEnable(GL_DEPTH_TEST);
}else{
glDisable(GL_DEPTH_TEST);
}
結(jié)果怎么樣呢拜姿?看圖說(shuō)話:
完美解決@友!蕊肥!
深度測(cè)試可以解決什么問(wèn)題谒获?
- 類似于甜甜圈的缺口問(wèn)題。當(dāng)旋轉(zhuǎn)時(shí)壁却,OpenGL無(wú)法區(qū)分物體的兩部分重疊情況批狱,導(dǎo)致缺口出現(xiàn);
- 利用深度測(cè)試解決隱藏面的消除展东。
總結(jié):
通過(guò)甜甜圈案例赔硫,讓我們了解了什么是隱藏面,以及如何消除隱藏面盐肃。
-
正背面消除:需要根據(jù)頂點(diǎn)數(shù)據(jù)順序判斷用戶可見(jiàn)部分與隱藏面爪膊,隱藏面直接丟棄,不繪制砸王,只繪制可見(jiàn)部分推盛;
-
深度測(cè)試:可以一次性解決隱藏面消除問(wèn)題,原理是不管有多少圖層谦铃,只顯示可見(jiàn)圖層耘成,剩余不可見(jiàn)的都丟棄。
覺(jué)得不錯(cuò)記得點(diǎn)贊哦!聽說(shuō)看完點(diǎn)贊的人逢考必過(guò)瘪菌,逢獎(jiǎng)必中撒会。?( ′???` )比心