文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android OpenGL ES 学习(十三) -离屏渲染FBO(截图)RBO, OES转 FBO

2023-09-09 10:45

关注

Android OpenGL ES 学习(一) – 基本概念

Android OpenGL ES 学习(二) – 图形渲染管线和GLSL

Android OpenGL ES 学习(三) – 绘制平面图形

Android OpenGL ES 学习(四) – 正交投屏

Android OpenGL ES 学习(五) – 渐变色

Android OpenGL ES 学习(六) – 使用 VBO、VAO 和 EBO/IBO 优化程序

Android OpenGL ES 学习(七) – 纹理

Android OpenGL ES 学习(八) –矩阵变换

Android OpenGL ES 学习(十) – GLSurfaceView 源码解析GL线程以及自定义 EGL

Android OpenGL ES 学习(十一) –渲染YUV视频以及视频抖音特效

Android OpenGL ES 学习(十二) - MediaCodec + OpenGL 解析H264视频+滤镜
Android OpenGL ES 学习(十三) -离屏渲染FBO(截图)RBO, OES转 FBO

代码工程地址: https://github.com/LillteZheng/OpenGLDemo.git

更多音视频,参考:Android 音视频入门/进阶教程

最近遇到需要截图的功能,发现直接使用 GLES30.glReadPixels 竟然达到了2s多,且会阻塞卡顿。
因此有必要学习一下 FBO 了。这次要实现的效果如下:

在这里插入图片描述

OpenGL 默认把 framebuffer(帧缓冲) 当做渲染窗口,在我们之前的程序中,都是使用了默认帧缓冲,它是在你程序启动时就生成和配置好的。
但是 OpenGL 也允许我们定义自己的帧缓冲 FBO ,它可以在不影响默认帧缓冲的情况,来存储我们几次不同的渲染采样的结果,等处理完再显示到窗口上。
比如摄像头拿到流之后,进行美颜效果,再显示出来。

1.1 优势

这样做的好处有:

  1. 提高渲染效率(后台绘制)
  2. 避免卡顿闪屏
  3. 实现纹理共享 (fbo 的纹理id)

二. FBO 概念

2.1 创建 FBO

在 OpenGL 中,创建FBO的方式非常简单:

val frameBuffers = IntArray(1)GLES30.glGenFramebuffers(1, frameBuffers, 0)

然后使用

GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, frameBuffers[0])

激活帧缓冲对象,但它并不完整;一个完整的帧缓冲需要满足以下的条件:

从上面的条件知道,帧缓冲至少需要附着一个颜附件,才能正常工作;
在这里插入图片描述

2.2 附件

需要给帧缓冲附加一个附件。附件是一个内存位置,它能够作为帧缓冲的一个缓冲,可以将它想象为一个图像。当创建一个附件的时候我们有两个选项:纹理或渲染缓冲对象(Renderbuffer Object)。

之后所有的渲染操作将会渲染到当前绑定帧缓冲的附件中。由于我们的帧缓冲不是默认帧缓冲,渲染指令将不会对窗口的视觉输出有任何影响。出于这个原因,渲染到一个不同的帧缓冲被叫做离屏渲染(Off-screen Rendering)

2.3 纹理附件

当把一个纹理附加到帧缓冲的时候,所有的渲染指令将会写入到这个纹理中,就像它是一个普通的颜色/深度或模板缓冲一样。使用纹理的优点是,所有渲染操作的结果将会被储存在一个纹理图像中,我们之后可以在着色器中很方便地使用它。

这样,当我们用 fbo 渲染完成后,可以拿到这个 纹理 的id,去做特效或者显示。

我们复制第七章纹理的代码 Android OpenGL ES 学习(七) – 纹理 在他的基础上实现使用 FBO 的功能,对FBO进行一个认知。效果如下:

在这里插入图片描述

为帧缓冲创建一个纹理和普通纹理的创建差不多:

 val textures = IntArray(1)        GLES30.glGenTextures(1, textures, 0)        val textureId = textures[0]        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId)        //纹理过滤        GLES30.glTexParameteri(            GLES30.GL_TEXTURE_2D,            GLES30.GL_TEXTURE_MIN_FILTER,            GLES30.GL_NEAREST        )        GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR)        GLES30.glTexImage2D(            GLES30.GL_TEXTURE_2D,            0,            GLES30.GL_RGB,            width,            height,            0,            GLES30.GL_RGB,            GLES30.GL_UNSIGNED_SHORT_5_6_5,            null        )

