美文网首页
Android OpenGL ES 基础原理

Android OpenGL ES 基础原理

作者: 我爱田Hebe | 来源:发表于2023-01-05 14:02 被阅读0次

    由于5G的发展,现在音视频越来越流行,我们的生活已经完全被抖音、视频号、B站等视频应用所包围。从这一点也能看到音视频的重要性。

    而作为一名Android开发者,是时候来了解一下关于Android方面渲染方面的知识。音视频的应用都离不开OpenGL ES的处理。对于视频的高效渲染与融合操作是至关重要的。

    上面的这种动画相信大家都很熟悉,类似的动画在各大直播间都会出现。那么这炫酷的原理实现内部都离不开OpenGL ES的高效渲染与更高级的融合处理。

    多的就先不说了,现在我们就来认识一下OpenGL ES。

    基本概念

    Android可以通过OpenGL来支持高效的2D和3D图形,同时OpenGL是一种跨平台的图形API。其中OpenGL ES是OpenGL规范的一种形式,适用于嵌入式设备。

    Android支持多种版本的OpenGL ES API:

    1. 1.0&1.1 Android1.0及以上
    2. 2.0 Android2.2及以上
    3. 3.0 Android4.3及以上
    4. 3.1 Android5.0及以上

    我们的内容主要是基于OpenGL ES 2.0来进行,也就是进行二维的图形渲染。

    坐标

    在Android中通过Canvas进行绘制的坐标原点是在屏幕的左上角,同时它的坐标范围都是以屏幕的宽高来定义。

    OpenGL ES则不同,它是以绘制区域的中心为原点,同时它的坐标范围是-1.0 ~ 1.0。也就是说它的坐标都是基于可绘制区域进行比例换算。并不是真正的值。

    形状与方向

    在OpenGL ES中,绘制的形状都是以三角形为基础,也就是说它必须由3个或者以上的点来进行绘制。所以它是由多个三角形进行组合成特定的形状,进过不同程度的交叉与重叠来达到不同的形状。

    例如以二维空间来定义

    同时还存在绘制顺序,所谓的绘制顺序也是以三角形为基础,通过三角形的三个顶点进行环绕绘制。默认是以逆时针进行绘制。

    对于二维图像可能绘制顺序没那么重要,但是对于三维图像就很重要了。三维图像是由于视角的问题,会存在正反面的关系。

    例如一款3D游戏,游戏中有一辆汽车,正对我们的为正面,我们看不到的一面为反面,虽然反面看不到,但OpenGL ES还是会进行绘制。为了对反面不做无用的绘制,可以使用OpenGL的面剔除操作,该操作允许渲染管道忽略形状的反面,这样就可以节约时间与内存并缩短处理周期。

    那么这里的正面就是沿逆时针绘制的面。

    GL程序

    OpenGL ES渲染需要借助GL程序,通过创建GL程序、顶点与片段着色器、加载着色器代码、编译代码、应用、数据填充,最终进行渲染。

    在创建GL程序之前,我们先来了解顶点着色器与片段着色器。

    着色器源码

    GL程序渲染的过程中需要确认顶点位置与对应的颜色,而这两个部分分别借助于顶点与片段着色器来实现。

    // 顶点
    private const val VERTEX_SHADER_SOURCE =
        "attribute vec4 a_Position;\n" +
                "void main() {\n" +
                "   gl_Position = a_Position;\n" +
                "}"
    // 片段
    private const val FRAGMENT_SHADER_SOURCE =
        "precision mediump float;\n" +
                "void main() {\n" +
                "   gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);\n" +
                "}"
    

    上面分别是顶点着色器与片段着色器的源码。attribute是变量修饰符,用的比较多的是以下三种。

    1. attribute:表示只读的顶点数据,应用在顶点着色器中。可修饰声明顶点、颜色等数据
    2. uniform:顶点着色器与片段着色器的共享数据,在程序中值的不变的,初始值由程序外部传入
    3. varying:顶点着色器输入,片段着色器输出;由顶点着色器传输给片段着色器中的插值数据

    vec4是变量类型,变量主要有以下几种

    除此之外还有数组与结构体,用来实现复杂的数据类型。

    我们将定义的a_Position赋值给gl_Position,这样GL程序就会使用定义的顶点数据进行渲染。

    同理gl_FragColor也是一样,代表对应渲染顶点位置时的颜色,这里直接写死了一个蓝色。

    创建GL程序

    首先我们要创建GL程序

    // 创建GL程序
    val programId = GLES20.glCreateProgram()
    

    拿到programId,为之后的程序操作做准备

    添加顶点与片段着色器

    首先创建顶点与片段着色器

    // 创建顶点与片段着色器
    val vertexShader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER)
    val fragmentShader = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER)
    

    将之前定义的着色器源码加载到着色器中

    // 加载顶点与片段着色器代码
    GLES20.glShaderSource(vertexShader, VERTEX_SHADER_SOURCE)
    GLES20.glShaderSource(fragmentShader, FRAGMENT_SHADER_SOURCE)
    

    通过GL程序进行编译

    // 编译顶点与片段着色器代码
    GLES20.glCompileShader(vertexShader)
    GLES20.glCompileShader(fragmentShader)
    

    最后将编译完的顶点与片段着色器添加到指定的GL程序中,也就是我们第一步创建的GL程序

    // 添加到GL程序中
    GLES20.glAttachShader(programId, vertexShader)
    GLES20.glAttachShader(programId, fragmentShader)
    

    链接与应用

    着色器装载完毕之后,剩下的就是将我们创建的GL程序进行链接与应用

    // 链接GL程序
    GLES20.glLinkProgram(programId)
    // 应用GL程序
    GLES20.glUseProgram(programId)
    

    这样我们的GL程序才算真正的完成了,下面就是数据的填充与渲染操作。

    数据填充

    在顶点着色器源码定义中,我们定义了a_Position变量,需要我们从外部将数据添加到a_Position,这样才能真正应用到gl_Position中。

    下面我们来进行数据的填充

    首先我们定义一个填充的顶点数据

    private val mVertexData = floatArrayOf(0.0f, 0.5f, -0.5f, -0.5f, 0.5f, -0.5f, -0.5f, 1.0f, -1.0f, 0.5f, 0f, 0.5f)
    
    private const val VERTEX_DIMENSION_SIZE = 2
    

    顶点维度是二维,所以这里mVertexData中定义了6个顶点数据,也就是2个三角形的数据。第一个在左上角,第二个在中间。

    我们将数据添加到Buffer中,并将索引位置定义到开始位置0

    // 加载顶点数据
    val vertexBuffer = ByteBuffer.allocateDirect(mVertexData.size * Float.SIZE_BYTES)
        .order(ByteOrder.nativeOrder())
        .asFloatBuffer()
    vertexBuffer.put(mVertexData)
    vertexBuffer.position(0)
    

    继续获取a_Position在GL程序中的参数位置

    // 获取对应参数位置
    val positionLocation = GLES20.glGetAttribLocation(programId, "a_Position")
    

    这一点与我们平常的编程不同,在GL程序中,如果要获取其中的变量,我们并不是直接拿到这个变量的本身,而是通过拿到它在GL中对应的位置索引,然后通过位置索引进行变量操作。

    获取之后还要进行启动激活

    // 启动对应参数位置
    GLES20.glEnableVertexAttribArray(positionLocation)
    

    最后就是填充

    // 填充顶点数据
    GLES20.glVertexAttribPointer(positionLocation, VERTEX_DIMENSION_SIZE, GLES20.GL_FLOAT, false, 0, vertexBuffer)
    

    VERTEX_DIMENSION_SIZE代表的是填充一个二维的顶点数据,类型为GLES20.GL_FLOAT。

    渲染

    GL程序与顶点数据都已经准备完毕,接下来是最后一步渲染。

    在渲染之前我们需要对屏幕进行清屏操作,默认屏幕是黑色,我们可以指定需要的清屏后的颜色

    // 设置清屏颜色
    GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f)
    // 清屏处理
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
    

    这里指定清屏颜色为白色

    在渲染之前设置渲染的视图位置与大小,最后再进行渲染。

    // 设置视图大小
    GLES20.glViewport(0, 0, mSurfaceViewWidth, mSurfaceViewHeight)
    // 渲染
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, mVertexData.size / VERTEX_DIMENSION_SIZE)
    

    在渲染操作中使用了GLES20.GL_TRIANGLES,这是一种渲染方式,它代表会以每3个顶点为一组的方式进行三角形渲染,所以我们运行之后就能看到2个三角形。

    参数0与mVertexData.size / VERTEX_DIMENSION_SIZE代表有6个顶点且从第0个位置开始,也就是第一个顶点位置。

    最后我们再来看下运行后的效果

    大功告成,与我们的预期完全一样。

    后续会继续聊聊颜色的动态填充、渲染的三种方式与纹理的操作,敬请期待。

    源码地址:OpenGL ES

    推荐

    android_startup: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。

    AwesomeGithub: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。

    flutter_github: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。

    android-api-analysis: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。

    daily_algorithm: 每日一算法,由浅入深,欢迎加入一起共勉。

    作者:午后一小憩
    链接:https://juejin.cn/post/7177033572824383543

    相关文章

      网友评论

          本文标题:Android OpenGL ES 基础原理

          本文链接:https://www.haomeiwen.com/subject/zchjcdtx.html