美文网首页
Android OpenGLES3绘图 - 帧缓冲

Android OpenGLES3绘图 - 帧缓冲

作者: rome753 | 来源:发表于2022-03-27 11:57 被阅读0次

    普通的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);
    }
    

    相关文章

      网友评论

          本文标题:Android OpenGLES3绘图 - 帧缓冲

          本文链接:https://www.haomeiwen.com/subject/xaoejrtx.html