注意我们在 glTexImage2D 的数据data 那里设置了 null,因为我们只需要内存地址,暂时不需要去填充它,然后环绕方式,我们也不用太去关注,大小也是。

最后,则需要将它附加到帧缓冲上了:

GLES30.glFramebufferTexture2D(            GLES30.GL_FRAMEBUFFER,            GLES30.GL_COLOR_ATTACHMENT0,            GLES30.GL_TEXTURE_2D,            textureId,            0        )

glFrameBufferTexture2D有以下的参数:

当然,除了颜色附件,还有深度和模板缓冲,这里暂时不需要关注。完整代码如下:

private var fboBean: FboBean? = null    private fun useFbo(width: Int, height: Int) {        val frameBuffers = IntArray(1)        GLES30.glGenFramebuffers(1, frameBuffers, 0)        val textures = IntArray(1)        GLES30.glGenTextures(1, textures, 0)        val textureId = textures[0]        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId)        //纹理过滤        GLES30.glTexParameteri(            GLES30.GL_TEXTURE_2D,            GLES30.GL_TEXTURE_MIN_FILTER,            GLES30.GL_NEAREST        )        GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR)        GLES30.glTexImage2D(            GLES30.GL_TEXTURE_2D,            0,            GLES30.GL_RGB,            width,            height,            0,            GLES30.GL_RGB,            GLES30.GL_UNSIGNED_SHORT_5_6_5,            null        )        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, frameBuffers[0])        GLES30.glFramebufferTexture2D(            GLES30.GL_FRAMEBUFFER,            GLES30.GL_COLOR_ATTACHMENT0,            GLES30.GL_TEXTURE_2D,            textureId,            0        )        val status = GLES30.glCheckFramebufferStatus(GLES30.GL_FRAMEBUFFER)        if (status != GLES30.GL_FRAMEBUFFER_COMPLETE) {            throw RuntimeException("Failed to create texture.")        }        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)        GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0)        fboBean = FboBean(frameBuffers[0], textureId, width, height)    }

3.1 怎么使用?

Android OpenGL ES 学习(七) – 纹理 中,我们首先通过加载图片,拿到原图片的纹理id,而我们截图只需要显示图片大小,因此,当我们加载好原始图片,就使用 fbo 即可:

 texture = loadTexture(TAG, MainApplication.context, R.mipmap.wuliuqi)?.apply {     Log.d(TAG, "useVaoVboAndEbo() called width = $width, height = $height")     useFbo(width, height)     Log.d(TAG, "bind frame buffer succeeded") }

3.2 渲染

那如何渲染呢?前面说到,当我们使用 GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER,fboId) 时,已经从默认帧缓冲切换到自己的帧缓冲了,然后所有的渲染都在你 fbo 的纹理上。
那这样就很简单,按照这样的步骤:

  1. 绑定 FBO GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER,fboId)
  2. 渲染纹理数据 GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, id)
  3. 解绑 FBO GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER,0)
  4. 渲染原图或者 FBO 的纹理

