LearnOpenGL - Android OpenGL ES 3.0 绘制三角形

系列文章目录



1.前言

经过一段时间 OpenGL 的学习,我们已经掌握了如何使用 glwf 在桌面端绘制简单图形。现在让我们把目光投向移动端,看看如何在 Android 上使用 OpenGL 绘制图形。本文假设你对 Android 基础有所了解,并使用 Kotlin 编写示例 demo,项目的代码你可以在 Android_OpenGLES3_Demo 中找到。

本文参考了以下资料


2. OpenGL ES 3.0

OpenGL ES(OpenGl for Embedded System)是 Khronos 公司为移动端提供的 OpenGL 子集,用于在嵌入式设备和移动端设备中渲染 2D 和 3D 图形,它是为了适应低功耗设备需求而设计的。OpenGL ES 去掉了一些冗余的 API,保留了最核心有用的部分。

Android 支持多版 OpenGL ES API,从 Android 1.0 开始支持 OpenGL ES 1.0 和 1.1,从 Android 2.2 开始支持 OpenGL ES 2.0,从 Android 4.3 开始支持 OpenGL ES 3.0,从 Android 5.0 开始支持 OpenGL ES 3.11。Android 对应 OpenGL ES 的版本支持如下表所示

Android 版本OpenGL ES 版本
Android 1.0OpenGL ES 1.0/1.1
Android 2.2OpenGL ES 2.0
Android 4.3OpenGL ES 3.0
Android 5.0OpenGL ES 3.1

由于历史原因,和一些兼容性的问题,很多项目中都使用了 OpenGL ES 2.0 版本而不是 3.0。对比两个版本,它们有着一下区别

  • 性能方面,OpenGL ES 3.0 相比于 OpenGL ES 2.0 增加了一些新功能和优化,例如更大的缓冲区、更多的格式、更多的统一变量、实例渲染、像素缓冲对象和遮挡查询等。这些功能可以提高图形渲染的效率和质量,但也可能增加开发的复杂度和资源消耗。

  • 兼容性方面,OpenGL ES 3.0 是向后兼容 OpenGL ES 2.0 的,也就是说使用 2.0 编写的应用程序是可以在 3.0 中继续使用的。但是,并不是所有的设备都支持 OpenGL ES 3.0 ,所以如果要考虑跨平台的兼容性,可能还需要使用 OpenGL ES 2.0 或者做一些适配工作。

  • 易用性方面,OpenGL ES 3.0 和 OpenGL ES 2.0 都是可编程图形管线,开发者可以自己编写图形管线中的顶点着色器和片段着色器两个阶段的代码。这样可以提供更多的灵活性和创造力,但也需要更多的编程技巧和知识。相比于 OpenGL ES 1.x 系列的固定功能管线,OpenGL ES 2.0/3.0 不支持 OpenGL ES 1.x 支持的固定功能定点单元。

那么为什么选择 OpenGL ES 3.0 呢?很直白的一个理由:ES 3.0 中使用的 OpenGL Shader Language(GLSL)版本是 300,而 LearnOpenGL 教程中使用的是 GLSL 330,两者几乎没有差异,只需最小的学习成本就能够掌握。但如果使用 ES 2.0,那么你还得花一些时间来学习和适应不同版本 GLSL。

3 GLSurfaceView 和 GLSurfaceView.Render

在 Android 中,GLSurfaceView 是一个基础自 SurfaceView 的类,它是专门用于显示 OpenGL 渲染的内容。 而 GLSurfaceView.Render 则负责具体地渲染画面。简单来说,GLSurfaceView 类似于 glwf 中窗口的概念,它负责画面的显示、OpenGL Context 的管理、渲染线程的控制等等,而 GLSurfaceView.Render 则是 OpenGL API 调用的逻辑的抽象,你需要创建自己的 Render 来绘制图形。

还是上代码进行说明

class MyOpenGLSurfaceView(context: Context?, attrs :AttributeSet?) : GLSurfaceView(context, attrs) {
    constructor(context: Context?) : this(context, null)

    var render = MyOpenGLSurfaceRender()

