OpenGL ES 纹理(7)

OpenGL ES 纹理(7)

简述

通过前面几章的学习,我们已经可以绘制渲染我们想要的逻辑图形了,但是如果我们想要渲染一张本地图片,这就需要纹理了。
纹理其实是一个可以用于采样的数据集,比较典型的就是图片了,我们知道我们的片段着色器会对每一个像素都执行一次来计算,该像素应该渲染什么颜色,纹理就是一个数据集,比如想要渲染一个图片,我们就是用图片的所有像素信息作为总数据集,然后片段着色器计算的时候就根据像素坐标去图片纹理数据集中取出对应的像素。
这一节我们就通过纹理来渲染一张图片。

接口介绍

  • glGenTextures 申请分配纹理
    public static native void glGenTextures(int n, int[] textures, int offset);
    第一个参数为需要分配纹理个数,第二个申请的纹理句柄(是一个出参),第三个是偏移
  • glTexParameteri 配置纹理参数
    public static native void glTexParameteri(int target, int pname, int param);
    第一个参数是目标,一般使用GL_TEXTURE_2D,第二和第三个参数分别是需要配置的参数的名称和值。
    下面我们列举几个常用的参数:
    • GL_TEXTURE_MAG_FILTER 当显示区域大于原来纹理时,如何放大图像
      • GL_NEAREST 会使用靠近的像素作为扩增的像素(计算量小但是效果差)
      • GL_LINEAR 求附近像素的加权平均值
    • GL_TEXTURE_MIN_FILTER 当显示区域小于原来纹理时,如何缩小图像,GL_NEAREST, GL_LINEAR值和GL_TEXTURE_MAG_FILTER类似。
    • GL_TEXTURE_WRAP_T / GL_TEXTURE_WRAP_S
      • 纹理的ST坐标系,对应XY坐标,纹理的坐标范围是0-1,这个参数就是控制如果超出这个坐标范围的表现。GL_CLAMP会使用边缘拉伸,GL_REPEAT会重复使用图像填充
  • GLUtils.texImage2D 加载纹理
    用于使用位图来填充纹理
  • glGenerateMipmap 开启多级细节。我们看越远的东西就会越小,这时候可能不需要加载完整的图像,可以降低图像细节提高性能。

纹理坐标系

我们之前提过,OpenGL ES的坐标系是(-1,-1)->(1,1)

在OpenGL ES中,纹理的坐标系是(0,0)-> (1,1)的,且纹理的起始坐标(0,0)是在左上角。

纹理渲染图片

原图

在这里插入图片描述

顶点数据

顶点数据和绘制正方形时候类似,依旧是4个顶点,然后索引缓冲区中6个点,以两个三角形拼接成一个正方形。
其中每个顶点有5个数据,前三个作为OpenGL的坐标,后两个作为纹理坐标,这里坐标对应关系是OpenGLES坐标系(-0.5,-0.5)对应纹理坐标系(1, 1),即OpenGL ES左下角对应图片的右下角,最终效果是图片顺时针旋转来90度。

private float[] vertexArray = new float[] {-0.5f, -0.5f, 0.0f, 1, 1f,0.5f, -0.5f, 0.0f, 1f, 0f,-0.5f, 0.5f, 0.0f, 0f, 1f,0.5f, 0.5f, 0.0f, 0f, 0f,
};private short[] indexArray = new short[] {0,1,2,1,2,3
};

着色器

顶点着色器有两个属性,一个vPosition为OpenGL ES坐标,另一个vCoordinate为纹理坐标。纹理坐标会通过varying变量outCoordinate透传给片段着色器。
片段着色器中有一个统一变量vTexture,类型为sampler2D,它相当于纹理的句柄,我门通过texture2D方法,传入纹理句柄和当前纹理坐标即可获取当前坐标的颜色。

