Android OpenGLES3绘图 - 帧缓冲

2022-03-27  本文已影响0人  rome753

普通的OpenGL绘图时是绘制到当前帧上面,由于GL环境跟当前屏幕进行了关联,也就直接绘制到屏幕了。这样有两个问题:1. 如果有的帧计算得快,有的计算得慢,而屏幕刷新率是固定的,就会拖慢整体帧率;2. 在着色器里面只能处理当前位置的点,没办法处理当前点跟其他点的关系。

如果将OpenGL计算后的帧缓存起来,不直接绘制。那么就可以利用双缓冲或多缓冲技术稳定帧率;在着色器里面可以从缓存帧读取所有点,就可以进行一些相对位置处理,比如将当前点跟周围点颜色计算平均值,进行图像模糊。这就是帧缓冲。

给一个普通的绘制添加帧缓冲也很方便,基本不影响原来的绘制。假设原来绘制的是三角形,那么先配置一下帧缓冲,可以配置成2D纹理,然后进行原来的绘制,三角形就会绘制到缓冲帧的纹理上面,最后将这个2D纹理绘制到屏幕上就可以了。

实现方法:

1 原图形Shader

原图形是很多个旋转的3D箱子

image.png

    static class Simple3DShader {

        int program;
        FloatBuffer vertexBuffer;
        int[] vao;
        int[] tex;

        public void init() {

            program = ShaderUtils.loadProgram3D();
            //分配内存空间,每个浮点型占4字节空间
            vertexBuffer = ByteBuffer.allocateDirect(vertices.length * 4)
                    .order(ByteOrder.nativeOrder())
                    .asFloatBuffer();
            //传入指定的坐标数据
            vertexBuffer.put(vertices);
            vertexBuffer.position(0);

            vao = new int[1];
            glGenVertexArrays(1, vao, 0);
            glBindVertexArray(vao[0]);

            int[] vbo = new int[1];
            glGenBuffers(1, vbo, 0);
            glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
            glBufferData(GL_ARRAY_BUFFER, vertices.length * 4, vertexBuffer, GL_STATIC_DRAW);

            tex = new int[2];
            glGenTextures(2, tex, 0);
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D, tex[0]);
            // 为当前绑定的纹理对象设置环绕、过滤方式
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            Bitmap bitmap = ShaderUtils.loadImageAssets("wall.jpg");
            GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
            glGenerateMipmap(GL_TEXTURE_2D);

            glActiveTexture(GL_TEXTURE1);
            glBindTexture(GL_TEXTURE_2D, tex[1]);
            // 为当前绑定的纹理对象设置环绕、过滤方式
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            Bitmap bitmap1 = ShaderUtils.loadImageAssets("face.png");
            GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap1, 0);
            glGenerateMipmap(GL_TEXTURE_2D);

            glUseProgram(program);
            int loc0 = glGetUniformLocation(program, "texture1");
            glUniform1i(loc0, 0);
            int loc1 = glGetUniformLocation(program, "texture2");
            glUniform1i(loc1, 1);

            // Load the vertex data
            glVertexAttribPointer(0, 3, GL_FLOAT, false, 5 * 4, 0);
            glEnableVertexAttribArray(0);

            glVertexAttribPointer(1, 2, GL_FLOAT, false, 5 * 4, 3 * 4);
            glEnableVertexAttribArray(1);


            glBindBuffer(GL_ARRAY_BUFFER, 0);
            glBindVertexArray(0);
        }

        public void draw(float[] modelMat, float[] viewMat, float[] projectionMat, float rot) {
            // Use the program object
            glUseProgram(program);
            glBindTexture(GL_TEXTURE_2D, tex[0]);
            glBindTexture(GL_TEXTURE_2D, tex[1]);

            int loc1 = glGetUniformLocation(program, "view");
            glUniformMatrix4fv(loc1, 1, false, viewMat, 0);
            int loc2 = glGetUniformLocation(program, "projection");
            glUniformMatrix4fv(loc2, 1, false, projectionMat, 0);

            glBindVertexArray(vao[0]);
//Matrix.setLookAtM();
            for (int i = 0; i < cubePositions.length; i++) {
                Matrix.setIdentityM(modelMat, 0);
                Matrix.translateM(modelMat, 0, cubePositions[i][0], cubePositions[i][1], cubePositions[i][2]);
                if (i % 3 == 0) {
                    Matrix.rotateM(modelMat, 0, rot, 0.5f, 1f, 0.2f);
                }
                int loc = glGetUniformLocation(program, "model");
                glUniformMatrix4fv(loc, 1, false, modelMat, 0);

                glDrawArrays ( GL_TRIANGLES, 0, vertices.length );
            }

        }

