1. 程式人生 > 其它 >最通俗的安卓OpenGL教學07——使用FBO

最通俗的安卓OpenGL教學07——使用FBO

技術標籤:OpenGL ES

當需要對紋理進行多次渲染時,而這些渲染取樣是不需要展示給使用者看的,就可以用一個單獨的緩衝物件(離屏渲染)
來儲存多次渲染取樣的結果,等處理完後再顯示到視窗上。

FBO的建立有以下步驟:

  1. 建立FBO
  2. 繫結FBO
  3. 設定FBO分配記憶體大小
  4. 把紋理繫結到FBO
  5. 檢查FBO繫結是否成功
  6. 解綁FBO

FBO的使用有以下步驟:

  1. 繫結FBO
  2. 獲取需要繪製的圖片紋理,然後繪製渲染
  3. 解綁FBO
  4. 把繫結到FBO的紋理繪製渲染出來

1. FBO的建立

在onSurfaceCreated時建立:

  		//建立 fbo
        int[] fbo_s = new int[1];
        GLES20.
glGenBuffers(1, fbo_s, 0); fbo = fbo_s[0]; //使 fbo 成為 fbo物件 GL_FRAMEBUFFER GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbo); //建立 1個 fbo 紋理 fboTextureID int[] textureIds = new int[1]; GLES20.glGenTextures(1, textureIds, 0);//第三個引數是指從哪兒開始取 fboTextureID =
textureIds[0]; GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, fboTextureID);// 在沒設定點的情況下預設是繫結 0號紋理 //設定紋理的環繞方式 GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT)
; //設定紋理的過濾方式 GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);

2.使用FBO

在onDrawFrame時使用:

 		//繫結 fbo 開始使用 FBO
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbo);

        //繫結 imgTextureId 開始使用紋理
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, imgTextureId);
        //繪製
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
        //解綁 FBO紋理
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

        yFboRender.onDraw(fboTextureID);

3. 完整程式碼

package com.york.media.opengl.demo.fbo;

import android.content.Context;
import android.opengl.GLES20;
import com.york.media.opengl.R;
import com.york.media.opengl.egl.TextureUtils;
import com.york.media.opengl.egl.YGLSurfaceView;
import com.york.media.opengl.egl.YShaderUtil;
import com.york.media.opengl.utils.LogUtil;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

/**
 * author : York
 * date   : 2020/12/20 19:42
 * desc   : FBO 幀緩衝物件
 * <p>
 * 當需要對紋理進行多次渲染時,而這些渲染取樣是不需要展示給使用者看的,就可以用一個單獨的緩衝物件(離屏渲染)
 * 來儲存多次渲染取樣的結果,等處理完後再顯示到視窗上。
 */
public class YUsedFboRender implements YGLSurfaceView.YGLRender {

    private final Context mContext;
    private final FloatBuffer vertexBuffer;
    private final FloatBuffer fragmentBuffer;
    private int program;
    private int vPosition;
    private int fPosition;

    private int fboTextureID;
    private int vbo;
    private int fbo;

    private int imgTextureId;
    private int fboWidth;
    private int fboHeight;

    private final YFboRender yFboRender;
    //頂點座標
    float[] vertexData = {
            -1f, -1f,
            1f, -1f,
            -1f, 1f,
            1f, 1f
    };

    //紋理座標
    float[] fragmentData = {
            0f, 1f,
            1f, 1f,
            0f, 0f,
            1f, 0f
    };


