GPU深度發掘(三)::OpenGL Frame Buffer Object 201

來源:互聯網
上載者:User

GPU深度發掘(三)::OpenGL Frame Buffer Object 201 
作者: Rob 'phantom' Jones  譯者:華文廣 更新:2007/6/15

介紹

在上一篇文章OpenGL FrameBuffer object 101中,我樣大概講述了FBO的一些基礎應用,文章中主要介紹了如何產生一個FBO,如何把資料渲染到一個單一的紋理上,以及把這個紋理在別的地方做一些應用。然而FBO擴充並不緊緊只能做到這些。在上一篇文章中我們主要講述了FBO的一個綜合特徵:綁定點(attachment point)。

在本篇文章中,我們將會進一步來講述FBO的一些深層次概念及應用。首先,我們來看一下如何在一個FBO對像中,通來迴圈多次渲染,實現把資料渲染到多個紋理上。講完這個之後,我們再來看一下如何通過使用OpenGL進階著色語言(GLSL),實現在同一時間渲染輸出到多個紋理上,當然,這裡還需要用到繪圖緩衝擴充(Draw Buffers extension)。

一個FBO與多個紋理

在上一篇文章中,我們講述了如何把一個紋理綁定到一個FBO中,用來作為一個顏色渲染對像(colour render target)。我們主要用到了下面這個函數。

 

glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, img, 0);

 

或許你還記得,在這個函數中,我們通過img這個儲存有紋理標誌的變最來把對應的紋理綁定到當前所啟用的FBO中去。在篇文章中,我們來著重關注一下第二個參數:GL_COLOR_ATTACHMENT0_EXT。

這個參數就是告訴OpenGL,把紋理綁定到FBO的0號顏色綁定點中去。然而,一個FBO對像會有多個顏色綁定點可以供我們使用。當前,規格說明書上說允許有16個綁定點(GL_COLOR_ATTACHMENT0_EXT 到 GL_COLOR_ATTACHMENT15_EXT) ,每一個綁定點都可以與一個單獨的紋理進行綁定。當然這個綁定點的個數會受到硬體及其驅動的限制,我們可以用以下的函數來查詢繫結點個數的最大值:

GLuint maxbuffers;
glGetIntergeri(GL_MAX_COLOR_ATTACHMENTS, &maxbuffers);

在這裡,變數maxbuffers儲存了顏色綁定點的最大值,在寫本文章的時候,當前顯卡硬體返回來的這個最大值一般是4。

所以,如果我們想把紋理標識量img與第二個顏色綁定點進行綁定的話,上面對應的函數就得做以下相應的修改:

glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_2D, img, 0);

正如你所看到的,想要增加一個綁定紋理,那是相當容易的事情。但是我們又該如何讓OpenGL分別把資料渲染到這些紋理上呢?

選擇輸出目標

OK,我們現在回頭來看一下這個特別的函數:glDrawBuffer(),一般我們在OpenGL開始的時候就會用到它。

這個函數,以及與它密切相關的一個函數glReadBuffer(),就是用來告訴OpenGL它應該往哪裡寫入資料以及應該從哪裡讀取資料。在預設的情況下,如果是單緩衝環境,兩者都是對前緩衝(GL_FRONT)進行讀寫,而雙緩衝環境則是對後緩衝(GLBACK)進行讀寫。但是在FBO擴充出來之後,這個函數的功能就被修改了,它允許你選擇GL_COLOR_ATTACHMENTx_EXT來作為渲染輸出或讀取的目標(這裡'x'指的就是FBO綁定點數字)。

當你綁定啟用一個FBO對像的時候,系統會自動把使用中色彩輸出目標指向GL_COLOR_ATTACHMENT0_EXT,也就是0號綁定點所綁定的紋理。因此,如果你就是想把資料輸出到這個預設的顏色綁定點的話,你不需要作任何額外的改動。但是當我們要想輸出到別的緩衝區的時候,我們就得親自告訴OpenGL我們所要的選擇。

