Android OpenGLES2.0开发(十):FBO离屏渲染

打印 上一主题 下一主题

主题 856|帖子 856|积分 2568

人生是一场单程的旅行,即使有些遗憾我们也没有从头再来的机会,与其纠结无法改变的已往不如微笑着爱惜未来。
  

  • Android OpenGLES开发:EGL环境搭建
  • Android OpenGLES2.0开发(一):艰难的开始
  • Android OpenGLES2.0开发(二):环境搭建
  • Android OpenGLES2.0开发(三):绘制一个三角形
  • Android OpenGLES2.0开发(四):矩阵变更和相机投影
  • Android OpenGLES2.0开发(五):绘制正方形和圆形
  • Android OpenGLES2.0开发(六):着色器语言GLSL
  • Android OpenGLES2.0开发(七):纹理贴图之显示图片
  • Android OpenGLES2.0开发(八):Camera预览
  • Android OpenGLES2.0开发(九):图片滤镜
  • Android OpenGLES2.0开发(十):FBO离屏渲染
弁言

之前的章节我们利用OpenGL ES绘制图形然后显示到屏幕上,貌似看着并没有什么题目。
假如我们现在不想显示到屏幕中,但还想利用OpenGL ES对图像的强大处理能力那又该怎样是好?抑或我们上一节学习到的图片滤镜,真实的情况需要多个滤镜叠加利用,把全部的滤镜都放到一个着色器中好像不是一种良好的编程模式。
上面提到题目,接下来我们会给出答案。
什么是FBO

FBO(Frame Buffer Object)即帧缓冲区对象,实际上是一个可添加缓冲区的容器,可以为其添加纹理或渲染缓冲区对象(RBO)。
FBO可以让我们的渲染不渲染到屏幕上,而是渲染到离屏Buffer中。可以明白为将OpenGL ES绘制的图像内容输出到一块内存中,而我们可以对这块内存进行多次绘制。固然这块内存中的数据也可生存到当地SD卡中,也可重新绘制到屏幕上用于显示。
   

  • FBO是一个容器,自身不能用于渲染,需要与一些可渲染的缓冲区绑定在一起,像纹理或者渲染缓冲区。
  • 可以给他添加添加纹理(Texture )或渲染缓冲区对象(RBO)
  • 它仅且提供了 3 个附着(Attachment):颜色附着、深度附着、模板附着。

  FBO优点

  • 灵活性和控制性‌:FBO允许开发者将渲染结果输出到自定义的帧缓冲区,而不是传统的窗口系统提供的帧缓冲区。这使得开发者可以更灵活地控制渲染过程和结果,适用于需要特殊渲染效果的场景‌
  • 性能优化‌:通过利用FBO,开发者可以制止不必要的渲染操作,例如在不需要渲染到屏幕的情况下,可以直接将渲染结果存储在内存中,从而提高渲染服从‌
  • 独立于窗口系统‌:FBO完全受OpenGL控制,不依赖于窗口系统提供的帧缓存。这意味着它可以在没有窗口系统的情况下利用,适用于无窗口环境的渲染需求‌
  • 多目标应用‌:FBO可以用于多种应用场景,包罗但不限于实时渲染、图像处理、视频游戏开发等。其灵活性和高性能使其在需要高质量图形输出的应用中具有显著上风‌
FBO怎么用

我们对OpenGL ES的绘制流程很熟悉了,怎样将渲染结果输出到FBO中实在很简单。

  • 创建FBO缓冲区,在这期间需要绑定纹理和深度缓冲区(可选)
  • 渲染到FBO,与之前的渲染没有本质的区别,只是在draw前绑定FBO缓冲区,绘制结束解绑FBO即可
  • 开释FBO,将FBO缓冲区开释
1. 创建FBO

