当前位置: 首页 > news >正文

OpenGL ES -> GLSurfaceView绘制点、线、三角形、正方形、圆(索引法绘制)

XML文件

<?xml version="1.0" encoding="utf-8"?>
<com.example.myapplication.MyGLSurfaceView
	xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

自定义GLSurfaceView代码

class MyGLSurfaceView(context: Context, attrs: AttributeSet) : GLSurfaceView(context, attrs) {
    private var mRenderer = MyGLRenderer()

    init {
        // 设置 OpenGL ES 3.0 版本
        setEGLContextClientVersion(3)
        setRenderer(mRenderer)
        // 设置渲染模式, 仅在需要重新绘制时才进行渲染,以节省资源
        renderMode = RENDERMODE_WHEN_DIRTY
    }
}

自定义GLSurfaceView.Renderer代码

class MyGLRenderer : GLSurfaceView.Renderer {
    private var mDrawData: DrawData? = null

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

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

    override fun onDrawFrame(gl: GL10?) {
        // 每一帧绘制时调用, 清除颜色缓冲区
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
        mDrawData?.drawSomething()
    }
}

GLSurfaceView.Renderer需要的绘制数据

class DrawData {
    private var mProgram : Int = -1
    private var NO_OFFSET = 0
    private var VERTEX_POS_DATA_SIZE = 3
    // 最终变化矩阵
    private val mMVPMatrix = FloatArray(16)
    // 投影矩阵
    private val mProjectionMatrix = FloatArray(16)
    // 相机矩阵
    private val mViewMatrix = FloatArray(16)

    private var mViewPortRatio = 1f

    // 1. 准备顶点数组,分配内存
    val vertex = floatArrayOf(
        -0.5f,  0.5f, 0.0f, // 左上
        -0.5f, -0.5f, 0.0f, // 左下
        0.5f, 0.5f, 0.0f, // 右上
        0.5f, -0.5f, 0.0f, // 右下
    )

    val vertexBuffer = ByteBuffer.allocateDirect(vertex.size * 4) // 分配直接内存
        .order(ByteOrder.nativeOrder()) // 使用小端, 即低地址存放低位数据, 高地址存放高位数据
        .asFloatBuffer()

    // 2. 创建顶点索引数组,分配内存
    val indices = run {
        val indices = mutableListOf<Int>()

        for (i in 0..(vertex.size / 3) -1) {
            indices.add(i)
        }

        indices.toIntArray()
    }

    val indexBuffer = ByteBuffer.allocateDirect(indices.size * 4)
        .order(ByteOrder.nativeOrder())
        .asIntBuffer()

    // 3. 创建顶点缓冲区对象和索引缓冲区对象
    fun initVertexBuffer(){
        // 创建顶点缓冲区对象(Vertex Buffer Object, VBO), 并上传顶点数组到缓冲区对象中
        vertexBuffer.put(vertex) // 将顶点数据放入 FloatBuffer
        vertexBuffer.position(0) // 在将数据放入缓冲区后,位置指针会指向缓冲区的末尾。重置位置指针为 0,使得在后续操作中可以从缓冲区的开始位置读取数据
        val vbo = IntArray(1)
        GLES30.glGenBuffers(1, vbo, 0) // 生成一个缓冲区对象ID,并存储在数组 vbo 中,存放位置为0
        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vbo[0]) // 绑定生成的顶点缓冲区对象,使其成为当前缓冲区操作的目标
        GLES30.glBufferData(
            GLES30.GL_ARRAY_BUFFER,
            vertex.size * 4, // 数据总字节数 = 顶点数 * Float占4字节
            vertexBuffer,
            GLES30.GL_STATIC_DRAW
        )