2 配置帧缓冲

创建帧缓冲对象fbo,创建纹理对象tcbo和渲染缓冲对象rbo,将它们俩跟fbo绑定,检查一下是否成功。

配置时需要纹理的宽高,要传入GLSurfaceView的宽高。这里做了一个延时初始化。

    int[] fbo;
    int[] tcbo;
    int[] rbo;

    private void initFramebuffer(int w, int h) {
        if (fbo != null) {
            return;
        }
        fbo = new int[1];
        glGenFramebuffers(1, fbo, 0);
        glBindFramebuffer(GL_FRAMEBUFFER, fbo[0]);

        // 生成纹理
        tcbo = new int[1];
        glGenTextures(1, tcbo, 0);
        glBindTexture(GL_TEXTURE_2D, tcbo[0]);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, null);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glBindTexture(GL_TEXTURE_2D, 0);

        // 将它附加到当前绑定的帧缓冲对象
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tcbo[0], 0);

        rbo = new int[1];
        glGenRenderbuffers(1, rbo, 0);
        glBindRenderbuffer(GL_RENDERBUFFER, rbo[0]);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, w, h);
        glBindRenderbuffer(GL_RENDERBUFFER, 0);

        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo[0]);

        if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
            Log.e("chao", "创建Framebuffer没有完成!");
        }
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }

3 帧缓冲Shader

创建顶点数组缓冲对象vbo,绘制一个带纹理的正方形,注意纹理需要用上一步创建的纹理对象tcbo

    static class ScreenShader {

        float vertices[] = { // vertex attributes for a quad that fills the entire screen in Normalized Device Coordinates.
                // positions   // texCoords
                -1.0f,  1.0f,  0.0f, 1.0f,
                -1.0f, -1.0f,  0.0f, 0.0f,
                1.0f, -1.0f,  1.0f, 0.0f,

                -1.0f,  1.0f,  0.0f, 1.0f,
                1.0f, -1.0f,  1.0f, 0.0f,
                1.0f,  1.0f,  1.0f, 1.0f
        };

        int program;
        int[] vao;

        public void init() {
            program = ShaderUtils.loadProgramFramebuffer();
            //分配内存空间,每个浮点型占4字节空间
            FloatBuffer vertexBuffer = ByteBuffer.allocateDirect(vertices.length * 4)
                    .order(ByteOrder.nativeOrder())
                    .asFloatBuffer();
            //传入指定的坐标数据
            vertexBuffer.put(vertices);
            vertexBuffer.position(0);

            vao = new int[1];
            glGenVertexArrays(1, vao, 0);
            glBindVertexArray(vao[0]);

            int[] vbo = new int[1];
            glGenBuffers(1, vbo, 0);
            glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
            glBufferData(GL_ARRAY_BUFFER, vertices.length * 4, vertexBuffer, GL_STATIC_DRAW);

            glVertexAttribPointer(0, 2, GL_FLOAT, false, 4 * 4, 0);
            glEnableVertexAttribArray(0);

            glVertexAttribPointer(1, 2, GL_FLOAT, false, 4 * 4, 2 * 4);
            glEnableVertexAttribArray(1);


            glUseProgram(program);
            int loc = glGetUniformLocation(program, "screenTexture");
            glUniform1i(loc, 1);


            glBindBuffer(GL_ARRAY_BUFFER, 0);
            glBindVertexArray(0);

        }

    }