完成代码如下:

    override fun onDrawFrame(gl: GL10?) {        resetMatrix()        // 步骤1:使用 glClearColor 设置的颜色,刷新 Surface        fboBean?.apply {            GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT )            // 绑定 FBO            GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER,fboId)            GLES30.glViewport(0, 0, width, height)            //解决倒影            //rotate(180f)            texture?.apply {                GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, id)            }            GLES30.glBindVertexArray(vao[0])            GLES30.glDrawElements(GLES30.GL_TRIANGLE_STRIP, 6, GLES30.GL_UNSIGNED_INT, 0)            val startTime = System.currentTimeMillis()            val bmp = readBufferPixelToBitmap(width, height)            image?.post {                image?.setImageBitmap(bmp)            }            val endTime = System.currentTimeMillis()            Log.d(TAG, "zsr onDrawFrame: ${endTime - startTime}")            // 解绑 FBO            GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)            GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER,0)        }        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT )        GLES30.glViewport(0, 0, screenWidth, screenHeight)        resetMatrix()        // 正交投影        if (aspectRatio > 1) {            Matrix.orthoM(UnitMatrix, 0, -aspectRatio, aspectRatio, -1f, 1f, -1f, 1f)        } else {            Matrix.orthoM(UnitMatrix, 0, -1f, 1f, -aspectRatio, aspectRatio, -1f, 1f)        }        GLES30.glUniformMatrix4fv(uMatrix, 1, false, UnitMatrix, 0)        //显示 fbo 的图片        fboBean?.let {            GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, it.textureId)        }?: GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, texture!!.id)        //使用原图       // GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, texture!!.id)        GLES30.glBindVertexArray(vao[0])        GLES30.glDrawElements(GLES30.GL_TRIANGLE_STRIP, 6, GLES30.GL_UNSIGNED_INT, 0)    }

然后你会发现,怎么是倒影的?
那是因为 FBO 的坐标是帧缓冲坐标,跟纹理坐标是相反的(图片来源(https://blog.csdn.net/yu540135101/article/details/102912319)):
图片来源(https://blog.csdn.net/yu540135101/article/details/102912319)

与 VBO ,VAO 相似,RBO(Renderbuffer Object) 是纹理附件的缓冲对象,它会将数据储存为OpenGL原生的渲染格式,它是为离屏渲染到帧缓冲优化过的。
如果你有多个渲染对象或者有深度和模板附件时,使用 RBO 是一个非常不错的选择。

渲染缓冲对象直接将所有的渲染数据储存到它的缓冲中,不会做任何针对纹理格式的转换,让它变为一个更快的可写储存介质。然而,渲染缓冲对象通常都是只写的,所以你不能读取它们(比如使用纹理访问)。当然你仍然还是能够使用glReadPixels来读取它,这会从当前绑定的帧缓冲,而不是附件本身,中返回特定区域的像素。

因为它的数据已经是原生的格式了,当写入或者复制它的数据到其它缓冲中时是非常快的。所以,交换缓冲这样的操作在使用渲染缓冲对象时会非常快。

创建 rbo 和 帧缓冲非常相似:

 //创建rboval rbos = IntArray(1)GLES30.glGenFramebuffers(1,rbos,0)

然后绑定这个缓冲对象,让之后所有的渲染缓冲操作在rbo上:

GLES30.glBindRenderbuffer(GLES30.GL_RENDERBUFFER,rbos[0])

由于渲染缓冲对象通常都是只写的,它们会经常用于深度和模板附件,因为大部分时间我们都不需要从深度和模板缓冲中读取值,只关心深度和模板测试。

说到深度测试,我们就自然而然想到我们的第九章 Android OpenGL ES 学习(九) – 坐标系统和。实现3D效果 了。

创建一个深度和模板渲染缓冲对象可以通过调用 glRenderbufferStorage 来实现

       GLES30.glRenderbufferStorage(GLES30.GL_RENDERBUFFER,GLES30.GL_DEPTH24_STENCIL8,width,height)      

这个对象是专门被设计作为帧缓冲附件使用的,而不是纹理那样的通用数据缓冲(General Purpose Data Buffer)。这里我们选择GL_DEPTH24_STENCIL8作为内部格式,它封装了24位的深度和8位的模板缓冲。

最后一件事就是附加这个渲染缓冲对象:

 GLES30.glFramebufferRenderbuffer(GLES30.GL_FRAMEBUFFER,GLES30.GL_DEPTH_STENCIL_ATTACHMENT,GLES30.GL_RENDERBUFFER,rbos[0])

4.1 渲染

跟之前一样,把 3D 效果先绘制到 FBO 上,此时 FBO 的 textureId 已经有了整个效果了,那我们其实就绘制纹理就可以了,就不用再 for 循环去绘制每一个面了。:

    override fun onDrawFrame(gl: GL10?) {      // notUseDeepTest()        resetMatrix()        fboBean?.apply {            // 绑定 FBO            GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER,fboId)            GLES30.glBindRenderbuffer(GLES30.GL_RENDERBUFFER,rboId)            GLES30.glViewport(0, 0, width, height)            useDeepTest()                        val startTime = System.currentTimeMillis()            val bmp = readBufferPixelToBitmap(width, height)            image?.post {                image?.setImageBitmap(bmp)            }            val endTime = System.currentTimeMillis()            Log.d(TAG, "zsr onDrawFrame: ${endTime - startTime}")            // 解绑 FBO            GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)            GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER,0)            GLES30.glBindRenderbuffer(GLES30.GL_RENDERBUFFER,0)        }       // useDeepTest()        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT or GLES30.GL_DEPTH_BUFFER_BIT)        resetMatrix()        GLES30.glBindVertexArray(vao[0])        //直接用 fbo 的纹理绘制即可        fboBean?.let {            GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, it.textureId)        }        GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 36)    }

效果如下:
在这里插入图片描述
这样,我们就对 FBO 学习差不多;基本上掌握了 FBO 的使用原理,想开头的效果,OES 装 FBO ,基本上自己也可以写了。

来源地址:https://blog.csdn.net/u011418943/article/details/131578103

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