OpenGL ES -- FBO

FBO

FBO(Frame Buffer Object)即帧缓冲对象。FBO有什么作用呢?通常使用OpenGL ES经过顶点着色器、片元着色器处理之后就通过使用OpenGL ES使用的窗口系统提供的帧缓冲区,这样绘制的结果是显示到窗口(屏幕)上。

但是对于有些复杂的渲染处理,通过多个滤镜处理,这时中间流程的渲染采样的结果就不应该直接输出显示屏幕,而应该等所有处理完成之后再显示到窗口上。这个时候FBO就派上用场了。

FBO是一个容器,自身不能用于渲染,需要与一些可渲染的缓冲区绑定在一起,像纹理或者渲染缓冲区。,它仅且提供了 3 个附着(Attachment),分别是颜色附着、深度附着和模板附着。
纹理对象可以连接到帧缓冲区对象的颜色附着点,同时也可以连接到FBO的深度附着点。另外一种可以连接到深度附着点和模板附着点的一类对象叫做渲染缓冲区对象(RBO)。

RBO (Render Buffer Object)即为渲染缓冲对象,是一个由应用程序分配的 2D 图像缓冲区。可以用于分配和存储color buffer(颜色)、depth buffer(深度)、stencil buffer(模板),可以用作 FBO 中的颜色、深度或者模板附着。

在使用FBO做离屏渲染时,可以只绑定纹理,也可以只绑定Render Buffer,也可以都绑定或者绑定多个,视使用场景而定。如只是对一个图像做变色处理等,只绑定纹理即可。如果需要往一个图像上增加3D的模型和贴纸,则一定还要绑定depth Render Buffer。

        //将纹理附着到帧缓冲中
        // GL_COLOR_ATTACHMENT0、GL_DEPTH_ATTACHMENT、GL_STENCIL_ATTACHMENT分别对应颜色缓冲、深度缓冲和模板缓冲。
        GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D,
                frameTextures[0],
                0);
image.png

FBO纹理坐标系

image.png

FBO纹理坐标系与纹理坐标系不一样,需要特别注意。

FBO工作流程

截屏2021-05-16 下午2.44.03.png

当把一个纹理附着到FBO上后,所有的渲染操作就会写入到该纹理上,所有的渲染操作会被存储到纹理图像上。主要流程包括创建FBO, 创建FBO纹理,绑定FBO,绘图,解绑FBO。这样渲染采样的结果就保存到了创建的FBO纹理中。下一步进一步处理时,就从这个创建的FBO纹理中取出纹理继续进行处理。

FBO使用示例代码

public class RectTextureFboRenderer implements GLSurfaceView.Renderer{
    private static final String TAG = "TriangleTextureRenderer";

    // -------------- 顶点和纹理坐标数据

    static float rectCoords[] ={

            -1.0f, -1.0f,
            1.0f, -1.0f,
            -1.0f, 1.0f,
            1.0f, 1.0f

    };

    //纹理坐标2
    private float textureVertex[] = {

            0.0f, 0.0f,
            1.0f, 0.0f,
            0.0f, 1.0f,
            1.0f, 1.0f,

    };

    // ------------------- 原始纹理绘图 ------------------
    private FloatBuffer vertexBuffer;
    private FloatBuffer textureBuffer;
    //渲染程序
    private int mProgram;

    //纹理id
    private int textureId;
    int mWidth,mHeight;

    // ------------------------ FBO --------------------
    protected int[] frameBuffer;
    protected int[] frameTextures;
    // 控制是否使用fbo
    boolean isUseFbo = false;
    // 使用fbo的前提下,是否绘制到屏幕上
    boolean isDrawToScreen = true;
    //fbo是否已创建
    boolean isFboCreated = false;


    // ---------------------- filter -------------------
    protected FloatBuffer vertexFilterBuffer;
    protected FloatBuffer textureFilterBuffer;
    private int mProgramFilter; //滤镜program

