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濾鏡的一些方法。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章