环境:OpenGL ES3,Android Studio2022.2.1.19

简介

  1. OpenGL:Open Graphic Library,一个2D和3D的图形库,可以用于视频渲染,图像/视频编辑等。目前常见的图像库还有Vulkan,WebGPU,Metal,Direct3D,OpenGL ES等。
  2. OpenGL ES(OpenGL for Embedded Systems):针对手机等嵌入式设备设计的2D,3D图像库

在屏幕上绘制一个三角形

  1. 新建一个Triangle类:
import android.opengl.GLES30
import android.opengl.Matrix
import java.nio.ByteBuffer
import java.nio.ByteOrder


class Triangle {
    private val vertexShaderCode = """
        #version 300 es
        uniform mat4 mTMatrix;
        layout(location = 0) in vec4 vPosition;
        void main() {
            gl_Position = mTMatrix * vPosition;
        }
    """.trimIndent()

    private val fragmentShaderCode = """
        #version 300 es
        precision mediump float;        // 浮点数精度中等
        uniform vec4 vColor;
        out vec4 fragColor;
        void main() {
            fragColor = vColor;
        }
    """.trimIndent()

    // 顶点数据 - 定义三角形的3个顶点
    private val triangleCoords = floatArrayOf(
        -0.5f, -0.5f, 0.0f,  // 左下角
        0.5f, -0.5f, 0.0f,  // 右下角
        0.0f,  0.5f, 0.0f   // 正上方
    )

    // 索引数据 - 定义如何连接顶点形成三角形
    private val indices = intArrayOf(
        0, 1, 2
    )

    private val color = floatArrayOf(0.5f, 0.5f, 0.5f, 1.0f) // 灰色
    private var translateMatrix = FloatArray(16)         // 模型变换矩阵,可以实现让三角形旋转,平移缩放等

    // OpenGL 对象
    private var vaoId = IntArray(1)   // VAO
    private var vboId = IntArray(1)   // VBO
    private var eboId = IntArray(1)   // EBO

    private var mProgram = 0
    private var mTMatrixHandle = 0
    private var mColorHandle = 0

    init {
        // 初始化变换矩阵为单位阵
        Matrix.setIdentityM(translateMatrix, 0)
        // 可以取消注释来测试变换
         //Matrix.translateM(translateMatrix, 0, 0.5f, 0.0f, 0f)
         //Matrix.rotateM(translateMatrix, 0, 45f, 0f, 0f, 1f)

        setupShaders()
        setupBuffers()
    }

    private fun setupShaders() {
        // 编译着色器
        val vertexShader = compileShader(GLES30.GL_VERTEX_SHADER, vertexShaderCode)
        val fragmentShader = compileShader(GLES30.GL_FRAGMENT_SHADER, fragmentShaderCode)

        // 创建着色器程序
        mProgram = GLES30.glCreateProgram()
        GLES30.glAttachShader(mProgram, vertexShader)
        GLES30.glAttachShader(mProgram, fragmentShader)
        GLES30.glLinkProgram(mProgram)

        // 检查链接状态
        val linkStatus = IntArray(1)
        GLES30.glGetProgramiv(mProgram, GLES30.GL_LINK_STATUS, linkStatus, 0)
        if (linkStatus[0] == 0) {
            val info = GLES30.glGetProgramInfoLog(mProgram)
            GLES30.glDeleteProgram(mProgram)
            throw RuntimeException("Program linking failed: $info")
        }

        // 清理着色器对象
        GLES30.glDeleteShader(vertexShader)
        GLES30.glDeleteShader(fragmentShader)

        // 获取uniform位置,方便向shader程序传入值
        mTMatrixHandle = GLES30.glGetUniformLocation(mProgram, "mTMatrix")
        mColorHandle = GLES30.glGetUniformLocation(mProgram, "vColor")
    }

    private fun setupBuffers() {
        // 1. 生成并绑定VAO
        GLES30.glGenVertexArrays(1, vaoId, 0)
        GLES30.glBindVertexArray(vaoId[0])

        // 2. 设置顶点数据到VBO
        val vertexBuffer = ByteBuffer.allocateDirect(triangleCoords.size * 4)
            .order(ByteOrder.nativeOrder())
            .asFloatBuffer()
            .apply {
                put(triangleCoords)
                position(0)
            }

        GLES30.glGenBuffers(1, vboId, 0)
        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vboId[0])
        // 将顶点数据从CPU传送到GPU
        GLES30.glBufferData(
            GLES30.GL_ARRAY_BUFFER,
            triangleCoords.size * 4,
            vertexBuffer,
            GLES30.GL_STATIC_DRAW
        )

