Camera2 教程 6:FBO實現濾鏡預覽

青橙相機

兩個簡單濾鏡切換

通過上兩個章節(jié)的實現,我們實現了相機預覽,因為是通過opengl實現,因此,可以通過改變不同的著色器程序,
實現不同的預覽效果,也就是不同的濾鏡效果

正常預覽的片元著色器程序:

uniform sampler2D inputImageTexture;
varying vec2 textureCoordinate;

void main()
{
  #從對應紋理位置取顏色值
  gl_FragColor = texture2D(inputImageTexture, textureCoordinate);
}


黑白濾鏡效果片元著色器程序:


uniform sampler2D inputImageTexture;
varying vec2 textureCoordinate;

const highp vec3 W = vec3(0.2125, 0.7154, 0.0721);

void main() {
    //從對應紋理位置取顏色值
    lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
    float luminance = dot(textureColor.rgb, W);
    gl_FragColor = vec4(vec3(luminance), textureColor.w);

}

因此通過動態(tài)切換著色器程序,我們可以實現不同的濾鏡效果

預覽濾鏡切換

預覽濾鏡切換核心代碼:

  • 首先,基于基類JImageOESFilter,只需要替換成黑白片元著色器(gray_oes_fragment_shader)即可
public class JImageOESGrayFilter extends  JImageOESFilter{
    public JImageOESGrayFilter(Context context) {
       super(context);
        vertexShader = Utils.readShaderFromResource(context, R.raw.base_oes_vertex_shader);
        fragmentShader = Utils.readShaderFromResource(context, R.raw.gray_oes_fragment_shader);
    }


}
  • 然后,在點擊事件中,改變Render中filter的對象賦值
  findViewById(R.id.switch_filter).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                glSurfaceView.setFilter(new JImageOESGrayFilter(JCamera2OESActivity.this));
            }
        });


  public void setFilter(JImageOESFilter filte){
        render.setFilter(filte);
    }

    public void setFilter(final JImageOESFilter filter) {
        runOnDraw(new Runnable() {

            @Override
            public void run() {
                final JImageOESFilter oldFilter = mFilterEngine;
                mFilterEngine = filter;
                if (oldFilter != null) {
                    oldFilter.destroy();
                }
                JLog.d("jiadongfeng4","oldfilter:"+oldFilter+" newFilter:"+mFilterEngine);
                mFilterEngine.ifNeedInit();
                GLES20.glUseProgram(mFilterEngine.getProgram());
                mFilterEngine.onOutputSizeChanged(outputWidth, outputHeight);
            }
        });
    }



以上代碼,創(chuàng)建一個隊列任務,當Render的onDrawFrme被調用時,會poll 出并執(zhí)行這個任務,完成Opengel程序對象,也就是濾鏡對象的切換

   @Override
    public void onDrawFrame(GL10 gl) {
        ...
        //執(zhí)行濾鏡切換任務
        runAll(runOnDraw);
     
        mFilterEngine.onDraw(transformMatrix, mOESTextureId,glCubeBuffer,glTextureBuffer);
     
    }

以上兩個簡單濾鏡切換過程只需要,替換對應的濾鏡對象即可;是簡單的濾鏡A-濾鏡B切換的場景

代碼具體實現參考 QCCamera中的JCamera2OESActivity和JImageOESGrayFilter,JImageOESRender

QCCamera

FBO

以上單個濾鏡場景的效果,渲染操作等都是在默認的幀緩沖進行操作的,這個默認的幀緩沖是在我們創(chuàng)建Surface的時候自動創(chuàng)建和配置好的,這個OpenGL ES默認的幀緩沖是由窗口系統(tǒng)提供的,是默認顯示到屏幕上的,也就是GlSurfaceView中的

實際應用場景中,會將幾個濾鏡效果疊加起來,效果疊加后,然后將最終的結果顯示到屏幕上。疊加處理過程,是放到FrameBuffer中進行處理的,處理完成后,才顯示到屏幕上。

這個場景是需要解決的問題是:濾鏡A+B+C+D的場景,也就是濾鏡組的場景 A處理-->B處理-->C處理-->顯示到屏幕上