        // 创建索引缓冲区对象(Index Buffer Object, VBO), 并上传顶点索引数组到缓冲区对象中
        indexBuffer.put(indices)
        indexBuffer.position(0)
        val ibo = IntArray(1)
        GLES30.glGenBuffers(1, ibo, 0)
        GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, ibo[0])
        GLES30.glBufferData(
            GLES30.GL_ELEMENT_ARRAY_BUFFER,
            indices.size * 4, // 数据总字节数 = 索引数 * Int占4字节
            indexBuffer,
            GLES30.GL_STATIC_DRAW
        )
    }

    // 4. 初始化着色器程序
    fun initShader()  {
        val vertexShaderCode = """#version 300 es
                layout (location = 0) in vec4 aPosition;
                uniform mat4 uMVPMatrix; // 新增投影矩阵
                
                void main() {
                    gl_Position = uMVPMatrix * aPosition; // 应用投影变换
                }""".trimIndent()       // 顶点着色器代码

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

        // 加载顶点着色器和片段着色器, 并创建着色器程序
        val vertexShader = LoadShaderUtil.loadShader(GLES30.GL_VERTEX_SHADER, vertexShaderCode)
        val fragmentShader = LoadShaderUtil.loadShader(GLES30.GL_FRAGMENT_SHADER, fragmentShaderCode)
        mProgram = GLES30.glCreateProgram()
        GLES30.glAttachShader(mProgram, vertexShader)
        GLES30.glAttachShader(mProgram, fragmentShader)
        GLES30.glLinkProgram(mProgram)
        GLES30.glUseProgram(mProgram)
    }

    // 5. 计算变换矩阵
    fun computeMVPMatrix(width: Float, height: Float) {
        // 正交投影矩阵
        takeIf { width > height }?.let {
            mViewPortRatio = width / height
            Matrix.orthoM(
                mProjectionMatrix, // 透视投影矩阵
                NO_OFFSET, // 偏移量
                -mViewPortRatio, // 近平面的坐标系左边界
                mViewPortRatio, // 近平面的坐标系右边界
                -1f, // 近平面的坐标系的下边界
                1f, // 近平面坐标系的上边界
                0f, // 近平面距离相机距离
                1f // 远平面距离相机距离
            )
        } ?: run {
            mViewPortRatio = height / width
            Matrix.orthoM(
                mProjectionMatrix, // 透视投影矩阵
                NO_OFFSET, // 偏移量
                -1f, // 近平面坐标系左边界
                1f, // 近平面坐标系右边界
                -mViewPortRatio, // 近平面坐标系下边界
                mViewPortRatio, // 近平面坐标系上边界
                0f, // 近平面距离相机距离
                1f // 远平面距离相机距离
            )
        }

        // 设置相机矩阵
        // 相机位置(0f, 0f, 1f)
        // 物体位置(0f, 0f, 0f)
        // 相机方向(0f, 1f, 0f)
        Matrix.setLookAtM(
            mViewMatrix, // 相机矩阵
            NO_OFFSET, // 偏移量
            0f, // 相机位置x
            0f, // 相机位置y
            1f, // 相机位置z
            0f, // 物体位置x
            0f, // 物体位置y
            0f, // 物体位置z
            0f, // 相机上方向x
            1f, // 相机上方向y
            0f // 相机上方向z
        )

        // 最终变化矩阵
        Matrix.multiplyMM(
            mMVPMatrix, // 最终变化矩阵
            NO_OFFSET, // 偏移量
            mProjectionMatrix, // 投影矩阵
            NO_OFFSET, // 投影矩阵偏移量
            mViewMatrix, // 相机矩阵
            NO_OFFSET // 相机矩阵偏移量
        )

        val matrixHandler = GLES30.glGetUniformLocation(mProgram, "uMVPMatrix")
        GLES30.glUniformMatrix4fv(matrixHandler, 1, false, mMVPMatrix, NO_OFFSET)
    }
    
    // 6. 使用着色器程序绘制图形
    fun drawSomething(){
        GLES30.glLineWidth(50.0f)
        // 获取顶点数据的位置, 并使用该位置的数据
        val positionHandle = GLES30.glGetAttribLocation(mProgram, "aPosition")
        GLES30.glEnableVertexAttribArray(positionHandle)
        GLES30.glVertexAttribPointer(positionHandle, VERTEX_POS_DATA_SIZE, GLES30.GL_FLOAT, false, 0, 0)

        // 设置片段着色器的颜色
        val colorHandle = GLES30.glGetUniformLocation(mProgram, "vColor")
        GLES30.glUniform4f(colorHandle, 1.0f, 0.5f, 0.5f, 1.0f) // 红色
        // 绘制图形
        GLES30.glLineWidth(30f)
        GLES30.glDrawElements(
            GLES30.GL_TRIANGLE_STRIP, // 绘制方式
            indices.size, // 顶点索引数
            GLES30.GL_UNSIGNED_INT, // 索引数据类型
            0 // 索引数据偏移量
        )
        GLES30.glDisableVertexAttribArray(positionHandle)
    }
}

object LoadShaderUtil{
    // 创建着色器对象
    fun loadShader(type: Int, source: String): Int {
        val shader = GLES30.glCreateShader(type)
        GLES30.glShaderSource(shader, source)
        GLES30.glCompileShader(shader)
        return shader
    }
}

绘制点、线、三角形、正方形、圆

绘制点GLES30.GL_POINTS

