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        |
+---------------------+         +-------------------+         +------------------+
相关推荐
〆、风神1 小时前
EasyExcel 数据字典转换器实战:注解驱动设计
android·java·注解
stevenzqzq1 小时前
Android studio xml布局预览中 Automotive和Autotive Distant Display的区别
android·xml·android studio
QING6182 小时前
Kotlin commonPrefixWith用法及代码示例
android·kotlin·源码阅读
QING6182 小时前
Kotlin groupByTo用法及代码示例
android·kotlin·源码阅读
兰琛7 小时前
Compose组件转换XML布局
android·xml·kotlin
水w9 小时前
【Android Studio】解决报错问题Algorithm HmacPBESHA256 not available
android·开发语言·android studio
VX_CXsjNo110 小时前
免费送源码:Java+SSM+Android Studio 基于Android Studio游戏搜索app的设计与实现 计算机毕业设计原创定制
java·spring boot·spring·游戏·eclipse·android studio·android-studio
隐-梵11 小时前
Android studio进阶教程之(二)--如何导入高德地图
android·ide·android studio
南宫生11 小时前
Java迭代器【设计模式之迭代器模式】
java·学习·设计模式·kotlin·迭代器模式
Kika写代码12 小时前
【Android】界面布局-线性布局LinearLayout-例子
android·gitee