OpenGL學習腳印: 幀緩沖對象(Frame Buffer Object) - IT閱讀
文章推薦指數: 80 %
FBO中包含兩種類型的附加圖像(framebuffer-attachable): 紋理圖像和RenderBuffer圖像(texture images and renderbuffer images)。
附加紋理時OpenGL渲染到 ...
IT閱讀
首頁
科技
技術
創投
數碼
設計
營銷
搜尋一下
OpenGL學習腳印:幀緩沖對象(FrameBufferObject)
分類:IT技術時間:2016-10-17
寫在前面
一直以來,我們在使用OpenGL渲染時,最終的目的地是默認的幀緩沖區,實際上OpenGL也允許我們創建自定義的幀緩沖區。
使用自定義的幀緩沖區,可以實現鏡面,離屏渲染,以及很酷的後處理效果。
本節將學習幀緩存的使用,文中示例代碼均可以在我的github下載。
本節內容整理自
1.OpenGLFrameBufferObject(FBO)
2.www.learnopengl.comFramebuffers
FBO概念
在OpenGL中,渲染管線中的頂點、紋理等經過一系列處理後,最終顯示在2D屏幕設備上,渲染管線的最終目的地就是幀緩沖區。
幀緩沖包括OpenGL使用的顏色緩沖區(colorbuffer)、深度緩沖區(depthbuffer)、模板緩沖區(stencilbuffer)等緩沖區。
默認的幀緩沖區由窗口系統創建,例如我們一直使用的GLFW庫來完成這項任務。
這個默認的幀緩沖區,就是目前我們一直使用的繪圖命令的作用對象,稱之為窗口系統提供的幀緩沖區ADVERTISEMENT(window-system-providedframebuffer)。
OpenGL也允許我們手動創建一個幀緩沖區,並將渲染結果重定向到這個緩沖區。
在創建時允許我們自定義幀緩沖區的一些特性,這個自定義的幀緩沖區,稱之為應用程序幀緩沖區(application-createdframebufferobject)。
同默認的幀緩沖區一樣,自定義的幀緩沖區也包含顏色緩沖區、深度和模板緩沖區,這些邏輯上的緩沖區(logicalbuffers)在FBO中稱之為可附加的圖像(framebuffer-attachableimages),他們是可以附加到FBO的二維像素數組(2Darraysofpixels)。
FBO中包含兩種類型的附加圖像(framebuffer-attachable):紋理圖像和RenderBuffer圖像(textureimagesandrenderbufferimages)。
附加紋理時OpenGL渲染到這個紋理圖像,在著色器中可以訪問到這個紋理對象;附加RenderBuffer時,OpenGL執行離屏渲染(offscreenrendering)。
ADVERTISEMENT
之所以用附加這個詞,表達的是FBO可以附加多個緩沖區,而且可以靈活地在緩沖區中切換,一個重要的概念是附加點(attachmentpoints)。
FBO中包含一個以上的顏色附加點,但只有一個深度和模板附加點,如下圖所示(來自songhoFBO):
一個FBO可以有
(GL_COLOR_ATTACHMENT0,…,GL_COLOR_ATTACHMENTn)
多個附加點,最多的附加點可以通過查詢GL_MAX_COLOR_ATTACHMENTS變量獲取。
值得註意的是:從上面的圖中我們可以看到,FBO本身並不包含任何緩沖對象,實際上是通過附加點指向實際的緩沖對象的。
這樣FBO可以快速地切換緩沖對象。
創建FBO
同OpenGL中創建其他緩沖對象一樣,創建和銷毀FBO的步驟也很簡單:ADVERTISEMENT
voidglGenFramebuffers(GLsizein,GLuint*ids)
voidglDeleteFramebuffers(GLsizein,constGLuint*ids)
創建之後,我們需要將FBO綁定到目標對象:
voidglBindFramebuffer(GLenumtarget,GLuintid)
這裏的target一般可以填寫GL_FRAMEBUFFER,這個緩沖區將會用來進行讀和寫操作;如果需要綁定到讀操作的緩沖區使用GL_READ_FRAMEBUFFER,支持glReadPixels這類讀操作;如果需要綁定到寫操作的緩沖區使用GL_DRAW_FRAMEBUFFER,支持渲染、清除等操作。
OpenGL要求,一個完整的FBO需要滿足以下條件(來自FrameBufffer):ADVERTISEMENT
至少附加一個緩沖區(顏色、深度或者模板)
至少有一個顏色附加
所有的附加必須完整(預分配了內存)
每個緩沖區的采樣數需要一致
關於采樣,後面會學習,暫時不做討論。
判斷一個FBO是否完整,可以如下:
if(glCheckFramebufferStatus(GL_FRAMEBUFFER)==GL_FRAMEBUFFER_COMPLETE)
如果FBO不完整將不能正常工作。
那麽我們需要按照上述要求構建一個完整的FBO。
創建紋理附加圖像
創建FBO的附加紋理如同平常使用紋理一樣,不同的是,這裏只是為紋理預分配空間,而不需要真正的加載紋理,因為當使用FBO渲染時渲染結果將會寫入到我們創建的這個紋理上去。
附加紋理使用函數glFramebufferTexture2D。
APIvoidglFramebufferTexture2D(GLenumtarget,
GLenumattachment,
GLenumtextarget,GLuinttexture,GLintlevel);
1.target表示綁定目標,參數可選為GL_DRAW_FRAMEBUFFER,GL_READ_FRAMEBUFFER,orGL_FRAMEBUFFER。
2.attechment表示附加點,可選值為GL_COLOR_ATTACHMENTi,GL_DEPTH_ATTACHMENT,GL_STENCIL_ATTACHMENTorGL_DEPTH_STENCIL_ATTACHMMENT。
3.textTarget表示紋理的綁定目標,我們使用二維紋理填寫GL_TEXTURE_2D即可。
4.texture表示實際的紋理對象。
5.level表示mipmap級別,我們填寫0即可。
這裏的texture是我們實際創建的紋理對象,在創建紋理對象時使用代碼:
GLuinttexture;
glGenTextures(1,&texture);
glBindTexture(GL_TEXTURE_2D,texture);
glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,800,600,0,GL_RGB,GL_UNSIGNED_BYTE,NULL);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
這裏需要註意的是glTexImage2D函數,末尾的NULL表示我們只預分配空間,而不實際加載紋理。
glTexImage2D函數也是一個OpenGL中相對復雜的一個函數。
APIvoidglTexImage2D(GLenumtarget,
GLintlevel,
GLintinternalFormat,
GLsizeiwidth,
GLsizeiheight,
GLintborder,
GLenumformat,
GLenumtype,
constGLvoid*data);
在前面二維紋理一節已經介紹過這個函數,這裏重點說下創建FBO紋理時需要註意的。
函數中後面三個參數format、type、data表示的是內存中圖像像素的信息,包括格式,類型和指向內存的指針。
而internalFormat表示的是OpenGL內存存儲紋理的格式,表示的是紋理中顏色成分的格式。
從紋理圖片的內存轉移到OpenGL內存紋理存儲是一個像素轉移操作(PixelTransfer),關於這個部分的細節比較多,不在這裏展開,感興趣地可以參考OpenGLwiki-PixelTransfer。
上面填寫的紋理格式GL_RGB,以及GL_UNSIGNED_BYTE表示紋理包含紅綠藍三色,並且每個成分用無符號字節表示。
600,800表示我們分配的紋理大小,註意這個紋理需要和我們渲染的屏幕大小保持一致,如果需要繪制與屏幕不一致的紋理,使用glViewport函數進行調節。
上面創建的紋理圖像,可以附加到FBO:
glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,texture,0);
這裏我們附加到了顏色附加點。
在繪制時,如果需要開啟深度測試還需要附加一個深度緩沖區,這裏我們也附加一個深度-模板到紋理中。
將創建紋理的代碼封裝到texture.h中,完整的用紋理圖像構建一個FBO的代碼如下:
/*
*附加紋理到Color,depth,stencilAttachment
*/
boolprepareFBO1(GLuint&colorTextId,GLuint&depthStencilTextId,GLuint&fboId)
{
glGenFramebuffers(1,&fboId);
glBindFramebuffer(GL_FRAMEBUFFER,fboId);
//附加紋理colorattachment
colorTextId=TextureHelper::makeAttachmentTexture(0,GL_RGB,WINDOW_WIDTH,WINDOW_HEIGHT,GL_RGB,GL_UNSIGNED_BYTE);
glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,colorTextId,0);
//附加depthstenciltextureattachment
depthStencilTextId=TextureHelper::makeAttachmentTexture(0,GL_DEPTH24_STENCIL8,WINDOW_WIDTH,
WINDOW_HEIGHT,GL_DEPTH_STENCIL,GL_UNSIGNED_INT_24_8);
glFramebufferTexture2D(GL_FRAMEBUFFER,GL_DEPTH_STENCIL_ATTACHMENT,GL_TEXTURE_2D,depthStencilTextId,0);
//檢測完整性
if(glCheckFramebufferStatus(GL_FRAMEBUFFER)!=GL_FRAMEBUFFER_COMPLETE)
{
returnfalse;
}
glBindFramebuffer(GL_FRAMEBUFFER,0);
returntrue;
}
到此,我們的FBO就滿足了基本要求,可以使用了。
在利用FBO作圖前,我們繼續介紹另一個附加圖像-RenderBuffer。
RenderBufferObject
紋理圖像附加到FBO後,執行渲染後,我們可以在後期著色器處理中訪問到紋理,這給一些需要多遍處理的操作提供了很大方便。
當我們不需要在後期讀取紋理時,我們可以使用Renderbuffer這種附加圖像,它主要用來存儲深度、模板這類沒有與之對應的紋理格式的緩沖區。
創建和銷毀RenderBuffer也很簡單,如下:
voidglGenRenderbuffers(GLsizein,GLuint*ids)
voidglDeleteRenderbuffers(GLsizein,constGluint*ids)
創建完畢後,仍然需要綁定道目標對象:
glBindRenderbuffer(GL_RENDERBUFFER,rbo);
需要註意的是,我們還需要為RBO預分配內存空間:
voidglRenderbufferStorage(GLenumtarget,
GLenuminternalFormat,
GLsizeiwidth,
GLsizeiheight)
這個函數為指定內部格式的RBO預分配空間。
當上述步驟完成後,我們可以將RBO綁定到FBO。
上面的紋理圖像中使用了紋理作為深度和模板緩沖區,這裏我們將深度模板緩沖區使用RBO代替:
/*
*附加紋理到ColorAttachment
*同時附加RBO到depthstencilAttachment
*/
boolprepareFBO2(GLuint&textId,GLuint&fboId)
{
glGenFramebuffers(1,&fboId);
glBindFramebuffer(GL_FRAMEBUFFER,fboId);
//附加紋理colorattachment
textId=TextureHelper::makeAttachmentTexture(0,GL_RGB,WINDOW_WIDTH,WINDOW_HEIGHT,GL_RGB,GL_UNSIGNED_BYTE);
glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,textId,0);
//附加depthstencilRBOattachment
GLuintrboId;
glGenRenderbuffers(1,&rboId);
glBindRenderbuffer(GL_RENDERBUFFER,rboId);
glRenderbufferStorage(GL_RENDERBUFFER,GL_DEPTH24_STENCIL8,
WINDOW_WIDTH,WINDOW_HEIGHT);//預分配內存
glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_DEPTH_STENCIL_ATTACHMENT,GL_RENDERBUFFER,rboId);
if(glCheckFramebufferStatus(GL_FRAMEBUFFER)!=GL_FRAMEBUFFER_COMPLETE)
{
returnfalse;
}
glBindFramebuffer(GL_FRAMEBUFFER,0);
returntrue;
}
到此,我們也利用RBO創建了一個完整的FBO。
繪制到紋理
上面利用紋理和RBO創建的FBO,我們在OpenGL中可以用來將場景繪制到紋理中。
首先綁定自定義的FBO執行渲染,然後綁定到默認FBO,我們繪制一個矩形,矩形使用FBO中的紋理填充,得到效果如下圖所示:
采用線框模式繪制:
glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
顯示的就是一個矩形:
從上面結果我們可以看到,利用FBO將場景繪制到紋理,在後期繪制矩形時使用這個紋理。
這種方式可以制作鏡子等效果,十分有用。
使用後處理效果(postprocessing)
上面場景繪制到紋理後,我們可以通過操作這個紋理圖像,而得到很酷的後處理效果。
例如在著色器中,將紋理的顏色進行反轉:
vec3inversion()//反色
{
returnvec3(1.0-texture(text,TextCoord));
}
得到的效果如下圖所示:
後處理也可以采取圖像處理的方式,例如使用kernel矩陣。
kernel矩陣一般取為3x3矩陣,這個矩陣的和一般為1。
通過kernel矩陣,將當前紋理坐標處的紋理擴展到周圍9個坐標處的紋理,然後通過權重計算出最終紋理的像素。
例如產生浮雕效果的kernel矩陣如下所示:
kernel=????2?10?111012???
在著色器中,我們定義當前紋理位置的9個周圍位置如下:
constfloatoffset=1.0/300;//9個位置的紋理坐標偏移量
//確定9個位置的偏移量
vec2offsets[9]=vec2[](
vec2(-offset,offset),//top-left左上方
vec2(0.0f,offset),//top-center正上方
vec2(offset,offset),//top-right右上方
vec2(-offset,0.0f),//center-left中間左邊
vec2(0.0f,0.0f),//center-center正中位置
vec2(offset,0.0f),//center-right中間右邊
vec2(-offset,-offset),//bottom-left底部左邊
vec2(0.0f,-offset),//bottom-center底部中間
vec2(offset,-offset)//bottom-right底部右邊
);
然後使用kernel矩陣中的權系數,計算最終的紋理像素:
//計算9個位置的紋理
vec3sampleText[9];
for(inti=0;i<9;++i)
{
sampleText[i]=vec3(texture(text,TextCoord.st+offsets[i]));
}
//利用權值求最終紋理顏色
vec3result=vec3(0.0);
for(inti=0;i<9;++i)
{
result+=sampleText[i]*kernel[i];
}
指定不同的kernel將會得到不同的效果,例如指定模糊矩陣,得到模糊的效果如下圖所示:
指定edge-detection矩陣,得到效果如下圖所示:
當著色器中計算紋理坐標的偏移量offset不同時,效果會有所改變。
想查看更多的kernel效果,可以訪問在線網站ImageKernels。
最後的說明
本節介紹了FBO的概念和使用,還有一些操作例如FBO的讀寫、復制操作沒有介紹到,同時glTextImage2D這個函數中紋理的內部格式以及內存中像素的格式和類型的說明將是一個比較繁瑣的工作,這些內容留到後續學習。
關於選擇附加紋理還是附加RBO,可以參考DifferencebetweenFramebufferobject,Renderbufferobjectandtexture?。
在附加深度和模板的紋理時(即代碼中我們使用depthStencilTextId而不是colorTextId繪制最終的結果),如果我們使用深度和模板的紋理繪圖將會得到如下效果:
這個圖中主要呈現紅色,我分析是因為圖中離觀察者較遠的距離時深度值基本為1,那麽取得的紋理顏色基本上就是(1.0,0.0,0.0,1.0),因而呈現紅色;而離觀察者近一些的地方,深度值基本上為0,則取得的紋理顏色就是(0.0,0.0,0.0,1.0),因而呈現出黑色。
如果觀察者靠近場景中的立方體,那麽得到的圖像將主要呈現黑色:
附加的GL_DEPTH24_STENCIL8紋理,底層如何解釋為采樣後的顏色值,還需要進一步學習和說明。
參考資料
1.OpenGLwikiImageFormat
2.OpenGLwikiFramebufferObject
3.OpenGLwikiPixelTransfer
4.WikiFramebufferobject
Tags:
應用程序Objectimages緩沖區color
文章來源:
相關文章
1970-01-01
1970-01-01
1970-01-01
1970-01-01
1970-01-01
1970-01-01
1970-01-01
1970-01-01
1970-01-01
1970-01-01
1970-01-01
1970-01-01
1970-01-01
1970-01-01
MySQL中union和join語句使用區別的辨析教程2016-11-05
相關文章
從數組到HashMap之算法解釋
《Nodejs開發加密貨幣》之二十六:輕松從Js文件生成UML類圖
遊戲制作中的大寶劍---常用的數據結構與算法
用戶故事地圖對應到Epic及其缺點
日期類型轉換
操作json
Java異步編程之Deferred
Linux下php安裝Redis擴展
velocity模板使用打成jar包後報路徑錯誤的解決辦法
物聯網框架ServerSuperIO(SSIO)更新、以及增加宿主程序和配置工具,詳細介紹
ad
延伸文章資訊
- 1帧缓冲
和OpenGL中的其它对象一样,我们会使用一个叫做 glGenFramebuffers 的函数来创建一个帧缓冲对象(Framebuffer Object, FBO): unsigned int ...
- 2Framebuffer Object - OpenGL Wiki
Framebuffer Objects are OpenGL Objects, which allow for the creation of user-defined Framebuffers...
- 3OpenGL幀快取物件(FBO:Frame Buffer Object) - 程式人生
在OpenGL渲染管線中,幾何資料和紋理經過多次轉化和多次測試,最後以二維畫素的形式顯示在螢幕上。OpenGL管線的最終渲染目的地被稱作幀快取(framebuffer ...
- 4Framebuffers - LearnOpenGL
Renderbuffer objects were introduced to OpenGL after textures as a possible type of framebuffer a...
- 5【OpenGL】OpenGL帧缓存对象(FBO:Frame Buffer Object)
OpenGL管线的最终渲染目的地被称作帧缓存(framebuffer)。帧缓冲是一些二维数组和OpenG所使用的存储区的集合:颜色缓存、深度缓存、模板缓存和累计缓存。