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

OpenGL ES -> GLSurfaceView纹理贴图VBO(Vertex Buffer Object)方法实现

贴图

在这里插入图片描述

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(context)

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

自定义GLSurfaceView.Renderer代码

class MyGLRenderer(private val mContext: Context) : 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 {
            initShader()
            initVertexBuffer()
            initTexture0(mContext, R.drawable.bitmap_shader)
        }
    }

    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?.enableTexture()
        mDrawData?.drawSomething()
        mDrawData?.disableTexture()
    }
}

GLSurfaceView.Renderer需要的绘制数据

class DrawData {
    private var mProgram: Int = -1
    private var NO_OFFSET = 0
    private val VERTEX_POS_DATA_SIZE = 3
    private val TEXTURE_POS_DATA_SIZE = 2

    // VBO(Vertex Buffer Object), 顶点缓冲对象,用于存储顶点数据和纹理数据
    private var mVBO = IntArray(2)

    // 最终变化矩阵
    private val mMVPMatrix = FloatArray(16)

    // 投影矩阵
    private val mProjectionMatrix = FloatArray(16)

    // 相机矩阵
    private val mViewMatrix = FloatArray(16)

    private var mViewPortRatio = 1f

    // 纹理ID
    var mTextureID = IntArray(2)

    // 1. 准备顶点坐标,分配直接内存
    // OpenGL ES坐标系:原点在中心,X轴向右为正,Y轴向上为正,Z轴向外为正
    val vertex = floatArrayOf(
        -1.0f, 1.0f, 0.0f, // 左上
        -1.0f, -1.0f, 0.0f, // 左下
        1.0f, 1.0f, 0.0f, // 右上
        1.0f, -1.0f, 0.0f, // 右下
    )

    val vertexBuffer = ByteBuffer.allocateDirect(vertex.size * 4)
        .order(ByteOrder.nativeOrder())
        .asFloatBuffer()
        .put(vertex)
        .position(0)

    // 2. 准备纹理坐标,分配直接内存
    // 纹理坐标系:原点在左下角,X轴向右为正,Y轴向上为正
    val textureCoords = floatArrayOf(
        0.0f, 1.0f, // 左上
        0.0f, 0.0f, // 左下
        1.0f, 1.0f, // 右上
        1.0f, 0.0f, // 右下
    )

    val textureBuffer = ByteBuffer.allocateDirect(textureCoords.size * 4)
        .order(ByteOrder.nativeOrder())
        .asFloatBuffer()
        .put(textureCoords)
        .position(NO_OFFSET)

    // 3. 初始化着色器程序
    fun initShader() {
        val vertexShaderCode = """#version 300 es
                uniform mat4 uMVPMatrix; // 变换矩阵
                in vec4 aPosition; // 顶点坐标
                in vec2 aTexCoord; // 纹理坐标 
                out vec2 vTexCoord; 
                void main() {
                    // 输出顶点坐标和纹理坐标到片段着色器
                    gl_Position = uMVPMatrix * aPosition; 
                    vTexCoord = aTexCoord;
                }""".trimIndent()       // 顶点着色器代码

        val fragmentShaderCode = """#version 300 es
                precision mediump float;
                uniform sampler2D uTexture_0;
                uniform sampler2D uTexture_1;
                in vec2 vTexCoord;
                out vec4 fragColor;
                void main() {
                    fragColor = texture(uTexture_0, vTexCoord) + texture(uTexture_1, vTexCoord);
                }""".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)

