OpenGL案例-繪制甜甜圈以及隱藏面消除(正背面剔除和深度測(cè)試)

一蔓纠、繪制甜甜圈

上篇文中已經(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é)果:


甜甜圈.png

二萤厅、隱藏面消除

看起來(lái)是不是很完美啊。成功繪制的喜悅讓我十分激動(dòng)靴迫,然后想看下甜甜圈的全貌惕味,于是我按了下上下左右,結(jié)果問(wèn)題就出現(xiàn)了:

隱藏面.gif

這是個(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)的物體,再繪制較近的物體涛救。


    油畫算法.png

但是油畫算法使用場(chǎng)景有限,如果多個(gè)圖形出現(xiàn)疊加的情況业扒,油畫算法也將無(wú)法處理检吆。


油畫算法缺點(diǎn).png

解決方案二:正背面剔除(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.平面圖形的正背面:

三角形正背面.png
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.立體圖形的正背面:

立體圖形正背面.png
  • 圖中左側(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):


隱藏面消除.gif

可以看到隱藏面已經(jīng)消除,之前旋轉(zhuǎn)式顯示的現(xiàn)象已經(jīng)不存在了柔袁。但是細(xì)心地同學(xué)肯定發(fā)現(xiàn)了呆躲,我們又出現(xiàn)了新的問(wèn)題:


新的問(wèn)題.png

了解和解決這個(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ì)算
    1. 深度值?般由16位、24位或者32位值表示仅孩,通常是24位托猩。位數(shù)越高的話,深度的精確度越好辽慕。深度值的范圍在[0,1]之間京腥,值越小表示越靠近觀察者,值越大表示遠(yuǎn)離觀察者溅蛉。
    2. 深度緩沖主要是通過(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 之間的值:
      深度計(jì)算公式.png

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è)試進(jìn)行正背面剔除.gif

完美解決@友!蕊肥!

深度測(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)必中撒会。?( ′???` )比心

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市师妙,隨后出現(xiàn)的幾起案子诵肛,更是在濱河造成了極大的恐慌,老刑警劉巖疆栏,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件曾掂,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡壁顶,警方通過(guò)查閱死者的電腦和手機(jī)珠洗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)若专,“玉大人许蓖,你說(shuō)我怎么就攤上這事〉魉ィ” “怎么了膊爪?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)嚎莉。 經(jīng)常有香客問(wèn)我米酬,道長(zhǎng),這世上最難降的妖魔是什么趋箩? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任赃额,我火速辦了婚禮,結(jié)果婚禮上叫确,老公的妹妹穿的比我還像新娘跳芳。我一直安慰自己,他們只是感情好竹勉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布飞盆。 她就那樣靜靜地躺著,像睡著了一般次乓。 火紅的嫁衣襯著肌膚如雪吓歇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天票腰,我揣著相機(jī)與錄音照瘾,去河邊找鬼。 笑死丧慈,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播逃默,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鹃愤,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了完域?” 一聲冷哼從身側(cè)響起软吐,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吟税,沒(méi)想到半個(gè)月后凹耙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肠仪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年肖抱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片异旧。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡意述,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吮蛹,到底是詐尸還是另有隱情荤崇,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布潮针,位于F島的核電站术荤,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏每篷。R本人自食惡果不足惜瓣戚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望雳攘。 院中可真熱鬧带兜,春花似錦、人聲如沸吨灭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)喧兄。三九已至无畔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間吠冤,已是汗流浹背浑彰。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拯辙,地道東北人郭变。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓颜价,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親诉濒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子周伦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容