    init {
        setEGLContextClientVersion(3)
        setRenderer(render)
    }
}
  • MyOpenGLSurfaceView 继承自 GLSurfaceView,注意要实现 GLSurfaceView(Context context, AttributeSet attrs) 构造函数,我们才能在 layout.xml 布局文件中直接使用 MyOpenGLSurfaceView,否则会出现 Binary XML file line # : Error inflating class 错误
  • init 中,setEGLContextClientVersion(3) 表示我们使用 OpenGL ES 3.0 版本
  • init 中,setRenderer 设置 Render
class MyOpenGLSurfaceRender : GLSurfaceView.Renderer {
   	// ...
    override fun onSurfaceCreated(p0: GL10?, p1: EGLConfig?) {
        //设置背景颜色
        GLES30.glClearColor(0.0f, 0.5f, 0.5f, 0.5f)

        //...
    }

    override fun onSurfaceChanged(p0: GL10?, width: Int, height: Int) {
        GLES30.glViewport(0, 0, width, height)
    }

    override fun onDrawFrame(p0: GL10?) {
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
        // ...
    }
}
  • MyOpenGLSurfaceRender 继承自 GLSurfaceView.Renderer,需要实现 onSurfaceCreatedonSurfaceChangedonDrawFrame
  • onSurfaceCreatedonSurfaceChangedonDrawFrame 一定会在同一个渲染线程中执行,你可以通过 Log 来确定这一点,例如 Log.d("xxx", "onSurfaceCreated thread " + Thread.currentThread())
  • onSurfaceCreated 当 surface 被创建或者重新创建时调用,在这个函数中放置一些创建资源的逻辑,例如设置 vbo 和 vao,编译 shader 等等。
  • onSurfaceChanged 当 surface 大小发生变化时调用,我们可以在这里设置 viewport
  • onDrawFrame 负责渲染当前帧。类比的话,可以将之前 LearnOpenGL 代码中 while 循环中的代码放置在这里

4. 绘制三角形

在 Android 中调用 OpenGL ES API 的方式与 OpenGL API 几乎无异,仿照 LearnOpenGL 中的代码,给出在 Kotlin 代码,只是有一些细节的地方需要注意,接下来讨论代码。

首先是工具类 Shader

class Shader(private val vertexShaderSource: String, private val fragmentShaderSource: String) {
    private val TAG = "Shader"
    var id : Int = 0

    fun prepareShaders(): Int{

        // compile vertex shader
        val vertexShader = createAndCompileShader(GLES30.GL_VERTEX_SHADER, vertexShaderSource)
        if(vertexShader == -1){
            return -1
        }

        // compile fragment shader
        val fragmentShader = createAndCompileShader(GLES30.GL_FRAGMENT_SHADER, fragmentShaderSource)
        if(fragmentShader == -1){
            return -1
        }

        id = createShaderProgram(vertexShader, fragmentShader)
        if(id == -1){
            return -1
        }

        return 0
    }

    private fun createAndCompileShader(type: Int, source: String): Int{
        val success: IntArray = intArrayOf(0)

        val shader = GLES30.glCreateShader(type)
        GLES30.glShaderSource(shader, source)
        GLES30.glCompileShader(shader)
        GLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, success, 0)
        if (success[0] == 0) {
            val log = GLES30.glGetShaderInfoLog(shader)
            Log.e(TAG, "compile vertex source failed.$log")
            GLES30.glDeleteShader(shader)
            return -1
        }
        return shader
    }

    private fun createShaderProgram(vertexShader: Int, fragmentShader: Int): Int{
        val success: IntArray = intArrayOf(0)

        val program = GLES30.glCreateProgram()
        GLES30.glAttachShader(program, vertexShader)
        GLES30.glAttachShader(program, fragmentShader)
        GLES30.glLinkProgram(program)

        GLES30.glGetProgramiv(program, GLES30.GL_LINK_STATUS, success, 0)
        if (success[0] == 0) {
            val log = GLES30.glGetShaderInfoLog(program)
            Log.e(TAG, "link shader program failed. $log")
            GLES30.glDeleteProgram(program)
            return -1
        }

        GLES30.glDeleteShader(vertexShader)
        GLES30.glDeleteShader(fragmentShader)

        return program
    }
}
  • 该类参考 我们自己的着色器类 - LearnOpenGL,输入 vertex 和 fragment shader 源码,负责 shader 的编译和链接工作
  • 调用 prepareShaders 来编译和链接 shader,如果失败将返回 -1;如果调用成功,使用 id 来引用 当前的 shader program

