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

news2024/10/5 17:22:09

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])

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

  • 附加至少一个缓冲(颜色、深度或模板缓冲)。
  • 至少有一个颜色附件(Attachment)。
  • 所有的附件都必须是完整的(保留了内存)。
  • 每个缓冲都应该有相同的样本数。

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

2.2 附件

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

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

2.3 纹理附件

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

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

三 使用 FBO 进行纹理显示

我们复制第七章纹理的代码 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有以下的参数:

  • target:帧缓冲的目标(绘制、读取或者两者皆有)
  • attachment:我们想要附加的附件类型。当前我们正在附加一个颜色附件。注意最后的0意味着我们可以附加多个颜色附件。我们将在之后的教程中提到。
  • textarget:你希望附加的纹理类型
  • texture:要附加的纹理本身
  • level:多级渐远纹理的级别。我们将它保留为0。

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

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)

四 RBO (缓冲对象附件)

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

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

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

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

 //创建rbo
val 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 ,基本上自己也可以写了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/725682.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

威胁和漏洞管理增强远程 IT 安全性

威胁和漏洞管理是保护组织设备和数据的主动方法。它可以帮助管理员识别漏洞并检查安全设置是否薄弱。通过使用此方法,可以在任何弱点成为安全漏洞之前对其进行修复。 对远程威胁和漏洞管理工具的需求 随着越来越多的员工远程工作,网络攻击的可能性也在…

基于Unity 3D实现的融合多元素风格游戏

完整资料进入【数字空间】查看——baidu搜索"writebug" 1.综合描述 1.1 产品背景 随着人们对游戏的追求,越来越多的优秀游戏被开发出来。目前,多风格元素游戏深受大众喜爱,例如绝地求生融合了 FPS 射击与生 存元素,守…

【软件分析/静态分析】chapter4 课程05/06 数据流分析—基础(Data Flow Analysis—Foundations)