创建FBO基本步骤是:


  • 创建2D纹理(颜色缓冲区)
  • 创建帧缓冲区
  • 创建深度缓冲区
  • 将纹理和深度缓冲区附着到帧缓冲区上
  1. /**
  2. * 创建帧缓冲区(FBO)
  3. *
  4. * @param width
  5. * @param height
  6. */
  7. public void createFrameBuffers(int width, int height) {
  8.     if (mFrameBuffer > 0) {
  9.         destroyFrameBuffers();
  10.     }
  11.     // 1.创建一个纹理对象并绑定它,这将是颜色缓冲区。
  12.     int[] values = new int[1];
  13.     GLES20.glGenTextures(1, values, 0);
  14.     GLESUtils.checkGlError("glGenTextures");
  15.     mOffscreenTexture = values[0];
  16.     GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mOffscreenTexture);
  17.     GLESUtils.checkGlError("glBindTexture " + mOffscreenTexture);
  18.     // 2.创建纹理存储对象
  19.     GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height,
  20.             0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
  21.     // 3.设置参数。我们可能正在使用二维的非幂函数,所以某些值可能无法使用。
  22.     // 设置缩小过滤为使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色
  23.     GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
  24.     //设置放大过滤为使用纹理中坐标最接近的若干个颜色,通过加权平均算法得到需要绘制的像素颜色
  25.     GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
  26.     //设置环绕方向S,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合
  27.     GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
  28.     //设置环绕方向T,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合
  29.     GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
  30.     GLESUtils.checkGlError("glTexParameter");
  31.     // 4.创建帧缓冲区对象并将其绑定
  32.     GLES20.glGenFramebuffers(1, values, 0);
  33.     GLESUtils.checkGlError("glGenFramebuffers");
  34.     mFrameBuffer = values[0];    // expected > 0
  35.     GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffer);
  36.     GLESUtils.checkGlError("glBindFramebuffer " + mFrameBuffer);
  37.     // 5.创建深度缓冲区并绑定它
  38.     GLES20.glGenRenderbuffers(1, values, 0);
  39.     GLESUtils.checkGlError("glGenRenderbuffers");
  40.     mDepthBuffer = values[0];    // expected > 0
  41.     GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, mDepthBuffer);
  42.     GLESUtils.checkGlError("glBindRenderbuffer " + mDepthBuffer);
  43.     // 为深度缓冲区分配存储空间。
  44.     GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16, width, height);
  45.     GLESUtils.checkGlError("glRenderbufferStorage");
  46.     // 6.将深度缓冲区和纹理(颜色缓冲区)附着到帧缓冲区对象
  47.     GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT,
  48.             GLES20.GL_RENDERBUFFER, mDepthBuffer);
  49.     GLESUtils.checkGlError("glFramebufferRenderbuffer");
  50.     GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
  51.             GLES20.GL_TEXTURE_2D, mOffscreenTexture, 0);
  52.     GLESUtils.checkGlError("glFramebufferTexture2D");
  53.     // 检查是否一切正常
  54.     int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
  55.     if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) {
  56.         throw new RuntimeException("Framebuffer not complete, status=" + status);
  57.     }
  58.     // 解绑纹理
  59.     GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
  60.     // 解绑Frame Buffer
  61.     GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
  62.     // 解绑Render Buffer
  63.     GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, 0);
  64.     GLESUtils.checkGlError("prepareFramebuffer done");
  65. }
复制代码
2. 渲染到FBO

渲染实在很简单,onDraw照旧执行本来的绘制流程,只是在方法前和后添加了FBO的逻辑
  1. public int draw(int textureId, float[] matrix) {
  2.     GLES20.glViewport(0, 0, mWidth, mHeight);
  3.     if (bindFBO) {
  4.         // 绑定FBO
  5.         GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffer);
  6.     }
  7.     onDraw(textureId, matrix);
  8.     if (bindFBO) {
  9.         // 解绑FBO
  10.         GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
  11.         //返回fbo的纹理id
  12.         return mOffscreenTexture;
  13.     } else {
  14.         return textureId;
  15.     }
  16. }
复制代码
3. 开释FBO

  1. /**
  2. * 销毁帧缓冲区(FBO)
  3. */
  4. public void destroyFrameBuffers() {
  5.     // 删除fbo的纹理
  6.     if (mOffscreenTexture > 0) {
  7.         GLES20.glDeleteTextures(1, new int[]{mOffscreenTexture}, 0);
  8.         mOffscreenTexture = -1;
  9.     }
  10.     if (mFrameBuffer > 0) {
  11.         GLES20.glDeleteFramebuffers(1, new int[]{mFrameBuffer}, 0);
  12.         mFrameBuffer = -1;
  13.     }
  14.     if (mDepthBuffer > 0) {
  15.         GLES20.glDeleteRenderbuffers(1, new int[]{mDepthBuffer}, 0);
  16.         mDepthBuffer = -1;
  17.     }
  18. }