// 1. 准备顶点数据
val vertex = floatArrayOf(
    -0.5f,  0.5f, 0.0f, // 左上
    -0.5f, -0.5f, 0.0f, // 左下
    0.5f, 0.5f, 0.0f, // 右上
    0.5f, -0.5f, 0.0f, // 右下
)
// 2. 准备顶点索引
val indices = run {
    val indices = mutableListOf<Int>()

    for (i in 0..(vertex.size / 3) -1) {
        indices.add(i)
    }

    indices.toIntArray()
}

GLES30.glDrawElements(
    GLES30.GL_TRIANGLE_STRIP, // 绘制方式
    indices.size, // 顶点索引数
    GLES30.GL_UNSIGNED_INT, // 索引数据类型
    0 // 索引数据偏移量
)
  • 效果图
    在这里插入图片描述

绘制线

两个点绘制一条线间隔绘制GLES30.GL_LINES

  • 绘制顺序:将传入的坐标作为单独线条绘制,ABCDEFG六个顶点,绘制AB、CD、EF三条线
// 1. 准备顶点数据
val vertex = floatArrayOf(
    -0.5f,  0.5f, 0.0f, // 左上
    -0.5f, -0.5f, 0.0f, // 左下
    0.5f, 0.5f, 0.0f, // 右上
    0.5f, -0.5f, 0.0f, // 右下
)
// 2. 准备顶点索引
val indices = run {
    val indices = mutableListOf<Int>()

    for (i in 0..(vertex.size / 3) -1) {
        indices.add(i)
    }

    indices.toIntArray()
}

GLES30.glLineWidth(30f)
GLES30.glDrawElements(
    GLES30.GL_LINES, // 绘制方式
    indices.size, // 顶点索引数
    GLES30.GL_UNSIGNED_INT, // 索引数据类型
    0 // 索引数据偏移量
)
  • 效果图
    在这里插入图片描述

两个点绘制一条线连续绘制GLES30.GL_LINE_STRIP

  • 绘制顺序:将传入的顶点作为折线绘制,ABCD四个顶点,绘制AB、BC、CD三条线
// 1. 准备顶点数据
val vertex = floatArrayOf(
    -0.5f,  0.5f, 0.0f, // 左上
    -0.5f, -0.5f, 0.0f, // 左下
    0.5f, 0.5f, 0.0f, // 右上
    0.5f, -0.5f, 0.0f, // 右下
)
// 2. 准备顶点索引
val indices = run {
    val indices = mutableListOf<Int>()

    for (i in 0..(vertex.size / 3) -1) {
        indices.add(i)
    }

    indices.toIntArray()
}

GLES30.glLineWidth(30f)
GLES30.glDrawElements(
    GLES30.GL_LINE_STRIP, // 绘制方式
    indices.size, // 顶点索引数
    GLES30.GL_UNSIGNED_INT, // 索引数据类型
    0 // 索引数据偏移量
)
  • 效果图
    在这里插入图片描述

两个点绘制一条线循环绘制GLES30.GL_LINE_LOOP

  • 绘制顺序:将传入的顶点作为闭合折线绘制,ABCD四个顶点,绘制AB、BC、CD、DA四条线
// 1. 准备顶点数据
val vertex = floatArrayOf(
    -0.5f,  0.5f, 0.0f, // 左上
    -0.5f, -0.5f, 0.0f, // 左下
    0.5f, 0.5f, 0.0f, // 右上
    0.5f, -0.5f, 0.0f, // 右下
)
// 2. 准备顶点索引
val indices = run {
    val indices = mutableListOf<Int>()

    for (i in 0..(vertex.size / 3) -1) {
        indices.add(i)
    }

    indices.toIntArray()
}

GLES30.glLineWidth(30f)
GLES30.glDrawElements(
    GLES30.GL_LINE_LOOP, // 绘制方式
    indices.size, // 顶点索引数
    GLES30.GL_UNSIGNED_INT, // 索引数据类型
    0 // 索引数据偏移量
)
  • 效果图
    在这里插入图片描述

绘制三角形

三个点绘制一条线间隔绘制GLES30.GL_TRIANGLES

  • 绘制顺序:将传入的顶点作为单独的三角形绘制,ABCDEF绘制ABC,DEF两个三角形
// 1. 准备顶点数据
val vertex = floatArrayOf(
    -0.5f,  0.5f, 0.0f, // 左上
    -0.5f, -0.5f, 0.0f, // 左下
    0.5f, 0.5f, 0.0f, // 右上
    0.5f, -0.5f, 0.0f, // 右下
)
// 2. 准备顶点索引
val indices = run {
    val indices = mutableListOf<Int>()

    for (i in 0..(vertex.size / 3) -1) {
        indices.add(i)
    }

    indices.toIntArray()
}