Triangle 类:

class Triangle {
    private val vertices = floatArrayOf(
        -0.5f, -0.5f, 0.0f, // left
        0.5f, -0.5f, 0.0f, // right
        0.0f, 0.5f, 0.0f  // top
    )

    val vaos: IntArray = intArrayOf(0)
    val vbos: IntArray = intArrayOf(0)

    fun prepareData(){
        // prepare vbo data
        val vertexBuffer = ByteBuffer.allocateDirect(vertices.size * Float.SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer()
        vertexBuffer.put(vertices)
        vertexBuffer.position(0)

        // generate vao and vbo
        GLES30.glGenVertexArrays(1, vaos, 0)
        GLES30.glGenBuffers(1, vbos, 0)

        GLES30.glBindVertexArray(vaos[0])

        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vbos[0])
        GLES30.glBufferData(
            GLES30.GL_ARRAY_BUFFER,
            Float.SIZE_BYTES * vertices.size,
            vertexBuffer,
            GLES30.GL_STATIC_DRAW
        )

        GLES30.glVertexAttribPointer(
            0, 3, GLES30.GL_FLOAT, false,
            3 * Float.SIZE_BYTES, 0
        )
        GLES30.glEnableVertexAttribArray(0)

        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0)
        GLES30.glBindVertexArray(0)
    }
}
  • 注意 vertexBuffer,由于 Kotlin 数据使用大端(BigEnd)存放,而 OpenGL 的数据为小端,因此在 Android 下使用 OpenGL 必须做大小端的转换。
  • 其他部分则是按部就班,创建 vbo 和 vao,设置 vbo 数据,设置 vao 属性等等,不再赘述
class MyOpenGLSurfaceRender : GLSurfaceView.Renderer {
    private val tri = Triangle()
    private val shader: Shader = Shader(
        VertexShaderSource.vertexShaderSource,
        FragmentShaderSource.fragmentShaderSource
    )

    override fun onSurfaceCreated(p0: GL10?, p1: EGLConfig?) {
        GLES30.glClearColor(0.0f, 0.5f, 0.5f, 0.5f)

        tri.prepareData()
        shader.prepareShaders()
    }

    override fun onSurfaceChanged(p0: GL10?, width: Int, height: Int) {
        GLES30.glViewport(0, 0, width, height)
    }

    override fun onDrawFrame(p0: GL10?) {
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)

        GLES30.glUseProgram(shader.id)
        GLES30.glBindVertexArray(tri.vaos[0])

        GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 3)
    }
}
  • onSurfaceCreated 函数中,创建三角形需要的数据和 shader
  • onDrawFrame 函数中,glUseProgram 使用编译好的 shader id;glBindVertexArray 使用设置好的 vao 进行渲染
  • 注意一点,所有 GLES30. 的函数调用都要在渲染线程中操作,也就是说这些函数必须在 onSurfaceCreatedonSurfaceChangedonDrawFrame 中调用,否则渲染失败。

最后,你可以在布局文件中,直接引用 MyOpenGLSurfaceView 作为控件,就像 Button 一样方便,例如

    <com.xinging.opengltest.MyOpenGLSurfaceView
        android:layout_width="match_parent"
        android:layout_height="500dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        />

在这里插入图片描述

5. 总结

本文介绍了如何在 Android 下使用 OpenGL ES 3.0 绘制三角形。通过自定义 GLSurfaceView 和 GLSurfaceView.Render ,在 Android 下调用 OpenGL API 来实现绘图;本文所有代码在 Android_OpenGLES3_Demo


