天天看點

Android OpenGLES2.0(十二)——FBO離屏渲染關于FBO離屏渲染FBO渲染步驟紋理(Texture)的使用Frame Buffer和Render Buffer最後的處理源碼

之前的部落格我們所做的示例都是直接渲染到螢幕上的,如果我們并不需要渲染到螢幕上,也就是離屏渲染,該怎麼做呢?FBO離屏渲染是一個很好的選擇。在這篇部落格中,我們将以渲染攝像頭資料為例,使用FBO進行離屏渲染。

關于FBO離屏渲染

所謂的FBO就是Frame Buffer Object。之前我們使用OpenGLES渲染,都是直接渲染到螢幕上,FBO可以讓我們的渲染不渲染到螢幕上,而是渲染到離屏Buffer中。這樣的作用是什麼呢?比如我們需要處理一張圖檔,在上傳時增加時間的水印,這個時候不需要顯示出來的。再比如我們需要對攝像頭采集的資料,一個彩色原大小的顯示出來,一個黑白的長寬各一半錄制成視訊。

像這些情況,我們就可以使用到FBO離屏渲染技術了,當然FBO并不是僅僅局限于此。

FBO渲染步驟

如果這時候,你正在尋求關于離屏渲染的幫助,并且看到了這篇部落格,那麼我想你應該是能夠把圖像直接渲染到螢幕上的。不能的話可以Google、百度或者直接看看前面的部落格。

圖像直接渲染到螢幕上的步驟:

  1. 編寫Shader。(檢查支援、權限什麼的就不再提了)
  2. 建立GL環境,直接使用GLSurfaceView,GLSurfaceView内部實作了建立GL環境。
  3. GL環境建立後,編譯Shader,建立GL Program。擷取可用Texture,設定渲染參數。(onSurfaceCreated中)
  4. 設定ViewPort。(onSurfaceChanged中)
  5. 清屏(onDrawFrame中)
  6. 啟用必要的屬性,useProgram,綁定紋理,傳入參數(頂點坐标、紋理坐标、變換矩陣等)。(onDrawFrame中)
  7. 繪制。(onDrawFrame中)
  8. 下一幀資料,requestRender,再一次從第5步開始執行。

FBO離屏渲染我們需要改動的地方為:

  • 擷取可用的Texture,不再隻擷取一個,針對我們假設的需求可以擷取兩個。一個是作為資料源的texture,另外一個是用來作為輸出圖像的texture,這時候這個texture相當于是一塊還沒畫東西的畫布。擷取一個可用的FrameBuffer,方法名和擷取可用Texture類似,為glGenFrameBuffers。
  • 繪制前先綁定FrameBuffer、RenderBuffer、Texture,并将RenderBuffer和Texture挂載到FrameBuffer上。

這隻是個大概的說法,并不太準确。具體代碼可參看Demo。

紋理(Texture)的使用

紋理在之前的圖檔處理、Camera預覽中就使用到了,一直都是默默的用,沒詳細說明,在這裡補充一下。紋理的使用通常為:在GL線程建立成功後,在GL線程中生成紋理,并設定紋理參數,然後在渲染時啟用紋理,綁定紋理,并将紋理傳入Shader(告訴Shader,采樣器是哪個)。

生成紋理

OpenGL生成紋理,其實是從未被使用的“紋理堆”(姑且這樣了解吧)中擷取指定個數的紋理,這些紋理不一定是連續的。生成紋理的方法為

GLES20.glGenTextures(int size,int[] textures,int start)

,其C函數為

void glGenTextures(GLsizei n,GLuint * textures);

。Android版的第一個參數為需要的紋理數,第二個參數為存儲獲得的紋理ID的數組,第三個參數為數組的起始位置。

gl的命名很有規律,類似于生成紋理的還有生成FrameBuffer的

GLES20.glGenFrameBuffers

、生成RenderBuffer的

GLES20.glGenRenderBuffers

等待。其他的方法基本也可以依次類推。

紋理過濾參數設定

glTexParameteri和glTexParameterf為紋理過濾設定函數,設定紋理參數前需要先綁定紋理,讓GPU明白需要設定的是哪個紋理,綁定紋理的方法為

GLES20.glBindTexture(int target,int texture)

使用示例如下:

GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture[]);
//設定縮小過濾為使用紋理中坐标最接近的一個像素的顔色作為需要繪制的像素顔色
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_NEAREST);
//設定放大過濾為使用紋理中坐标最接近的若幹個顔色,通過權重平均算法得到需要繪制的像素顔色
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR);
//設定環繞方向S,截取紋理坐标到[1/2n,1-1/2n]。将導緻永遠不會與border融合
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_CLAMP_TO_EDGE);
//設定環繞方向T,截取紋理坐标到[1/2n,1-1/2n]。将導緻永遠不會與border融合
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_CLAMP_TO_EDGE);
           

以上為2D紋理

GLES20.GL_TEXTURE_2D