private final String vertexShaderCode ="attribute vec4 vPosition;" +"attribute vec2 vCoordinate;" +"varying vec2 outCoordinate;" +"void main() {" +"  gl_Position = vPosition;" +"  outCoordinate = vCoordinate;" +"}";private final String fragmentShaderCode ="precision mediump float;" +"varying vec2 outCoordinate;" +"uniform sampler2D vTexture;" +"void main() {" +"  gl_FragColor = texture2D(vTexture,outCoordinate);" +"}";

顶点数据填充并加载纹理

顶点填充数据逻辑和孩子i去哪都一样,这里额外调用来一个loadTexture(getContext(), R.drawable.test),R.drawable.test是我们的需要显示的图片资源,我们来看看loadTexture里面做了什么。

public void onSurfaceCreated(GL10 gl, EGLConfig config) {// 清除颜色GLES30.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);// 创建顶点缓冲区int[] idBuffer = new int[2];GLES30.glGenBuffers(2, idBuffer, 0);vertexBufferId = idBuffer[0];elementBufferId = idBuffer[1];// 顶点缓冲区数据填充FloatBuffer vertexBuffer = ByteBuffer.allocateDirect(vertexArray.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();vertexBuffer.put(vertexArray);vertexBuffer.position(0);GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vertexBufferId);GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER,vertexArray.length * 4,vertexBuffer,GLES30.GL_STATIC_DRAW);GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0);ShortBuffer indexBuffer = ByteBuffer.allocateDirect(indexArray.length * 4).order(ByteOrder.nativeOrder()).asShortBuffer();indexBuffer.put(indexArray);indexBuffer.position(0);GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, elementBufferId);GLES30.glBufferData(GLES30.GL_ELEMENT_ARRAY_BUFFER,indexArray.length * 4,indexBuffer,GLES30.GL_STATIC_DRAW);GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, 0);loadTexture(getContext(), R.drawable.test);// shadershaderProgramId = initShaderProgram(vertexShaderCode, fragmentShaderCode);
}
loadTexture

这里调用的方法我们在之前都已经介绍过了,这个过程其实和创建顶点缓冲区很想,先申请一个纹理,申请纹理会返回一个纹理句柄,然后绑定纹理,配置参数,配置纹理对应的位图,再解绑纹理,后续就可以通过这个纹理句柄来使用这个纹理了。

