OpenGL ES->GLSurfaceView绘制图形的流程

自定义View代码

kotlin 复制代码
class MyGLSurfaceView(context: Context, attrs: AttributeSet) : GLSurfaceView(context, attrs), GLSurfaceView.Renderer {
    var mProgrem = 0
    init {
        // 设置 OpenGL ES 3.0 版本
        setEGLContextClientVersion(3)
        // 设置当前类为渲染器, 注册回调接口的实现类
        setRenderer(this)
        // 设置渲染模式, 仅在需要重新绘制时才进行渲染,以节省资源
        renderMode = RENDERMODE_WHEN_DIRTY
    }

    override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
        // 当 Surface 创建时调用, 进行 OpenGL ES 环境的初始化操作, 设置清屏颜色为黑色 (Red=0, Green=0, Blue=0, Alpha=1)
        GLES30.glClearColor(0.0f, 0.0f, 0.0f, 1.0f)

        // 初始化缓冲区
        initializeBuffers()
        // 顶点着色器代码
        val vertexShaderCode = """#version 300 es
                                layout (location = 0) in vec4 aPosition;
                                
                                void main() {
                                  gl_Position = aPosition;
                                }""".trimIndent()

        // 片段着色器代码
        val fragmentShaderCode = """#version 300 es
                precision mediump float;
                uniform vec4 vColor;
                out vec4 fragColor;
                
                void main() {
                  fragColor = vColor;
                }""".trimIndent()

        // 初始化着色器
        mProgrem = initializeShaders(vertexShaderCode, fragmentShaderCode)

    }

    override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
        // 当 Surface 尺寸发生变化时调用,例如设备的屏幕方向发生改变, 设置视口为新的尺寸,视口是指渲染区域的大小
        GLES30.glViewport(0, 0, width, height)
    }

    override fun onDrawFrame(gl: GL10?) {
        // 每一帧绘制时调用, 清除颜色缓冲区和深度缓冲区
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT or GLES30.GL_DEPTH_BUFFER_BIT)
        // 绘制图形
        drawSomething(mProgrem)
    }

    fun initializeBuffers(){
        // 1. 准备菱形的顶点数据
        val vertices = floatArrayOf(
            0.0f,  0.5f, 0.0f,  // 顶点 1 (顶部)
            -0.5f,  0.0f, 0.0f,  // 顶点 2 (左侧)
            0.5f,  0.0f, 0.0f,  // 顶点 4 (右侧)
            0.0f, -0.5f, 0.0f   // 顶点 3 (底部)
        )

        // 2. 分配顶点数据的直接字节缓冲区
        val vertexBuffer = ByteBuffer.allocateDirect(vertices.size * 4) // 每个 float 占 4 个字节, 指定缓冲区所需要的字节数
            .order(ByteOrder.nativeOrder()) // 指定字节顺序(确定是大端还是小端), 默认情况下为小端, 即低地址存放低位数据, 高地址存放高位数据
            .asFloatBuffer() // 转换为FloatBuffer, 因为顶点数据是float类型
        vertexBuffer.put(vertices) // 将顶点数据放入 FloatBuffer
        vertexBuffer.position(0) // 在将数据放入缓冲区后,位置指针会指向缓冲区的末尾。重置位置指针为 0,使得在后续操作中可以从缓冲区的开始位置读取数据

        // 3. 创建顶点缓冲区对象(Vertex Buffer Object, VBO)
        val vbo = IntArray(1)
        GLES30.glGenBuffers(1, vbo, 0) // 生成一个缓冲区对象ID,并存储在数组 vbo 中,存放位置为0
        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vbo[0]) // 绑定生成的顶点缓冲区对象,使其成为当前缓冲区操作的目标

        // 4. 将顶点数据复制到缓冲区中
        GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, vertices.size * 4, FloatBuffer.wrap(vertices), GLES30.GL_STATIC_DRAW)


    }
    
    fun drawSomething(program : Int){
        // 8. 获取顶点数据的位置, 并使用该位置的数据
        val positionHandle = GLES30.glGetAttribLocation(program, "aPosition")
        GLES30.glEnableVertexAttribArray(positionHandle)
        GLES30.glVertexAttribPointer(positionHandle, 3, GLES30.GL_FLOAT, false, 0, 0)

        // 9. 设置片段着色器的颜色
        val colorHandle = GLES30.glGetUniformLocation(program, "vColor")
        GLES30.glUniform4f(colorHandle, 1.0f, 0.0f, 0.0f, 1.0f) // 红色
        
        // 10. 绘制菱形
        GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4)

        // 11. 禁用顶点数据
        GLES30.glDisableVertexAttribArray(positionHandle)
    }

    fun initializeShaders(vertexShaderCode: String, fragmentShaderCode: String) : Int {
        // 5. 创建和编译顶点着色器程序
        val vertexShader = GLES30.glCreateShader(GLES30.GL_VERTEX_SHADER)
        GLES30.glShaderSource(vertexShader, vertexShaderCode)
        GLES30.glCompileShader(vertexShader)

        // 6. 创建和编译片段着色器程序
        val fragmentShader = GLES30.glCreateShader(GLES30.GL_FRAGMENT_SHADER)
        GLES30.glShaderSource(fragmentShader, fragmentShaderCode)
        GLES30.glCompileShader(fragmentShader)

        // 7. 创建着色器程序, 将顶点着色器和片段着色器链接到一起
        val program = GLES30.glCreateProgram()
        GLES30.glAttachShader(program, vertexShader)
        GLES30.glAttachShader(program, fragmentShader)
        GLES30.glLinkProgram(program)
        GLES30.glUseProgram(program)
        return program
    }
}

