GPU深度發掘(二)::OpenGL FrameBuffer Object 101(ZZ)

來源:互聯網
上載者:User
 

作者:by Rob 'phantom' Jones                 

介紹

 

Frame Buffer Object(FBO)擴充,被推薦用於把資料渲染到紋理對像。相對於其它同類技術,如資料拷貝或交換緩衝區等,使用FBO技術會更高效並且更容易實現。
在這篇文章中,我將會快速地講解一下如何來使用這一擴充,同時會介紹一些在使用過程中我們要注意的地方。學會該技術後,你便可以把一些渲染到紋理(render to texture)的功能加入到你的程式中,實現更快速的運行。

建立

和OpenGL中的其它對像一樣,如紋理對像(texture object), 像素緩衝對像(pixel buffer objects) , 項點緩衝對像(vertex buffer object)等,在使用一個FBO對像之前,你必須先要產生該對像,並取得一個有效對像標識。

GLuint fbo;
glGenFramebuffersEXT(1, &fbo);

要對一個FBO進行任何的操作,你必須先要對它進行綁定。這一步驟與我們平時使用VBO或者紋理的過程很像。綁定對像後,我們便可以對FBO進行各種操作了,以下代碼示範如何進行綁定。

glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);

第一個參數是“目標(target)”,指的是你要把FBO與哪個框架緩衝區進行綁定,目前來說,我個參數就只有一些預定義的選擇(GL_FRAMEBUFFER_EXT),但將來擴充的發展,可能會來現其它的選擇,讓你把FBO與其它的目標進行綁定。整型變數fbo,是用來儲存FBO對像標識的,這個標識我們已在前面產生了。要實現任何與FBO有關的操作,我們必須有一個FBO被綁定,否則調用就會出錯

加入一個深度緩衝(Depth Buffer)

一個FBO它本身其實沒有多大用處,要想讓它能被更有效利用,我們需要把它與一些可被渲染的緩衝區綁定在一起,這樣的緩衝區可以是紋理,也可以是下面我們將要介紹的渲染緩衝區(renderbuffers)。

一個渲染緩衝區,其實就是一個用來支援離屏渲染的緩衝區。通常是框架緩衝區的一部份,一般不具有紋理格式。常見的模版緩衝和深度緩衝就是這樣一類對像。

在這裡,我們要為我們的FBO指定一個渲染緩衝區。這樣,當我們渲染的時候,我們便把這個渲染緩衝區作為FBO的一個深度緩衝來使用。

和FBO的產生一樣,我們首先也要為渲染緩衝區指定一個有效標識。

GLuint depthbuffer;
glGenRenderbuffersEXT(1, &depthbuffer);

成功完成上面一步之後,我們就要對該緩衝區進行綁定,讓它成為當前渲染緩衝,下面是實現代碼。

glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthbuffer);

和FBO的綁定函數一樣,第一個參數是“目標(target)”,指的是你要與哪個目標進行綁定,目前來說,只能是一些預定義好的目標。變數dephtbuffer用來儲存對像標識。

這裡有一個關鍵的地方,也就是我們產生的渲染緩衝對像,它本身並不會自動分配記憶體空間。因此我們要調用OpenGL的函數來給它分配指定大小的記憶體空間,在這裡,我們分配一個固定大小的深度緩顯空間。

glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, width, height);

上面這一函數成功運行之後,OpenGL將會為我們分配好一個大小為width x height的深度緩衝區。注意的是,這裡用了GL_DEPTH_COMPONENT,就是指我們的空間是用來儲存深度值的,但除了這個之外,渲染緩衝區 還可以用來儲存普通的RGB/RGBA格式的資料或者是模板緩衝的資訊。

准被好了深度緩衝的顯存空間後,接下來要做的工作就是把它與前面我們準備好了的FBO對像綁定在一起。

 

glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthbuffer);

這個函數看起來有點複雜,但其實它很好理解的。它要做的全部工作就是把把前面我們產生的深度緩衝對像與當前的FBO對像進行綁定,當然我們要注意一個FBO有多個不同綁定點,這裡是要綁定在FBO的深度緩衝綁定點上。

加入用於渲染的紋理

到現在為止,我們還沒有辦法往FBO中寫入顏色資訊。這也是我們接下來正要討論的,我們有以下兩種方法來實現它:

  1. 把一個顏色渲染緩衝與FBO綁定。
  2. 把一個紋理與FBO綁定。

