Opengl ES之FBO

FBO介紹FBO幀緩沖對象,它的主要作用一般就是用作離屏渲染,例如做Camera相機圖像采集進行后期處理時就可能會用到FBO 。假如相機出圖的是OES紋理,為了方便后期處理,一般先將OES紋理通過FBO轉換成普通的2D紋理,然后再通過FBO等增加美顏等其他各種特效濾鏡,最后將FBO一路流送進編碼器進行編碼,另外一路渲染到屏幕上進行預覽顯示 。
FBO總結起來就是可以暫時將未處理完的幀不直接渲染到屏幕上 , 而是渲染到離屏Buffer中緩存起來,在恰當的時機再取出來渲染到屏幕 。
FBO(Frame Buffer Object)幀緩沖對象提供了與顏色緩沖區(color buffer)、深度緩沖區(depth buffer)和模版緩沖區(stencil buffer) ,但并不會直接為這些緩沖區分配空間 , 而只是為這些緩沖區提供一個或多個掛接點 。我們需要分別為各個緩沖區創建對象,申請空間,然后掛接到相應的掛接點上 。

Opengl ES之FBO

文章插圖
從上圖可以看出FBO中包含了:
  1. 多個顏色附著點(GL_COLOR_ATTACHMENT0、GL_COLOR_ATTACHMENT1...)
  2. 一個深度附著點(GL_DEPTH_ATTACHMENT)
  3. 一個模板附著點(GL_STENCIL_ATTACHMENT)
所謂的顏色附著(紋理附著)就是用于將顏色渲染到紋理中去的意思 。后面我們主要介紹FBO的顏色附著 。
如何使用FBO
  1. 使用函數glGenFramebuffers生成一個FBO對象,保存對象ID 。
  2. 使用函數glBindFramebuffer綁定FBO 。
  3. 使用函數glFramebufferTexture2D關聯紋理和FBO,并執行渲染步驟 。后續如果需要使用FBO的效果時只需要操作與FBO綁定的紋理即可 。
  4. 使用函數glBindFramebuffer解綁FBO,一般在Opengl中ID參數傳遞0就是解綁 。
  5. 使用函數glDeleteFramebuffers刪除FBO 。
