OpenGL Frame BufferObject(FBO)
Overview:
在OpenGL渲染管線中,幾何數據和紋理經過多次轉化和多次測試,最後以二維像素的形式顯示在屏幕上。OpenGL管線的最終渲染目的地被稱作幀緩存(framebuffer)。幀緩衝是一些二維數組和OpenG所使用的存儲區的集合:顏色緩存、深度緩存、模板緩存和累計緩存。一般情況下,幀緩存完全由window系統生成和管理,由OpenGL使用。這個默認的幀緩存被稱作“window系統生成”(window-system-provided)的幀緩存。
在OpenGL擴展中,GL_EXT_framebuffer_object提供了一種創建額外的不能顯示的幀緩存對象的接口。爲了和默認的“window系統生成”的幀緩存區別,這種幀緩衝成爲應用程序幀緩存(application-createdframebuffer)。通過使用幀緩存對象(FBO),OpenGL可以將顯示輸出到引用程序幀緩存對象,而不是傳統的“window系統生成”幀緩存。而且,它完全受OpenGL控制。
相似於window系統提供的幀緩存,一個FBO也包含一些存儲顏色、深度和模板數據的區域。(注意:沒有累積緩存)我們把FBO中這些邏輯緩存稱之爲“幀緩存關聯圖像”,它們是一些能夠和一個幀緩存對象關聯起來的二維數組像素。
有兩種類型的“幀緩存關聯圖像”:紋理圖像(texture images)和渲染緩存圖像(renderbuffer images)。如果紋理對象的圖像數據關聯到幀緩存,OpenGL執行的是“渲染到紋理”(render to texture)操作。如果渲染緩存的圖像數據關聯到幀緩存,OpenGL執行的是離線渲染(offscreen rendering)。
這裏要提到的是,渲染緩存對象是在GL_EXT_framebuffer_object擴展中定義的一種新的存儲類型。在渲染過程中它被用作存儲單幅二維圖像。
下面這幅圖顯示了幀緩存對象、紋理對象和渲染緩存對象之間的聯繫。多多個紋理對象或者渲染緩存對象能夠通過關聯點關聯到一個幀緩存對象上。
在一個幀緩存對象中有多個顏色關聯點(GL_COLOR_ATTACHMENT0_EXT,...,GL_COLOR_ATTACHMENTn_EXT),一個深度關聯點(GL_DEPTH_ATTACHMENT_EXT),和一個模板關聯點(GL_STENCIL_ATTACHMENT_EXT)。每個FBO中至少有一個顏色關聯點,其數目與實體顯卡相關。可以通過GL_MAX_COLOR_ATTACHMENTS_EXT來查詢顏色關聯點的最大數目。FBO有多個顏色關聯點的原因是這樣可以同時將顏色而換成渲染到多個FBO關聯區。這種“多渲染目標”(multiple rendertargets,MRT)可以通過GL_ARB_draw_buffers擴展實現。需要注意的是:FBO本身並沒有任何圖像存儲區,只有多個關聯點。
FBO提供了一種高效的切換機制;將前面的幀緩存關聯圖像從FBO分離,然後把新的幀緩存關聯圖像關聯到FBO。在幀緩存關聯圖像之間切換比在FBO之間切換要快得多。FBO提供了glFramebufferTexture2DEXT()來切換2D紋理對象和glFramebufferRenderbufferEXT()來切換渲染緩存對象。
創建FBO
創建FBO和產生VBO類似。
glGenFramebuffersEXT()
Void gelGenFramebuffersEXT(GLsizei n,GLuint* ids);
void glDeleteFramebuffersEXT(GLsizei n, const GLuint* ids);
glGenFramebuffersEXT()需要兩個參數:第一個是要創建的幀緩存的數目,第二個是指向存儲一個或者多個ID的變量或數組的指針。它返回未使用的FBO的ID。ID爲0表示默認幀緩存,即window系統提供的幀緩存。
當FBO不再被使用時,FBO可以通過調用glDeleteFrameBuffersEXT()來刪除。
glBindFramebufferEXT()
一旦一個FBO被創建,在使用它之前必須綁定。
void glBindFramebufferEXT(GLenum target, GLuint id)
第一個參數target應該是GL_FRAMEBUFFER_EXT,第二個參數是FBO的ID號。一旦FBO被綁定,之後的所有的OpenGL操作都會對當前所綁定的FBO造成影響。ID號爲0表示缺省幀緩存,即默認的window提供的幀緩存。因此,在glBindFramebufferEXT()中將ID號設置爲0可以解綁定當前FBO。
渲染緩存對象(Renderbuffer Object)
另外,渲染緩存是爲離線渲染而新引進的。它允許將一個場景直接渲染到一個渲染緩存對象中,而不是渲染到紋理對象中。渲染緩存對象是用於存儲單幅圖像的數據存儲區域。該圖像按照一種可渲染的內部格式存儲。它用於存儲沒有相關紋理格式的OpenGL邏輯緩存,比如模板緩存或者深度緩存。
glGenRenderbuffersEXT()
void glGenRenderbuffersEXT(GLsizei n, GLuint* ids)
void glDeleteRenderbuffersEXT(GLsizei n, const Gluint* ids)
一旦一個渲染緩存被創建,它返回一個非零的正整數。ID爲0是OpenGL保留值。
glBindRenderbufferEXT()
void glBindRenderbufferEXT(GLenum target, GLuint id)
和OpenGL中其他對象一樣,在引用渲染緩存之前必須綁定當前渲染緩存對象。他target參數應該是GL_RENDERBUFFER_EXT。
glRenderbufferStorageEXT()
void glRenderbufferStorageEXT(GLenum target, GLenum internalFormat,
GLsizei width, GLsizei height)
當一個渲染緩存被創建,它沒有任何數據存儲區域,所以我們還要爲他分配空間。這可以通過用glRenderbufferStorageEXT()實現。第一個參數必須是GL_RENDERBUFFER_EXT。第二個參數可以是用於顏色的(GL_RGB,GL_RGBA,etc.),用於深度的(GL_DEPTH_COMPONENT),或者是用於模板的格式(GL_STENCIL_INDEX)。Width和height是渲染緩存圖像的像素維度。
width和height必須比GL_MAX_RENDERBUFFER_SIZE_EXT小,否則將會產生GL_UNVALID_VALUE錯誤。
glGetRenderbufferParameterivEXT()
void glGetRenderbufferParameterivEXT(GLenum target, GLenum param,GLint* value);
我們也可以得到當前綁定的渲染緩存對象的一些參數。Target應該是GL_RENDERBUFFER_EXT,第二個參數是所要得到的參數名字。最後一個是指向存儲返回值的整型量的指針。渲染緩存的變量名有如下:
GL_RENDERBUFFER_WIDTH_EXT
GL_RENDERBUFFER_HEIGHT_EXT
GL_RENDERBUFFER_INTERNAL_FORMAT_EXT
GL_RENDERBUFFER_RED_SIZE_EXT
GL_RENDERBUFFER_GREEN_SIZE_EXT
GL_RENDERBUFFER_BLUE_SIZE_EXT
GL_RENDERBUFFER_ALPHA_SIZE_EXT
GL_RENDERBUFFER_DEPTH_SIZE_EXT
GL_RENDERBUFFER_STENCIL_SIZE_EXT
將圖像和FBO關聯
FBO本身沒有圖像存儲區。我們必須幀緩存關聯圖像(紋理或渲染對象)關聯到FBO。這種機制允許FBO快速地切換(分離和關聯)幀緩存關聯圖像。切換幀緩存關聯圖像比在FBO之間切換要快得多。而且,它節省了不必要的數據拷貝和內存消耗。比如,一個紋理可以被關聯到多個FBO上,圖像存儲區可以被多個FBO共享。
把2D紋理圖像關聯到FBO
glFramebufferTexture2DEXT(GLenum target,
GLenumattachmentPoint,
GLenum textureTarget,
GLuint textureId,
GLint level)
glFramebufferTexture2DEXT()把一幅紋理圖像關聯到一個FBO。第一個參數一定是GL_FRAMEBUFFER_EXT,第二個參數是關聯紋理圖像的關聯點。第三個參數textureTarget在多數情況下是GL_TEXTURE_2D。第四個參數是紋理對象的ID號。最後一個參數是要被關聯的紋理的mipmap等級
如果參數textureId被設置爲0,那麼紋理圖像將會被從FBO分離。如果紋理對象在依然關聯在FBO上時被刪除,那麼紋理對象將會自動從當前幫的FBO上分離。然而,如果它被關聯到多個FBO上然後被刪除,那麼它將只被從綁定的FBO上分離,而不會被從其他非綁定的FBO上分離。
把渲染緩存對象關聯到FBO
void glFramebufferRenderbufferEXT(GLenum target,
GLenum attachmentPoint,
GLenum renderbufferTarget,
GLuint renderbufferId)
通過調用glFramebufferRenderbufferEXT()可以關聯渲染緩存圖像。前兩個參數和glFramebufferTexture2DEXT()一樣。第三個參數只能是GL_RENDERBUFFER_EXT,最後一個參數是渲染緩存對象的ID號。
如果參數renderbufferId被設置爲0,渲染緩存圖像將會從FBO的關聯點分離。如果渲染緩存圖像在依然關聯在FBO上時被刪除,那麼紋理對象將會自動從當前綁定的FBO上分離,而不會從其他非綁定的FBO上分離。
檢查FBO狀態
一旦關聯圖像(紋理和渲染緩存)被關聯到FBO上,在執行FBO的操作之前,你必須檢查FBO的狀態,這可以通過調用glCheckFramebufferStatusEXT()實現。如果這個FBObuilding完整,那麼任何繪製和讀取命令(glBegin(),glCopyTexImage2D(), etc)都會失敗。
GLenum glCheckFramebufferStatusEXT(GLenum target)
glCheckFramebufferStatusEXT()檢查當前幀緩存的關聯圖像和幀緩存參數。這個函數不能在glBegin()/glEnd()之間調用。Target參數必須爲GL_FRAMEBUFFER_EXT。它返回一個非零值。如果所有要求和準則都滿足,它返回GL_FRAMEBUFFER_COMPLETE_EXT。否則,返回一個相關錯誤代碼告訴我們哪條準則沒有滿足。
FBO完整性準則有:
(1)幀緩存關聯圖像的寬度和高度必須非零。
(2)如果一幅圖像被關聯到一個顏色關聯點,那麼這幅圖像必須有顏色可渲染的內部格式(GL_RGBA, GL_DEPTH_COMPONENT, GL_LUMINANCE, etc)。
(3)如果一幅被圖像關聯到GL_DEPTH_ATTACHMENT_EXT,那麼這幅圖像必須有深度可渲染的內部格式(GL_DEPTH_COMPONENT,GL_DEPTH_COMPONENT24_EXT, etc)。
(4)如果一幅被圖像關聯到GL_STENCIL_ATTACHMENT_EXT,那麼這幅圖像必須有模板可渲染的內部格式(GL_STENCIL_INDEX,GL_STENCIL_INDEX8_EXT, etc)。
(5)FBO至少有一幅圖像關聯。
(6)被關聯到FBO的縮影圖像必須有相同的寬度和高度。
(7)被關聯到顏色關聯點上的所有圖像必須有相同的內部格式。
注意:即使以上所有條件都滿足,你的OpenGL驅動也可能不支持某些格式和參數的組合。如果一種特別的實現不被OpenGL驅動支持,那麼glCheckFramebufferStatusEXT()返回GL_FRAMEBUFFER_UNSUPPORTED_EXT。
示例:渲染到紋理
源代碼下載:http://www.songho.ca/opengl/gl_fbo.html
包括渲染到紋理、只渲染到深度緩存和使用模板緩存渲染對象的輪廓。
有時候,你需要產生動態紋理。比較常見的例子是產生鏡面反射效果、動態環境貼圖和陰影等效果。動態紋理可以通過把場景渲染到紋理來實現。渲染到紋理的一種傳統方式是將場景繪製到普通的幀緩存上,然後調用glCopyTexSubImage2D()拷貝幀緩存圖像至紋理。
使用FBO,我們能夠將場景直接渲染到紋理,所以我們不必使用window系統提供的幀緩存。並且,我們能夠去除額外的數據拷貝(從幀緩存到紋理);。
這個demo實現了使用FBO和不使用FBO兩種情況下渲染到紋理的操作,並且比較了性能差異。除了能夠獲得性能上的提升,使用FBO的還有另外一個優點。在傳統的渲染到紋理的模式中(不使用FBO),如果紋理分辨率比渲染窗口的尺寸大,超出窗口區域的部分將被剪切掉。然後,使用FBO就不會有這個問題。你可以產生比顯示窗口大的幀緩存渲染圖像。
以下代碼在渲染循環開始之前,對FBO和幀緩存關聯圖像進行了初始化。注意只有一幅紋理圖像被關聯到FBO,但是,一個深度渲染圖像被關聯到FBO的深度關聯點。實際上我們並沒有使用這個深度緩存,但是FBO本身需要它進行深度測試。如果我們不把這個深度可渲染的圖像關聯到FBO,那麼由於缺少深度測試渲染輸出結果是不正確的。如果在FBO渲染期間模板測試也是必要的,那麼也需要把額外的渲染圖像和GL_STENCIL_ATTACHMENT_EXT關聯起來。
- // create a texture object
- GLuint textureId;
- glGenTextures(1, &textureId);
- glBindTexture(GL_TEXTURE_2D, textureId);
- glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
- 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_GENERATE_MIPMAP, GL_TRUE); // automatic mipmap
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, TEXTURE_WIDTH, TEXTURE_HEIGHT, 0,
- GL_RGBA, GL_UNSIGNED_BYTE, 0);
- glBindTexture(GL_TEXTURE_2D, 0);
- // create a renderbuffer object to store depth info
- GLuint rboId;
- glGenRenderbuffersEXT(1, &rboId);
- glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, rboId);
- glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT,
- TEXTURE_WIDTH, TEXTURE_HEIGHT);
- glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
- // create a framebuffer object
- GLuint fboId;
- glGenFramebuffersEXT(1, &fboId);
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);
- // attach the texture to FBO color attachment point
- glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
- GL_TEXTURE_2D, textureId, 0);
- // attach the renderbuffer to depth attachment point
- glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
- GL_RENDERBUFFER_EXT, rboId);
- // check FBO status
- GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
- if(status != GL_FRAMEBUFFER_COMPLETE_EXT)
- fboUsed = false;
- // switch back to window-system-provided framebuffer
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
- ...
渲染到紋理的過程和普通的繪製過程基本一樣。我們只需要把渲染的目的地由window系統提供的幀緩存改成不可顯示的應用程序創建的幀緩存(FBO)就可以了。
- ...
- // set rendering destination to FBO
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);
- // clear buffers
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- // draw a scene to a texture directly
- draw();
- // unbind FBO
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
- // trigger mipmaps generation explicitly
- // NOTE: If GL_GENERATE_MIPMAP is set to GL_TRUE, then glCopyTexSubImage2D()
- // triggers mipmap generation automatically. However, the texture attached
- // onto a FBO should generate mipmaps manually via glGenerateMipmapEXT().
- glBindTexture(GL_TEXTURE_2D, textureId);
- glGenerateMipmapEXT(GL_TEXTURE_2D);
- glBindTexture(GL_TEXTURE_2D, 0);
- ...
注意到,glGenerateMipmapEXT()也是作爲FBO擴展的一部分,用來在改變了紋理圖像的基級之後顯式生成mipmap的。如果GL_GENERATE_MIPMAP被設置爲GL_TRUE,那麼glTex{Sub}Image2D()和 glCopyTex{Sub}Image2D()將會啓用自動mipmap生成(在OpenGL版本1.4或者更高版本中)。然後,當紋理基級被改變時,FBO操作不會自動產生mipmaps。因爲FBO不會調用glCopyTex{Sub}Image2D()來修改紋理。因此,要產生mipmap,glGenerateMipmapEXT()必須被顯示調用。