        // 删除着色器对象
        GLES30.glDeleteShader(vertexShader)
        GLES30.glDeleteShader(fragmentShader)
    }

    // 4. 创建顶点缓冲区对象
    fun initVertexBuffer() {
        // 绑定VBO
        GLES30.glGenBuffers(mVBO.size, mVBO, NO_OFFSET)

        // 绑定顶点缓冲区数据到VBO[0]
        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVBO[0])
        GLES30.glBufferData(
            GLES30.GL_ARRAY_BUFFER,
            vertex.size * 4,
            vertexBuffer,
            GLES30.GL_STATIC_DRAW
        )
        // 解绑顶点缓冲区
        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0)

        // 绑定纹理缓冲区数据到VBO[1]
        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVBO[1])
        GLES30.glBufferData(
            GLES30.GL_ARRAY_BUFFER,
            textureCoords.size * 4,
            textureBuffer,
            GLES30.GL_STATIC_DRAW
        )
        // 解绑纹理缓冲区
        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0)
    }

    // 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 // 相机矩阵偏移量
        )

        // 纹理坐标系为(0, 0), (1, 0), (1, 1), (0, 1)的正方形逆时针坐标系,从Bitmap生成纹理,即像素拷贝到纹理坐标系
        // 变换矩阵需要加上一个y方向的翻转, x方向和z方向不改变
        Matrix.scaleM(
            mMVPMatrix,
            NO_OFFSET,
            1f,
            -1f,
            1f,
        )
    }

    // 6. 使用着色器程序绘制图形
    fun drawSomething() {
        // 解析变换矩阵
        val matrixHandle = GLES30.glGetUniformLocation(mProgram, "uMVPMatrix")
        GLES30.glUniformMatrix4fv(matrixHandle, 1, false, mMVPMatrix, NO_OFFSET)

        // 解析顶点缓冲区数据到VBO[0]
        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVBO[0])
        val positionHandle = GLES30.glGetAttribLocation(mProgram, "aPosition")
        GLES30.glEnableVertexAttribArray(positionHandle)
        GLES30.glVertexAttribPointer(
            positionHandle,
            VERTEX_POS_DATA_SIZE,
            GLES30.GL_FLOAT,
            false,
            0,
            NO_OFFSET
        )

        // 解析纹理缓冲区数据到VBO[1]
        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVBO[1])
        val textureHandle = GLES30.glGetAttribLocation(mProgram, "aTexCoord")
        GLES30.glEnableVertexAttribArray(textureHandle)
        GLES30.glVertexAttribPointer(
            textureHandle,
            TEXTURE_POS_DATA_SIZE,
            GLES30.GL_FLOAT,
            false,
            0,
            NO_OFFSET
        )

        // 绘制纹理
        GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, NO_OFFSET, vertex.size / VERTEX_POS_DATA_SIZE)

        // 禁用顶点缓冲区数据
        GLES30.glDisableVertexAttribArray(positionHandle)
        // 禁用纹理缓冲区数据
        GLES30.glDisableVertexAttribArray(textureHandle)
        // 解绑VBO
        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0)
    }

    fun enableTexture() {
        // 激活纹理编号0
        GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mTextureID[0])
        val textureSampleHandle = GLES30.glGetUniformLocation(mProgram, "uTexture_0")
        GLES30.glUniform1i(textureSampleHandle, 0)

        // 激活纹理编号1
        GLES30.glActiveTexture(GLES30.GL_TEXTURE1)
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mTextureID[1])
        val textureSampleHandle1 = GLES30.glGetUniformLocation(mProgram, "uTexture_1")
        GLES30.glUniform1i(textureSampleHandle1, 1)
    }

    fun disableTexture() {
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)
    }

    fun initTexture0(context: Context, resourceId: Int){
        mTextureID[0] = loadTexture(context, resourceId)
    }

    // 加载纹理
    fun loadTexture(context: Context, resourceId: Int): Int {
        val textureId = IntArray(1)
        // 生成纹理
        GLES30.glGenTextures(1, textureId, 0)
        // 绑定纹理
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId[0])
        // 设置纹理参数
        GLES30.glTexParameteri(
            GLES30.GL_TEXTURE_2D,
            GLES30.GL_TEXTURE_MIN_FILTER,
            GLES30.GL_LINEAR
        ) // 纹理缩小时使用线性插值
        GLES30.glTexParameteri(
            GLES30.GL_TEXTURE_2D,
            GLES30.GL_TEXTURE_MAG_FILTER,
            GLES30.GL_LINEAR
        ) // 纹理放大时使用线性插值
        GLES30.glTexParameteri(
            GLES30.GL_TEXTURE_2D,
            GLES30.GL_TEXTURE_WRAP_S,
            GLES30.GL_CLAMP_TO_EDGE
        ) // 纹理坐标超出范围时,超出部分使用最边缘像素进行填充
        GLES30.glTexParameteri(
            GLES30.GL_TEXTURE_2D,
            GLES30.GL_TEXTURE_WRAP_T,
            GLES30.GL_CLAMP_TO_EDGE
        ) // 纹理坐标超出范围时,超出部分使用最边缘像素进行填充
        // 加载图片
        val options = BitmapFactory.Options().apply {
            inScaled = false // 不进行缩放
        }
        val bitmap = BitmapFactory.decodeResource(context.resources, resourceId, options)
        // 将图片数据加载到纹理中
        GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, bitmap, 0)
        // 释放资源
        bitmap.recycle()
        // 解绑纹理
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)
        return textureId[0]
    }


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

效果图

在这里插入图片描述

相关文章:

  • 人工智能+乡村振兴+文旅+低空无人机产业链技术详解
  • MySQL5.7.44-winx64版本Windows Server下载安装教程图解
  • 江科大51单片机笔记【9】DS1302实时时钟(上)
  • 网络安全规划重安全性需求
  • sql注入的一般过程
  • 力扣hot100——贪心
  • 从0到1入门Linux
  • 使用 DeepSeek 配合 即梦AI 生成视频的详细教程
  • 第一章——计算机系统概论
  • MRI学习笔记-Meta分析之SDM-PSI
  • 从 Git 仓库流程到 C++ 类设计:一次巧妙的类比实现
  • 图论-腐烂的橘子
  • Kotlin D2
  • VMware Fusion虚拟机Mac版安装Ubuntu系统
  • mac安装nvm=>node=>nrm
  • 高并发内存池 · 基本认识
  • 数据保险箱:备份文件的关键价值与自动化实践
  • 知识周汇 | Python操作Excel全攻略系列(二):文件操作篇
  • leetcode700-二叉搜索树中的搜索
  • 【愚公系列】《Python网络爬虫从入门到精通》041-Matplotlib 图表的常用设置
  • 现在流行什么做网站/搜狗首页排名优化
  • 企业网站建设联系方式/百度下载安装免费
  • 做网站公司找哪家公司/seo的收费标准
  • 最新军事热点/优化网站排名软件
  • 优质的营销网站建设/免费刷粉网站推广
  • 网站分类 维护/网站运营推广选择乐云seo