OpenGL ES for Android(帧缓冲)

1,332 阅读4分钟

简介

我们学过的颜色缓冲,深度缓冲已经模板缓冲结合起来就叫帧缓冲。默认系统会定义一个帧缓冲(在移动端就是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_FRAMEBUFFE 
  R, 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_FRAMEBUFFE 
 R);
          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;
  }

这些显示效果如下


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

如有不足敬请谅解,路过的看官帮忙点赞关注下。