因此,如果我們想要渲染到GL_COLOR_ATTACHMANT1_EXT,那麼我們就必須先啟用一個FBO,並正確地指定一個寫入緩衝的綁定點。假設我們已經為FBO的1號顏色綁定點綁定好了一個紋理對像,下面就是實現代碼:

glBindFrameBuffer(GL_FRAMEBUFFER_EXT, fbo);
glPushAttrib(GL_VIEWPORT_BIT | GL_COLOR_BUFFER_BIT);
glViewport(0,0,width, height);

// Set the render target
glDrawBuffer(GL_COLOR_ATTACHMENT1_EXT);

// Render as normal here
// output goes to the FBO and it抯 attached buffers


glPopAttrib();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

值得注意的是,這裡用到了glPushAttrib()函數,它主要是用來儲存視口及顏色緩衝的一些屬性,因為在FBO運算過程中我們要對這些屬性進行修改。在FBO運算完成之後,我們可以使用glPopAttrib()函數來還原之前的設定。這樣做主要是因為在FBO運算過程中的一些屬性的改變會直接影響到主渲染程式,通過屬性還原,能讓主程式渲染還原到正常狀態。

當我們把多個紋理綁定到一個FBO對像的時候,有一個非常重要的地方,那就是所有這些紋理都要有同樣大小的尺寸及色彩深度。所以,我們不能把一個512*512 32bit的紋理與一個256*256 16bit的紋理綁定到同一個FBO對像中去。因而,如果你能夠接受這一小小的限制的話,便可以實現用一個FBO渲染輸出到多個紋理上,這比起在多個FBO對像中進行切換,速度會快很多。當然,多FBO對像切換也不算是什麼極度緩慢的操作,但是盡量避免不必要的開銷,通常都是一種比較好的編程習慣。

第一個例子

在第一個樣本程式中,示範了如何渲染到2個紋理上,當然這裡一個接一個地渲染輸出,然後把這些紋理應用到另一個立方體上。代碼是基於上一篇文章所寫的例子,只是作了一些細小的變動而已。

首先,在初始化函數中,我們是啟動FBO的是候把第二個紋理也綁定到FBO對像中去。注意如何有別於與第一個紋理的綁定,這裡使用GL_COLOR_ATTACHMENT1_EXT作為綁定點。

對於情境的渲染輸出基本上都是相同的,只不過這裡我們一共進行了兩次繪圖,第一次用立方體原來的顏色進行繪圖,而第二次繪圖的時候把顏色的亮度調為原來的一半。

你或許已經注意到了,在樣本程式中,當我們渲染輸出到FBO的時候,我們要明確地告訴OpenGL,先是渲染到GL_COLOR_ATTACHMENT0_EXT,然後是GL_COLOR_ATTACHMAENT1_EXT。這是因為FBO會記住上一次你讓它渲染輸出的緩衝區。因此,在繪圖函數中當我們第二次繪圖的時候,第一個綁定的紋理作為目標輸出是不會自動更新的,直到我們調用glDrawBuffer()函數。想要看一下這一函數所影響的效果的話,可以注釋掉第103行,這行就是一個glDrawBuffer()函數的調用,你將會看到這時立方左手邊的紋理再也不會出現變化。

多個渲染目標(Multiple Render Targets)

現在我們知道如何把多個紋理綁定到一個FBO中去,然而我們仍然是一次只繪製一張紋理,然後通過切換繪圖目標實現對多個紋理的寫入,有沒有更有用更高效的方法呢?在本文章開頭曾提及過,我們要介紹如何?在同一時間裡渲染輸出到多個紋理上。

其實,一旦你明白如何綁定多個紋理,剩下要做就非常簡單了。你現在還需要用到的技術包括有繪圖緩衝擴充(Draw buffers extension)和OpenGL著色語言(GLSL),而這兩者現在都成了OpenGL2.0核心的組成部份。

