学习OpenGL ES for Android(二十二)— 帧缓冲

我们学过的颜色缓冲,深度缓冲已经模板缓冲结合起来就叫帧缓冲。默认系统会定义一个帧缓冲(在移动端就是EGL创建的),而且我们还可以创建自定义的帧缓冲来替代系统创建的。大概的步骤如下:

  1. 创建一个帧缓冲并绑定;
  2. 正常绘制我们的图像(此时绘制的图像会绘制到自定义的帧缓冲上);
  3. 重新绑定到系统帧缓冲上;
  4. 绘制我们自定义帧缓冲的内容(此时我们可以对帧缓冲的内容进行各种处理以此实现各种效果)。

创建帧缓冲非常简单,使用以下方法

glGenFramebuffers( int n, int[] framebuffers, int offset ):n表示数量(通常是1);framebuffers用于存储创建后的帧缓冲;offset:偏移量;默认情况下我们只需要创建一个自定义的帧缓冲就可以了。

之后我们可以使用glBindFramebuffer( int target, int framebuffer )来绑定到自定义的帧缓冲;target必须是GLES20.GL_FRAMEBUFFER;framebuffer就是我们创建好的帧缓冲。

之后我们需要附加至少一个缓冲(颜色、深度或模板缓冲),至少有一个颜色附件(Attachment)。我们最常用的是纹理附件,我们绘制的结果会存储在一个纹理图像内,可以方便的使用和处理它。创建一个纹理附件和生成一个纹理非常相似,但是它不是使用GLUtils.texImage2D生成纹理,而是使用GLES20.glTexImage2D创建一个用来存储我们需要绘制的结果。我们把创建帧缓冲的过程封装一下,代码如下

public static int[] createFrameBuffer(int width, int height) {
        int[] values = new int[1];
        // 纹理缓冲
        GLES20.glGenTextures(1, values, 0);
        int mOffscreenTexture = values[0];   // expected > 0
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mOffscreenTexture);

        // 创建纹理存储。
        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0,
                GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);

        // 设置参数。
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
                GLES20.GL_NEAREST);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
                GLES20.GL_LINEAR);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
                GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
                GLES20.GL_CLAMP_TO_EDGE);

        // 自定义帧缓冲
        GLES20.glGenFramebuffers(1, values, 0);
        int mFramebuffer = values[0];    // expected > 0
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFramebuffer);

        // 深度缓冲
        GLES20.glGenRenderbuffers(1, values, 0);
        int mDepthBuffer = values[0];    // expected > 0
        GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, mDepthBuffer);

        // 为深度缓冲区分配存储空间。
        GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16,
                width, height);

        // 将深度缓冲区和纹理(颜色缓冲区)附加到帧缓冲区对象。
        GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT,
                GLES20.GL_RENDERBUFFER, mDepthBuffer);
        GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
                GLES20.GL_TEXTURE_2D, mOffscreenTexture, 0);

        // 判断是否创建成功
        int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
        if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) {
            // 未创建成功
            throw new RuntimeException("Framebuffer not complete, status=" + status);
        }
        // 切换到默认缓冲
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

        return new int[]{mOffscreenTexture, mFramebuffer, mDepthBuffer};
    }

代码中的glBindRenderbuffer是创建一个渲染缓冲区,使用glFramebufferRenderbuffer和帧缓冲绑定之后,可以减少内存的浪费。

下面是使用帧缓冲的绘制流程关键代码

    @Override
    public void onDrawFrame(GL10 gl) {
        int[] result = OpenGLUtil.createFrameBuffer(disWidth, disHeight);
        frameBufferTexture = result[0];
        frameBuffer = result[1];
        renderBuffer = result[2];
        // 绑定到我们自定义的帧缓冲
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffer);
        super.onDrawFrame(gl);

        drawFloor();
        drawCube();

        // 重新绑定到系统的帧缓冲
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
        GLES20.glDisable(GLES20.GL_DEPTH_TEST);
        GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
        // 绘制我们自定义帧缓冲的内容
        drawFrameBuffers();

        release();
    }

我们绘制了地板和两个箱子,不做任何处理的情况下显示效果如下,

就像我们把两张图片合成了一张然后再显示。然后我们可以对“合成后的图像”进行各种后期处理了。

反相效果,我们用1减去颜色即可,修改着色器代码

……
main(){
    vec4 tex = texture2D(texture, TextCoord);
    gl_FragColor = vec4(vec3(1.0 - tex.rgb), 1.0);
}

显示效果如下,

灰度:移除场景中除了黑白灰以外所有的颜色,让整个图像灰度化(Grayscale)。正常情况对颜色值平均值即可,也可以用加权来处理。虽然可能看不出差别,但是在一些更复杂的情况下效果会比较好。着色器代码如下,

……
void main() {
    vec4 tex = texture2D(texture, TextCoord);
    float average = (tex.r + tex.g + tex.b) / 3.0;
    // 加权
    //float average = 0.2126 * tex.r + 0.7152 * tex.g + 0.0722 * tex.b;
    gl_FragColor = vec4(average, average, average, 1.0);
}

显示效果

核效果,模糊和边缘检测:这三种效果都是需要一个核(Kernel)的数组,它类似矩阵,需要当前像素点和它周围八个像素点的处理效果,当然还需要这些像素点的坐标位置offsets。在这里我们在java中处理好数据后再传入到着色器中,减小着色器的计算压力。修改后的着色器代码

……
uniform float kernel[9];
uniform vec2 offsets[9];

void main() {
    vec4 sum = vec4(0.0);
    for (int i = 0; i < 9; i++){
        vec4 texc = texture2D(texture, TextCoord + offsets[i]);
        sum += texc * kernel[i];
    }
    gl_FragColor = sum;
}

这些显示效果如下

当然我们还可以只在指定的范围内做处理,在其他范围内不做处理。

关于帧缓冲更详细的内容可以参考https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/05%20Framebuffers/,本章源码https://github.com/jklwan/OpenGLProject/blob/master/sample/src/main/java/com/chends/opengl/renderer/advanced/opengl/FrameBuffersRenderer.java

发布了53 篇原创文章 · 获赞 17 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/jklwan/article/details/103978252
今日推荐