前者在某些地方會用到,後面的章節我們會深入討論。現在我們先來說說第二種方法。

在你想要把紋理與一個FBO進行綁定之前,我們得先要產生這個紋理。這個產生紋理的過程種我們平時見到的紋理產生沒什麼區別。

 

GLuint img;
glGenTextures(1, &img);
glBindTexture(GL_TEXTURE_2D, img);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8,  width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

這個執行個體中,我們產生一個普通的RGBA映像,大小是width x height,與前面我們產生的渲染緩衝區的大小是一樣的,這一點很重要,也就是FBO中所有的綁定對像,都必須要有相同的寬度和高度。還有要注意的就是:這裡我們沒有上傳任何的資料,只是讓OpenGL保留分配好的空間,稍後我們將會用到。

產生好紋理之後,接下來的工作就是把這個紋理與FBO綁定在一起,以便我們可以把資料渲染到紋理空間中去。

 

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

這裡再次看到這個看起來非常可怕的函數,當然它也並沒有我們想像中那麼難理解。參數GL_COLOR_ATTACHMENT0_EXT是告訴OpenGL把紋理對像綁定到FBO的0號綁定點(一個FBO在同一個時間內可以綁定多個顏色緩衝區,每個對應FBO的一個綁定點),參數GL_TEXTURE_2D是指定紋理的格式,img儲存的是紋理標識,指向一個之前就準備好了的紋理對像。紋理可以是多重新對應的映像,最後一個參數指定級級為0,指的是使用原映像。

最後還有一步要做的工作,就是檢查一下FBO的準備工作是否全部完成,是否以經能被正確使用了。

這個測試工作由下面一個函數來完成,它會返回一個當前綁定的FBO是否正確的狀態資訊。

 

GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);

如果所有工作都已經做好,那麼返回的狀態值是GL_FRAMEBUFFER_COMPLETE_EXT,也就是說你的FBO已經準備好,並可以用來作為渲染對像了。否則就會返回其它一個錯誤碼,通過尋找定義文檔,可以找到相關的錯誤資訊,從而了角錯誤大概是在哪一步驟中產生的。
 

渲染到紋理

所有困難的工作就是前面建立FBO環境的部份,剩下來的工作就相當簡單了,相關的事情就只是調用一下以下這個函數:glBindFramebufferEXT().

當我們要把資料渲染並輸出到FBO的時候,我們只需要用這個函數來把一個FBO對像進行綁定。當我們要停止輸出到FBO,我們只要把參數設為0,再重新調用一次該函數就可以了。當然,停止向FBO輸出,這也是很重要的,當我們完成了FBO的工作,就得停止FBO,讓映像可以在螢幕上正確輸出。

glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
glPushAttrib(GL_VIEWPORT_BIT);
glViewport(0,0,width, height);


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

glPopAttrib();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

上面另外三行代碼glPushAttrib/glPopAttrib 及 glViewport,是用來確保在你跳出FBO渲染的時候可以返回原正常的渲染路徑。glViewport在這裡的調用是十分必要的,我們不要常試把資料渲染到一個大於或小於FBO大小的地區。 函數glPushAtrrib 和 glPopAttrib 是用來快速儲存視口資訊。這一步也是必要的,因為FBO會共用主內容相關的所有資訊。任何的變動,都會同時影響到FBO及主上下文,當然也就會直接影響到你的正常螢幕渲染。

這裡一個重要訊息,你可能也注意到了,我們只是在繪製的時候綁定或解除FBO,但是我們沒有重新綁定紋理或渲染緩衝區,這裡因為在FBO中會一直儲存了這種綁定關係,除非你要把它們分開或FBO對像被銷毀了。

使用已渲染出來的紋理

來到這裡,我們已經把螢幕的資料渲染到了一個映像紋理上。現在我們來看一看如何來使用這張已經渲染好了的映像紋理。這個操作的本身其實是很簡單的,我們只要把這張映像紋理當作普通紋理一樣,綁定為當前紋理就可以了。

glBindTexture(GL_TEXTURE_2D, img);

以上這一函數調用完成之後,這張映像紋理就成了一個在繪圖的時候用於被讀取的普通紋理。