public int loadTexture(final Context context, final int resourceId) {GLES30.glGenTextures(1, textureHandle, 0);if (textureHandle[0] != 0) {// 绑定纹理GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureHandle[0]);// 设置纹理参数GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR_MIPMAP_LINEAR);GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);// 加载纹理Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId);GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, bitmap, 0);// 生成多级细节GLES30.glGenerateMipmap(GLES30.GL_TEXTURE_2D);// 释放Bitmap资源bitmap.recycle();}// 解绑纹理GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);return textureHandle[0];
}

顶点布局以及渲染

渲染的流程和之前类似,处理配置顶点布局以外,这里主要新增的逻辑就是需要通过glBindTexture绑定纹理,还有将纹理句柄通过统一变量传给片段着色器。

public void onDrawFrame(GL10 gl) {// 清除屏幕GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);// 使能着色器程序GLES30.glUseProgram(shaderProgramId);// 绑定纹理GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureHandle[0]);GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vertexBufferId);int positionLocation = GLES30.glGetAttribLocation(shaderProgramId, "vPosition");GLES30.glEnableVertexAttribArray(positionLocation);GLES30.glVertexAttribPointer(positionLocation, 3, GLES30.GL_FLOAT, false, 5 * 4, 0);GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vertexBufferId);int coordinateLocation = GLES30.glGetAttribLocation(shaderProgramId, "vCoordinate");GLES30.glEnableVertexAttribArray(coordinateLocation);GLES30.glVertexAttribPointer(coordinateLocation, 2, GLES30.GL_FLOAT, false, 5 * 4, 3 * 4);// 配置纹理句柄到统一变量中去int vTextureLocation = GLES30.glGetUniformLocation(shaderProgramId, "vTexture");GLES30.glUniform1f(vTextureLocation, textureHandle[0]);GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, elementBufferId);// 调用DrawCall绘制三角形GLES30.glDrawElements(GLES30.GL_TRIANGLES, 6, GLES30.GL_UNSIGNED_SHORT, 0);// 清除配置GLES30.glDisableVertexAttribArray(positionLocation);GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, 0);GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0);GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);GLES30.glUseProgram(0);
}

效果

如预期一样,这里相比原图顺时针旋转了90度。

在这里插入图片描述

小结

本节通过演示如何使用纹理加载渲染一个图片来介绍了纹理的使用方式,除此之外还通过纹理坐标系和OpenGL ES坐标系的映射关系将图片做了一个旋转,其实我们还有一个方式专门来做图像的变换,就是变化矩阵,我们下一节会专门来介绍这个。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/881250.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《C++编程秘籍:实现高效加密数字签名算法》

在当今数字化时代,信息安全至关重要。加密数字签名算法作为保障数据完整性和真实性的重要手段,在 C编程中有着广泛的应用需求。本文将探讨如何在 C中实现高效的加密数字签名算法,为开发者提供实用的指南和思路。 一、加密数字签名算法的重要…

【STM32开发之寄存器版】(六)-通用定时器中断

一、前言 STM32定时器分类 STM32103ZET6具备8个定时器TIMx(x 1,2,...,8)。其中,TIM1和TIM8为高级定时器,TIM2-TIM6为通用定时器,TIM6和TIM7为基本定时器,本文将以TIM3通用定时器为例,分析STM32定时器工作的底层寄存器…

深度学习基础—残差网络ResNets

1.残差网络结构 当网络训练的很深很深的时候,效果是否会很好?在这篇论文中,作者给出了答案:Deep Residual Learning for Image Recognitionhttps://www.cv-foundation.org/openaccess/content_cvpr_2016/papers/He_Deep_Residual_…

EmEditor传奇脚本编辑器

主程序:EmEditor.exe 目前已有功能 可以自己指定一个快捷键 实现以下功能(默认快捷键为:F1) 以下全功能 都是鼠标所在行 按快捷键 (默认快捷键:F1) 1.在Merchant.txt中 一键打开NPC 没有…

PHP中for 和 foreach 有什么区别

在深入探讨PHP中的for和foreach循环的区别之前,我们先简要回顾一下这两种循环的基本概念和用途。随后,我们将从语法、用途、性能、可读性以及实际应用等多个方面,详细阐述它们之间的差异和各自的优势。 一、引言 在PHP中,for和f…

Linux 外设驱动 应用 1 IO口输出

从这里开始外设驱动介绍,这里使用的IMX8的芯片作为驱动介绍 开发流程: 修改设备树,配置 GPIO1_IO07 为 GPIO 输出。使用 sysfs 接口或编写驱动程序控制 GPIO 引脚。编译并测试。 这里假设设备树,已经配置好了。不在论述这个问题…

Steam Deck掌机可装“黑苹果” 开发者成功安装macOS 15 Sequoia

在Steam Deck掌机上运行Windows 11相对轻松,但要让其成功搭载“黑苹果”系统则颇具挑战性。近日,有博主勇于尝试,将macOS 15 Sequoia安装到了Steam Deck上。 开发者kaitlyn在X平台上分享道:“在朋友们的鼎力相助下,我…

SQL专项练习第六天

Hive 在处理不同数据需求时的灵活性和强大功能,包括间隔连续问题的处理、行列转换、交易数据查询、用户登录统计以及专利数据分析等方面。本文将介绍五个 Hive 数据处理问题的解决方案,并通过实际案例进行演示。 先在home文件夹下建一个hivedata文件夹&a…

win用户数据保存路径更改

注销当前用户,而后以“Administrator”登录。 打开命令行窗口,输入以下命令: robocopy "C:\Users\lyLab" "G:\Users\lyLab" /E /COPYALL /XJ /XD /r:1 /w:1注销Administrator,重新用你的用户名登录Windows&…

Unity Shader Graph基础包200+节点及术语解释

目录 Master Stack: Vertex block: Fragment block​编辑 Alpha Clip Threshold Dither transparency Graph Inspector Texture 2d Array/Texture 3d Virtual Texture Sampler State Keywords Boolean keyword 右键显示所有节点 简化测试系列节点&#x…

初入网络学习第一篇

引言 不磨磨唧唧,跟着学就好了,这个是我个人整理的学习内容梳理,学完百分百有收获。 1、使用的网络平台:eNSP 下载方法以及内容参考这篇文章 华为 eNSP 模拟器安装教程(内含下载地址)_ensp下载-CSDN博客https://b…

DBMS-3.4 SQL(4)——存储过程和函数触发器

本文章的素材与知识来自李国良老师和王珊老师。 存储过程和函数 一.存储过程 1.语法 2.示例 (1) 使用DELIMITER更换终止符后用于编写存储过程语句后,在下次执行SQL语句时记得再使用DELIMITER将终止符再换回分号。 使用DELIMITER更换终止符…

数据分布过于集中 怎么办,python 人工智能 ,数据分析,机器学习pytorch tensorflow ,

数据分布过于集中,意味着数据的大部分值都聚集在某个特定区间内,这可能会导致统计分析的结果不够稳健,或者模型训练时出现过拟合等问题。针对这种情况,可以考虑以下几种方法来处理: 变换成 1. **数据转换**&#xff1…

MySQL多表查询:列子查询

先看我的表数据 dept表 emp表 列子查询,也就是多列作为子查询去寻找一些问题 常用操作符:IN, NOT IN, ANY, SOME, ALL 1.查询 "销售部" 和 "市场部" 的所有员工的信息(拆分成以下两个问题) a. 查询"销…

基于STM32的数字温度传感器设计与实现

引言 STM32 是由意法半导体(STMicroelectronics)开发的基于 ARM Cortex-M 内核的微控制器系列,以其强大的处理能力、丰富的外设接口和低功耗著称,广泛应用于嵌入式系统设计中。在这篇文章中,我们将介绍如何基于 STM32…

如何用python抓取豆瓣电影TOP250

1.如何获取网站信息? (1)调用requests库、bs4库 #检查库是否下载好的方法:打开终端界面(terminal)输入pip install bs4, 如果返回的信息里有Successfully installed bs4 说明安装成功(request…

【QT Quick】C++交互:QML对象操作

本节课程将详细讲解如何通过 C 代码访问并操作 QML 对象。通常来说,我们的需求是访问 QML 的属性和信号,而避免直接修改 QML,因为这样做会改变业务逻辑。 查找 QML 对象 在 Qt Quick 中,我们可以通过 C 代码与 QML 对象进行交互…

Python知识点:运用Python技术,如何使用Word2Vec进行词向量训练

开篇,先说一个好消息,截止到2025年1月1日前,翻到文末找到我,赠送定制版的开题报告和任务书,先到先得!过期不候! 如何使用Python的Word2Vec进行词向量训练 在自然语言处理中,词向量是…

OJ在线评测系统 微服务 OpenFeign调整后端下 nacos注册中心配置 不给前端调用的代码 全局引入负载均衡器

OpenFeign内部调用二 4.修改各业务服务的调用代码为feignClient 开启nacos注册 把Client变成bean 该服务仅内部调用,不是给前端的 将某个服务标记为“内部调用”的目的主要有以下几个方面: 安全性: 内部API通常不对外部用户公开,这样可以防止…

Linux操作系统——概念扫盲I

目录 虚拟机概念刨析 在那之前,询问什么是虚拟化? 现在来看看什么是虚拟机 虚拟机有啥好的 小差:那JVM也叫Java Virtual Machine,有啥区别呢? Reference 虚拟机概念刨析 我们下面来简单聊聊虚拟机这个概念。对于…