复制代码
完备代码

在上一篇中我们实现了多个图片滤镜的效果,为了使得他们都支持FBO功能,我们建立一个抽象类将FBO的功能放到抽象类中,完备的代码如下:
先定义个接口如下:
  1. public interface AFilter {
  2.     void setTextureSize(int width, int height);
  3.     void setFrameBuffer(int frameBuffer);
  4.     int getFrameBuffer();
  5.     int getOffscreenTexture();
  6.     void setBindFBO(boolean bindFBO);
  7.     void bindFBO();
  8.     void unBindFBO();
  9.     void surfaceCreated();
  10.     void surfaceChanged(int width, int height);
  11.     int draw(int textureId, float[] matrix);
  12.     void onDraw(int textureId, float[] matrix);
  13.     void release();
  14. }
复制代码
抽象类中实现FBO功能:
  1. /**
  2. * 滤镜效果抽象类
  3. */
  4. public abstract class BaseFilter implements AFilter {
  5.     /**
  6.      * 离屏渲染纹理id
  7.      */
  8.     private int mOffscreenTexture = -1;
  9.     /**
  10.      * 帧缓冲区
  11.      */
  12.     private int mFrameBuffer = -1;
  13.     /**
  14.      * 深度缓冲区
  15.      */
  16.     private int mDepthBuffer = -1;
  17.     /**
  18.      * 是否使用离屏渲染
  19.      */
  20.     protected boolean bindFBO;
  21.     private int mWidth;
  22.     private int mHeight;
  23.     @Override
  24.     public void setBindFBO(boolean bindFBO) {
  25.         this.bindFBO = bindFBO;
  26.     }
  27.     @Override
  28.     public void setFrameBuffer(int frameBuffer) {
  29.         mFrameBuffer = frameBuffer;
  30.     }
  31.     @Override
  32.     public int getFrameBuffer() {
  33.         return mFrameBuffer;
  34.     }
  35.     @Override
  36.     public int getOffscreenTexture() {
  37.         return mOffscreenTexture;
  38.     }
  39.     @Override
  40.     public void bindFBO() {
  41.         if (mFrameBuffer > 0) {
  42.             GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffer);
  43.         }
  44.     }
  45.     @Override
  46.     public void unBindFBO() {
  47.         GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
  48.     }
  49.     /**
  50.      * 创建帧缓冲区(FBO)
  51.      *
  52.      * @param width
  53.      * @param height
  54.      */
  55.     public void createFrameBuffers(int width, int height) {
  56.         if (mFrameBuffer > 0) {
  57.             destroyFrameBuffers();
  58.         }
  59.         // 1.创建一个纹理对象并绑定它,这将是颜色缓冲区。
  60.         int[] values = new int[1];
  61.         GLES20.glGenTextures(1, values, 0);
  62.         GLESUtils.checkGlError("glGenTextures");
  63.         mOffscreenTexture = values[0];
  64.         GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mOffscreenTexture);
  65.         GLESUtils.checkGlError("glBindTexture " + mOffscreenTexture);
  66.         // 2.创建纹理存储对象
  67.         GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height,
  68.                 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
  69.         // 3.设置参数。我们可能正在使用二维的非幂函数,所以某些值可能无法使用。
  70.         // 设置缩小过滤为使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色
  71.         GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
  72.         //设置放大过滤为使用纹理中坐标最接近的若干个颜色,通过加权平均算法得到需要绘制的像素颜色
  73.         GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
  74.         //设置环绕方向S,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合
  75.         GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
  76.         //设置环绕方向T,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合
  77.         GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
  78.         GLESUtils.checkGlError("glTexParameter");
  79.         // 4.创建帧缓冲区对象并将其绑定
  80.         GLES20.glGenFramebuffers(1, values, 0);
  81.         GLESUtils.checkGlError("glGenFramebuffers");
  82.         mFrameBuffer = values[0];    // expected > 0
  83.         GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffer);
  84.         GLESUtils.checkGlError("glBindFramebuffer " + mFrameBuffer);
  85.         // 5.创建深度缓冲区并绑定它
  86.         GLES20.glGenRenderbuffers(1, values, 0);
  87.         GLESUtils.checkGlError("glGenRenderbuffers");
  88.         mDepthBuffer = values[0];    // expected > 0
  89.         GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, mDepthBuffer);
  90.         GLESUtils.checkGlError("glBindRenderbuffer " + mDepthBuffer);
  91.         // 为深度缓冲区分配存储空间。
  92.         GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16, width, height);
  93.         GLESUtils.checkGlError("glRenderbufferStorage");
  94.         // 6.将深度缓冲区和纹理(颜色缓冲区)附着到帧缓冲区对象
  95.         GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT,
  96.                 GLES20.GL_RENDERBUFFER, mDepthBuffer);
  97.         GLESUtils.checkGlError("glFramebufferRenderbuffer");
  98.         GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
  99.                 GLES20.GL_TEXTURE_2D, mOffscreenTexture, 0);
  100.         GLESUtils.checkGlError("glFramebufferTexture2D");
  101.         // 检查是否一切正常
  102.         int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
  103.         if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) {
  104.             throw new RuntimeException("Framebuffer not complete, status=" + status);
  105.         }
  106.         // 解绑纹理
  107.         GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
  108.         // 解绑Frame Buffer
  109.         GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
  110.         // 解绑Render Buffer
  111.         GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, 0);
  112.         GLESUtils.checkGlError("prepareFramebuffer done");
  113.     }
  114.     /**
  115.      * 销毁帧缓冲区(FBO)
  116.      */
  117.     public void destroyFrameBuffers() {
  118.         // 删除fbo的纹理
  119.         if (mOffscreenTexture > 0) {
  120.             GLES20.glDeleteTextures(1, new int[]{mOffscreenTexture}, 0);
  121.             mOffscreenTexture = -1;
  122.         }
  123.         if (mFrameBuffer > 0) {
  124.             GLES20.glDeleteFramebuffers(1, new int[]{mFrameBuffer}, 0);
  125.             mFrameBuffer = -1;
  126.         }
  127.         if (mDepthBuffer > 0) {
  128.             GLES20.glDeleteRenderbuffers(1, new int[]{mDepthBuffer}, 0);
  129.             mDepthBuffer = -1;
  130.         }
  131.     }
  132.     @Override
  133.     public void surfaceChanged(int width, int height) {
  134.         mWidth = width;
  135.         mHeight = height;
  136.         if (bindFBO) {
  137.             createFrameBuffers(width, height);
  138.         }
  139.     }
  140.     public int draw(int textureId, float[] matrix) {
  141.         GLES20.glViewport(0, 0, mWidth, mHeight);
  142.         if (bindFBO) {
  143.             // 绑定FBO
  144.             bindFBO();
  145.         }
  146.         onDraw(textureId, matrix);
  147.         if (bindFBO) {
  148.             // 解绑FBO
  149.             unBindFBO();
  150.             //返回fbo的纹理id
  151.             return mOffscreenTexture;
  152.         } else {
  153.             return textureId;
  154.         }
  155.     }
  156.     @Override
  157.     public void release() {
  158.         destroyFrameBuffers();
  159.     }
  160. }