根據你在初始化時所指定的不同紋理濾波方式,你也許會希望為該紋理產生多重映像(mipmap)資訊。如果要建立多重映像資訊,多數的人都是在上傳紋理資料的時候,通過調用函數gluBuild2DMipmaps()來實現,當然有些朋友可能會知道如何使用自動產生多重映像的擴充,但是在FBO擴充中,我們增加了第三種產生映像的方法,也就是使用GenerateMipmapEXT()函數。

這個函數的作用就是讓OpenGL幫你自動建立多重映像資訊。中間實現的過程,根據不同的顯卡會有所不同,我們只關心它們最終的結果是一樣就行了。值得注意的是:對於這種通過FBO渲染出來的紋理,要實現多重映像的話,只有這一種方法是正確的,這裡你不可以使用自動產生函數來產生多重映像,這其中的原因有很多,如果你想深入瞭解的話,可以查看一下技術文檔。

使用這一函數使方便,你所要做的就是先把該紋理對像綁定為當前紋理,然後調用一次該函數就可以了。

glGenerateMipmapEXT(GL_TEXTURE_2D);

OpenGL將會自動為我們產生所需要的全部資訊,到現在我們的紋理便可以正常使用了。

一個重點要注意的地方:如果你打算使用多重映像(如 GL_LINEAR_MIPMAP_LINEAR),該函數glGenerateMipmapEXT()必須要在執行渲染到紋理之前調用。

在建立紋理的時候,我們可以按以下代碼來做。

glGenTextures(1, &img);glBindTexture(GL_TEXTURE_2D, img);glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8,  width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);glGenerateMipmapEXT(GL_TEXTURE_2D);

到現在,這張紋理和普通紋理沒什麼區別,我們就按處理普通紋理的方法來使用就可以了。

清理

最後,當你完成了所有的FBO操作之後,請別忘了要清理或刪除掉那些不要了的FBO對像,和清理紋理對像相似,這一步只要以下一個函數就可以完成:

glDeleteFramebuffersEXT(1, &fbo);

同樣的,你如果分配了渲染緩衝對像,也別忘了要把它清理掉。本執行個體中我們分配的是深度緩衝渲染對像,我們用以下函數來清除它:

glDeleteRenderbuffersEXT(1, &depthbuffer);
 

到這裡,所有的FBO對像及渲染緩衝都被釋放掉了,我們的清理工作也就完成了。

最後的思考

這一篇文章只是對FBO擴充的一個初步介紹,希望對你有所協助,更多詳細的知識,可以查看一下FBO spec ,或者看一下《More OpenGL Game Programming》這本書中關於擴充部分的章節。

問題的返潰及相關技術的討論,可以登陸物理開發網的GPGPU/CUDA論壇進行交流。

在文檔結束之前,我要說一下在使用FBO來寫程式的過程中,一些值得我們去注意的地方: 

  1. 就目前來說,你沒辦法得到模版緩衝的綁定點。雖然在技術上是定義了這麼一種深度模版的紋理格式,目的是讓我們可以渲染到模版,但這一技術到目前為止還缺乏硬體的支援。
  2. 不要頻繁地建立及銷毀FBO對像。好的做法應該是在程式建立的同時產生FBO對像,然後在我們需要用到的地方使用它。 
  3. 一個紋理,如果被定義為用於做渲染紋理,那麼我們就要盡量避免使用glTexImage之類的函數來修改該紋理的資料,這樣做多數情況下會讓你的程式出現問題。
本文樣本程式中要注意的地方

對應這篇文章所討論的內容,我們寫了一個相應的程式,其功能就是給FBO加入一個深度緩衝對像及一個紋理對像。我們發現,在ATI的顯卡中有一個bug,也就是當我們給同時FBO加入一個深度緩衝及一個紋理的時候,就會出現嚴重的衝突。從這裡也告訴我們,當我們在寫好一個FBO相關的程式的時候,一定要在不同的硬體及不同的驅動下進行廣泛的測試,直到沒有任何渲染問題為止。

I'd also like to put out a big thanks to Rick Appleton for helping me test out and debug the code on NVIDA hardware, couldn't have done it without you mate

本程式需要有GLUT函數庫的支援才能正確運行,我使用的是FreeGLUT.

程式下載

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

參考

GPU深度發掘
More OpenGL Game Programming
Framebuffer Object Spec
GDC 2005 Framebuffer Object pdf

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.