Android中的OpenGL

news2024/11/24 17:49:27

前面有关 Android 音视频的渲染都是使用MediaCodec进行渲染,MediaCodec也有自己的弊端比如无法进行视频的编辑处理,而视频可以 OpenGL ES来进行渲染,可以很好进行处理,比如添加滤镜等,这里介绍下 Android 中 OpenGL,也就是 OpenGL ES,它是免费、跨平台的、功能完善的 2D/3D 图形库接口 API,他针对多种嵌入式系统进行了专门设计,它是一个精心提取出来的 OpenGL 的子集,主要内容如下:

  1. 介绍
  2. GLSurfaceView
  3. 渲染器Renderer
  4. 坐标映射
  5. 绘制三角形
  6. 绘制效果

介绍

Android 可通过开放图形库 OpenGL ES 来支持高性能 2D 和 3D 图形,OpenGL 是一种跨平台的图形 API,用于为 3D 图形处理硬件指定标准的软件接口。OpenGL ES 是 OpenGL 规范的一种形式,适用于嵌入式设备,Android 支持多版 OpenGL ES API,各版本情况如下:

  • OpenGL ES 1.0 和 1.1 - 此 API 规范受 Android 1.0 及更高版本的支持。
  • OpenGL ES 2.0 - 此 API 规范受 Android 2.2(API 级别 8)及更高版本的支持。
  • OpenGL ES 3.0 - 此 API 规范受 Android 4.3(API 级别 18)及更高版本的支持。
  • OpenGL ES 3.1 - 此 API 规范受 Android 5.0(API 级别 21)及更高版本的支持。

在 AndroidManifest.xml 中声明 OpenGL ES 的版本

<uses-feature android:glEsVersion="0x00020000" android:required="true" />

GLSurfaceView

GLSurfaceViewSurfaceViewOpenGL实现,从 Android 1.5 开始加入,在 SurfaceView的基础上添加了 EGL 的管理以及自带的渲染线程 GLThread,其主要功能如下:

  • 管理一个Surface,这个Surface是一块特殊的内存,可以组合到 Android 的 View 系统中,也就是可以和View一起使用。
  • 管理一个 EGL,这个EGL可以让 OpenGL 渲染到这个Surface上,EGL是 Android 与 OpenGL之间的桥梁。
  • 支持用户自定义渲染器Renderer对象。
  • 使用专用线程上进行渲染。
  • 支持按需渲染(on-demand)和连续渲染(continuous )。
  • Optionally wraps, traces, and/or error-checks the renderer’s OpenGL calls.

EGL 窗口、OpenGL 表面、GL 表面含义都相同。

GLSurfaceView常用设置如下:

EGL配置

EGLConfigChooser的默认实现是SimpleEGLConfigChooser,默认情况下GLSurfaceView将选择深度缓冲深度至少为 16 位的PixelFormat.RGB_888格式的 surface,默认的EGLConfigChooser实现是SimpleEGLConfigChooser,具体如下:

private class SimpleEGLConfigChooser extends ComponentSizeChooser {
    public SimpleEGLConfigChooser(boolean withDepthBuffer) {
        super(8, 8, 8, 0, withDepthBuffer ? 16 : 0, 0);
    }
}

可以通过如下方式修改EGLConfig的默认行为:

// 设置默认EGLConfig的深度缓冲,true则为16位的深度缓冲
setEGLConfigChooser(boolean needDepth)
// 指定自定义的EGLConfigChooser
setEGLConfigChooser(android.opengl.GLSurfaceView.EGLConfigChooser configChooser)
// 指定各个分量的值
public void setEGLConfigChooser(int redSize, int greenSize, int blueSize,
            int alphaSize, int depthSize, int stencilSize)
渲染

通过setRenderer设置渲染器并启动渲染线程GLThread,渲染模式有两种如下:

  • RENDERMODE_CONTINUOUSLY:适合重复渲染的场景,默认的渲染模式。
  • RENDERMODE_WHEN_DIRTY:只有Surface被创建后渲染一次,只调用了requestRender才会继续渲染。

渲染模式可以通过setRenderMode来进行设置,具体如下:

// 设置渲染器
public void setRenderer(Renderer renderer)
// 设置渲染模式,仅在setRenderer之后调用生效
public void setRenderMode(int renderMode)
setDebugFlags和setGLWrapper

