版本記錄
版本號(hào) | 時(shí)間 |
---|---|
V1.0 | 2018.01.06 |
前言
OpenGL 圖形庫(kù)項(xiàng)目中一直也沒(méi)用過(guò)同窘,最近也想學(xué)著使用這個(gè)圖形庫(kù)锈至,感覺(jué)還是很有意思搞隐,也就自然想著好好的總結(jié)一下驹愚,希望對(duì)大家能有所幫助。下面內(nèi)容來(lái)自歡迎來(lái)到OpenGL的世界劣纲。
1. OpenGL 圖形庫(kù)使用(一) —— 概念基礎(chǔ)
2. OpenGL 圖形庫(kù)使用(二) —— 渲染模式逢捺、對(duì)象、擴(kuò)展和狀態(tài)機(jī)
3. OpenGL 圖形庫(kù)使用(三) —— 著色器癞季、數(shù)據(jù)類型與輸入輸出
4. OpenGL 圖形庫(kù)使用(四) —— Uniform及更多屬性
5. OpenGL 圖形庫(kù)使用(五) —— 紋理
6. OpenGL 圖形庫(kù)使用(六) —— 變換
7. OpenGL 圖形庫(kù)的使用(七)—— 坐標(biāo)系統(tǒng)之五種不同的坐標(biāo)系統(tǒng)(一)
8. OpenGL 圖形庫(kù)的使用(八)—— 坐標(biāo)系統(tǒng)之3D效果(二)
9. OpenGL 圖形庫(kù)的使用(九)—— 攝像機(jī)(一)
10. OpenGL 圖形庫(kù)的使用(十)—— 攝像機(jī)(二)
11. OpenGL 圖形庫(kù)的使用(十一)—— 光照之顏色
12. OpenGL 圖形庫(kù)的使用(十二)—— 光照之基礎(chǔ)光照
13. OpenGL 圖形庫(kù)的使用(十三)—— 光照之材質(zhì)
14. OpenGL 圖形庫(kù)的使用(十四)—— 光照之光照貼圖
15. OpenGL 圖形庫(kù)的使用(十五)—— 光照之投光物
16. OpenGL 圖形庫(kù)的使用(十六)—— 光照之多光源
17. OpenGL 圖形庫(kù)的使用(十七)—— 光照之復(fù)習(xí)總結(jié)
18. OpenGL 圖形庫(kù)的使用(十八)—— 模型加載之Assimp
19. OpenGL 圖形庫(kù)的使用(十九)—— 模型加載之網(wǎng)格
20. OpenGL 圖形庫(kù)的使用(二十)—— 模型加載之模型
深度測(cè)試
在坐標(biāo)系統(tǒng)小節(jié)中劫瞳,我們渲染了一個(gè)3D箱子,并且運(yùn)用了深度緩沖(Depth Buffer)來(lái)防止被阻擋的面渲染到其它面的前面绷柒。在這一節(jié)中志于,我們將會(huì)更加深入地討論這些儲(chǔ)存在深度緩沖(或z緩沖(z-buffer)
)中的深度值(Depth Value)
,以及它們是如何確定一個(gè)片段是處于其它片段后方的废睦。
深度緩沖就像顏色緩沖(Color Buffer)
(儲(chǔ)存所有的片段顏色:視覺(jué)輸出)一樣伺绽,在每個(gè)片段中儲(chǔ)存了信息,并且(通常)和顏色緩沖有著一樣的寬度和高度。深度緩沖是由窗口系統(tǒng)自動(dòng)創(chuàng)建的奈应,它會(huì)以16澜掩、24或32位float的形式儲(chǔ)存它的深度值。在大部分的系統(tǒng)中杖挣,深度緩沖的精度都是24位的肩榕。
當(dāng)深度測(cè)試(Depth Testing)
被啟用的時(shí)候,OpenGL會(huì)將一個(gè)片段的的深度值與深度緩沖的內(nèi)容進(jìn)行對(duì)比惩妇。OpenGL會(huì)執(zhí)行一個(gè)深度測(cè)試株汉,如果這個(gè)測(cè)試通過(guò)了的話,深度緩沖將會(huì)更新為新的深度值歌殃。如果深度測(cè)試失敗了郎逃,片段將會(huì)被丟棄。
現(xiàn)在大部分的GPU都提供一個(gè)叫做提前深度測(cè)試(Early Depth Testing)的硬件特性挺份。提前深度測(cè)試允許深度測(cè)試在片段著色器之前運(yùn)行褒翰。只要我們清楚一個(gè)片段永遠(yuǎn)不會(huì)是可見(jiàn)的(它在其他物體之后),我們就能提前丟棄這個(gè)片段匀泊。
片段著色器通常開銷都是很大的优训,所以我們應(yīng)該盡可能避免運(yùn)行它們。當(dāng)使用提前深度測(cè)試時(shí)各聘,片段著色器的一個(gè)限制是你不能寫入片段的深度值揣非。如果一個(gè)片段著色器對(duì)它的深度值進(jìn)行了寫入,提前深度測(cè)試是不可能的躲因。OpenGL不能提前知道深度值早敬。
深度測(cè)試默認(rèn)是禁用的,所以如果要啟用深度測(cè)試的話大脉,我們需要用GL_DEPTH_TEST
選項(xiàng)來(lái)啟用它:
glEnable(GL_DEPTH_TEST);
當(dāng)它啟用的時(shí)候搞监,如果一個(gè)片段通過(guò)了深度測(cè)試的話,OpenGL會(huì)在深度緩沖中儲(chǔ)存該片段的z值镰矿;如果沒(méi)有通過(guò)深度緩沖琐驴,則會(huì)丟棄該片段。如果你啟用了深度緩沖秤标,你還應(yīng)該在每個(gè)渲染迭代之前使用GL_DEPTH_BUFFER_BIT
來(lái)清除深度緩沖绝淡,否則你會(huì)仍在使用上一次渲染迭代中的寫入的深度值:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
可以想象,在某些情況下你會(huì)需要對(duì)所有片段都執(zhí)行深度測(cè)試并丟棄相應(yīng)的片段苍姜,但不希望更新深度緩沖牢酵。基本上來(lái)說(shuō)衙猪,你在使用一個(gè)只讀的(Read-only)深度緩沖馍乙。OpenGL允許我們禁用深度緩沖的寫入玉罐,只需要設(shè)置它的深度掩碼(Depth Mask)設(shè)置為GL_FALSE就可以了:
glDepthMask(GL_FALSE);
注意這只在深度測(cè)試被啟用的時(shí)候才有效果。
深度測(cè)試函數(shù)
OpenGL允許我們修改深度測(cè)試中使用的比較運(yùn)算符潘拨。這允許我們來(lái)控制OpenGL什么時(shí)候該通過(guò)或丟棄一個(gè)片段,什么時(shí)候去更新深度緩沖饶号。我們可以調(diào)用glDepthFunc
函數(shù)來(lái)設(shè)置比較運(yùn)算符(或者說(shuō)深度函數(shù)(Depth Function)
):
glDepthFunc(GL_LESS);
這個(gè)函數(shù)接受下面表格中的比較運(yùn)算符:
默認(rèn)情況下使用的深度函數(shù)是GL_LESS
铁追,它將會(huì)丟棄深度值大于等于當(dāng)前深度緩沖值的所有片段。
讓我們看看改變深度函數(shù)會(huì)對(duì)視覺(jué)輸出有什么影響茫船。我們將使用一個(gè)新的代碼配置琅束,它會(huì)顯示一個(gè)沒(méi)有光照的基本場(chǎng)景,里面有兩個(gè)有紋理的立方體算谈,放置在一個(gè)有紋理的地板上涩禀。你可以在這里找到源代碼。
在源代碼中然眼,我們將深度函數(shù)改為GL_ALWAYS
:
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_ALWAYS);
這將會(huì)模擬我們沒(méi)有啟用深度測(cè)試時(shí)所得到的結(jié)果艾船。深度測(cè)試將會(huì)永遠(yuǎn)通過(guò),所以最后繪制的片段將會(huì)總是會(huì)渲染在之前繪制片段的上面高每,即使之前繪制的片段本就應(yīng)該渲染在最前面屿岂。因?yàn)槲覀兪亲詈箐秩镜匕宓模鼤?huì)覆蓋所有的箱子片段:
將它重新設(shè)置為GL_LESS
鲸匿,這會(huì)將場(chǎng)景還原為原有的樣子:
深度值精度
深度緩沖包含了一個(gè)介于0.0和1.0之間的深度值爷怀,它將會(huì)與觀察者視角所看見(jiàn)的場(chǎng)景中所有物體的z值進(jìn)行比較。觀察空間的z值可能是投影平截頭體的近平面(Near)和遠(yuǎn)平面(Far)之間的任何值带欢。我們需要一種方式來(lái)將這些觀察空間的z值變換到[0, 1]范圍之間运授,其中的一種方式就是將它們線性變換到[0, 1]范圍之間。下面這個(gè)(線性)方程將z值變換到了0.0到1.0之間的深度值:
這里的near和far值是我們之前提供給投影矩陣設(shè)置可視平截頭體的(見(jiàn)坐標(biāo)系統(tǒng))那個(gè) near 和 far 值乔煞。這個(gè)方程需要平截頭體中的一個(gè)z值归薛,并將它變換到了[0, 1]的范圍中。z值和對(duì)應(yīng)的深度值之間的關(guān)系可以在下圖中看到:
注意所有的方程都會(huì)將非常近的物體的深度值設(shè)置為接近0.0的值无蜂,而當(dāng)物體非常接近遠(yuǎn)平面的時(shí)候牢硅,它的深度值會(huì)非常接近1.0。
然而剥啤,在實(shí)踐中是幾乎永遠(yuǎn)不會(huì)使用這樣的線性深度緩沖(Linear Depth Buffer)
的锦溪。要想有正確的投影性質(zhì),需要使用一個(gè)非線性的深度方程府怯,它是與 1/z 成正比的刻诊。它做的就是在z值很小的時(shí)候提供非常高的精度,而在z值很遠(yuǎn)的時(shí)候提供更少的精度牺丙≡蜓模花時(shí)間想想這個(gè):我們真的需要對(duì)1000單位遠(yuǎn)的深度值和只有1單位遠(yuǎn)的充滿細(xì)節(jié)的物體使用相同的精度嗎复局?線性方程并不會(huì)考慮這一點(diǎn)。
由于非線性方程與 1/z 成正比粟判,在1.0和2.0之間的z值將會(huì)變換至1.0到0.5之間的深度值亿昏,這就是一個(gè)float提供給我們的一半精度了,這在z值很小的情況下提供了非常大的精度档礁。在50.0和100.0之間的z值將會(huì)只占2%的float精度角钩,這正是我們所需要的。這樣的一個(gè)考慮了遠(yuǎn)近距離的方程是這樣的:
如果你不知道這個(gè)方程是怎么回事也不用擔(dān)心呻澜。重要的是要記住深度緩沖中的值在屏幕空間中不是線性的(在透視矩陣應(yīng)用之前在觀察空間中是線性的)递礼。深度緩沖中0.5的值并不代表著物體的z值是位于平截頭體的中間了,這個(gè)頂點(diǎn)的z值實(shí)際上非常接近近平面羹幸!你可以在下圖中看到z值和最終的深度緩沖值之間的非線性關(guān)系:
可以看到脊髓,深度值很大一部分是由很小的z值所決定的,這給了近處的物體很大的深度精度栅受。這個(gè)(從觀察者的視角)變換z值的方程是嵌入在投影矩陣中的将硝,所以當(dāng)我們想將一個(gè)頂點(diǎn)坐標(biāo)從觀察空間至裁剪空間的時(shí)候這個(gè)非線性方程就被應(yīng)用了。如果你想深度了解投影矩陣究竟做了什么屏镊,我建議閱讀這篇文章袋哼。
如果我們想要可視化深度緩沖的話,非線性方程的效果很快就會(huì)變得很清楚闸衫。
深度緩沖的可視化
我們知道片段著色器中涛贯,內(nèi)建gl_FragCoord
向量的z值包含了那個(gè)特定片段的深度值。如果我們將這個(gè)深度值輸出為顏色蔚出,我們可以顯示場(chǎng)景中所有片段的深度值弟翘。我們可以根據(jù)片段的深度值返回一個(gè)顏色向量來(lái)完成這一工作:
void main()
{
FragColor = vec4(vec3(gl_FragCoord.z), 1.0);
}
如果你再次運(yùn)行程序的話,你可能會(huì)注意到所有東西都是白色的骄酗,看起來(lái)就想我們所有的深度值都是最大的1.0稀余。所以為什么沒(méi)有靠近0.0(即變暗)的深度值呢?
你可能還記得在上一部分中說(shuō)到趋翻,屏幕空間中的深度值是非線性的睛琳,即它在z值很小的時(shí)候有很高的精度,而z值很大的時(shí)候有較低的精度踏烙。片段的深度值會(huì)隨著距離迅速增加师骗,所以幾乎所有的頂點(diǎn)的深度值都是接近于1.0的。如果我們小心地靠近物體讨惩,你可能會(huì)最終注意到顏色會(huì)漸漸變暗辟癌,顯示它們的z值在逐漸變小:
這很清楚地展示了深度值的非線性性質(zhì)荐捻。近處的物體比起遠(yuǎn)處的物體對(duì)深度值有著更大的影響黍少。只需要移動(dòng)幾厘米就能讓顏色從暗完全變白寡夹。
然而,我們也可以讓片段非線性的深度值變換為線性的厂置。要實(shí)現(xiàn)這個(gè)菩掏,我們需要僅僅反轉(zhuǎn)深度值的投影變換。這也就意味著我們需要首先將深度值從[0, 1]范圍重新變換到[-1, 1]范圍的標(biāo)準(zhǔn)化設(shè)備坐標(biāo)(裁剪空間)昵济。接下來(lái)我們需要像投影矩陣那樣反轉(zhuǎn)這個(gè)非線性方程(方程2)智绸,并將這個(gè)反轉(zhuǎn)的方程應(yīng)用到最終的深度值上。最終的結(jié)果就是一個(gè)線性的深度值了砸紊。聽起來(lái)是可行的,對(duì)吧囱挑?
首先我們將深度值變換為NDC醉顽,不是非常困難:
float z = depth * 2.0 - 1.0;
接下來(lái)使用獲取到的z值,應(yīng)用逆變換來(lái)獲取線性的深度值:
float linearDepth = (2.0 * near * far) / (far + near - z * (far - near));
這個(gè)方程是用投影矩陣推導(dǎo)得出的平挑,它使用了方程2來(lái)非線性化深度值游添,返回一個(gè)near與far之間的深度值。這篇注重?cái)?shù)學(xué)的文章為感興趣的讀者詳細(xì)解釋了投影矩陣通熄,它也展示了這些方程是怎么來(lái)的唆涝。
將屏幕空間中非線性的深度值變換至線性深度值的完整片段著色器如下:
#version 330 core
out vec4 FragColor;
float near = 0.1;
float far = 100.0;
float LinearizeDepth(float depth)
{
float z = depth * 2.0 - 1.0; // back to NDC
return (2.0 * near * far) / (far + near - z * (far - near));
}
void main()
{
float depth = LinearizeDepth(gl_FragCoord.z) / far; // 為了演示除以 far
FragColor = vec4(vec3(depth), 1.0);
}
由于線性化的深度值處于near與far之間,它的大部分值都會(huì)大于1.0并顯示為完全的白色唇辨。通過(guò)在main函數(shù)中將線性深度值除以far廊酣,我們近似地將線性深度值轉(zhuǎn)化到[0, 1]的范圍之間。這樣子我們就能逐漸看到一個(gè)片段越接近投影平截頭體的遠(yuǎn)平面赏枚,它就會(huì)變得越亮亡驰,更適用于展示目的。
如果我們現(xiàn)在運(yùn)行程序饿幅,我們就能看見(jiàn)深度值隨著距離增大是線性的了凡辱。嘗試在場(chǎng)景中移動(dòng),看看深度值是怎樣以線性變化的栗恩。
顏色大部分都是黑色透乾,因?yàn)樯疃戎档姆秶?.1的近平面到100的遠(yuǎn)平面,它離我們還是非常遠(yuǎn)的磕秤。結(jié)果就是乳乌,我們相對(duì)靠近近平面,所以會(huì)得到更低的(更暗的)深度值市咆。
深度沖突
一個(gè)很常見(jiàn)的視覺(jué)錯(cuò)誤會(huì)在兩個(gè)平面或者三角形非常緊密地平行排列在一起時(shí)會(huì)發(fā)生钦扭,深度緩沖沒(méi)有足夠的精度來(lái)決定兩個(gè)形狀哪個(gè)在前面。結(jié)果就是這兩個(gè)形狀不斷地在切換前后順序床绪,這會(huì)導(dǎo)致很奇怪的花紋客情。這個(gè)現(xiàn)象叫做深度沖突(Z-fighting)其弊,因?yàn)樗雌饋?lái)像是這兩個(gè)形狀在爭(zhēng)奪(Fight)誰(shuí)該處于頂端。
在我們一直使用的場(chǎng)景中膀斋,有幾個(gè)地方的深度沖突還是非常明顯的梭伐。箱子被放置在地板的同一高度上,這也就意味著箱子的底面和地板是共面的(Coplanar)仰担。這兩個(gè)面的深度值都是一樣的糊识,所以深度測(cè)試沒(méi)有辦法決定應(yīng)該顯示哪一個(gè)。
如果你將攝像機(jī)移動(dòng)到其中一個(gè)箱子的內(nèi)部摔蓝,你就能清楚地看到這個(gè)效果的赂苗,箱子的底部不斷地在箱子底面與地板之間切換,形成一個(gè)鋸齒的花紋:
深度沖突是深度緩沖的一個(gè)常見(jiàn)問(wèn)題贮尉,當(dāng)物體在遠(yuǎn)處時(shí)效果會(huì)更明顯(因?yàn)樯疃染彌_在z值比較大的時(shí)候有著更小的精度)拌滋。深度沖突不能夠被完全避免,但一般會(huì)有一些技巧有助于在你的場(chǎng)景中減輕或者完全避免深度沖突猜谚、
1. 防止深度沖突
第一個(gè)也是最重要的技巧是永遠(yuǎn)不要把多個(gè)物體擺得太靠近败砂,以至于它們的一些三角形會(huì)重疊。通過(guò)在兩個(gè)物體之間設(shè)置一個(gè)用戶無(wú)法注意到的偏移值魏铅,你可以完全避免這兩個(gè)物體之間的深度沖突昌犹。在箱子和地板的例子中,我們可以將箱子沿著正y軸稍微移動(dòng)一點(diǎn)览芳。箱子位置的這點(diǎn)微小改變將不太可能被注意到斜姥,但它能夠完全減少深度沖突的發(fā)生。然而沧竟,這需要對(duì)每個(gè)物體都手動(dòng)調(diào)整疾渴,并且需要進(jìn)行徹底的測(cè)試來(lái)保證場(chǎng)景中沒(méi)有物體會(huì)產(chǎn)生深度沖突。
第二個(gè)技巧是盡可能將近平面設(shè)置遠(yuǎn)一些屯仗。在前面我們提到了精度在靠近近平面時(shí)是非常高的搞坝,所以如果我們將近平面遠(yuǎn)離觀察者,我們將會(huì)對(duì)整個(gè)平截頭體有著更大的精度魁袜。然而桩撮,將近平面設(shè)置太遠(yuǎn)將會(huì)導(dǎo)致近處的物體被裁剪掉,所以這通常需要實(shí)驗(yàn)和微調(diào)來(lái)決定最適合你的場(chǎng)景的近平面距離峰弹。
另外一個(gè)很好的技巧是犧牲一些性能店量,使用更高精度的深度緩沖。大部分深度緩沖的精度都是24位的鞠呈,但現(xiàn)在大部分的顯卡都支持32位的深度緩沖融师,這將會(huì)極大地提高精度。所以蚁吝,犧牲掉一些性能旱爆,你就能獲得更高精度的深度測(cè)試舀射,減少深度沖突。
我們上面討論的三個(gè)技術(shù)是最普遍也是很容易實(shí)現(xiàn)的抗深度沖突技術(shù)了怀伦。還有一些更復(fù)雜的技術(shù)脆烟,但它們依然不能完全消除深度沖突。深度沖突是一個(gè)常見(jiàn)的問(wèn)題房待,但如果你組合使用了上面列舉出來(lái)的技術(shù)邢羔,你可能不會(huì)再需要處理深度沖突了。
后記
未完桑孩,待續(xù)~~~