复制代码
FBO示例

1. 灰度图渲染并将结果生存到SD卡

我们想从显存中获取图像数据,OpenGL ES为我们提供了一个接口GLES20.glReadPixels,在图像渲染完后,我们可以通过该API获取图像数据到内存中
  1. public static native void glReadPixels(
  2.     int x,
  3.     int y,
  4.     int width,
  5.     int height,
  6.     int format,
  7.     int type,
  8.     java.nio.Buffer pixels
  9. );
复制代码
这个接口可以获取RGBA原始图像数据,我们可以将他转换为Bitmap,然后Bitmap转为byte[]生存到当地
  1. public static byte[] Bitmap2Bytes(Bitmap bm, int compress) {
  2.     ByteArrayOutputStream baos = new ByteArrayOutputStream();
  3.     bm.compress(Bitmap.CompressFormat.JPEG, compress, baos);
  4.     return baos.toByteArray();
  5. }
  6. private Bitmap readBufferPixelToBitmap(int width, int height) {
  7.     ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4);
  8.     buf.order(ByteOrder.LITTLE_ENDIAN);
  9.     GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);
  10.     buf.rewind();
  11.     Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
  12.     bmp.copyPixelsFromBuffer(buf);
  13.     return bmp;
  14. }
复制代码
完备的渲染代码如下:
  1. static class MyRenderer implements Renderer {
  2.     private Context mContext;
  3.     private GrayFilter mImageFilter = new GrayFilter();
  4.     private int mTextureId;
  5.     private Bitmap mBitmap;
  6.     private float[] mMVPMatrix = new float[16];
  7.     public MyRenderer(Context context) {
  8.         mContext = context;
  9.         mBitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.girl);
  10.         mImageFilter.setBindFBO(true);
  11.         Matrix.setIdentityM(mMVPMatrix, 0);
  12.     }
  13.     @Override
  14.     public void onSurfaceCreated(GL10 gl, EGLConfig config) {
  15.         Log.i(TAG, "onSurfaceCreated...");
  16.         mTextureId = GLESUtils.create2DTexture(mBitmap);
  17.         mImageFilter.surfaceCreated();
  18.     }
  19.     @Override
  20.     public void onSurfaceChanged(GL10 gl, int width, int height) {
  21.         Log.i(TAG, "onSurfaceChanged...");
  22.         mImageFilter.surfaceChanged(mBitmap.getWidth(), mBitmap.getHeight());
  23.     }
  24.     @Override
  25.     public void onDrawFrame(GL10 gl) {
  26.         long start = System.currentTimeMillis();
  27.         mImageFilter.draw(mTextureId, mMVPMatrix);
  28.         Log.i(TAG, "onDrawFrame:" + (System.currentTimeMillis() - start) + "ms");
  29.         mImageFilter.bindFBO();
  30.         Bitmap resultBitmap = readBufferPixelToBitmap(mBitmap.getWidth(), mBitmap.getHeight());
  31.         mImageFilter.unBindFBO();
  32.         Log.i(TAG, "readBufferPixelToBitmap:" + (System.currentTimeMillis() - start) + "ms");
  33.         byte[] jpgData = Bitmap2Bytes(resultBitmap, 100);
  34.         FileUtils.writeFile(mContext.getExternalFilesDir("gles").getAbsolutePath() + File.separator + "gray.jpg", jpgData);
  35.     }
  36.     @Override
  37.     protected void finalize() throws Throwable {
  38.         super.finalize();
  39.         if (mImageFilter != null) {
  40.             mImageFilter.release();
  41.         }
  42.     }
  43.     public static byte[] Bitmap2Bytes(Bitmap bm, int compress) {
  44.         ByteArrayOutputStream baos = new ByteArrayOutputStream();
  45.         bm.compress(Bitmap.CompressFormat.JPEG, compress, baos);
  46.         return baos.toByteArray();
  47.     }
  48.     private Bitmap readBufferPixelToBitmap(int width, int height) {
  49.         ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4);
  50.         buf.order(ByteOrder.LITTLE_ENDIAN);
  51.         GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);
  52.         buf.rewind();
  53.         Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
  54.         bmp.copyPixelsFromBuffer(buf);
  55.         return bmp;
  56.     }
  57. }