4 最终绘制

先绑定帧缓冲对象,开启深度测试,用原图形Shader绘制3D箱子图案;然后解绑帧缓冲对象fbo,关闭深度测试(绑定当前屏幕),用帧缓冲Shader绘制fbo中的纹理。绘制结果跟原图形完全一致。

    @Override
    public void onDrawFrame(GL10 gl) {
        ...
//        // 第一阶段处理(Pass),绘制3D图形到Framebuffer
        glBindFramebuffer(GL_FRAMEBUFFER, fbo[0]);
        glClearColor(0.5f, 0.5f, 0.5f, 0.5f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glEnable(GL_DEPTH_TEST);
        simple3DShader.draw(modelMat, viewMat, projectionMat, rot);

        // 第二阶段处理,把Framebuffer绘制为2D纹理
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        screenShader.draw(tcbo);

    }

        // screenShader.draw
        public void draw(int[] tcbo) {
            glUseProgram(program);
            glBindVertexArray(vao[0]);
            glDisable(GL_DEPTH_TEST);
            glBindTexture(GL_TEXTURE_2D, tcbo[0]);
            glDrawArrays(GL_TRIANGLES, 0, 6);
        }

做帧缓冲的过程可能出错,但是没关系,绘制原图形与绘制帧缓冲矩形纹理是非常独立的。可以将这两个绘制分开调试,都没问题了再结合起来。

5 后期处理

有了帧缓冲后,可以在帧缓冲的片段着色器上做一些简单的图像处理:反相、灰度;核效果:模糊、锐化、边缘检测等。

核效果就是将当前点周围包括自己的9个点数值进行简单的卷积运算,结果赋给当前点,使图像呈现某种视觉效果。

模糊.png 锐化.png 边缘检测.png
#version 300 es
out vec4 FragColor;

in vec2 TexCoords;

uniform sampler2D screenTexture;

//void main()
//{
//     FragColor = texture(screenTexture, TexCoords);
//
//     // 反相效果
//     FragColor = vec4(1.0 - texture(screenTexture, TexCoords).rgb, 1.0);
//
//     // 灰度效果
//     float ave = (FragColor.r + FragColor.g + FragColor.b) / 3.0;
//     FragColor = vec4(ave, ave, ave, 1.0);
//}

const float offset = 1.0 / 300.0;

void main()
{
     vec2 offsets[9] = vec2[](
     vec2(-offset,  offset), // 左上
     vec2( 0.0f,    offset), // 正上
     vec2( offset,  offset), // 右上
     vec2(-offset,  0.0f),   // 左
     vec2( 0.0f,    0.0f),   // 中
     vec2( offset,  0.0f),   // 右
     vec2(-offset, -offset), // 左下
     vec2( 0.0f,   -offset), // 正下
     vec2( offset, -offset)  // 右下
     );

     // 锐化核
     float kernel[9] = float[](
     -1.0, -1.0, -1.0,
     -1.0,  9.0, -1.0,
     -1.0, -1.0, -1.0
     );

//     // 边缘检测核
//     float kernel[9] = float[](
//     1.0, 1.0, 1.0,
//     1.0,-8.0, 1.0,
//     1.0, 1.0, 1.0
//     );

//     // 模糊核
//     float kernel[9] = float[](
//     1.0 / 16.0, 2.0 / 16.0, 1.0 / 16.0,
//     2.0 / 16.0, 4.0 / 16.0, 2.0 / 16.0,
//     1.0 / 16.0, 2.0 / 16.0, 1.0 / 16.0
//     );

     vec3 sampleTex[9];
     for(int i = 0; i < 9; i++)
     {
          sampleTex[i] = vec3(texture(screenTexture, TexCoords.st + offsets[i]));
     }
     vec3 col = vec3(0.0);
     for(int i = 0; i < 9; i++)
     col += sampleTex[i] * kernel[i];

     FragColor = vec4(col, 1.0);
}
上一篇下一篇

猜你喜欢

热点阅读