OpenGL ES -> GLSurfaceView纹理贴图

embedded/2025/3/4 10:04:47/

贴图

在这里插入图片描述

XML文件

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

自定义GLSurfaceView代码

kotlin">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代码

kotlin">class MyGLRenderer(private val mContext : Context) : GLSurfaceView.Renderer {private var mDrawData: DrawData? = nulloverride 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()loadTexture(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?.drawSomething()}
}

GLSurfaceView.Renderer需要的绘制数据

kotlin">class DrawData {private var mProgram : Int = -1private var NO_OFFSET = 0private val VERTEX_POS_DATA_SIZE = 3private val TEXTURE_POS_DATA_SIZE = 2// 纹理IDprivate var mTextureID = IntArray(1)// VBO IDsprivate var mVertexVBO = 0private var mTexCoordVBO = 0// 最终变化矩阵private val mMVPMatrix = FloatArray(16)// 投影矩阵private val mProjectionMatrix = FloatArray(16)// 相机矩阵private val mViewMatrix = FloatArray(16)private var mViewPortRatio = 1f// 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()// 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()// 3. 创建顶点缓冲区对象fun initVertexBuffer(){// 初始化顶点坐标缓冲区vertexBuffer.put(vertex)vertexBuffer.position(NO_OFFSET)// 初始化纹理坐标缓冲区textureBuffer.put(textureCoords)textureBuffer.position(NO_OFFSET)// 创建两个VBO,一个用于顶点坐标,一个用于纹理坐标val vbo = IntArray(2)GLES30.glGenBuffers(vbo.size, vbo, NO_OFFSET) // 生成一个缓冲区对象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)// 绑定纹理缓冲区GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vbo[1])GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER,textureCoords.size * 4, // 数据总字节数 = 顶点数 * Float占4字节textureBuffer,GLES30.GL_STATIC_DRAW)mVertexVBO = vbo[0]mTexCoordVBO = vbo[1]}// 4. 初始化着色器程序fun initShader()  {val vertexShaderCode = """#version 300 esin vec4 aPosition; // 顶点坐标uniform mat4 uMVPMatrix; // 变换矩阵in vec2 aTexCoord; // 纹理坐标 out vec2 vTexCoord;void main() {// 输出顶点坐标和纹理坐标到片段着色器gl_Position = uMVPMatrix * aPosition; vTexCoord = aTexCoord;}""".trimIndent()       // 顶点着色器代码val fragmentShaderCode = """#version 300 esprecision mediump float; // 定义float 精度为 mediumpout vec4 fragColor; // 输出片段颜色in vec2 vTexCoord; // 接收顶点着色器传递过来的纹理坐标uniform sampler2D uTexture; // 纹理取样器void main() {// 使用内置函数texture, 根据纹理坐标和取样器sampler2D计算片段颜色fragColor = texture(uTexture, 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)}// 5. 加载纹理fun loadTexture(context: Context, resourceId: Int) {// 生成纹理GLES30.glGenTextures(mTextureID.size, mTextureID, NO_OFFSET)// 绑定纹理GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mTextureID[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, NO_OFFSET, bitmap, NO_OFFSET)// 释放资源bitmap.recycle()// 解绑纹理GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, NO_OFFSET)}// 6. 计算变换矩阵fun computeMVPMatrix(width: Float, height: Float) {// 正交投影矩阵takeIf { width > height }?.let {mViewPortRatio = width / heightMatrix.orthoM(mProjectionMatrix, // 正交投影矩阵NO_OFFSET, // 偏移量-mViewPortRatio, // 近平面的坐标系左边界mViewPortRatio, // 近平面的坐标系右边界-1f, // 近平面的坐标系的下边界1f, // 近平面坐标系的上边界0f, // 近平面距离相机距离1f // 远平面距离相机距离)} ?: run {mViewPortRatio = height / widthMatrix.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, // 相机位置x0f, // 相机位置y1f, // 相机位置z0f, // 物体位置x0f, // 物体位置y0f, // 物体位置z0f, // 相机上方向x1f, // 相机上方向y0f // 相机上方向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,)val matrixHandler = GLES30.glGetUniformLocation(mProgram, "uMVPMatrix")GLES30.glUniformMatrix4fv(matrixHandler, 1, false, mMVPMatrix, NO_OFFSET)}// 7. 使用着色器程序绘制图形fun drawSomething(){// 激活纹理编号GLES30.glActiveTexture(GLES30.GL_TEXTURE0)GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mTextureID[0])// 激活纹理取样器val textureSampleHandle = GLES30.glGetUniformLocation(mProgram, "uTexture")GLES30.glUniform1i(textureSampleHandle, NO_OFFSET)// 激活变换矩阵val matrixHandle = GLES30.glGetUniformLocation(mProgram, "uMVPMatrix")GLES30.glUniformMatrix4fv(matrixHandle, 1, false, mMVPMatrix, NO_OFFSET)// 输入顶点数据val positionHandle = GLES30.glGetAttribLocation(mProgram, "aPosition")GLES30.glEnableVertexAttribArray(positionHandle)GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVertexVBO)GLES30.glVertexAttribPointer(positionHandle, VERTEX_POS_DATA_SIZE, GLES30.GL_FLOAT, false, 0, NO_OFFSET)// 绑定纹理数据val textureHandle = GLES30.glGetAttribLocation(mProgram, "aTexCoord")GLES30.glEnableVertexAttribArray(textureHandle)GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mTexCoordVBO)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)}
}object LoadShaderUtil{// 创建着色器对象fun loadShader(type: Int, source: String): Int {val shader = GLES30.glCreateShader(type)GLES30.glShaderSource(shader, source)GLES30.glCompileShader(shader)return shader}
}