setDebugFlags用于设置 Debug 标记,方便调试跟踪代码,可选值为DEBUG_CHECK_GL_ERRORDEBUG_LOG_GL_CALLSsetGLWrapper可以通过自定义GLWrapper来委托 GL 接口来添加一些自定义行为,具体如下:

// DEBUG_CHECK_GL_ERROR:每次GL调用都会检查,如果出现glError则会抛出异常
// DEBUG_LOG_GL_CALLS:以TAG为GLSurfaceView将日志记录在verbose级别的日志中
setDebugFlags(int debugFlags)
// 用于调试跟踪代码,可自定义GLWrapper包装GL接口并返回GL接口,可在
setGLWrapper(android.opengl.GLSurfaceView.GLWrapper glWrapper)

渲染器Renderer

这部分在前面提到过,这里单独说一下,要想在 GL 表面上执行渲染操作,需要实现Renderer对象完成实际渲染操作,通过如下方式给GLSurfaceView设置渲染器对象Renderer以及制定渲染模式,如下:

// 给GLSurfaceView设置渲染器对象Renderer
public void setRenderer(Renderer renderer)
// 设置渲染模式,仅在setRenderer之后调用生效
public void setRenderMode(int renderMode)

设置渲染器Renderer的时候,同时会创建独立线程GLThread并开启该线程,这个线程就是独立于 UI 线程的渲染线程。

这里就涉及到两个线程 UI 线程和渲染线程,自然涉及到线程之间的通信,可以使用 volatilesynchronized等实现线程之间的通信。

如果是在 UI 线程中调用渲染线程中的操作,可以使用GLSurfaceViewqueueEvent 方法来将该操作执行到渲染线程中,一般需要自定义GLSurfaceView的时候会用到,同样如果在渲染线程可以通过runOnUiThread来将与 UI 相关的操作执行到 UI 线程。

下面看下渲染器Reander的基本实现:

public class GLES20Renderer implements Renderer {
    private static final String TAG = GLES20Renderer.class.getSimpleName();

    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        Log.i(TAG, "onSurfaceCreated");
        GLES20.glClearColor(0.0f, 0.0f, 1.0f, 1);
    }

    public void onSurfaceChanged(GL10 gl, int width, int height) {
        Log.i(TAG, "onSurfaceChanged");
        GLES20.glViewport(0, 0, width, height);
    }

    public void onDrawFrame(GL10 gl) {
        Log.i(TAG, "onDrawFrame");
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
    }
}

坐标映射

先来了解下 OpenGL 的世界坐标系和与之对应的 Android 上的纹理坐标系,如下图所示:

opengl location.png

在 Android 中使用 OpenGL 就要进行相应坐标的转换,下面看下 OpenGL 坐标系在 Android 屏幕中的映射关系,如下图所示:

img

如上图所示,左侧是默认的 OpenGL 坐标系,右侧是 OpenGL 坐标系在 Android 屏幕上的映射,可以明显看到图中的三角形是变形了的,为了保证图像比例就需要应用 OpenGL 投影模式和相机视图来转换坐标,这就涉及到投影矩阵和视图矩阵,这部分内容会在后续的文章中介绍。

绘制三角形

通过以上内容,Android OpenGL 算是初步入门了,按照习惯来个小案例,这里使用 OpenGL 绘制一个三角形,如下Triangle是三角形数据封装及着色器的的使用,后续渲染直接调用draw方法进行渲染绘制,如下:

// Triangle
class Triangle(context: Context) {
    companion object {
        // 坐标数组中每个顶点的坐标数
        private const val COORDINATE_PER_VERTEX = 3
    }

    private var programHandle: Int = 0
    private var positionHandle: Int = 0
    private var colorHandler: Int = 0
    private var vPMatrixHandle: Int = 0
    private var vertexStride = COORDINATE_PER_VERTEX * 4

    // 三角形的三条边
    private var triangleCoordinate = floatArrayOf(     // 逆时针的顺序的三条边
        0.0f, 0.5f, 0.0f,      // top
        -0.5f, -0.5f, 0.0f,    // bottom left
        0.5f, -0.5f, 0.0f      // bottom right
    )