🔗 课程链接:李樾老师和谭天老师的: 南京大学《软件分析》课程05(Data Flow Analysis - Foundations I)_哔哩哔哩_bilibili 目录 第四章 数据流分析——基础 4.1 从另一个视角看迭代算法(Iterative Algor…

【乌鲁木齐】基于ArcGIS、ENVI、InVEST、FRAGSTATS等多技术融合提升环境、生态、水文、土地、土壤、农业、大气等领域应用

【原文链接】:【乌鲁木齐】基于ArcGIS、ENVI、InVEST、FRAGSTATS等多技术融合提升环境、生态、水文、土地、土壤、农业、大气https://mp.weixin.qq.com/s?__bizMzU5NTkyMzcxNw&mid2247545692&idx1&sn1934aa8be717557c6c97c6b5e2a49151&chksmfe68f5…

web---text示例1

一、题目&#xff1a; <!-- 题目&#xff1a; 今日搜索热词 1、阿卡贝拉阿卡贝拉 (意大利: Acappella )即无伴奏合唱。 其起源可追溯至中世纪的教会音乐&#xff0c;当时的教会音乐只以人声清唱&#xff0c;并不应用乐器。 音频示例:阿卡贝拉.千与千寻2、翻唱“翻唱”…

Windows server防火墙如何设置阻止IP访问防火墙限制ip地址访问

无论是服务器还是本机电脑&#xff0c;安全都是非常重要的&#xff0c;一些安全软件设置后会拦截到一些异常IP&#xff0c;我们可以通过防火墙将异常IP加入黑名单&#xff0c;禁止访问&#xff0c;今天芳芳就和你们分享Windows防火墙如何设置IP禁止访问&#xff0c;希望可以帮助…

Redis通用命令数据结构

目录 Redis通用命令 官网查看不同命令https://redis.io/commands/ help命令&#xff1a;查询各种命令用法 keys命令&#xff1a;查看符合模板的所有key Redis的key结构 ​编辑 数据结构 String类型 String常用指令 Hash类型 Hash常用命令 List类型 List常用命令 …

Java框架之ORM

写在前面 本文看下Java操作数据库相关的内容。 1&#xff1a;JDBC 我们知道关系型数据库不止一种&#xff0c;像MySQL&#xff0c;Oracle&#xff0c;db2&#xff0c;PostgreSQL,sql server等&#xff0c;为了规范对这些不同数据的连接&#xff0c;数据的CRUD操作&#xff0…

Spring Boot 中的请求映射是什么,如何使用

Spring Boot 中的请求映射是什么&#xff0c;如何使用 Spring Boot 是一个快速开发 Spring 应用程序的框架&#xff0c;它可以帮助开发者快速搭建一个完整的 Web 应用程序。在 Spring Boot 中&#xff0c;请求映射&#xff08;Request Mapping&#xff09;是一个重要的概念&am…

Element 时间选择器 el-time-picker 禁用 秒 选项

文章目录 默认格式 HH:mm:ss禁用 秒 选项el-time-picker 设置日期和时间组合 默认格式 HH:mm:ss 时间选择器 format 标签默认为 HH:mm:ss <el-time-picker is-range v-model"value" range-separator"-" format"HH:mm:ss" start-placeholder…

openfeign的几种实现方式

第一种&#xff1a; 假如A 服务调用B服务的某个方法&#xff0c;如果不建立新的feign模块&#xff0c;可以进行一下步骤 在调用者A中引入依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign&…

砷化镓微纳米器件的制造工艺条件

引言 与硅基器件相比&#xff0c;III-V半导体具有更高的电子迁移率和峰值速度&#xff0c;因此更适合光电子学以外的高性能应用。目前III-V材料的不同工艺技术仍然具有挑战性&#xff0c;需要进一步发展。ICP-RIE&#xff08;电感耦合等离子体反应性离子蚀刻&#xff09;蚀刻技…

node搭建本地https和wss服务(SSL证书安全)

node 后台 app.js配置 const express require(express) //加载express资源 const bodyParserrequire("body-parser")//一个Express中间件,用于解析HTTP请求体,获得请求的数据const app express() //返回一个express服务器对象 const https require(https) const …

基于SpringBoot+jsp的商务安全邮箱邮件收发系统设计与实现

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…

2178. 拆分成最多数目的正偶数之和

给你一个整数 finalSum 。请你将它拆分成若干个 互不相同 的正偶数之和&#xff0c;且拆分出来的正偶数数目 最多 。 比方说&#xff0c;给你 finalSum 12 &#xff0c;那么这些拆分是 符合要求 的&#xff08;互不相同的正偶数且和为 finalSum&#xff09;&#xff1a;(2 1…

Vue3---激活状态显示

Router-Link组件默认支持激活样式显示的类名&#xff0c;只需要给active-class属性设置对应的类名即可 <RouterLink active-class"active" :to"/category/${item.id}">{{ item.name }}</RouterLink><style scoped langscss> .active {c…

VBA小工具:EXCEL如何批量给每行下面插入n个空行?

目录 1EXCEL本身的方法 2 VBA实现1&#xff1a; 2.1 粗糙代码 2.2 需要注意的问题 3 VBA实现2&#xff1a; 3.1 改进代码&#xff1a;根据用户自己先选中的区域&#xff0c;进行插入空行操作。 3.1.1 用户可在EXCEL里选中区域&#xff0c;而不是改VBA代码 3.1.2 自动识…

web学习--登录认证--会话技术--cookie--session--令牌--java-jwt使用--jjwt使用

前置学习: httpspringmvc 文章目录 会话技术cookie设置cookie获取cookiecookieAPI优缺点cookie的删除 session设置session删除session的某个值获取sesssion优缺点 令牌JWTJWT介绍JWT的使用java-jwtjjwt手动解析 会话技术 会话&#xff1a;用户打开浏览器&#xff0c;访问web服…

Error:invalid character in indentifier

Error&#xff1a;invalid character in indentifier 解决方案&#xff1a; 一般是中英文写错了

【电影推荐系统】实时推荐

目录 原因 由于实时性&#xff0c;所以算法设计需要满足一下两点 算法设计 算法实现 算法公式 完整代码 原因 用户对电影的偏好随着时间的推移总是会发生变化的。此时离线系统无法解决&#xff0c;需要实时推荐。 由于实时性&#xff0c;所以算法设计需要满足一下两点 …