    public RectTextureFboRenderer() {
        // 1---------- 原始纹理相关初始化
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(rectCoords.length*4);
        byteBuffer.order(ByteOrder.nativeOrder());
        vertexBuffer = byteBuffer.asFloatBuffer();
        //把这门语法() 推送给GPU
        vertexBuffer.put(rectCoords);
        vertexBuffer.position(0);

        textureBuffer = ByteBuffer.allocateDirect(textureVertex.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        //传入指定的数据
        textureBuffer.put(textureVertex);
        textureBuffer.position(0);

        // 2----------- filter滤镜相关初始化
        ByteBuffer byteBuffer2 = ByteBuffer.allocateDirect(rectCoords.length*4);
        byteBuffer2.order(ByteOrder.nativeOrder());
        vertexFilterBuffer = byteBuffer.asFloatBuffer();
        //把这门语法() 推送给GPU
        vertexFilterBuffer.put(rectCoords);
        vertexFilterBuffer.position(0);

        textureFilterBuffer = ByteBuffer.allocateDirect(textureVertex.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        //传入指定的数据
        textureFilterBuffer.put(textureVertex);
        textureFilterBuffer.position(0);
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        GLES30.glClearColor(0.5f,0.5f,0.5f,1.0f);
        // 1--- 原始纹理相关初始化
        //编译顶点着色程序
        String vertexShaderStr = ResReadUtils.readResource(R.raw.vertex_base_shader);
        int vertexShaderId = ShaderUtils.compileVertexShader(vertexShaderStr);
        //编译片段着色程序
        String fragmentShaderStr = ResReadUtils.readResource(R.raw.fragment_base_shader);
        int fragmentShaderId = ShaderUtils.compileFragmentShader(fragmentShaderStr);
        //连接程序
        mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);
        //加载纹理
        textureId = TextureUtils.loadTexture(AppCore.getInstance().getContext(),R.drawable.world_map);


        // 2--滤镜Filter相关初始化
        String vertexFilterShaderStr = ResReadUtils.readResource(R.raw.vertex_base_shader);
        int vertexFilterShaderId = ShaderUtils.compileVertexShader(vertexFilterShaderStr);
        //编译片段着色程序
        String fragmentShaderFilterStr = ResReadUtils.readResource(R.raw.fragment_edge_shader);
        int fragmentShaderFilterId = ShaderUtils.compileFragmentShader(fragmentShaderFilterStr);
        //连接程序
        mProgramFilter = ShaderUtils.linkProgram(vertexFilterShaderId, fragmentShaderFilterId);
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        mWidth = width;
        mHeight = height;
        float ratio = (float) width/height;
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        if(isUseFbo){
            //创建FBO
            if(!isFboCreated){
                isFboCreated = true;
                createFBO(mWidth,mHeight);
            }
            GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER,frameBuffer[0]);
            GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, frameTextures[0]);
        }
        // 1----------------------- 绘制原始纹理
        GLES30.glUseProgram(mProgram);
        int aPositionLocation = GLES30.glGetAttribLocation(mProgram,"vPosition");
        GLES30.glEnableVertexAttribArray(aPositionLocation);
        //x y z 所以数据size 是3
        GLES30.glVertexAttribPointer(aPositionLocation,2,GLES30.GL_FLOAT,false,0,vertexBuffer);

