纹理可以理解为一个二维数组,它可以存储大量的数据,这些数据可以发送到着色器上。一般情况下我们所说的纹理是表示一副2D图,此时纹理存储的数据就是这个图的像素数据。

所谓的纹理贴图,就是使用Opengl将这个纹理数据渲染出来,这个过程有点像装修工人给墙体贴瓷砖,而瓷砖好比作纹理。

纹理坐标

如果为了将一副纹理图贴到Opengl绘制的一个矩形上,那么就需要解决一个问题,如何知道矩形的具体某个点对应纹理图的某个点呢?为了解决这个问题就引出了纹理坐标,

通过矩形的顶点坐标与纹理坐标关联,这样就明确了每个顶点应该显示纹理图的那部分像素数据。

纹理坐标在x和y轴上,范围为0到1之间。使用纹理坐标获取纹理颜色叫做采样(Sampling)。纹理坐标起始于(0, 0),也就是纹理图片的左下角,终始于(1, 1),即纹理图片的右上角,如下图所示:

纹理环绕

纹理坐标的值介于0到1之间,如果我们把纹理坐标设置成大于1那么会发生什么呢?OpenGL默认的行为是重复这个纹理图像,那么利用这个默认的特性我们能做些什么呢?那么比较火的抖音四分屏、九分屏滤镜不就是可以用这个特性巧妙地实现吗。

以下是通过改变纹理坐标实现四分屏和九分屏的一个小技巧:

// 4分屏
const static GLfloat TEXTURE_COORD[] = {
2.0f,2.0f, // 右下
2.0f,0.0f, // 右上
0.0f,2.0f, // 左下
0.0f,0.0f // 左上
}; // 九分屏
const static GLfloat TEXTURE_COORD[] = {
3.0f,3.0f, // 右下
3.0f,0.0f, // 右上
0.0f,3.0f, // 左下
0.0f,0.0f // 左上
};

当然,当纹理坐标超过1这个范围时,Opengl也提供了其他的选择,例如:

GL_REPEAT	// 对纹理的默认行为。重复纹理图像。
GL_MIRRORED_REPEAT //和GL_REPEAT一样,但每次重复图片是镜像放置的。
GL_CLAMP_TO_EDGE //纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。
GL_CLAMP_TO_BORDER //超出的坐标为用户指定的边缘颜色。

以上特性可以通过函数glTexParameteri设置:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);

纹理过滤

纹理过滤实际就是纹理在放大缩小的过程中像素的处理方式。其中在Opengl ES常用的两种纹理过滤方式是GL_NEAREST(邻近过滤)和GL_LINEAR(也叫线性过滤)。

  • GL_NEAREST是OpenGL默认的纹理过滤方式。当设置为GL_NEAREST的时候,OpenGL会选择中心点最接近纹理坐标的那个像素。

  • GL_LINEAR(也叫线性过滤,(Bi)linear Filtering)它会基于纹理坐标附近的纹理像素,计算出一个插值,近似出这些纹理像素之间的颜色。一个纹理像素的中心距离纹理坐标越近,那么这个纹理像素的颜色对最终的样本颜色的贡献越大。

GL_NEAREST产生了颗粒状的图案,我们能够清晰看到组成纹理的像素,而GL_LINEAR能够产生更平滑的图案,很难看出单个的纹理像素。

同理,纹理过滤特性也是通过函数glTexParameteri设置:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

纹理单元

纹理单元的主要目的是让我们在着色器中可以使用多于一个的纹理。通过把纹理单元赋值给采样器,我们可以一次绑定多个纹理,只要我们首先激活对应的纹理单元。

例如使用Opengl ES对视频解码后的YUV进行渲染就需要用到纹理单元的相关知识点。

Opengl中纹理的使用

在Opengl中使用纹理主要有以下几个步骤:

  • 创建纹理glGenTextures
  • 激活纹理glActiveTexture
  • 绑定纹理glBindTexture,传递特定的纹理id进行绑定
  • 上传纹理数据glTexImage2D
  • 解除纹理绑定,glBindTexture,传递0进行解除绑定

纹理坐标映射关系

在了解纹理贴图之前我们先回顾一下三个坐标系统,分别是纹理坐标系统、手机屏幕坐标系统、Opengl坐标系统。这三个坐标系统的的原点各不相同,纹理坐标系统我们上面已经介绍过了,这里不再重复。而手机屏幕坐标系统则是原点位于左上角,X轴向右为正,Y轴向下为正的坐标系统。

