1. 程式人生 > IOS開發 >OpenGL學習(四)-- 正面&背面剔除和深度測試

OpenGL學習(四)-- 正面&背面剔除和深度測試

我的 OpenGL 專題學習目錄,希望和大家一起學習交流進步!


一、渲染中可能會出現的問題(不希望出現的幾何圖形)

預設情況下,我們所渲染的每個點、線或三角形都會再螢幕上進行光柵化,並按照在組合圖元批次時指定的順序排列,這在某些情況下會產生問題。

如果我們繪製一個由很多個三角形組成的實體物件,那麼第一個繪製的三角形可能會被後面繪製的三角形覆蓋。如下圖這個像游泳圈似的模型,其中一些三角形在游泳圈的背面,另一些在正面,正常我們應該是看不到背面的(不考慮透明幾何體的特殊情況)。這樣的話,三角形繪製的順序可能會一團糟,就變成了下圖的樣子:

存在問題的圖形.png

二、解決方法

1. 油畫法(painters algorithm):

對這些三角形排序,先渲染較遠的三角形,再在它們上方渲染較近的三角形。但這種方法在圖形處理中效率很低,必須在任何發生重疊的地方對每個畫素進行兩次寫操作,速度會變慢。並且對獨立的三角形排序的開銷會過高。所以一般不推薦使用。

2. 正面&背面剔除:

對正面和背面三角形進行區分的原因之一就是為了進行剔除。背面剔除能極大提高效能,避免上圖出現的問題。它很高效,在渲染的圖元裝配階段就整體拋棄了一些三角形。

開啟背面剔除: glEnable(GL_CULL_FACE);

關閉背面剔除: glDisable(GL_CULL_FACE);

請注意,我們並沒有知名剔除的是正面還是背面。這是由另外一個函式 glCullFace 控制的。

void glCullFace(GLenum mode);
複製程式碼

mode 引數的可用值為 GL_FRONTGL_BACKGL_FRONT_AND_BACK。這樣要消除不透明物體的內部幾何圖形就需要兩行程式碼:

void glCullFace(GL_BACK);
glEnable(GL_CULL_FACE);
複製程式碼

在某些情況下,剔除實體幾何體的正面也很有必要,比如要顯示圖形內部渲染的時候。在渲染透明物件時(下面馬上就會講到混合),我們經常會對一個物件進行兩次渲染,第一次會開啟透明並剔除正面,第二次則消除背面。這樣就在渲染正面之前渲染了背面,這也是渲染透明物體的需要。

但是在開啟背面剔除後,會發現上面的游泳圈模型還是顯示的有問題,如圖:

未開啟深度測試.png
這是因為沒有開啟深度測試。

3. 深度測試:

  • 深度:

深度 就是在 openGL 座標系中,畫素點的 Z 座標距離觀察者的距離。觀察者可能放在座標系的任何位置,那麼,就不能簡單的說 Z 數值越大或越小,就是越靠近觀察者。 如果觀察者在Z軸的正方向,Z 值大的靠近觀察者,如果是在Z軸的反方向,則 Z 值小的更靠近觀察者。

  • 深度緩衝區(DepthBuffer):

深度緩衝區 原理就是把一個距離觀察平面(近裁剪面)的深度值(或距離)與視窗中的每個畫素相關聯。 首先,使用 glClear(GL_DEPTH_BUFFER_BIT),把所有畫素的深度值設定為最大值。 如果啟用了深度緩衝區,在繪製每個畫素之前,OpenGL 會把它的深度值和已經儲存在這個畫素的深度值進行比較。如果,新畫素深度值 < 原先畫素深度值,則新畫素值會取代原先的;反之,新畫素值被遮擋,它的顏色值和深度將被丟棄。 這個比較、丟棄的過程就叫做 深度測試,深度測試是另一種高效消除隱藏面的技術。

申請一個顏色緩衝區和一個深度緩衝區:

glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
複製程式碼

啟用 深度測試,只需呼叫 glEnable(GL_DEPTH_TEST);

關閉 深度測試: glDisable(GL_DEPTH_TEST);

如果沒有深度緩衝區,那麼啟動深度測試的命令將被忽略。 在繪製場景前,清除顏色緩衝區和深度緩衝區:

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
複製程式碼

開啟了深度測試後,我們終於得到了一個我們想要的游泳圈模型:

完美的游泳圈.png

清除深度緩衝區的預設值是1.0,表示最大的深度值,深度值的範圍在[0,1]之間。 使用者通過 glDepthFunc(GLenum func) 函式指定深度測試的規則,這個函式包括一個引數,如下表:

引數 說明
GL_ALWAYS 總是通過測試
GL_NEVER 總是不通過測試
GL_LESS 當前深度值 < 儲存的深度值時通過
GL_EQUAL 當前深度值 = 儲存的深度值時通過
GL_LEQUAL 當前深度值 <= 儲存的深度值時通過
GL_GREATER 當前深度值 > 儲存的深度值時通過
GL_NOTEQUAL 當前深度值 != 儲存的深度值時通過
GL_GEQUAL 當前深度值 >= 儲存的深度值時通過

z-fighting(z衝突、閃爍)問題:

閃爍問題.png
當深度值精確度很低時,容易引起ZFighting現象,表現為兩個物體靠的很近時確定誰在前,誰在後時出現了歧義。

  • 避免深度值相同造成的z-fighting衝突問題的幾種做法:
    1. 在第二次繪製時,插入一個少量的偏移。
    1. 使用 glPolygonOffset 函式調節片段的深度值,使得深度值偏移而不產生重疊。
    1. 使用更高位數的深度緩衝區,通常使用的深度緩衝區是 24 位的,現在有一些硬體使用使用 32 位的緩衝區,使精確度得到提高。

以上的總結參考了並部分摘抄了以下文章,非常感謝以下作者的分享!:

1、《OpenGL超級寶典 第5版》

2、《OpenGL程式設計指南(第八版)》

3、作者 The fool 的《OpenGL學習腳印:深度測試(depth testing)》

4、作者 lysc_forever的《OpenGL中的深度、深度快取、深度測試》

轉載請備註原文出處,不得用於商業傳播——凡幾多