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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容