Opengl?ES之FBO幀緩沖對象使用詳解
FBO介紹
FBO幀緩沖對象,它的主要作用一般就是用作離屏渲染,例如做Camera相機圖像采集進行后期處理時就可能會用到FBO。假如相機出圖的是OES紋理,為了方便后期處理,
一般先將OES紋理通過FBO轉(zhuǎn)換成普通的2D紋理,然后再通過FBO等增加美顏等其他各種特效濾鏡,最后將FBO一路流送進編碼器進行編碼,另外一路渲染到屏幕上進行預(yù)覽顯示。
FBO總結(jié)起來就是可以暫時將未處理完的幀不直接渲染到屏幕上,而是渲染到離屏Buffer中緩存起來,在恰當(dāng)?shù)臅r機再取出來渲染到屏幕。
FBO(Frame Buffer Object)幀緩沖對象提供了與顏色緩沖區(qū)(color buffer)、深度緩沖區(qū)(depth buffer)和模版緩沖區(qū)(stencil buffer) ,但并不會直接為這些緩沖區(qū)分配空間,而只是為這些緩沖區(qū)提供一個或多個掛接點。我們需要分別為各個緩沖區(qū)創(chuàng)建對象,申請空間,然后掛接到相應(yīng)的掛接點上。
從上圖可以看出FBO中包含了:
- 多個顏色附著點(GL_COLOR_ATTACHMENT0、GL_COLOR_ATTACHMENT1...)
- 一個深度附著點(GL_DEPTH_ATTACHMENT)
- 一個模板附著點(GL_STENCIL_ATTACHMENT)
所謂的顏色附著(紋理附著)就是用于將顏色渲染到紋理中去的意思。后面我們主要介紹FBO的顏色附著。
如何使用FBO
- 使用函數(shù)
glGenFramebuffers
生成一個FBO對象,保存對象ID。 - 使用函數(shù)
glBindFramebuffer
綁定FBO。 - 使用函數(shù)
glFramebufferTexture2D
關(guān)聯(lián)紋理和FBO,并執(zhí)行渲染步驟。后續(xù)如果需要使用FBO的效果時只需要操作與FBO綁定的紋理即可。 - 使用函數(shù)
glBindFramebuffer
解綁FBO,一般在Opengl中ID參數(shù)傳遞0就是解綁。 - 使用函數(shù)
glDeleteFramebuffers
刪除FBO。
當(dāng)掛接完成之后,我們在執(zhí)行FBO下面的操作之前,可以檢查一下FBO的狀態(tài),使用函數(shù)GLenum glCheckFramebufferStatus(GLenum target)
檢查。
本著學(xué)以致用的原則,我們將結(jié)合之前的文章,例如紋理貼圖、VBO/VAO、EBO等相關(guān)知識點,使用這些知識點結(jié)合FBO繪制做一個實踐的例子:首先將紋理渲染到FBO上去,然后再將FBO的紋理渲染到屏幕上。
插個話。。??傆腥吮I用不貼原文鏈接,看看是誰。。。
首先上代碼,然后我們挑重要的稍微解讀一下:
FBOOpengl.h
class FBOOpengl:public BaseOpengl{ public: FBOOpengl(); void onFboDraw(); virtual ~FBOOpengl(); // override要么就都寫,要么就都不寫,不要一個虛函數(shù)寫override,而另外一個虛函數(shù)不寫override,不然可能編譯不過 virtual void onDraw() override; virtual void setPixel(void *data, int width, int height, int length) override; private: void fboPrepare(); GLint positionHandle{-1}; GLint textureHandle{-1}; GLuint vbo{0}; GLuint vao{0}; GLuint ebo{0}; // 本身圖像紋理id GLuint imageTextureId{0}; // fbo紋理id GLuint fboTextureId{0}; GLint textureSampler{-1}; GLuint fboId{0}; // 用于fbo的vbo和vao 也可以用數(shù)組的形式,這里為了方便理解先獨立開來 GLuint fboVbo{0}; GLuint fboVao{0}; int imageWidth{0}; int imageHeight{0}; };
注意:override作為現(xiàn)代C++的一個關(guān)鍵字,使用的時候需要注意一點,要么就整個類的虛函數(shù)都用,要么整個類的虛函數(shù)都不用,不要一個虛函數(shù)用override修飾,另外一個虛函數(shù)又不用override關(guān)鍵字修飾,不然很有可能會編譯不過的。
在FBOOpengl中為了區(qū)分屏幕渲染和FBO離屏渲染,我們聲明了兩套VAO和VBO。
FBOOpengl.cpp
#include "FBOOpengl.h" #include "../utils/Log.h" // 頂點著色器 static const char *ver = "#version 300 es\n" "in vec4 aPosition;\n" "in vec2 aTexCoord;\n" "out vec2 TexCoord;\n" "void main() {\n" " TexCoord = aTexCoord;\n" " gl_Position = aPosition;\n" "}"; // 片元著色器 static const char *fragment = "#version 300 es\n" "precision mediump float;\n" "out vec4 FragColor;\n" "in vec2 TexCoord;\n" "uniform sampler2D ourTexture;\n" "void main()\n" "{\n" " FragColor = texture(ourTexture, TexCoord);\n" "}"; const static GLfloat VERTICES_AND_TEXTURE[] = { 0.5f, -0.5f, // 右下 // 紋理坐標(biāo) 1.0f,1.0f, 0.5f, 0.5f, // 右上 // 紋理坐標(biāo) 1.0f,0.0f, -0.5f, -0.5f, // 左下 // 紋理坐標(biāo) 0.0f,1.0f, -0.5f, 0.5f, // 左上 // 紋理坐標(biāo) 0.0f,0.0f }; // 紋理坐標(biāo)原點在圖片的左上角 又是倒置的?什么鬼?疑惑吧? //const static GLfloat FBO_VERTICES_AND_TEXTURE[] = { // 1.0f, -1.0f, // 右下 // // 紋理坐標(biāo) // 1.0f,1.0f, // 1.0f, 1.0f, // 右上 // // 紋理坐標(biāo) // 1.0f,0.0f, // -1.0f, -1.0f, // 左下 // // 紋理坐標(biāo) // 0.0f,1.0f, // -1.0f, 1.0f, // 左上 // // 紋理坐標(biāo) // 0.0f,0.0f //}; // 真正的紋理坐標(biāo)在圖片的左下角 const static GLfloat FBO_VERTICES_AND_TEXTURE[] = { 1.0f, -1.0f, // 右下 // 紋理坐標(biāo) 1.0f,0.0f, 1.0f, 1.0f, // 右上 // 紋理坐標(biāo) 1.0f,1.0f, -1.0f, -1.0f, // 左下 // 紋理坐標(biāo) 0.0f,0.0f, -1.0f, 1.0f, // 左上 // 紋理坐標(biāo) 0.0f,1.0f }; // 使用byte類型比使用short或者int類型節(jié)約內(nèi)存 const static uint8_t indices[] = { // 注意索引從0開始! // 此例的索引(0,1,2,3)就是頂點數(shù)組vertices的下標(biāo), // 這樣可以由下標(biāo)代表頂點組合成矩形 0, 1, 2, // 第一個三角形 1, 2, 3 // 第二個三角形 }; FBOOpengl::FBOOpengl() { initGlProgram(ver,fragment); positionHandle = glGetAttribLocation(program,"aPosition"); textureHandle = glGetAttribLocation(program,"aTexCoord"); textureSampler = glGetUniformLocation(program,"ourTexture"); LOGD("program:%d",program); LOGD("positionHandle:%d",positionHandle); LOGD("textureHandle:%d",textureHandle); LOGD("textureSample:%d",textureSampler); // VAO glGenVertexArrays(1, &vao); glBindVertexArray(vao); // vbo glGenBuffers(1, &vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(VERTICES_AND_TEXTURE), VERTICES_AND_TEXTURE, GL_STATIC_DRAW); // stride 步長 每個頂點坐標(biāo)之間相隔4個數(shù)據(jù)點,數(shù)據(jù)類型是float glVertexAttribPointer(positionHandle, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *) 0); // 啟用頂點數(shù)據(jù) glEnableVertexAttribArray(positionHandle); // stride 步長 每個顏色坐標(biāo)之間相隔4個數(shù)據(jù)點,數(shù)據(jù)類型是float,顏色坐標(biāo)索引從2開始 glVertexAttribPointer(textureHandle, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *) (2 * sizeof(float))); // 啟用紋理坐標(biāo)數(shù)組 glEnableVertexAttribArray(textureHandle); // EBO glGenBuffers(1,&ebo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,ebo); glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(indices),indices,GL_STATIC_DRAW); // 這個順序不能亂啊,先解除vao,再解除其他的,不然在繪制的時候可能會不起作用,需要重新glBindBuffer才生效 // vao解除 glBindVertexArray(0); // 解除綁定 glBindBuffer(GL_ARRAY_BUFFER, 0); // 解除綁定 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0); LOGD("program:%d", program); LOGD("positionHandle:%d", positionHandle); LOGD("colorHandle:%d", textureHandle); } void FBOOpengl::setPixel(void *data, int width, int height, int length) { LOGD("texture setPixel"); imageWidth = width; imageHeight = height; glGenTextures(1, &imageTextureId); // 激活紋理,注意以下這個兩句是搭配的,glActiveTexture激活的是那個紋理,就設(shè)置的sampler2D是那個 // 默認(rèn)是0,如果不是0的話,需要在onDraw的時候重新激活一下? // glActiveTexture(GL_TEXTURE0); // glUniform1i(textureSampler, 0); // 例如,一樣的 glActiveTexture(GL_TEXTURE2); glUniform1i(textureSampler, 2); // 綁定紋理 glBindTexture(GL_TEXTURE_2D, imageTextureId); // 為當(dāng)前綁定的紋理對象設(shè)置環(huán)繞、過濾方式 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); // 生成mip貼圖 glGenerateMipmap(GL_TEXTURE_2D); // 解綁定 glBindTexture(GL_TEXTURE_2D, 0); } void FBOOpengl::fboPrepare(){ // VAO glGenVertexArrays(1, &fboVao); glBindVertexArray(fboVao); // vbo glGenBuffers(1, &fboVbo); glBindBuffer(GL_ARRAY_BUFFER, fboVbo); glBufferData(GL_ARRAY_BUFFER, sizeof(FBO_VERTICES_AND_TEXTURE), FBO_VERTICES_AND_TEXTURE, GL_STATIC_DRAW); // stride 步長 每個頂點坐標(biāo)之間相隔4個數(shù)據(jù)點,數(shù)據(jù)類型是float glVertexAttribPointer(positionHandle, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *) 0); // 啟用頂點數(shù)據(jù) glEnableVertexAttribArray(positionHandle); // stride 步長 每個顏色坐標(biāo)之間相隔4個數(shù)據(jù)點,數(shù)據(jù)類型是float,顏色坐標(biāo)索引從2開始 glVertexAttribPointer(textureHandle, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *) (2 * sizeof(float))); // 啟用紋理坐標(biāo)數(shù)組 glEnableVertexAttribArray(textureHandle); // EBO glGenBuffers(1,&ebo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,ebo); glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(indices),indices,GL_STATIC_DRAW); // 這個順序不能亂啊,先解除vao,再解除其他的,不然在繪制的時候可能會不起作用,需要重新glBindBuffer才生效 // vao解除 glBindVertexArray(0); // 解除綁定 glBindBuffer(GL_ARRAY_BUFFER, 0); // 解除綁定 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0); glGenTextures(1, &fboTextureId); // 綁定紋理 glBindTexture(GL_TEXTURE_2D, fboTextureId); // 為當(dāng)前綁定的紋理對象設(shè)置環(huán)繞、過濾方式 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glBindTexture(GL_TEXTURE_2D, GL_NONE); glGenFramebuffers(1,&fboId); glBindFramebuffer(GL_FRAMEBUFFER,fboId); // 綁定紋理 glBindTexture(GL_TEXTURE_2D,fboTextureId); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fboTextureId, 0); // 這個紋理是多大的? glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, imageWidth, imageHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); // 檢查FBO狀態(tài) if (glCheckFramebufferStatus(GL_FRAMEBUFFER)!= GL_FRAMEBUFFER_COMPLETE) { LOGE("FBOSample::CreateFrameBufferObj glCheckFramebufferStatus status != GL_FRAMEBUFFER_COMPLETE"); } // 解綁 glBindTexture(GL_TEXTURE_2D, GL_NONE); glBindFramebuffer(GL_FRAMEBUFFER, GL_NONE); } void FBOOpengl::onFboDraw() { fboPrepare(); glBindFramebuffer(GL_FRAMEBUFFER, fboId); // 主要這個的大小要與FBO綁定時的紋理的glTexImage2D 設(shè)置的大小一致呀 glViewport(0,0,imageWidth,imageHeight); // FBO繪制 // 清屏 glClearColor(0.0f, 0.0f, 1.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glUseProgram(program); // 激活紋理 glActiveTexture(GL_TEXTURE1); glUniform1i(textureSampler, 1); // 綁定紋理 glBindTexture(GL_TEXTURE_2D, imageTextureId); // VBO與VAO配合繪制 // 使用vao glBindVertexArray(fboVao); // 使用EBO // 使用byte類型節(jié)省內(nèi)存 glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_BYTE,(void *)0); glUseProgram(0); // vao解除綁定 glBindVertexArray(0); if (nullptr != eglHelper) { eglHelper->swapBuffers(); } glBindTexture(GL_TEXTURE_2D, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0); } void FBOOpengl::onDraw() { // 先在FBO離屏渲染 onFboDraw(); // 恢復(fù)繪制屏幕寬高 glViewport(0,0,eglHelper->viewWidth,eglHelper->viewHeight); // 繪制到屏幕 // 清屏 glClearColor(0.0f, 1.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glUseProgram(program); // 激活紋理 glActiveTexture(GL_TEXTURE2); glUniform1i(textureSampler, 2); // 綁定紋理 glBindTexture(GL_TEXTURE_2D, fboTextureId); // VBO與VAO配合繪制 // 使用vao glBindVertexArray(vao); // 使用EBO // 使用byte類型節(jié)省內(nèi)存 glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_BYTE,(void *)0); glUseProgram(0); // vao解除綁定 glBindVertexArray(0); // 禁用頂點 glDisableVertexAttribArray(positionHandle); if (nullptr != eglHelper) { eglHelper->swapBuffers(); } glBindTexture(GL_TEXTURE_2D, 0); } FBOOpengl::~FBOOpengl() noexcept { glDeleteBuffers(1,&ebo); glDeleteBuffers(1,&vbo); glDeleteVertexArrays(1,&vao); // ... 刪除其他,例如fbo等 }
按照之前Opengl ES之紋理貼圖 一文所說的,在Opengl ES中進行紋理貼圖時直接以圖片的左上角為(0,0)原點進行貼圖,以糾正紋理貼圖倒置的問題,那么這次在綁定FBO之后之后我們就這么干,
使用以下的頂點坐標(biāo)和紋理坐標(biāo):
// 紋理坐標(biāo)原點在圖片的左上角 又是倒置的?什么鬼?疑惑吧? const static GLfloat FBO_VERTICES_AND_TEXTURE[] = { 1.0f, -1.0f, // 右下 // 紋理坐標(biāo) 1.0f,1.0f, 1.0f, 1.0f, // 右上 // 紋理坐標(biāo) 1.0f,0.0f, -1.0f, -1.0f, // 左下 // 紋理坐標(biāo) 0.0f,1.0f, -1.0f, 1.0f, // 左上 // 紋理坐標(biāo) 0.0f,0.0f };
一運行,我們驚喜地發(fā)現(xiàn),實際情況居然和 Opengl ES之紋理貼圖 一文所說的不一樣了,經(jīng)過FBO后的貼圖再渲染到屏幕時,居然圖片是倒置的,如下圖:
這是什么為什么呢?
默認(rèn)情況下,OpenGL ES 通過繪制到窗口系統(tǒng)提供的幀緩沖區(qū),也就是屏幕本身就是一個默認(rèn)的FBO,而使用FBO進行紋理貼圖的時候需要以真正的紋理坐標(biāo)(原點0,0在圖片的左下角)為基準(zhǔn)進行貼圖。因此如果直接使用屏幕進行紋理貼圖,其實是應(yīng)該細(xì)分成兩個過程的,先以左下角為紋理坐標(biāo)原點進行貼圖,然后將貼圖后的屏幕默認(rèn)FBO旋轉(zhuǎn)繞X軸旋轉(zhuǎn)180度與屏幕坐標(biāo)(左上角是坐標(biāo)原點)重合,但是這兩個細(xì)分的過程可以做個取巧就是直接以左上角為紋理坐標(biāo)原點進行貼圖,得到的結(jié)果是一樣的。
但是我們在單獨使用FBO時,仍應(yīng)該遵循以左下角為紋理坐標(biāo)原點的原則進行紋理貼圖。因此我們只需修改一下頂點坐標(biāo)和紋理坐標(biāo),以左下角為紋理坐標(biāo)作為原點進行FBO貼圖,然后再將FBO旋繞到屏幕上即可:
// 真正的紋理坐標(biāo)在圖片的左下角 const static GLfloat FBO_VERTICES_AND_TEXTURE[] = { 1.0f, -1.0f, // 右下 // 紋理坐標(biāo) 1.0f,0.0f, 1.0f, 1.0f, // 右上 // 紋理坐標(biāo) 1.0f,1.0f, -1.0f, -1.0f, // 左下 // 紋理坐標(biāo) 0.0f,0.0f, -1.0f, 1.0f, // 左上 // 紋理坐標(biāo) 0.0f,1.0f };
運行結(jié)果如圖:
以上就是Opengl ES之FBO幀緩沖對象使用詳解的詳細(xì)內(nèi)容,更多關(guān)于Opengl ES FBO幀緩沖對象的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C++利用inotify+epoll實現(xiàn)異步文件監(jiān)控的方法
這篇文章講給大家詳細(xì)介紹一下C++利用inotify+epoll實現(xiàn)異步文件監(jiān)控的方法,inotify是一種異步文件監(jiān)控機制,文章通過代碼示例介紹的非常詳細(xì),具有一定的參考價值,需要的朋友可以參考下2023-08-08C++歸并法+快速排序?qū)崿F(xiàn)鏈表排序的方法
這篇文章主要介紹了C++歸并法+快速排序?qū)崿F(xiàn)鏈表排序的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04