繪圖緩衝擴充( Draw Buffers Extension)

現在介紹第一個擴充:繪圖緩衝區的建立。建立繪圖緩衝,我們使用一個系統提供的函數glDrawBuffer(),你也許還記得起這個函數,在前面我們說過,它可以用來指定當前渲染輸出的顏色緩衝區。但是在繪圖緩衝擴充中,這個函數的功能也同時得到了擴充,它可以用來指定多個同時寫入的顏色緩衝區。一次可以同時寫入的緩衝的個數,可以用以下函數來查詢:

GLuint maxbuffers;
glGetIntergeri(GL_MAX_DRAW_BUFFERS, &maxbuffers);

函數正確執行之後,變數maxBuffers儲存了我們一次可以同渲染的緩衝區的個數,在我寫這個文檔的時候,這個數字一般是4,但是最新顯卡GeForce8x00系列允許我們一次同時輸出到8個緩衝區。

因而,如果我們已經把兩個紋理分別綁定到綁定點0和1,現在我們想同時渲染輸出到這兩個紋理上,我們就可以按以下代碼來寫:

GLenum buffers[] = ...{ GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT };
glDrawBuffers(2, buffers);

當上面的函數正確運行之後,OpenGL 便建立了一個雙顏色緩衝渲染輸出的環境。

使用 FBO 和 GLSL 實現MRT

現在,如果我們使用標準的固定功能管線來進行渲染,兩個紋理會得到同樣的資料。然而,如果使用GLSL來重寫片段著色代碼,我們就可以做到把不同的資料發送到這兩個紋理上。

通常來說,當你寫一個GLSL的片段著色程式的時候,你會把顏色值輸出到gl_FragColor中去,正常情況下,這個顏色值便會被寫入到框架緩衝區中去。然而,在這裡,我們還有第二種顏色資訊輸出的方法,那就是使用gl_FragData[]數組。

這個特別的變數允許我們直接指定資料往哪裡走。資料輸出會對應哪一個紋理呢?這就與函數glDrawBuffers()中給定的參數的對應順序有關。如上面這種情況,緩衝區的對應關係就如所示:

glDrawBuffers value FragData syntax
GL_COLOR_ATTACHMENT0_EXT gl_FragData[0]
GL_COLOR_ATTACHMENT1_EXT gl_FragData[1]

上面函數調用的時候,如果參數順序發生了改變,那麼對應的映射關係也會發生改變,如下所示:

GLenum buffers[] = ...{ GL_COLOR_ATTACHMENT1_EXT, GL_COLOR_ATTACHMENT0_EXT };
glDrawBuffers(2, buffers);
glDrawBuffers value FragData syntax
GL_COLOR_ATTACHMENT1_EXT gl_FragData[0]
GL_COLOR_ATTACHMENT0_EXT gl_FragData[1]

假如說我們想把綠色輸出到其中一個目標而藍色輸出到另一個,GLSL的代碼就可以這樣寫,如下:

#version 110

void main()
...{
    gl_FragData[0] = vec4(0.0, 1.0, 0.0);
    gl_FragData[1] = vec4(0.0, 0.0, 1.0);
}

第一行是說我們驅動至少支援GLSL 1.10以上(OGL2.0)。函數主體的功能就只是把綠色寫入到第一個緩衝區,藍色定入到第二個。

 

譯註:CG代碼如下:

void main(out float4 col0:COLOR0,out float4 col0:COLOR1)
...{
        col0 = float4(0.0,1.0,0.0,1.0);
        col1 = float4(0.0,0.0,1.0,1.0);
}

 

第二個例子

第二個例子是第一個例子與上一篇文章的例子的結合。它實現了第一個例子同樣的輸出,但是這裡我們只把立方體繪製了一次。我們通過使用一個著色程式來實現控制輸出。

程式和之前的差不多,主要的區別在於初始化代碼。我們把GLSL代碼匯入放在一邊不談,因為這個不是本文章討論的範圍。而能讓多目標渲染正常工作主要代碼就是以下兩行:

GLenum mrt[] = ...{ GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT }
glDrawBuffers(2, mrt);

這兩行就是告訴OpenGL,我們希望渲染到兩個緩衝區及我們希望渲染到哪兩個緩衝區。要記住FBO是有記憶功能的,也就是它會記上一次渲染所使用過的輸出目標。通過上面兩行代碼,我們可以改變FBO渲染輸出的目標,讓它可以正確地實現同時渲染到多個紋理。

繪圖迴圈函數看起來來前面的十分相似,渲染到FBO還是用到了同樣的代碼。有所變動的,就是關於綁定並調用GLSL程式的那一部份。GLSL主要就是用來控制顏色的多路輸出。後面的代碼就基本上和本文第一個例子沒什麼區別,只是第一個例子中把立方體畫了兩次,而這裡只要畫一次就可以了。

關於程式中的兩個GLSL著色程式,在這裡稍為提及一下,大體上看一下它們是如何讓MRT正常工作的。

頂點著色程式,就是對於你發送到顯卡上的每一個頂點都會運行一遍的一段代碼。本程式中只是簡單地把每個頂點的顏色值通過glColor()傳遞級片段著色程式,並對每個頂點進行了一些必要的矩陣變換,使得我們能在正確的位置繪製一個正方體。

片段著色程式的代碼如下:

#version 110

void main(void)
...{
    gl_FragData[0] = vec4(gl_Color.r, gl_Color.g,gl_Color.b,1.0);
    gl_FragData[1] = vec4(gl_Color.r/2.0, gl_Color.g/2.0,gl_Color.b/2.0,1.0);
}

這裡關鍵的地方就是兩個gl_FragData,它們用來明確它定我們到底要要把資料寫入到哪個緩衝區中去。本執行個體中gl_FragData[0]指的是第一個紋理,它儲存了一份沒有被修改過的顏色值,也就是從頂點著顏中傳遞過來的原始顏色。而對於gl_FragData[1],它對應的是第二個紋理,同樣是用來儲存從頂點著色中傳遞過來的顏色,但顏色的亮度就被改成了原來的一半。從結果上看,它的效果和第一個程式是一樣的。

最後的思考

本文章主要通過兩個例子是快速地介紹了FBO擴充兩種不同的應用。

第一個例子中,允許你使用同一個FBO實現渲染輸出到多個紋理中去,從而讓我們可以不須要在多個FBO中頻繁切換,本例子中所示範的技術是非常有用的,因為當對於在不同的FBO進行切換來說,在同一個FBO中切換不同的渲染目標它的速度要快得多。因此如果你能把你的紋理作一些分組,盡量讓多個紋理在同一時間內被渲染,這樣會為你節省大量的時間。

第二個例子是讓你體會一下這種叫做多渲染目標(Multiple Render Targets)的技術。雖然本文中的關於本技術所舉的例子沒有很大的實用價值,但是MRT技術是其它許多GPU進階技術的基礎,如render-to-vertex buffer及post-processing等,因此這種可以輸出到多個顏色緩衝的能力是非常有用的,值得我們大家去深入學習和研究一下。

更多細節,可以查看一下Framebuffer Object 及Draw Buffers 等的規範說明書。在More OpenGL Game Programming 也是一片我寫的文章,其中有一個關於FBO和GLSL的章節。對相關的技術也略為討論了一下。

例子中一些要注意的地方

本文中的例子,在編譯和運行過程中都要用到GLUT(我使用的是FreeGLUT )。

聲明

本譯文可以自由轉載,要求保留原作者資訊並註明文章出自物理開發網:www.physdev.com

更多GPU深度發掘文章,請關注物理開發網

 

參考

More OpenGL Game Programming
Framebuffer Object Spec
GDC 2005 Framebuffer Object pdf

See Also:
OpenGL
Programming

 


 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.