    // 颜色数组
    private val color = floatArrayOf(0.63671875f, 0.76953125f, 0.22265625f, 1.0f)
    private var vertexBuffer: FloatBuffer =
        // (number of coordinate values * 4 bytes per float)
        ByteBuffer.allocateDirect(triangleCoordinate.size * 4).run {
            // ByteBuffer使用本机字节序
            this.order(ByteOrder.nativeOrder())
            // ByteBuffer to FloatBuffer
            this.asFloatBuffer().apply {
                put(triangleCoordinate)
                position(0)
            }
        }

    init {
        // read shader sourceCode
        val vertexShaderCode = GLUtil.readShaderSourceCodeFromRaw(context, R.raw.vertex_shader_triangle_default)
        val fragmentShaderCode =
            GLUtil.readShaderSourceCodeFromRaw(context, R.raw.fragment_shader_triangle)
        if (vertexShaderCode.isNullOrEmpty() || fragmentShaderCode.isNullOrEmpty()) {
            throw RuntimeException("vertexShaderCode or fragmentShaderCode is null or empty")
        }
        // compile shader
        val vertexShaderHandler = GLUtil.compileShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode)
        val fragmentShaderHandler =
            GLUtil.compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode)
        // create and link program
        programHandle = GLUtil.createAndLinkProgram(vertexShaderHandler, fragmentShaderHandler)
    }

    /**
 	 *  绘制方法
 	 */
    fun draw(mvpMatrix: FloatArray) {
        GLES20.glUseProgram(programHandle)
        // 获取attribute变量的地址索引
        // get handle to vertex shader's vPosition member
        positionHandle = GLES20.glGetAttribLocation(programHandle, "vPosition").also {
            // enable vertex attribute,默认是disable
            GLES20.glEnableVertexAttribArray(it)
            GLES20.glVertexAttribPointer(
                it, // 着色器中第一个顶点属性的位置
                COORDINATE_PER_VERTEX,
                GLES20.GL_FLOAT,
                false,
                vertexStride, // 连续的顶点属性组之间的间隔
                vertexBuffer
            )
        }
        // get handle to fragment shader's vColor member
        colorHandler = GLES20.glGetUniformLocation(programHandle, "vColor").also {
            GLES20.glUniform4fv(it, 1, color, 0)
        }
        // draw triangle
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, triangleCoordinate.size / COORDINATE_PER_VERTEX)
        GLES20.glDisableVertexAttribArray(positionHandle)
    }
}

渲染器实现如下:

// 渲染器实现
class MRenderer(private var context: Context) : GLSurfaceView.Renderer {
    private val tag = MRenderer::class.java.simpleName
    private lateinit var triangle: Triangle
    private val vPMatrix = FloatArray(16) // 模型视图投影矩阵
    private val projectionMatrix = FloatArray(16)
    private val viewMatrix = FloatArray(16)
    override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
        // 创建Surface时调用,在渲染开始时调用,用来创建渲染开始时需要的资源
        Log.d(tag, "onSurfaceCreated")
        triangle = Triangle(context)
    }

    override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
        // Surface改变大小时调用,设置视口
        Log.d(tag, "onSurfaceChanged")
        GLES20.glViewport(0, 0, width, height)
    }

    override fun onDrawFrame(gl: GL10?) {
        // 绘制当前frame,用于渲染处理具体的内容
        Log.d(tag, "onDrawFrame")
        triangle.draw(vPMatrix)
    }
}

上面都是基本的绘制操作,没啥好说的,其中着色器的使用流程会在后续文章中进行介绍,这里就不贴其他代码了,感兴趣的可以直接在文末查看源代码。

绘制效果

上面的绘制没有使用投影矩阵和相机视图来进行坐标转换,当横竖屏切换到时候会到导致变形,这个会在下篇文章中进行修正,看下上述代码绘制的效果图,如下图所示:


Android 技术提升知识点归整

Android 性能调优系列https://qr18.cn/FVlo89
Android 车载学习指南https://qr18.cn/F05ZCM
Android Framework核心知识点笔记https://qr18.cn/AQpN4J
Android 音视频学习笔记https://qr18.cn/Ei3VPD
Jetpack全家桶(含Compose)https://qr18.cn/A0gajp
Kotlin 入门到精进https://qr18.cn/CdjtAF
Flutter 基础到进阶实战https://qr18.cn/DIvKma
Android 八大知识体系https://qr18.cn/CyxarU
Android 中高级面试题锦https://qr18.cn/CKV8OZ