参考

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Android OpenGL ES 3.0是一个强大的图形渲染API,用于开发Android平台上的高性能3D应用程序和游戏。从入门到精通OpenGL ES 3.0需要系统性的学习教程,以下是一个简要的学习路径: 1. 基础知识:首先需要了解OpenGL ES 3.0的基础知识,包括图形渲染管线、坐标系、顶点和片元着色器等。可以通过阅读相关的教程、书籍或者在线资源来获得这方面的知识。 2. 环境搭建:学习OpenGL ES 3.0之前,需要先搭建好学习环境。可以下载安装Android Studio和相关的开发工具,以及配置好OpenGL ES 3.0的开发环境。 3. 学习资源:寻找一些高质量的学习资源,如教程、书籍或者在线课程。可以选择一些经典的OpenGL ES 3.0教程,其中包括基础知识、实例代码和案例分析等。 4. 实践练习:学习OpenGL ES 3.0最重要的一点就是不断地进行实践练习。可以按照教程中的示例代码,逐步实现一些简单的图形渲染效果。通过实践来加深对OpenGL ES 3.0的理解,掌握各种绘制技术和渲染效果。 5. 深入研究:在掌握了基础知识和实践经验之后,可以进一步深入研究OpenGL ES 3.0的高级特性和扩展功能。包括纹理映射、着色器编程、光照和阴影效果等。可以参考一些专业书籍和高级教程来进一步提升自己的技术水平。 6. 项目实践:最后一步是通过实际项目的实践来巩固所学的知识。可以尝试开发一些简单的游戏或者应用程序,利用OpenGL ES 3.0来实现复杂的图形渲染效果。通过实际项目的经验,可以进一步提升自己的技术能力和解决问题的能力。 总之,学习Android OpenGL ES 3.0需要系统性的学习教程,并通过不断实践和项目实践来提升自己的技术水平。只有在不断学习和实践中,才能逐步精通OpenGL ES 3.0并运用到实际开发中。 ### 回答2: Android OpenGL ES 3.0 是一种强大的图形渲染技术,用于在Android设备上创建高性能的3D图形和特效。要系统地学习和掌握Android OpenGL ES 3.0,您可以按照以下步骤: 1. 学习基础知识:首先,您需要了解计算机图形学和OpenGL ES的基本概念。这包括了解3D图形渲染的原则、OpenGL ES的架构、状态机模型等。可以通过阅读相关的教材或者参考互联网上的优质教程来学习这些内容。 2. 编程环境设置:为了开始使用Android OpenGL ES 3.0,您需要配置开发环境。这包括安装和配置Android开发工具包(Android SDK)以及适当的集成开发环境(如Android Studio)。确保您的开发环境正确设置,并具备OpenGL ES 3.0的支持。 3. 学习OpenGL ES API:学习OpenGL ES 3.0的API是掌握该技术的关键。您需要理解OpenGL ES的基本绘图函数、顶点和片段着色器编程、纹理映射等概念。可以通过查阅OpenGL ES 3.0的官方文档或参考书籍来学习这些API。 4. 实践项目:通过实践项目来巩固所学的知识。您可以从最简单的项目开始,如画一个三角形,然后逐步扩展,添加更多的图形对象和特效。这样您可以深入了解OpenGL ES 3.0的使用和性能优化。 5. 学习高级主题:一旦掌握了基础知识,您可以进一步学习OpenGL ES 3.0的高级主题。这可能包括光照、阴影、投影、深度测试和其他高级特性。这些主题的学习可以通过参考更高级的教程、专业书籍或者参与相关论坛和社区来深入研究。 6. 性能优化:了解如何优化OpenGL ES 3.0的性能也是非常重要的。您可以学习如何使用缓冲区对象、顶点缓冲区对象(VBO)、纹理压缩和其他优化技术来提高应用程序的帧率和响应速度。 总而言之,要系统学习和掌握Android OpenGL ES 3.0,您需要深入理解计算机图形学和OpenGL ES的原理,学习API的使用和性能优化技术,并通过实践项目来强化您的理解和应用能力。这需要坚持不懈的学习和实践,但通过这样的系统学习,您将能够成为一名Android OpenGL ES 3.0的专家。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值