而Opengl坐标系统则是原点位于中心,X轴向右为正,Y轴向下为正,其值介于-1到1之间的一套坐标系统。

既然纹理贴图就像装修工人贴瓷砖一样,那么直接将纹理坐标和Opengl的顶点坐标一一对应起来即可,也就是如下图:

我们按照这个映射关系建立贴图:

// 顶点坐标,使用绘制两个三角形组成一个矩形的形式(三角形带)
// 第一第二第三个点组成一个三角形,第二第三第四个点组成一个三角形
const static GLfloat VERTICES[] = {
0.5f,-0.5f, // 右下
0.5f,0.5f, // 右上
-0.5f,-0.5f, // 左下
-0.5f,0.5f // 左上
}; // 纹理坐标(原点在左下角,这样贴图看到的会是倒置的
const static GLfloat TEXTURE_COORD[] = {
1.0f,0.0f, // 右下
1.0f,1.0f, // 右上
0.0f,0.0f, // 左下
0.0f,1.0f // 左上
};

运行发现图是贴上去了,但是看到的贴图却是倒置的,如下:

这是为什么呢?

因为纹理的生成是由图片像素来生成的,而图像的存储是从左上角开始的,但是纹理坐标原点却是在左下角的(笔者也不知道为什么要这么奇葩),所以就产生了倒置现象,因此正确的映射关系应该是以图片的左上角为原点做映射才对,而这也刚好与手机屏幕坐标系统匹配。

也就说正确的映射关系是需要先将以左下角为原点的纹理坐标进行倒置,然后再建立映射关系,这也是为什么有些博客说纹理坐标的原点是在左上角的原因(其实这是不对的,纹理坐标就是在图片的左下角,说在左上角的就是一个技巧),那么纹理坐标倒置后再映射如图:

!

废话少说,放码过来...


#include "TextureMapOpengl.h" #include "../utils/Log.h" // 顶点着色器
static const char *ver = "#version 300 es\n"
"in vec4 aPosition;\n"
"in vec2 aTexCoord;\n"
"out vec2 TexCoord;\n"
"void main() {\n"
" TexCoord = aTexCoord;\n"
" gl_Position = aPosition;\n"
"}"; // 片元着色器
static const char *fragment = "#version 300 es\n"
"precision mediump float;\n"
"out vec4 FragColor;\n"
"in vec2 TexCoord;\n"
"uniform sampler2D ourTexture;\n"
"void main()\n"
"{\n"
" FragColor = texture(ourTexture, TexCoord);\n"
"}"; // 使用绘制两个三角形组成一个矩形的形式(三角形带)
// 第一第二第三个点组成一个三角形,第二第三第四个点组成一个三角形
const static GLfloat VERTICES[] = {
0.5f,-0.5f, // 右下
0.5f,0.5f, // 右上
-0.5f,-0.5f, // 左下
-0.5f,0.5f // 左上
}; // 纹理坐标(原点在左下角,这样贴图看到的会是倒置的
//const static GLfloat TEXTURE_COORD[] = {
// 1.0f,0.0f, // 右下
// 1.0f,1.0f, // 右上
// 0.0f,0.0f, // 左下
// 0.0f,1.0f // 左上
//}; // 贴图纹理坐标(参考手机屏幕坐标系统,原点在左上角)
//由于对一个OpenGL纹理来说,它没有内在的方向性,因此我们可以使用不同的坐标把它定向到任何我们喜欢的方向上,然而大多数计算机图像都有一个默认的方向,它们通常被规定为y轴向下,X轴向右
const static GLfloat TEXTURE_COORD[] = {
1.0f,1.0f, // 右下
1.0f,0.0f, // 右上
0.0f,1.0f, // 左下
0.0f,0.0f // 左上
}; // 四分屏 GL_REPEAT环绕方式
//const static GLfloat TEXTURE_COORD[] = {
// 2.0f,2.0f, // 右下
// 2.0f,0.0f, // 右上
// 0.0f,2.0f, // 左下
// 0.0f,0.0f // 左上
//}; // 九分屏 GL_REPEAT环绕方式
//const static GLfloat TEXTURE_COORD[] = {
// 3.0f,3.0f, // 右下
// 3.0f,0.0f, // 右上
// 0.0f,3.0f, // 左下
// 0.0f,0.0f // 左上
//}; TextureMapOpengl::TextureMapOpengl():BaseOpengl() {
initGlProgram(ver,fragment);
positionHandle = glGetAttribLocation(program,"aPosition");
textureHandle = glGetAttribLocation(program,"aTexCoord");
textureSampler = glGetUniformLocation(program,"ourTexture");
LOGD("program:%d",program);
LOGD("positionHandle:%d",positionHandle);
LOGD("textureHandle:%d",textureHandle);
LOGD("textureSample:%d",textureSampler);
} void TextureMapOpengl::setPixel(void *data, int width, int height, int length) {
LOGD("texture setPixel");
glGenTextures(1, &textureId); // 激活纹理,注意以下这个两句是搭配的,glActiveTexture激活的是那个纹理,就设置的sampler2D是那个
// 默认是0,如果不是0的话,需要在onDraw的时候重新激活一下?
// glActiveTexture(GL_TEXTURE0);
// glUniform1i(textureSampler, 0); // 例如,一样的
glActiveTexture(GL_TEXTURE2);
glUniform1i(textureSampler, 2); // 绑定纹理
glBindTexture(GL_TEXTURE_2D, textureId);
// 为当前绑定的纹理对象设置环绕、过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
// 生成mip贴图
glGenerateMipmap(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, textureId); // 解绑定
glBindTexture(GL_TEXTURE_2D, 0);
} void TextureMapOpengl::onDraw() {
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(program); // 激活纹理
glActiveTexture(GL_TEXTURE2);
glUniform1i(textureSampler, 2); // 绑定纹理
glBindTexture(GL_TEXTURE_2D, textureId); /**
* size 几个数字表示一个点,显示是两个数字表示一个点
* normalized 是否需要归一化,不用,这里已经归一化了
* stride 步长,连续顶点之间的间隔,如果顶点直接是连续的,也可填0
*/
// 启用顶点数据
glEnableVertexAttribArray(positionHandle);
glVertexAttribPointer(positionHandle,2,GL_FLOAT,GL_FALSE,0,VERTICES); // 纹理坐标
glEnableVertexAttribArray(textureHandle);
glVertexAttribPointer(textureHandle,2,GL_FLOAT,GL_FALSE,0,TEXTURE_COORD); // 4个顶点绘制两个三角形组成矩形
glDrawArrays(GL_TRIANGLE_STRIP,0,4); glUseProgram(0); // 禁用顶点
glDisableVertexAttribArray(positionHandle);
if(nullptr != eglHelper){
eglHelper->swapBuffers();
} glBindTexture(GL_TEXTURE_2D, 0);
} TextureMapOpengl::~TextureMapOpengl() {
LOGD("TextureMapOpengl析构函数");
}

