Single Depth peeling 順序無關渲染(OIT)

  • 2020 年 11 月 23 日
  • 筆記

什麼是順序無關渲染

在3D渲染中,物體的渲染是按一定的順序渲染的,這也就可能導致半透明的物體先於不透明的物體渲染,結果就是可能出現半透明物體後的物體由於深度遮擋而沒有渲染出來。對於這種情況通常會先渲染所有的不透明物體再渲染半透明物體或者按深度進行排序來解決。但這樣仍然無法解決半透明物體之間的透明效果渲染錯誤問題,特別是物體之間存在交叉無法通過簡單的排序來解決。於是就有一些用專門來解決半透明物體渲染演算法,OIT演算法即Order Independent Transparency(順序無關的半透明渲染)。Depth Peeling是眾多OIT演算法里可以得到精確blending結果的一個,在非遊戲的3d應用場景中應該還是很有價值的。

​ 兩個交叉半透明四邊形(未使用OIT渲染)

​ 兩個交叉半透明四邊形(使用OIT渲染)

Single Depth Peeling原理

Single Depth Peeling原文

Single Depth Peeling 顧名思義,就是通過多次繪製,每次繪製剝離離相機最靠近的一層,像剝洋蔥一樣層層剝開,按順序混合就得到了精確的混合結果。既然有Single Depth Peeling,還有一種優化版本就是Dual Depth Peeling,從前後兩個方向剝離,不在本次討論的範圍,有興趣可以參考鏈接論文。

深度剝離是一種對深度值進行排序的技術。它的原理比較直觀,通常的深度檢測是將場景中Z值最小的像素輸出到螢幕上,就是里相機最近的像素。如此一來就一定有離相機第二近的點,第三近的點·····。通過多次渲染的方法,第一次正常渲染,將深度值存入紋理就得到來離相機最近像素的深度和顏色。第二遍渲染時,把每個像素的深度與上次的深度值做比較,凡是小於上次深度值的都通過測試,在加上FBO深度測試的最小值功能就能得到下一個最小的深度值與顏色值,以此類推即可。

缺點

需要剝離N次才能完成,就需要N個Pass,N是深度複雜度。因此性能是嚴重的瓶頸,另外如何確定N也是個問題。

具體流程

1、創建兩對顏色紋理和兩對GL_FLOAT類型的深度紋理用來pingpong。

2、clear深度紋理為0,關閉OpenGL混合

2、正常渲染,大於深度紋理上的值都可以通過測試,加上深度緩衝測試的最小深度值就可以得到離相機最近的深度與顏色值。將顏色結果與顏色紋理中的顏色做混合,深度寫入深度紋理。

3、使用上次得到的顏色與深度作為輸入紋理重複2的操作,直到剝離完成。

如何從前向後混合顏色

從前向後直接混合明顯是錯誤的,但是我們可以根據混合演算法推導出反向混合的演算法,具體推導可以參考Dual Peth Peeling的paper。具體混合演算法為:

glBlendEquation(GL_FUNC_ADD); 
glBlendFuncSeparate(GL_DST_ALPHA, GL_ONE,  GL_ZERO, GL_ONE_MINUS_SRC_ALPHA);

如何確定N

我們無法確定需要剝離多少次,因為不同的渲染目標的深度複雜度是不同的。目前來說最好的方法是採用遮擋查詢的方式來檢測是否剝離完成。但這種方式需要GPU同步,也會帶來嚴重的性能問題。方式如下

GLuint queryId;
glBeginQuery(GL_SAMPLES_PASSED, queryId);
//depth peeling
glEndQuery(GL_SAMPLES_PASSED);
GLuint queryReady = GL_FALSE;
glGetQueryObjectuiv(queryId, GL_QUERY_RESULT_AVAILABLE, &queryReady);
GLuint samples = 0;
glGetQueryObjectuiv(mOITQueryId, GL_QUERY_RESULT, &samples);

samples為0時就剝離完成了,不能0則繼續剝離。

實際應用中值得注意的地方

由於深度精度問題可能會造成交叉的地方有接縫,具體做法如下:

1、深度緩衝及紋理使用GL_FLOAT類型增加精度。

2、紋理需要使用高精度的紋理 precision highp sampler2D;

3、離攝像機過於仍然會由於精度不足而出現接縫,這時就需要動態調整攝像機遠近平面來提升精度

4、優化遮擋查詢中的同步操作

5、避免遮擋查詢出現死循環

優化方向

1、本文採用從前向後剝離,在細節要求不高的情況下可以固定N,忽略後續的剝離影響不大。

2、使用Dpeth Peeling的優化版本Dual Dpeth Peeling

3、使用高版本才能支援的per pixel linked list方法