GLES30.glDrawElements(
    GLES30.GL_TRIANGLES, // 绘制方式
    indices.size, // 顶点索引数
    GLES30.GL_UNSIGNED_INT, // 索引数据类型
    0 // 索引数据偏移量
)
  • 效果图
    -

绘制正方形

三个点绘制一条线连续绘制GLES30.GL_TRIANGLE_STRIP

  • 绘制顺序:将传入的顶点作为三角条带绘制,ABCDEF绘制ABC,BCD,CDE,DEF四个三角形
// 1. 准备顶点数据
val vertex = floatArrayOf(
    -0.5f,  0.5f, 0.0f, // 左上
    -0.5f, -0.5f, 0.0f, // 左下
    0.5f, 0.5f, 0.0f, // 右上
    0.5f, -0.5f, 0.0f, // 右下
)
// 2. 准备顶点索引
val indices = run {
    val indices = mutableListOf<Int>()

    for (i in 0..(vertex.size / 3) -1) {
        indices.add(i)
    }

    indices.toIntArray()
}

GLES30.glDrawElements(
    GLES30.GL_TRIANGLE_STRIP, // 绘制方式
    indices.size, // 顶点索引数
    GLES30.GL_UNSIGNED_INT, // 索引数据类型
    0 // 索引数据偏移量
)
  • 效果图
    -

绘制圆

三个点绘制一条线连续绘制GLES30.GL_TRIANGLE_FAN

  • 绘制顺序:将传入的顶点作为扇面绘制,ABCDEF绘制ABC、ACD、ADE、AEF四个三角形
// 1. 准备顶点数据
val vertex = run {
    val radius = 0.5f
    val segments = 36 // 分段数(越多越圆滑)
    val angleStep = (2 * PI / segments).toFloat()

    val vertices = mutableListOf<Float>()
    // 中心点
    vertices.add(0f)
    vertices.add(0f)
    vertices.add(0f)

    // 圆周上的点
    for (i in 0..segments) {
        val angle = i * angleStep
        vertices.add(radius * cos(angle))
        vertices.add(radius * sin(angle))
        vertices.add(0f)
    }

    vertices.toFloatArray()
}

// 2. 准备顶点索引
val indices = run {
    val indices = mutableListOf<Int>()
    // 添加中心点索引 0
    indices.add(0)
    
    // 添加圆周上的顶点索引(从 0 开始)
    for (i in 1..vertex.size / 3) {
        indices.add(i)
    }
    
    indices.toIntArray()
}

GLES30.glDrawElements(
    GLES30.GL_TRIANGLE_FAN, // 绘制方式
    indices.size, // 顶点索引数
    GLES30.GL_UNSIGNED_INT, // 索引数据类型
    0 // 索引数据偏移量
)
  • 效果图
    在这里插入图片描述

相关文章:

  • 力扣 3248. 矩阵中的蛇(Java实现)
  • 【HDLbits--Comb组合逻辑】
  • HAProxy- https、四层负载实现与 负载均衡关键技术
  • JavaScript系列(87)--Webpack 高级配置详解
  • PXE 安装ubuntu22.04自动判断UEFI或者Legacy引导
  • 第九节: Vue 3 中的 provide 与 inject:优雅的跨组件通信
  • Apache Doris 索引的全面剖析与使用指南
  • 【WSL2】 Ubuntu20.04 GUI图形化界面 VcXsrv ROS noetic Vscode 主机代理 配置
  • 检查SSH安全配置-关于“MaxStartups参数”
  • django filter 不等于
  • 面试基础--线程生命周期、线程池(ThreadPoolExecutor 工作原理)
  • vue3除了pinia/vuex的其他通讯方式还有那些
  • AWVS(web)扫描器安装与使用
  • 记一次项目上vCenter集群恢复过程
  • 网络应用层之HTTPS
  • kubernetes-完美下载
  • java进阶1——JVM
  • 从0开始的操作系统手搓教程14——进一步完成中断子系统
  • 网络原理---HTTP/HTTPS
  • 在 MySQL 的 InnoDB 存储引擎中,部分数据库优化策略
  • 做网站需要购买地域名吗/站长工具推荐网站
  • 网站建设的需求分析/线上免费推广平台都有哪些
  • 自己的博客和自己的网站做友链/深圳小程序开发公司
  • 餐饮网站建设方案书/seo关键词布局技巧
  • 做本地网站应该选什么内容/上海专业seo公司
  • 建设网站banner/百度seo关键词排名优化工具