仔细看注释多理解...

往期笔记

Opengl ES之EGL环境搭建

Opengl ES之着色器

Opengl ES之三角形绘制

Opengl ES之四边形绘制

关注我,一起进步,人生不止coding!!!

Opengl ES之纹理贴图的更多相关文章

  1. 2.x最终照着教程,成功使用OpenGL ES 绘制纹理贴图,添加了灰度图

    在之前成功绘制变色的几何图形之后,今天利用Openg ES的可编程管线绘制出第一张纹理. 学校时候不知道OpenGL的重要性,怕晦涩的语法.没有跟老师学习OpenGL的环境配置,现在仅仅能利用coco ...

  2. OpenGL ES无法获取贴图数据原因

    最近在做一个项目,要从贴图中获取图像数据,查了很多资料,也琢磨很久,获取到的数据都是0.终于在一次偶然的机会,发现了端倪,成功了. 不得不说这"一分灵感"真的很重要 以下是在获取贴 ...

  3. Android OpenGL ES(八)----纹理编程框架

    1.把纹理载入进OpenGL中 我们的第一个任务就是把一个图像文件的数据载入到一个OpenGL的纹理中. 作为開始.让我们又一次舍弃第二篇的框架.又一次创建一个程序,新建一个util工具包,在该包下创 ...

  4. 从零开始的openGL——四、纹理贴图与n次B样条曲线

    前言 在上篇文章中,介绍了如何加载绘制模型以及鼠标交互的实现,并且遗留了个问题,就是没有模型表面没有纹理,看起来很丑.这篇文章将介绍如何贴纹理,以及曲线的绘制. 纹理贴图 纹理加载 既然是贴图,那首先 ...

  5. OpenGL ES 压缩纹理

    什么是压缩纹理 在实际应用特别是游戏中纹理占用了相当大的包体积,而且GPU无法直接解码目前流行的图片格式,图片必须转换为RGB等类型的格式才能上传到GPU内存,这显然增加了GPU内存的占用.为了处理这 ...

  6. Opengl ES之VBO和VAO

    前言 本文主要介绍了什么是VBO/VAO,为什么需要使用VBO/VAO以及如何使用VBO和VAO. VBO 什么是VBO VBO(vertex Buffer Object):顶点缓冲对象.是在显卡存储 ...

  7. Opengl ES之FBO

    FBO介绍 FBO帧缓冲对象,它的主要作用一般就是用作离屏渲染,例如做Camera相机图像采集进行后期处理时就可能会用到FBO.假如相机出图的是OES纹理,为了方便后期处理, 一般先将OES纹理通过F ...

  8. IOS 中openGL使用教程3(openGL ES 入门篇 | 纹理贴图(texture)使用)

    在这篇文章中,我们将学习如何在openGL中使用纹理贴图. penGL中纹理可以分为1D,2D和3D纹理,我们在绑定纹理对象的时候需要指定纹理的种类.由于本文将以一张图片为例,因此我们为我们的纹理对象 ...

  9. OpenGL ES教程系列(经典合集)

    为了搞透播放器的开发,花了些时间收集这些资料,虽然我已经搞定opengles渲染视频的内容,但是想玩玩opengles,往深里玩,图像处理这块是个好的方向,所以opengles是值得好好学的.   O ...