后续如有新知识点,将会持续更新,尽请期待……

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

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

相关文章

GrowingIO是什么?如何将GrowingIO数据导入其他系统

GrowingIO是什么&#xff1f;GrowingIO 是一站式数据增长引擎整体方案服务商&#xff0c;以数据智能分析为核心&#xff0c;通过构建客户数据平台&#xff0c;打造增长营销闭环&#xff0c;帮助企业提升数据驱动能力&#xff0c;赋能商业决策、实现业务增长。GrowingIO 专注于零…

MyBatis-Plus框架解析?

简单介绍&#xff1a;MyBatis-Plus&#xff08;简称 MP&#xff09;&#xff08;由苞米豆公司开源&#xff09;是一个 MyBatis 的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。MP会内置集成部分SQL方法&#xff0c;可以直接…

【应用管理总结 Objective-C语言】

一、把应用管理这个案例,给大家总结一下: 1.今天,经过一天的努力,我们终于把这个九宫格应用管理案例的所有功能都实现了吧, 我们一起来,一边看效果,一边来总结, 2.大家先想一下,当我们实现这个效果,按照最终的那个版本来想一下,这个代码是什么样的一个思路, 1)…

QT打包的两种方式

QT打包的两种方式&#xff1a; 一个是QT5自带的windeployqt&#xff08;不需要下载安装&#xff09;&#xff0c;它可以找到程序&#xff08;exe&#xff09;用到的所有库文件&#xff0c;并且都拷贝到exe程序的当前文件。此时打包的exe较小&#xff0c;需要和拷贝进来的文件放…

大话数据结构-图的深度优先遍历和广度优先遍历

4 图的遍历 图的遍历分为深度优先遍历和广度优先遍历两种。 4.1 深度优先遍历 深度优先遍历&#xff08;Depth First Search&#xff09;&#xff0c;也称为深度优先搜索&#xff0c;简称DFS&#xff0c;深度优先遍历&#xff0c;是指从某一个顶点开始&#xff0c;按照一定的规…

抗锯齿和走样(笔记)

Artifacts&#xff08;瑕疵&#xff09;&#xff1a; 比如人眼采样频率跟不上陀螺的旋转速度&#xff0c;这时就有可能看到陀螺在反方向旋转怎么做抗锯齿&#xff08;滤波&#xff09;&#xff1a; 在采样之前先进行一个模糊操作&#xff0c;可以降低锯齿的明显程度 通过傅里叶…

七【SpringMVC参数绑定】

目录&#x1f6a9;一 . 视图传参到控制器&#x1f6a9;二 . SpringMVC跳转方式&#x1f6a9;三 SpringMVC处理json请求和响应&#x1f6a9;四 SpringMVC静态资源处理✅作者简介&#xff1a;Java-小白后端开发者 &#x1f96d;公认外号&#xff1a;球场上的黑曼巴 &#x1f34e;…

Flask自定义接口,实现mock应用

问题&#xff1a;后端接口已提供&#xff0c;前端需要依赖后端接口返回的数据进行前端页面的开发&#xff0c;如何配合前端&#xff1f; mock接口 flask自定义接口实现查询接口&#xff1a;查询全部、部分查询 具体看下面的代码&#xff1a; #导入包 from flask import Fla…

企业如何选择固定资产管理系统?

如何促进企业内部信息化的建设&#xff0c;实现企业的高效管理和运转&#xff0c;是企业管理员经常考虑的问题。尤其是企业资金占比较多的固定资产该如何高效管理&#xff0c;是大家经常你讨论的问题。我们都知道行政部门管理着百上千件物品&#xff0c;且还要定期进行盘点&…

【python】标准库详解

注&#xff1a;最后有面试挑战&#xff0c;看看自己掌握了吗 文章目录Standard Library简介python内置对象如何安装发布第三方模块10最好用的模块汇总包的本质datetime模块案例Math模块random模块OS模块sys模块time模块总结自定义模块标准库模块用help查看time模块常用第三方库…