的綁定和參數設定,如果是相機,我們通常使用的是擴充紋理

GLES11Ext.GL_TEXTURE_EXTERNAL_OES

。詳細用法及使用差別參考Demo中的相機預覽和圖檔處理。

使用紋理

我們在使用紋理時,一般需要用

GLES20.glActiveTexture

指明啟用的紋理單元,說啟用其實也不太合适,

GLES20.glActiveTexture

并不是激活紋理單元,而是選擇目前活躍的紋理單元。預設情況下目前活躍的紋理單元為

GLES20.GL_TEXTURE0

。使用示例如下:

//激活紋理單元1
GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
//綁定2D紋理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,getTextureId());
//将紋理設定給Shader
GLES20.glUniform1i(mHTexture,);
           

Frame Buffer和Render Buffer

Frame Buffer Object(FBO)即為幀緩沖對象,用于離屏渲染緩沖。相對于其它同類技術,如資料拷貝或交換緩沖區等,使用FBO技術會更高效并且更容易實作。而且FBO不受視窗大小限制。FBO可以包含許多顔色緩沖區,可以同時從一個片元着色器寫入。FBO是一個容器,自身不能用于渲染,需要與一些可渲染的緩沖區綁定在一起,像紋理或者渲染緩沖區。

Render Buffer Object(RBO)即為渲染緩沖對象,分為color buffer(顔色)、depth buffer(深度)、stencil buffer(模闆)。

在使用FBO做離屏渲染時,可以隻綁定紋理,也可以隻綁定Render Buffer,也可以都綁定或者綁定多個,視使用場景而定。如隻是對一個圖像做變色處理等,隻綁定紋理即可。如果需要往一個圖像上增加3D的模型和貼紙,則一定還要綁定depth Render Buffer。

同Texture使用一樣,FrameBuffer使用也需要調用

GLES20.glGenFrameBuffers

生成FrameBuffer,然後在需要使用的時候調用

GLES20.glBindFrameBuffer

Frame Buffer隻與Texture綁定

FrameBuffer的建立,Texture的建立及參數設定,代碼如下:

GLES20.glGenFramebuffers(, fFrame, );
GLES20.glGenTextures(, textures, start);
for (int i = ; i < ; i++) {
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[i]);
    GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, ,gl_format, width, height,
        , gl_format, GLES20.GL_UNSIGNED_BYTE, null);
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_NEAREST);
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR);
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_CLAMP_TO_EDGE);
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_CLAMP_TO_EDGE);
}
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,);
           

建立完畢後,在渲染時使用:

//綁定FrameBuffer
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fFrame[]);
//為FrameBuffer挂載Texture[1]來存儲顔色
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
    GLES20.GL_TEXTURE_2D, textures[], );
//綁定FrameBuffer後的繪制會繪制到textures[1]上了
GLES20.glViewport(,,w,h);
mFilter.draw();
//解綁FrameBuffer
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER,);
           

Frame Buffer與Texture、Render Buffer綁定

有時候隻綁定紋理并不能滿足我們的要求,當我們需要深度的時候,還需要綁定Render Buffer。在綁定紋理的基礎上再綁定RenderBuffer其實也就比較簡單了。首先使用前還是先生成RenderBuffer,并為RenderBuffer建立資料存儲的格式和渲染對象的尺寸:

//生成Render Buffer
GLES20.glGenRenderbuffers(,fRender,);
//綁定Render Buffer
GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER,fRender[]);
//設定為深度的Render Buffer,并傳入大小
GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER,GLES20.GL_DEPTH_COMPONENT16,
    width, height);
//為FrameBuffer挂載fRender[0]來存儲深度
GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT,
    GLES20.GL_RENDERBUFFER, fRender[]);
//解綁Render Buffer
GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER,);
           

然後渲染時,增加深度綁定和解綁:

GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBufferId);
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
    GLES20.GL_TEXTURE_2D, textureId, );
//為FrameBuffer挂載fRender[0]來存儲深度
GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT,
            GLES20.GL_RENDERBUFFER, fRender[]);
GLES20.glViewport(,,width,height);
mFilter.draw();
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER,);
//解綁Render Buffer
GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER,);
           

最後的處理

不管是FrameBuffer、RenderBuffer還是Texture不再使用時,都應該删除掉,删除的方法類似:

//删除Render Buffer
GLES20.glDeleteRenderbuffers(, fRender, );
//删除Frame Buffer
GLES20.glDeleteFramebuffers(, fFrame, );
//删除紋理
GLES20.glDeleteTextures(, fTexture, );
           

源碼

所有的代碼全部在一個項目中,托管在Github上——Android OpenGLES 2.0系列部落格的Demo

工程内容是将一張圖檔,使用FBO轉為黑白圖檔并儲存的例子。為了友善,直接使用GLSurfaceView來提供GL環境。

歡迎轉載,轉載請保留文章出處。湖廣午王的部落格[http://blog.csdn.net/junzia/article/details/53861519]