复制代码
  留意:我们在调用glReadPixels前需要绑定FBO否则无法读取到图像,读取完成后解绑FBO
  离开了屏幕渲染,我们利用GPU强大的图像处理能力,通过后台计算导出了图像数据,该灰度图是从SD卡中获取

2. 多种滤镜叠加

假如没有FBO,我们要实现多种滤镜叠加,我们只能把全部的滤镜都写到一个GLSL中,通过流程控制来利用哪些滤镜。而随着代码量的增加,滤镜的增加删除都需要改GLSL这无疑是灾难。
而现在有了FBO,我们可以动态的对这块显存操作,利用不同的滤镜渲染。如许我们可以像链式调用一样,动态的添加删除滤镜。

我们利用了灰度滤镜,亮度滤镜以及将FBO绘制到屏幕上的滤镜

  • 起首我们将原图绘制到灰度滤镜的FBO中
  • 我们将灰度FBO对应纹理作为下一个滤镜的原图传入,将灰度图通过亮度滤镜提亮或变暗
  • 最后我们将亮度FBO对应纹理进行显示,实现屏幕显示多种滤镜叠加的效果
  1. private GrayFilter mGrayFilter;
  2. private HueFilter mHueFilter;
  3. private OriginFilter mOriginFilter;
  4. @Override
  5. public void onDraw(int textureId, float[] matrix) {
  6.     int fboId = mGrayFilter.draw(textureId, MatrixUtils.getOriginalMatrix());
  7.     fboId = mHueFilter.draw(fboId, MatrixUtils.getOriginalMatrix());
  8.     mOriginFilter.draw(fboId, matrix);
  9. }