        // 3. 设置顶点属性指针
        GLES30.glVertexAttribPointer(
            0,                   // layout location = 0
            3,                   // 每个顶点的分量数 (x, y, z)
            GLES30.GL_FLOAT,          // 数据类型
            false,          // 是否归一化
            3 * 4,              // 步长 (3个float * 4字节)
            0                   // 偏移量
        )
        GLES30.glEnableVertexAttribArray(0)

        // 4. 设置索引数据到EBO
        val indexBuffer = ByteBuffer.allocateDirect(indices.size * 4)
            .order(ByteOrder.nativeOrder())
            .asIntBuffer()
            .apply {
                put(indices)
                position(0)
            }

        GLES30.glGenBuffers(1, eboId, 0)
        GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, eboId[0])
        GLES30.glBufferData(
            GLES30.GL_ELEMENT_ARRAY_BUFFER,
            indices.size * 4,
            indexBuffer,
            GLES30.GL_STATIC_DRAW
        )

        // 5. 解绑VAO
        GLES30.glBindVertexArray(0)

        // 可选:解绑其他缓冲区
        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0)
        GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, 0)
    }

    private fun compileShader(type: Int, shaderCode: String): Int {
        val shader = GLES30.glCreateShader(type)
        GLES30.glShaderSource(shader, shaderCode)
        GLES30.glCompileShader(shader)

        // 检查编译状态
        val compileStatus = IntArray(1)
        GLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, compileStatus, 0)
        if (compileStatus[0] == 0) {
            val info = GLES30.glGetShaderInfoLog(shader)
            GLES30.glDeleteShader(shader)
            throw RuntimeException("Shader compilation failed: $info")
        }

        return shader
    }

    fun draw() {
        // 使用着色器程序
        GLES30.glUseProgram(mProgram)

        // 绑定VAO(这会自动绑定关联的VBO和EBO)
        GLES30.glBindVertexArray(vaoId[0])

        // 设置uniform变量
        GLES30.glUniformMatrix4fv(mTMatrixHandle, 1, false, translateMatrix, 0)
        GLES30.glUniform4fv(mColorHandle, 1, color, 0)

        // 使用EBO绘制
        GLES30.glDrawElements(
            GLES30.GL_TRIANGLES,        // 绘制模式
            indices.size,               // 索引数量
            GLES30.GL_UNSIGNED_INT,     // 索引类型
            0                     // 偏移量
        )

        // 解绑VAO
        GLES30.glBindVertexArray(0)
    }

    fun release() {
        // 删除OpenGL资源
        GLES30.glDeleteVertexArrays(1, vaoId, 0)
        GLES30.glDeleteBuffers(1, vboId, 0)
        GLES30.glDeleteBuffers(1, eboId, 0)
        GLES30.glDeleteProgram(mProgram)
    }
}
  1. 在OpenGL上下文中进行三角形的绘制
import Triangle
import android.content.Context
import android.opengl.GLES30
import android.opengl.GLSurfaceView
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10

class MGLSurfaceView : GLSurfaceView {
    constructor(context: Context) : super(context) {
        // 使用OpenGL ES3
        setEGLContextClientVersion(3)
        // 为当前view设置render
        setRenderer(MRender())

    }
}

class MRender : GLSurfaceView.Renderer{
    private lateinit var triangle : Triangle
    override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
        // 清空屏幕的默认颜色
        GLES30.glClearColor(0.0f, 0.5f, 0.0f, 1.0f);

        triangle = Triangle()

    }

    override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
        // 更新视口的大小
        GLES30.glViewport(0, 0, width, height)
    }

    // 每一帧都调用
    override fun onDrawFrame(gl: GL10?) {
        //清空当前缓冲区的颜色缓冲区
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)

        triangle.draw()
    }


}
  1. 主类:
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {
    private lateinit var glView: MGLSurfaceView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        glView = MGLSurfaceView(this)
        setContentView(glView)
    }
}