总结

    1. 准备菱形的顶点数据:需要绘制的顶点数据存放在Float的数组中
    1. 分配顶点数据的直接字节缓冲区,使用 ByteBuffer 直接与底层硬件进行数据传输,避免了不必要的内存拷贝,从而提高性能
    • 常见内存拷贝图

      kotlin 复制代码
      +--------------+          +--------------+          +--------------+
      |   float[]    |  ---->   |  FloatBuffer |  ---->   |    GPU       |
      |  (Java Heap) |          |  (Java Heap) |          |   (Native)   |
      +--------------+          +--------------+          +--------------+
    • 使用 ByteBuffer

      kotlin 复制代码
      +--------------+          +--------------+          +--------------+
      |   float[]    |  ---->   | ByteBuffer   |  ---->   |    GPU       |
      |  (Java Heap) |          | (Direct)     |          |   (Native)   |
      +--------------+          +--------------+          +--------------+
    1. 创建顶点缓冲区对象(Vertex Buffer Object, VBO)
    1. 将顶点数据复制到缓冲区中
    1. 创建和编译顶点着色器程序

      kotlin 复制代码
      // 定义顶点着色器代码的多行字符串
      val vertexShaderCode = """
          #version 300 es                // 指定使用 OpenGL ES 3.0 版本
          layout (location = 0) in vec4 aPosition;  // 定义输入变量 aPosition,并指定其位置为 0
      
          void main() {                  // 主函数
              gl_Position = aPosition;   // 将输入的顶点位置赋值给 gl_Position,进行顶点变换
          }
      """.trimIndent()                   // 去除多行字符串的公共缩进
    1. 创建和编译片段着色器程序

      kotlin 复制代码
      // 定义片段着色器代码的多行字符串
      val fragmentShaderCode = """
          #version 300 es                  // 指定使用 OpenGL ES 3.0 版本
          precision mediump float;         // 设置默认的浮点数精度为中等精度
          uniform vec4 vColor;             // 定义一个 uniform 变量 vColor,用于传递颜色
          out vec4 fragColor;              // 定义一个输出变量 fragColor,用于存储片段的颜色
      
          void main() {                    // 主函数
              fragColor = vColor;          // 将 uniform 变量 vColor 的值赋给输出变量 fragColor
          }
      """.trimIndent()                     // 去除多行字符串的公共缩进
    1. 创建着色器程序, 将顶点着色器和片段着色器链接到一起
    1. 获取顶点数据的位置, 并使用该位置的数据
    1. 设置片段着色器的颜色
    1. 绘制菱形
    1. 禁用顶点数据
kotlin 复制代码
+---------------------+         +-------------------+         +------------------+
| Application (Java)  |  ---->  |   OpenGL Driver   |  ---->  |      GPU         |
+---------------------+         +-------------------+         +------------------+
| float[] vertices    |         |   Bind VBO        |         |  Process VBO     |
|                     |         |   Upload Data     |         |  Use Data        |
+---------------------+         +-------------------+         +------------------+
相关推荐
浩宇软件开发9 分钟前
Android开发,使用TabLayout+ViewPager2实现校园健康安全宣传
android studio·android开发
Dnelic-3 小时前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记
Eastsea.Chen5 小时前
MTK Android12 user版本MtkLogger
android·framework
长亭外的少年12 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
JIAY_WX12 小时前
kotlin
开发语言·kotlin
建群新人小猿15 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神16 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri
兰琛16 小时前
20241121 android中树结构列表(使用recyclerView实现)
android·gitee
Y多了个想法17 小时前
RK3568 android11 适配敦泰触摸屏 FocalTech-ft5526
android·rk3568·触摸屏·tp·敦泰·focaltech·ft5526
NotesChapter18 小时前
Android吸顶效果,并有着ViewPager左右切换
android