复制代码
  这里需要留意下:我们GrayFilter、HueFilter内部都拥有本身的FBO。看起来没有题目,假如现在要叠加几十个滤镜,每个滤镜都有本身的FBO,显存会吃不消。
聪明的你应该能想到,只需要存在2个FBO即可实现不限数量的滤镜叠加,一个FBO用于存储上一次渲染的结果,一个FBO存储当前渲染的结果,两个FBO做到正确切换即可。
  多滤镜叠加代码如下:
  1. /**
  2. * 离屏渲染多种滤镜叠加
  3. */
  4. public class OverlayFilter extends BaseFilter {
  5.     private int mTextureWidth;
  6.     private int mTextureHeight;
  7.     private GrayFilter mGrayFilter;
  8.     private HueFilter mHueFilter;
  9.     private OriginFilter mOriginFilter;
  10.     public OverlayFilter() {
  11.         mGrayFilter = new GrayFilter();
  12.         mGrayFilter.setBindFBO(true);
  13.         mHueFilter = new HueFilter(new float[]{-0.2f, -0.2f, -0.2f});
  14.         mHueFilter.setBindFBO(true);
  15.         mOriginFilter = new OriginFilter();
  16.     }
  17.     @Override
  18.     public void setTextureSize(int width, int height) {
  19.         mTextureWidth = width;
  20.         mTextureHeight = height;
  21.         mGrayFilter.setTextureSize(mTextureWidth, mTextureHeight);
  22.         mHueFilter.setTextureSize(mTextureWidth, mTextureHeight);
  23.     }
  24.     @Override
  25.     public void surfaceCreated() {
  26.         mGrayFilter.surfaceCreated();
  27.         mHueFilter.surfaceCreated();
  28.         mOriginFilter.surfaceCreated();
  29.     }
  30.     @Override
  31.     public void surfaceChanged(int width, int height) {
  32.         super.surfaceChanged(width, height);
  33.         mGrayFilter.surfaceChanged(mTextureWidth, mTextureHeight);
  34.         mHueFilter.surfaceChanged(mTextureWidth, mTextureHeight);
  35.         mOriginFilter.surfaceChanged(width, height);
  36.     }
  37.     @Override
  38.     public void onDraw(int textureId, float[] matrix) {
  39.         int fboId = mGrayFilter.draw(textureId, MatrixUtils.getOriginalMatrix());
  40.         fboId = mHueFilter.draw(fboId, MatrixUtils.getOriginalMatrix());
  41.         mOriginFilter.draw(fboId, matrix);
  42.     }
  43.     @Override
  44.     public void release() {
  45.         super.release();
  46.         mGrayFilter.release();
  47.         mHueFilter.release();
  48.         mOriginFilter.release();
  49.     }
  50. }
复制代码
灰度图+变暗两种效果叠加效果图
最后

该章节我们学习了OpenGL ES的一种高端技术离屏渲染,我们可以不依赖屏幕就能做到对图像进行后台渲染,他比直接渲染到屏幕更加高效,也为我们深入利用OpenGL ES提供了新思路,新方法。
OpenGL ES系列:https://github.com/xiaozhi003/AndroidOpenGLDemo.git,假如对你有资助可以star下,万分感谢^_^

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

农妇山泉一亩田

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表