當掛接完成之后 , 我們在執行FBO下面的操作之前,可以檢查一下FBO的狀態 , 使用函數GLenum glCheckFramebufferStatus(GLenum target)檢查 。
本著學以致用的原則 , 我們將結合之前的文章,例如紋理貼圖、VBO/VAO、EBO等相關知識點,使用這些知識點結合FBO繪制做一個實踐的例子:首先將紋理渲染到FBO上去,然后再將FBO的紋理渲染到屏幕上 。
插個話 。。??傆腥吮I用不貼原文鏈接,看看是誰 。。。
首先上代碼,然后我們挑重要的稍微解讀一下:FBOOpengl.h
class FBOOpengl:public BaseOpengl{public:FBOOpengl();void onFboDraw();virtual ~FBOOpengl();// override要么就都寫,要么就都不寫,不要一個虛函數寫override,而另外一個虛函數不寫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};// 本身圖像紋理idGLuint imageTextureId{0};// fbo紋理idGLuint fboTextureId{0};GLint textureSampler{-1};GLuint fboId{0};// 用于fbo的vbo和vao也可以用數組的形式,這里為了方便理解先獨立開來GLuint fboVbo{0};GLuint fboVao{0};int imageWidth{0};int imageHeight{0};};注意:override作為現代C++的一個關鍵字,使用的時候需要注意一點,要么就整個類的虛函數都用,要么整個類的虛函數都不用,不要一個虛函數用override修飾,另外一個虛函數又不用override關鍵字修飾 , 不然很有可能會編譯不過的 。
在FBOOpengl中為了區分屏幕渲染和FBO離屏渲染 , 我們聲明了兩套VAO和VBO 。
FBOOpengl.cpp
#include "FBOOpengl.h"#include "../utils/Log.h"http:// 頂點著色器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, // 右下// 紋理坐標1.0f,1.0f,0.5f, 0.5f, // 右上// 紋理坐標1.0f,0.0f,-0.5f, -0.5f, // 左下// 紋理坐標0.0f,1.0f,-0.5f, 0.5f, // 左上// 紋理坐標0.0f,0.0f};// 紋理坐標原點在圖片的左上角又是倒置的?什么鬼?疑惑吧?//const static GLfloat FBO_VERTICES_AND_TEXTURE[] = {//1.0f, -1.0f, // 右下//// 紋理坐標//1.0f,1.0f,//1.0f, 1.0f, // 右上//// 紋理坐標//1.0f,0.0f,//-1.0f, -1.0f, // 左下//// 紋理坐標//0.0f,1.0f,//-1.0f, 1.0f, // 左上//// 紋理坐標//0.0f,0.0f//};// 真正的紋理坐標在圖片的左下角const static GLfloat FBO_VERTICES_AND_TEXTURE[] = {1.0f, -1.0f, // 右下// 紋理坐標1.0f,0.0f,1.0f, 1.0f, // 右上// 紋理坐標1.0f,1.0f,-1.0f, -1.0f, // 左下// 紋理坐標0.0f,0.0f,-1.0f, 1.0f, // 左上// 紋理坐標0.0f,1.0f};// 使用byte類型比使用short或者int類型節約內存const static uint8_t indices[] = {// 注意索引從0開始!// 此例的索引(0,1,2,3)就是頂點數組vertices的下標,// 這樣可以由下標代表頂點組合成矩形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);// VAOglGenVertexArrays(1, &vao);glBindVertexArray(vao);// vboglGenBuffers(1, &vbo);glBindBuffer(GL_ARRAY_BUFFER, vbo);glBufferData(GL_ARRAY_BUFFER, sizeof(VERTICES_AND_TEXTURE), VERTICES_AND_TEXTURE, GL_STATIC_DRAW);// stride 步長 每個頂點坐標之間相隔4個數據點 , 數據類型是floatglVertexAttribPointer(positionHandle, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *) 0);// 啟用頂點數據glEnableVertexAttribArray(positionHandle);// stride 步長 每個顏色坐標之間相隔4個數據點 , 數據類型是float,顏色坐標索引從2開始glVertexAttribPointer(textureHandle, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float),(void *) (2 * sizeof(float)));// 啟用紋理坐標數組glEnableVertexAttribArray(textureHandle);// EBOglGenBuffers(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激活的是那個紋理,就設置的sampler2D是那個// 默認是0,如果不是0的話,需要在onDraw的時候重新激活一下?//glActiveTexture(GL_TEXTURE0);//glUniform1i(textureSampler, 0);// 例如,一樣的glActiveTexture(GL_TEXTURE2);glUniform1i(textureSampler, 2);// 綁定紋理glBindTexture(GL_TEXTURE_2D, imageTextureId);// 為當前綁定的紋理對象設置環繞、過濾方式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(){// VAOglGenVertexArrays(1, &fboVao);glBindVertexArray(fboVao);// vboglGenBuffers(1, &fboVbo);glBindBuffer(GL_ARRAY_BUFFER, fboVbo);glBufferData(GL_ARRAY_BUFFER, sizeof(FBO_VERTICES_AND_TEXTURE), FBO_VERTICES_AND_TEXTURE, GL_STATIC_DRAW);// stride 步長 每個頂點坐標之間相隔4個數據點,數據類型是floatglVertexAttribPointer(positionHandle, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *) 0);// 啟用頂點數據glEnableVertexAttribArray(positionHandle);// stride 步長 每個顏色坐標之間相隔4個數據點,數據類型是float,顏色坐標索引從2開始glVertexAttribPointer(textureHandle, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float),(void *) (2 * sizeof(float)));// 啟用紋理坐標數組glEnableVertexAttribArray(textureHandle);// EBOglGenBuffers(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);// 為當前綁定的紋理對象設置環繞、過濾方式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狀態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 設置的大小一致呀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配合繪制// 使用vaoglBindVertexArray(fboVao);// 使用EBO// 使用byte類型節省內存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();// 恢復繪制屏幕寬高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配合繪制// 使用vaoglBindVertexArray(vao);// 使用EBO// 使用byte類型節省內存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等}

推薦閱讀