效果图

在这里插入图片描述


http://www.ppmy.cn/embedded/169885.html

相关文章

SFP28(25 Gigabit Small Form-factor Pluggable)详解

1. SFP28的定义 SFP28是SFP的升级版本&#xff0c;专为25Gbps高速网络设计&#xff0c;其名称中的“28”代表其支持28 Gbps电气接口&#xff08;实际传输速率为25Gbps&#xff09;。它延续了SFP/SFP的小型化&#xff08;14mm宽&#xff09;和热插拔特性&#xff0c;但通过优化…

ansible自动化运维工具学习笔记

目录 ansible环境部署 控制端准备 被控制端准备 ansible批量管理主机的方式主要有两种 配置准备&#xff1a; ssh密码认证方式管理机器 密码登录&#xff0c;需要各主机密码相同 配置免密登录 ssh密钥方式批量管理主机 ansible实现批量化主机管理的模式 ansible-doc命令 comman…

如何诊断服务器硬盘故障?

诊断服务器硬盘故障是维护服务器稳定性和可靠性的关键步骤。以下是一篇记述性的教学文章&#xff0c;介绍如何诊断服务器硬盘故障的方法&#xff1a; 1. 观察服务器行为&#xff1a; 首先&#xff0c;观察服务器的表现。如果服务器出现异常噪音、频繁重启、文件读写错误或数据…

深入理解推理语言模型(RLM)

大语言模型从通用走向推理&#xff0c;万字长文解析推理语言模型&#xff0c;建议收藏后食用。 本文基于苏黎世联邦理工学院的论文《Reasoning Language Models: A Blueprint》进行整理&#xff0c;你将会了解到&#xff1a; 1、RLM的演进与基础&#xff1a;RLM融合LLM的知识广…

C# OnnxRuntime部署DAMO-YOLO香烟检测

目录 说明 效果 模型信息 项目 代码 下载 参考 说明 效果 模型信息 Model Properties ------------------------- --------------------------------------------------------------- Inputs ------------------------- name&#xff1a;input tensor&#xff1a;Floa…

k8s内存不足问题

所有pods占用内存 kubectl top pods -A所有nodes占用内存cpu情况 kubectl top nodes删除一些没用的服务&#xff0c;清理空间然后重新部署或者加服务器的cpu和内存

清华大学DeepSeek详细使用教程共6版免费下载

「清华北大-Deepseek使用手册」 链接&#xff1a;https://pan.quark.cn/s/98782f7d61dc 「清华大学Deepseek整理&#xff09; 1&#xff0d;6版本链接&#xff1a;https://pan.quark.cn/s/72194e32428a AI学术工具公测链接:https://pan.baidu.com/s/104w_uBB2F42Da0qnk78_ew …

接口管理工具深度对比:Apipost与Apifox在Redis/MongoDB支持上的关键差异

在现代软件开发中&#xff0c;数据库是驱动各类应用和服务运行的核心组件。无论是企业级应用、互联网服务&#xff0c;还是物联网解决方案&#xff0c;数据库的类型和数量通常都因业务需求和技术架构的复杂性而不断拓展。 与此同时&#xff0c;接口管理工具作为开发和维护这些…