30 openEuler使用LVM管理硬盘-简介和安装

文章目录30 openEuler使用LVM管理硬盘-简介和安装30.1 LVM简介30.1.1 基本概念30.2 安装30 openEuler使用LVM管理硬盘-简介和安装 30.1 LVM简介 LVM是逻辑卷管理&#xff08;Logical Volume Manager&#xff09;的简称&#xff0c;它是Linux环境下对磁盘分区进行管理的一种机…

【苹果内购支付】关于uniapp拉起苹果内购支付注意事项、实现步骤以及踩过的坑

前言 Hello&#xff01;又是很长时间没有写博客了&#xff0c;因为最近又开始从事新项目&#xff0c;也是第一次接触关于uniapp开发原生IOS应用的项目&#xff0c;在这里做一些关于我在项目中使用苹果内购支付所实现的方式以及要注意的事项&#xff0c;希望能给正在做uniapp开…

Hive 数据倾斜

数据倾斜&#xff0c;即单个节点任务所处理的数据量远大于同类型任务所处理的数据量&#xff0c;导致该节点成为整个作业的瓶颈&#xff0c;这是分布式系统不可能避免的问题。从本质来说&#xff0c;导致数据倾斜有两种原因&#xff0c;一是任务读取大文件&#xff0c;二是任务…

Centos7 服务器基线检查处理汇总

1、服务器超时设置 问题描叙 TMOUT的值大于key2且小于等于{key2}且小于等于key2且小于等于{key1}视为合规 查看命令&#xff1a;export检测结果 超时时间:0处理方式 备份/etc/profile文件 cp /etc/profile /etc/profile_bak编辑profile文件 vim /etc/profile修改/新增 TMO…

Spring Cloud(微服务)学习篇(三)

Spring Cloud(微服务)学习篇(三) 1 nacos中使用openFeign(调用方式)实现短信发送 1.1 在shop-sms-api中创建com.zlz.shop.sms.api.service/vo/dto/util,目录结构如下所示 1.2 在pom.xml(shop-sms-api)中加入如下依赖 <dependencies><dependency><groupId>…

西电算法分析与设计核心考点汇总(期末真题,教材算法导论)

文章目录前言一、历年考题1.1 判断题1.2 单选题1.3 复杂度计算1.4 分治1.5 算法设计&#xff08;01背包&#xff0c;最短路径&#xff09;1.6 最大子数组问题1.7 算法设计&#xff08;最长回文串&#xff09;二、核心考点2.1 概述部分考点2.1.1 循环不变式loop-invariants2.1.2…

绪论 基本概念

数据结构 第一章 绪论 概念 数据data&#xff1a;是对客观事物的符号表示。在计算机科学中是指所有能输入到计算机中并被计算机程序处理的符号的总称。 数据元素(data element:是数据的基本单位&#xff0c;在计算机程序中通常作为一个整体进行考虑和处理。 数据对象(data …

软件测试面试题和简历模板(面试前准备篇)

一、问题预测 1、让简单介绍下自己&#xff08;这个不用说了每次面试开场&#xff09; 面试官&#xff0c;你好&#xff0c;我叫xxx&#xff0c;xx年本科毕业&#xff0c;从事软件测试将近3年的时间。在此期间做过一些项目也积累过一些经验&#xff0c;能够独立地完成软件测试…

经典的卷积神经网络(VGG,GoogLeNet等)

LeNet LeNet原文地址&#xff1a;https://ieeexplore.ieee.org/abstract/document/726791 Lenet是一个 7 层的神经网络&#xff08;不包含输入层&#xff09;&#xff0c;包含 3 个卷积层&#xff0c;2 个池化层&#xff0c;2 个全连接层。它的网络结构图如下所示&#xff1a…

广州华锐互动:VR虚拟课件互动教学平台在学校教育中的影响和作用

VR虚拟课件互动教学平台是广州华锐互动为各大高校开发的一款基于VR虚拟现实技术的教学工具&#xff0c;VR虚拟课件互动教学平台中包含了各类VR互动课件以及相关教学资源&#xff0c;学生可以自主进入平台汇总进行沉浸式的学习体验&#xff0c;帮助学生更好地理解学习相关教学内…