1. 程式人生 > >Android平臺Camera實時濾鏡實現方法探討(六)--建立幀快取物件(FBO)加速實時濾鏡處理

Android平臺Camera實時濾鏡實現方法探討(六)--建立幀快取物件(FBO)加速實時濾鏡處理

上一章探討了如何採用SurfaceTexture+GLSurfaceView顯示YUV資料,減少了片段著色器的工作量和程式碼量,但是採用GLSL採用的外部紋理真正的內容是在實體記憶體中,GPU只負責維護元資料,這樣就增加了GPU取資料的時間,若進行一些運算密集的演算法例如高斯濾波,每次都會到外部紋理取資料,這樣則會造成明顯的卡頓,所以僅採用外部紋理實現實時濾鏡行不通。同時採用JNI格式轉換隨著預覽解析度提高時間也達不到要求,採用Shader轉換則每次加權計算都需要進行格式轉換,也增加了運算量。

好在OpenGL允許使用者建立幀快取物件,可以將資料渲染到紋理。因此我們可以將對應相機預覽影象的外部紋理先渲染到內部紋理中,剩下的濾鏡再繫結到該紋理,對其進行操作,這樣就增加渲染速度,經實驗幀數達到要求。

以下程式碼和思路參考GPUImage中的GPUImageFilterGroup,因為Android平臺採用的是OpenGL ES,與OpenGL略有不同。

一.分配幀緩衝物件:

1)建立幀緩衝物件

有N+1個濾鏡(其中第一個從外部紋理接收的無濾鏡效果),就需要分配N個幀緩衝物件,首先建立大小為N的兩個陣列mFrameBuffers和mFrameBufferTextures,分別用來儲存緩衝區id和紋理id,通過GLES20.glGenFramebuffers(1, mFrameBuffers, i)來建立幀緩衝物件

2)建立紋理

建立輸出紋理,方法基本相同,不同之處在於glTexImage2D最後一個引數為null,不指定資料指標。

3)繫結幀緩衝區

 GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffers[i]),第一個引數是target,指的是你要把FBO與哪種幀緩衝區進行繫結。

4)繫結紋理

GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,GLES20.GL_TEXTURE_2D, mFrameBufferTextures[i], 0)

glFramebufferTexture2D()把一幅紋理影象關聯到一個FBO。第一個引數一定是GL_FRAMEBUFFER_,第二個引數是關聯紋理影象的關聯點。一個幀緩衝區物件可以有多個顏色關聯點(GL_COLOR_ATTACHMENT0, ..., GL_COLOR_ATTACHMENTn),L_DEPTH_ATTACHMENT, 和GL_STENCIL_ATTACHMENT。第三個引數textureTarget在多數情況下是GL_TEXTURE_2D。第四個引數是紋理物件的ID號。最後一個引數是要被關聯的紋理的mipmap等級 如果引數textureId被設定為0,那麼紋理影象將會被從FBO分離。如果紋理物件在依然關聯在FBO上時被刪除,那麼紋理物件將會自動從當前幫的FBO上分離。然而,如果它被關聯到多個FBO上然後被刪除,那麼它將只被從繫結的FBO上分離,而不會被從其他非繫結的FBO上分離。

5)解綁預設幀快取和紋理

GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);

GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

二.依次繪製:

       首先第一個一定是繪製與SurfaceTexture繫結的外部紋理處理後的無濾鏡效果,之後的操作與第一個一樣,都是繪製到紋理。首先與之前相同傳入紋理id,並重新繫結到對應的緩衝區物件GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffers[i]),之後draw對應的紋理id。若不是最後一個濾鏡,需要解綁緩衝區,下一個濾鏡的新的紋理id即上一個濾鏡的緩衝區物件所對應的紋理id,同樣執行上述步驟,直到最後一個濾鏡。

三.關鍵程式碼:

1)建立部分:

            for (int i = 0; i < size - 1; i++) {
                GLES20.glGenFramebuffers(1, mFrameBuffers, i);
                
                GLES20.glGenTextures(1, mFrameBufferTextures, i);
                GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mFrameBufferTextures[i]);
                GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0,
                        GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
                GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                        GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
                GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                        GLES20.GL_TEXTURE_MIN_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.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffers[i]);
                GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
                        GLES20.GL_TEXTURE_2D, mFrameBufferTextures[i], 0);

                GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
                GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
            }

繪製部分:

            for (int i = 0; i < size; i++) {
                GPUImageFilter filter = mMergedFilters.get(i);
                boolean isNotLast = i < size - 1;
                if (isNotLast) {
                    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffers[i]);
                    GLES20.glClearColor(0, 0, 0, 0);
                }

                if (i == 0) {
                    filter.onDraw(previousTexture, cubeBuffer, textureBuffer);
                } else if (i == size - 1) {
                    filter.onDraw(previousTexture, mGLCubeBuffer, (size % 2 == 0) ? mGLTextureFlipBuffer : mGLTextureBuffer);
                } else {
                    filter.onDraw(previousTexture, mGLCubeBuffer, mGLTextureBuffer);
                }

                if (isNotLast) {
                    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
                    previousTexture = mFrameBufferTextures[i];
                }

根據上述思路,我們可以對GPUImage進行修改,首先是取消JNI部分轉換,將SurfaceTexure與GLSurfaceView繫結(GPUImage中雖然使用了SurfaceTexture,但明顯沒有用到它的作用,並且addPreviewCallback也使用錯誤,畢竟2年沒有修改過了)。然後選取濾鏡時,將Camera輸出到緩衝區物件,選取的濾鏡對該紋理進行渲染即可。可以重構GPUImage中的濾鏡基類,將所有濾鏡的基類都歸為Group濾鏡,然後修改Group濾鏡的一些方法。