随机推荐

  1. NOI / 2.5基本算法之搜索-6044:鸣人和佐助详解

    总时间限制: 1000ms 内存限制: 65536kB 题目 佐助被大蛇丸诱骗走了,鸣人在多少时间内能追上他呢? 已知一张地图(以二维矩阵的形式表示)以及佐助和鸣人的位置.地图上的每个位置都可以走到, ...

  2. H5移动端实现一键复制或长摁复制

    今天接到了一个新的需求,要求我们对表单中的某一个字段进行复制,这个表单是不可选的,拿到需求的时候有点懵,不清楚下手点在哪,后来网上找了找,终于有了点眉目,感觉网上有些是实现不了的,特地在这里记录下进行 ...

  3. 把酒言欢话聊天,基于Vue3.0+Tornado6.1+Redis发布订阅(pubsub)模式打造异步非阻塞(aioredis)实时(websocket)通信聊天系统

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_202 "表达欲"是人类成长史上的强大"源动力",恩格斯早就直截了当地指出,处在蒙昧时代即低 ...

  4. ADB命令用法大全

    ​ 一.ADB简介 Android Debug Bridge,安卓调试桥,它借助adb.exe(Android SDK安装目录platform-tools下),用于电脑端与模拟器或者真实设备交互:使用 ...

  5. 运筹帷幄决胜千里,Python3.10原生协程asyncio工业级真实协程异步消费任务调度实践

    我们一直都相信这样一种说法:协程是比多线程更高效的一种并发工作方式,它完全由程序本身所控制,也就是在用户态执行,协程避免了像线程切换那样产生的上下文切换,在性能方面得到了很大的提升.毫无疑问,这是颠扑 ...

  6. Spring源码 11 IOC refresh方法6

    参考源 https://www.bilibili.com/video/BV1tR4y1F75R?spm_id_from=333.337.search-card.all.click https://ww ...

  7. Java开发学习(二十六)----SpringMVC返回响应结果

    SpringMVC接收到请求和数据后,进行了一些处理,当然这个处理可以是转发给Service,Service层再调用Dao层完成的,不管怎样,处理完以后,都需要将结果告知给用户. 比如:根据用户ID查 ...

  8. Excelize 2.5.0 正式发布,这些新增功能值得关注

    Excelize 是 Go 语言编写的用于操作 Office Excel 文档基础库,基于 ECMA-376,ISO/IEC 29500 国际标准.可以使用它来读取.写入由 Microsoft Exc ...

  9. 系统CPU飙高,怎么排查?

    cpu是整个电脑的核心计算资源,对于一个应用进程来说,cpu的最小执行单元是线程. 导致cpu飙高的原因有几个方面: cpu上下文切换过多,对于cpu来说,同一时刻下每个cpu核心只能运行一个线程,如 ...

  10. C++中的cout.setf(ios::fixed)是什么意思?

    问题描述:在阅读一段代码时,发现代码的最后一部分出现 ... cout.setf(ios::fixed); cout.setf(ios::showpoint); ... 解决: cout.setf() ...