        int aTextureLocation = GLES20.glGetAttribLocation(mProgram,"vTextureCoord");
        Log.e(TAG, "onDrawFrame: textureLocation="+aTextureLocation);
        //纹理坐标数据 x、y,所以数据size是 2
        GLES30.glVertexAttribPointer(aTextureLocation, 2, GLES30.GL_FLOAT, false, 0, textureBuffer);
        int vTextureLoc = GLES20.glGetUniformLocation(mProgram, "vTexture");
        //启用顶点颜色句柄
        GLES30.glEnableVertexAttribArray(aTextureLocation);
        GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
        //绑定纹理
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D,textureId);
        // GL_TEXTURE1 , 1
        GLES30.glUniform1i(vTextureLoc,0);
        GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP,0, rectCoords.length/2);
        //禁止顶点数组的句柄
        GLES30.glDisableVertexAttribArray(aPositionLocation);
        GLES30.glDisableVertexAttribArray(aTextureLocation);
        //解绑纹理
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
        // 关闭FBO
        if(isUseFbo){
            //解绑FBO
            GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);
            GLES30.glDeleteFramebuffers(1,frameBuffer,0);
        }

        //
        // 2------------------------ 绘制filter滤镜
        if(isUseFbo && isDrawToScreen){
            GLES30.glUseProgram(mProgramFilter);
            int vPositionCoordLoc = GLES30.glGetAttribLocation(mProgramFilter,"vPosition");
            int vTextureCoordLoc = GLES20.glGetAttribLocation(mProgramFilter,"vTextureCoord");
            int vTextureFilterLoc = GLES20.glGetUniformLocation(mProgramFilter, "vTexture");
            //x y 所以数据size 是2
            GLES30.glVertexAttribPointer(vPositionCoordLoc,2,GLES30.GL_FLOAT,false,0,vertexFilterBuffer);
            GLES30.glEnableVertexAttribArray(vPositionCoordLoc);

            //纹理坐标是xy 所以数据size是 2
            GLES30.glVertexAttribPointer(vTextureCoordLoc, 2, GLES30.GL_FLOAT, false, 0, textureFilterBuffer);
            //启用顶点颜色句柄
            GLES30.glEnableVertexAttribArray(vTextureCoordLoc);

            GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
            //绑定纹理
            GLES30.glBindTexture(GLES30.GL_TEXTURE_2D,frameTextures[0]);
            GLES30.glUniform1i(vTextureFilterLoc,0);

            GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP,0, rectCoords.length/2);

            //解绑纹理
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
            //禁止顶点数组的句柄
            GLES30.glDisableVertexAttribArray(vPositionCoordLoc);
            GLES30.glDisableVertexAttribArray(vTextureFilterLoc);
        }

    }



    public void createFBO(int width, int height) {
        if (frameTextures != null) {
            return;
        }
        // 創建FBO
        frameBuffer = new int[1];
        frameTextures = new int[1];
        GLES30.glGenFramebuffers(1, frameBuffer, 0);
        // 创建FBO纹理
        TextureUtils.glGenTextures(frameTextures);

        /**
         * 2、fbo与纹理关联
         */
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, frameTextures[0]);
        // 设置FBO分配内存大小
        GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, width, height, 0, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE,
                null);
        //纹理关联 fbo
        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, frameBuffer[0]);  //綁定FBO
        //将纹理附着到帧缓冲中
        // GL_COLOR_ATTACHMENT0、GL_DEPTH_ATTACHMENT、GL_STENCIL_ATTACHMENT分别对应颜色缓冲、深度缓冲和模板缓冲。
        GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D,
                frameTextures[0],
                0);
        // 检测fbo绑定是否成功
        if(GLES30.glCheckFramebufferStatus(GLES30.GL_FRAMEBUFFER) != GLES30.GL_FRAMEBUFFER_COMPLETE){
            throw new RuntimeException("FBO附着异常");
        }

        //7. 解绑纹理和FBO
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);

    }


}

着色器

vertex_base_shader.glsl

attribute vec4 vPosition; // 顶点坐标

attribute vec2 vTextureCoord;  //纹理坐标

varying vec2 aCoord;

void main(){
    gl_Position = vPosition;
    aCoord = vTextureCoord;
}

fragment_base_shader.glsl

precision mediump float;// 数据精度
varying vec2 aCoord;
uniform sampler2D  vTexture;
void main(){
    vec4 rgba = texture2D(vTexture, aCoord);//rgba
    gl_FragColor = rgba;
}

fragment_edge_shader.glsl

precision mediump float;// 数据精度
varying vec2 aCoord;

uniform sampler2D  vTexture;
void main(){
    vec4 rgba = texture2D(vTexture, aCoord);//rgba
        float c = (rgba.r*0.3+ rgba.g*0.59+rgba.b*0.11) /3.0;
        gl_FragColor = vec4(c, c, c, 1.0);
}

执行结果

1 isUseFbo = false,不使用fbo,绘制原始纹理。


image.png

2 isUseFbo = true; isDrawToScreen = false。使用fbo,但是不绘制显示到窗口。


image.png

3 2 isUseFbo = true; isDrawToScreen = true。使用fbo,经过灰度处理后,绘制显示到窗口。


image.png

参考:
https://blog.csdn.net/cauchyweierstrass/article/details/53166940
https://www.jianshu.com/p/78a64b8fb315
https://wuwang.blog.csdn.net/article/details/53861519
https://zhuanlan.zhihu.com/p/115218923

你可能感兴趣的:(OpenGL ES -- FBO)