OpenGL实现多层绘制(Layered Rendering) [转]
http://blog.csdn.net/u010462297/article/details/50589991
引言
在某些情況下會需要用到多層繪制。FBO下有多個顏色掛接點(Color Attachment),可以用不同的掛接點掛接不同的紋理對象,實現繪制多張紋理(MRT),這在之前的文章里已經有所描述。但是有時候這種方法是不夠好用的:
- 當紋理非常多時,掛接點往往不夠;
- 用多層繪制還能實現一些更為方便的功能,比如只調用一次shader就完成多個不同視點圖像的繪制。
特別是后面一點,實在是利器,配合視口矩陣(Viewport Array),簡直是方便。
網上的資料非常有限,不過幸好有 OpenGL Wiki 這種官方的文檔,只是文檔往往也寫得不夠詳細,英文的看著也費勁得很。折騰了好久,終于試出來了。
要實現Layered Rendering,需要用到多層紋理Array Texture、幀緩存對象FBO、幾何著色器Geometry Shader。
多層紋理(Array Texture)
多層紋理就是一個紋理對象的一層MipMap下儲存著多張紋理。最常用的二維紋理是GL_TEXTURE_2D,對應的多層紋理就是GL_TEXTURE_2D_ARRAY。其實這種紋理跟三維紋理差不多,用的函數往往也是三維紋理相關的函數,區別只在于第三維:深度,多層紋理的深度值就是層號,而三維紋理的深度值是像素坐標。
生成
二維多層紋理的生成方法跟普通的二維紋理幾乎沒有區別,只不過將GL_TEXTURE_2D換成GL_TEXTURE_2D_ARRAY。當然,在傳入紋理數據時,應當換成glTexImage3D函數,而這個函數相比glTexImage2D也就多了一個深度變量,這個變量的值是紋理層數。對于Layered Rendering而言,是要將這個紋理綁到FBO上,然后畫到這個紋理里,因此只需要調用:
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, width, height, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
1
1
這里是寫了2層width*height分辨率的紋理,用的是RGBA像素排列格式,最后一個參數是nullptr因為不必傳內容。
讀取
當然,紋理的讀出還是有一些講究的。在內存里紋理是按行優先的順序存儲,因此實際上拿到的w×h×d分辨率的二維多層紋理的數據相當于w×hd分辨率的普通二維紋理,每一層將在高度方向疊起來。
幀緩存對象(FBO)
FBO可講的地方不多,很多文章都有介紹過。不過依然要注意兩個地方。
完備性
FBO要能用,就必須實現其完備性(Completeness)。一般二維紋理掛接到FBO的顏色掛接點上,然后再生成一個深度緩存,掛到深度掛接點上,不會有什么問題。但是,對于多層紋理而言,則有一個強制要求:FBO的所有掛接點上掛接的必須都是多層的對象。所以,如果還需要深度信息,則深度緩存必須也做成多層的。所以,應該再開一個多層紋理,掛接到深度掛接點上來作為深度緩存。
紋理掛接
其實無論什么性質的紋理,掛接到FBO上時都可以統一用以下這句代碼:
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texID, 0);
1
1
假設將紋理texID掛接到0號顏色掛接點。這就不必關心紋理到底是什么格式。
幾何著色器(Geometry Shader)
要實現Layered Rendering,必須要用到Geometry Shader。以前一直都是跟Vertex Shader和Fragment Shader打交道,后來用了一下Compute Shader,現在終于用到這個了。
Geometry是介于Vertex和Fragment之間的一道步驟,其作用應該是對Vertex給出的頂點的進一步細分描述吧,具體并不太懂。但是它能夠使頂點“無中生有”,這是畫多層圖像的關鍵。
試想一下,對于多層紋理而言,每一層的紋理坐標實際上是相同的。層號如何指定呢?固然可以在建立VBO時給紋理坐標第三個維度值,但是這并不會讓OpenGL畫到你想畫的層上:Fragment的過程就是將頂點像素化的過程,而這個過程是二維的……最后拿到的二維圖像必然沒有深度,層數無從談起。
但是Geometry卻有個關鍵的內建輸出變量:out int gl_Layer,正是它能夠指定繪制的層數。我們不妨先將我寫出來的geometry shader的內容貼出來,注意顯卡至少要支持到OpenGL 4.0,#version這句至少是400:
#version 450
layout (triangles, invocations = 2) in; //輸入三角形,2次調用
layout (triangle_strip, max_vertices = 3) out; //輸出三角形
in vec2 gTexCoord[]; //從Vertex傳過來的紋理坐標
out vec2 fTexCoord; //傳到Fragment去的紋理坐標
out int gl_Layer; //層數的標記
void main()
{
for(int k=0; k<gl_in.length(); k++) //針對三角形每個頂點
{
gl_Layer = gl_InvocationID; //用調用編號標記層號
fTexCoord = gTexCoord[k]; //紋理坐標傳遞
gl_Position = gl_in[k].gl_Position; //頂點坐標傳遞
EmitVertex(); //開始傳遞頂點信息
}
EndPrimitive(); //結束
}
需要注意的是,triangles意味著你在OpenGL的繪制指令必須是GL_TRIANGLE、GL_TRIANGLE_STRIP或者GL_TRIANGLE_FAN。其他的對應關系可以在Wiki查到。三角形有3個頂點,這一組3個頂點將同時進入Geometry中,因此在Geometry中能拿到一個gl_in[]的內建數組,這個數組的大小應該跟繪制時一組頂點的數量一致,三角形就是3。而在這里我不需要增加頂點,因此輸出也還是3個頂點。同時紋理坐標也理所當然地變成了數組。因此需要一個循環來對三角形的每個頂點進行操作。
這里頂點坐標和紋理坐標都不必改,因此直接傳遞過去了。重點在于gl_Layer這一句。gl_Layer這個內建變量用于指示當前繪制的層號,這個值將影響像素化后像素繪制到哪一層上。OpenGL要求一組頂點(這里是一個三角形)內部的gl_Layer必須一致。這里賦值之后順便把它傳到Fragment里作為標志。
關鍵的一步在于第二行的invocations = 2和gl_InvocationID這個內建變量。invocations=n指示Geometry對每組頂點做n次運算,用gl_InvocationID來標記每次運算的序號。我們可以將這個序號送到gl_Layer來作為層號的值!這樣geometry就會將這一組頂點重復發送兩次,而這兩次是發送到不同層上的,從而實現不同層的繪制!
接下來只要在Fragment里拿到這個gl_Layer,根據需要分層作處理就可以了。
其他用途:多視口繪制
除了gl_Layer外,還有一個內建變量叫做gl_ViewportIndex,用于標記視口矩陣(Viewport
Array)的序號。視口矩陣可以往顯卡送入多個視口,通過這個序號指定要使用哪個。因此,可以將這個特性用于同一場景的多視口繪制,比如類似3Ds
MAX的三視圖繪制。可以把Vertex
Shader的大部分工作交給Geometry來完成,在Geometry內部對每個不同視口進行不同的頂點變換,從而主程序不需要再用循環,只要一次性將數據傳入顯卡,通過一次Geometry的流程實現多視口繪制。具體操作可以下回試試。
參考文獻
OpenGL Wiki: https://www.opengl.org/wiki/
Stack Overflow:http://stackoverflow.com/
總結
以上是生活随笔為你收集整理的OpenGL实现多层绘制(Layered Rendering) [转]的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深度思考的能力,决定了你能走多远
- 下一篇: 安卓-04-实例01-XML布局UI界面