首先,定義和創(chuàng)建FBO

  • JImageOESFilter濾鏡類
    //FBO對象引用
    private int[] frameBuffers = new int[1];
    //與FBO對象綁定的紋理id
    public int[] frameBufferTextures = new int[1];



    public void onOutputSizeChanged(final int width, final int height) {
        outputWidth = width;
        outputHeight = height;
        //當surface有變化時,重新生成FBO
        if (frameBuffers != null) {
            destroyFramebuffers();
        }
        OpenGlUtils.createFrameBuffer(frameBuffers, frameBufferTextures, width, height);
    }
   
  • OpenGlUtils
    public static void createFrameBuffer(int[] frameBuffer, int[] frameBufferTexture,
                                         int width, int height) {
        //產生FBO ID
        GLES20.glGenFramebuffers(frameBuffer.length, frameBuffer, 0);
        //產生紋理ID
        GLES20.glGenTextures(frameBufferTexture.length, frameBufferTexture, 0);
        for (int i = 0; i < frameBufferTexture.length; i++) {
            //綁定紋理
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, frameBufferTexture[i]);
            //加載圖像數據, 并上傳紋理
            //pixels 可能是一個空指針。在這種情況下,會分配紋理內存以適應寬度width和高度的紋理height
            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, frameBuffer[i]);
            //將texture和level指定的紋理圖像附加為當前綁定的幀緩沖區(qū)對象的邏輯緩沖區(qū)之一
            GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, frameBufferTexture[i], 0);
            //解綁紋理
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
            //解綁FBO
            GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
        }
        checkGlError("createFrameBuffer");
    }

GLES20.glGenFramebuffers(frameBuffer.length, frameBuffer, 0):
第一個是要創(chuàng)建的幀緩存的數目;
第二個是指向存儲一個或者多個ID的變量或數組的指針,它返回未使用的FBO的ID,ID為0表示默認幀緩存,即window系統(tǒng)提供的幀緩存
第三個參數表示framebuffer數組賦值時的偏移量,為0表示,即為frameBuffer分配frameBuffer.length個長度

GLES20.glGenTextures(frameBufferTexture.length, frameBufferTexture, 0):
同上,產生n個紋理id

GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, frameBufferTexture[i]):
綁定frameBufferTexture[i]紋理,后續(xù)所有操作都針對此紋理生效,直到調用 * GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0)*解綁

GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
設置當前對應紋理id的紋理寬高,內容格式等,因為當前還沒有內容,因此最后一個參數為null

glTexParameterf:
設置紋理的采樣參數

GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffer[i]):
一旦一個FBO被創(chuàng)建,在使用它之前必須綁定;一旦FBO被綁定,之后的所有的OpenGL操作都會對當前所綁定的FBO造成影響;
ID號為0表示缺省幀緩存,即默認的window提供的幀緩存。因此,在glBindFramebufferEXT()中將ID號設置為0可以解綁定當前FBO;

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

第一個參數一定是GL_FRAMEBUFFER
第二個參數是關聯(lián)紋理圖像的關聯(lián)點
第三個參數textureTarget在圖像情況下是GL_TEXTURE_2D
第四個參數是紋理對象的ID號
最后一個參數是要被關聯(lián)的紋理的mipmap等級

通過以上操作,將把一幅紋理圖像frameBufferTexture[i]關聯(lián)到一個FBO frameBuffer[i]

GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0):
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0):

解綁操作,表示接下來的opengl操作,對frameBufferTexture[i]和frameBuffer[i]再無影響

注意:跟FBO綁定的紋理ID類型為GL_TEXTURE_2D,不是GL_TEXTURE_EXTERNAL_OES類型,因此經過FBO緩存處理后的數據紋理id:frameBufferTexture,必須由OPENGL當成GL_TEXTURE_2D類型進行處理

使用FBO

JImageOESFilter

    public int onDrawFBO(float[] transformMatrix, int textureId, FloatBuffer cubeBuffer, FloatBuffer textureBuffer) {

        //
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffers[0]);
        
        //根據紋理textureId類型的不同,執(zhí)行不同的邏輯
        onDraw(transformMatrix, textureId, cubeBuffer, textureBuffer);
        glBindFramebuffer(GL_FRAMEBUFFER, 0);

        return frameBufferTextures[0];
    }

GLES20.glBindFramebuffer:
綁定FBO,后續(xù)opengl所有的數據操作都在改FBO中進行

onDraw:
這里使用的紋理id類型為GL_TEXTURE_EXTERNAL_OES的例子,GL_TEXTURE_2D的處理方式大同小異,在
Camera2 教程 5:兩種預覽方式比較章節(jié),我們已經講解了兩者的差異

glBindFramebuffer(GL_FRAMEBUFFER, 0):
解綁FBO,后續(xù)opengl的操作,就跟這個沒有關系了

最后返回frameBufferTextures[0],給下一個流程或者濾鏡處理

用一張圖表示下FBO過程:

FBO.png

注意以上流程中,FBO濾鏡處理后的數據是存儲在FBO中的,是顯示不到屏幕上的;最后一個FBO數據處理后將對應的紋理id放到Opengl中渲染才能顯示到默認屏幕上

代碼工程

完整的FBO例子請參考以下工程的JCamera2OESFboActivity

QCCamera

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容