    public YUsedFboRender(Context context, int width, int height) {
        this.mContext = context;
        fboWidth = width;
        fboHeight = height;
        yFboRender = new YFboRender(context);
        //讀取頂點座標
        vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer().put(vertexData);
        vertexBuffer.position(0);

        //讀取紋理座標
        fragmentBuffer = ByteBuffer.allocateDirect(fragmentData.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer().put(fragmentData);
        fragmentBuffer.position(0);

    }

    @Override
    public void onSurfaceCreated() {
        yFboRender.onCreate();

        //載入頂點著色器 shader
        String vertexSource = YShaderUtil.getRawResource(mContext, R.raw.screen_vert);
        //載入片元著色器 shader
        String fragmentSource = YShaderUtil.getRawResource(mContext, R.raw.screen_frag);
        //獲取源程式
        program = YShaderUtil.createProgram(vertexSource, fragmentSource);
        //從渲染程式中得到著頂點色器中的屬性
        vPosition = GLES20.glGetAttribLocation(program, "vPosition");
        //從渲染程式中得到片元著色器中的屬性
        fPosition = GLES20.glGetAttribLocation(program, "fPosition");
        //建立 VBO
        int[] vbo_s = new int[1];
        GLES20.glGenBuffers(1, vbo_s, 0);
        vbo = vbo_s[0];
        //繫結 VBO
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo);

        //分配 VBO需要的快取大小
        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, vertexData.length * 4 + fragmentData.length * 4, null, GLES20.GL_STATIC_DRAW);
        //設定頂點座標資料的值到 VBO
        GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, 0, vertexData.length * 4, vertexBuffer);
        //設定紋理座標資料的值到 VBO
        GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, vertexData.length * 4, fragmentData.length * 4, fragmentBuffer);
        //解綁 VBO,指的是離開對 VBO的配置,進入下一個狀態
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);

        //建立 fbo
        int[] fbo_s = new int[1];
        GLES20.glGenBuffers(1, fbo_s, 0);
        fbo = fbo_s[0];
        //使 fbo 成為 fbo物件  GL_FRAMEBUFFER
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbo);

        //建立 1個 fbo 紋理 fboTextureID
        int[] textureIds = new int[1];
        GLES20.glGenTextures(1, textureIds, 0);//第三個引數是指從哪兒開始取
        fboTextureID = textureIds[0];
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, fboTextureID);// 在沒設定點的情況下預設是繫結 0號紋理

        //設定紋理的環繞方式
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
        //設定紋理的過濾方式
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);

        LogUtil.d("fbo_Width=" + fboWidth + ",fboHeight=" + fboHeight);
        //分配FBO記憶體大小
        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, fboWidth, fboHeight, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
        //把 2D紋理 fboTextureID 繫結到 FBO 物件
        GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, fboTextureID, 0);
        //檢查FBO繫結是否成功
        if (GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER) != GLES20.GL_FRAMEBUFFER_COMPLETE) {
            LogUtil.e("fbo bind error !");
        }

        //繫結紋理到 fbo成功後 退出紋理繫結,進入下一環節
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);

        imgTextureId = TextureUtils.createImageTexture(mContext, R.drawable.nobb);

    }

    @Override
    public void onSurfaceChanged(int width, int height) {
        //設定視窗大小
        GLES20.glViewport(0, 0, width, height);
        fboWidth = width;
        fboHeight = height;
        yFboRender.onChange(fboWidth, fboHeight);
    }

    @Override
    public void onDrawFrame() {
        //清除螢幕,此處用的是紅色
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        GLES20.glClearColor(1f, 0f, 0f, 1f);
        //使用著色器源程式
        GLES20.glUseProgram(program);

        //開始使用 VBO
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo);
        //使能頂點屬性陣列,使之有效
        GLES20.glEnableVertexAttribArray(vPosition);
        //使能之後,為頂點屬性賦值,從VBO裡獲取 繫結頂點座標; 注意:最後一個引數如果是 vertexBuffer,那麼就沒有用到 VBO,那就還是從CPU裡取頂點
        GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 8, 0);
        //使能片元屬性陣列,使之有效
        GLES20.glEnableVertexAttribArray(fPosition);
        //使能之後,為片元屬性賦值,從VBO裡獲取 繫結紋理座標; 注意:最後一個引數為 VBO裡的偏移量
        GLES20.glVertexAttribPointer(fPosition, 2, GLES20.GL_FLOAT, false, 8, vertexData.length * 4);
        //退出 VBO的使用
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);

        //繫結 fbo 開始使用 FBO
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbo);

        //繫結 imgTextureId 開始使用紋理
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, imgTextureId);
        //繪製
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
        //解綁 FBO紋理
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

        yFboRender.onDraw(fboTextureID);

    }
}

package com.york.media.opengl.demo.fbo;

import android.content.Context;
import android.opengl.GLES20;

import com.york.media.opengl.R;
import com.york.media.opengl.egl.YShaderUtil;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

/**
 * author : York
 * date   : 2020/12/20 20:29
 * desc   :
 */
public class YFboRender {

    private final Context context;
    private final FloatBuffer vertexBuffer;
    private final FloatBuffer fragmentBuffer;
    private int vPosition;
    private int fPosition;
    private int program;
    private int vbo;


    private final float[] vertexData = {
            -1f, -1f,
            1f, -1f,
            -1f, 1f,
            1f, 1f
    };
    //因為 fbo的片元座標系與安卓的不一樣,所以看上去是倒的
    private final float[] fragmentData = {
            0f, 0f,
            1f, 0f,
            0f, 1f,
            1f, 1f
    };

    public YFboRender(Context context) {
        this.context = context;

        vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer()
                .put(vertexData);
        vertexBuffer.position(0);

        fragmentBuffer = ByteBuffer.allocateDirect(fragmentData.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer()
                .put(fragmentData);
        fragmentBuffer.position(0);

    }

    public void onCreate() {

        String vertexSource = YShaderUtil.getRawResource(context, R.raw.screen_vert);
        String fragmentSource = YShaderUtil.getRawResource(context, R.raw.screen_frag);

        program = YShaderUtil.createProgram(vertexSource, fragmentSource);

        vPosition = GLES20.glGetAttribLocation(program, "vPosition");
        fPosition = GLES20.glGetAttribLocation(program, "fPosition");

        int[] vbo_s = new int[1];
        GLES20.glGenBuffers(1, vbo_s, 0);
        vbo = vbo_s[0];

        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo);
        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, vertexData.length * 4 + fragmentData.length * 4, null, GLES20.GL_STATIC_DRAW);
        GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, 0, vertexData.length * 4, vertexBuffer);
        GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, vertexData.length * 4, fragmentData.length * 4, fragmentBuffer);
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
    }

    public void onChange(int width, int height) {
        GLES20.glViewport(0, 0, width, height);
    }

    public void onDraw(int textureId) {

        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        GLES20.glClearColor(0f, 1f, 0f, 1f);
        GLES20.glUseProgram(program);
        //使用 vbo
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo);
        GLES20.glEnableVertexAttribArray(vPosition);
        GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 8, 0);
        GLES20.glEnableVertexAttribArray(fPosition);
        GLES20.glVertexAttribPointer(fPosition, 2, GLES20.GL_FLOAT, false, 8, vertexData.length * 4);
        //解綁vbo,退出使用vbo 進入下一環節,繪製紋理
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);

        //繫結紋理
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
        //繪製紋理
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
        //解綁紋理
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);

    }
}

效果依舊槓槓滴: