代码仓库

点击这里

1.为什么使用纹理

在之前的章节中,我们绘制了一个矩形,给了它特定的颜色,也通过矩阵操作了它平移旋转。但无论是炫酷的3A大作,还是简单的益智游戏,都是很漂亮,很有设计感的。单纯通过给顶点颜色,几乎很难做到,那么我们可以通过给这个矩形贴上一张图片,让他变得华丽起来。比如我的一个场景(可看这里的视频):
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gfyjvkX3-1623148877864)(https://oscimg.oschina.net/oscnet/up-f4dcd721a39d5639cc2aa941ee60b5fad5c.png)]
上图中,背景的图片,圆形的图片,小雨滴,甚至81192这几个字也是图片,也就是纹理。它几乎无处不在,极大的丰富我们的画面。

2.纹理采样与纹理环绕

纹理坐标在几何上是连续的,而片段或者像素是离散的,当我们要将坐标映射到纹理上,从纹理上去除颜色值给OpenGL,就是采样的过程。

  • 3.1 采样/纹理过滤
    OpenGL中的过滤方式由多种,常用的两种:GL_NEAREST和GL_LINEAR。
    GL_NEAREST(最近邻):

    从图上可以看出,要采样这个坐标的颜色,在GL_NEAREST模式下,会找一个离他最近的一个中心点的颜色,来作为最后的结果。
    GL_LINEAR(线性插值):

    线性插值的情况下,会找周围相邻的几个点的颜色,做一个平均。当然也不是绝对的平均,会根据距离做加权平均。也不一定是选取周围4个,这些值不固定,与算法实现有关系,但道理是相通的。
    两种过滤方式,会产生明显的差别,可以参看如下图示:

    在OpenGL中可以很简单的设置:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  • 3.2 纹理环绕
    OpenGL规定纹理坐标的范围是[0,1],如果超过了怎么办呢?
    实际中我们见过很多这种情况,铺地板。假如每块地板都是一个纹理,不停的铺同一种地板就是一种纹理环绕方式:重复。参看如下图示:

    OpenGL提供多种环绕方式:
    GL_REPEAT 重复纹理图像。 这是OpenGL的默认选项。
    GL_MIRRORED_REPEAT 也是重复,但是图片被镜像了。
    GL_CLAMP_TO_EDGE 超出的部分全都用边缘的颜色代替。
    GL_CLAMP_TO_BORDER 超出的部分用户自己指定颜色。
    看下图:

    在OpenGL中我们可以这样设置:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);

如果自己设置颜色,则可以通过这样设置:

float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
  • 3.3 mipmap贴图
    思考一个问题,如果一个物体离我们很远,即便它很大,我们也只是看它是一点点的。就像拍照一样,雷峰塔再高,我们与它合影,拍在画面里也仅仅是一些屏幕的像素。那么问题来了,当一个说很高清的物体离我们很远的时候,通常只需要很少的像素就能表示它,但是我们却因为它的分辨率很大,很难产生理想的片段输出。因此发明了一个叫多级渐远纹理(mipmap)的技术。注意:既然叫多级渐远,那么通常就用来表示远处的物体,也就是缩小时用的。放大的情况并不适合它。

mipmap本质上说就是一堆图片,以原始分辨率的图开始,以后每一张是上一张尺寸的一半,看下面的图:

我们按照距离来划分,在不同的距离范围内,从不同的尺寸上采样,这样就不会出现离得非常远,还要从原始分辨率上采样了,直接在一个小尺寸的图像上采样即可。
需要声明的一个问题是,这些图片也不是连续变化的,是以除以2的尺寸递减,那么必然会出现断档的情况,比如要用0.3倍的图像时,怎么办呢?
答案是让不同尺寸下的图片也进行所谓的纹理过滤,也就是OpenGL通过前后两张图帮我们生成一个中间的过渡图,然后再采样。
生成mipmap非常简单,OpenGL已经提供了一个方法,只需要调用一下即可:

glGenerateMipmap(GL_TEXTURE_2D);

可以用如下代码设置缩小时的纹理过滤:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
3.如何使用纹理
  • 2.1 现实中,如何给一个矩形贴一张画呢?
    如果我们的画跟矩形一样大,那么只要画的四个顶点跟矩形的四个顶点都对齐就能保证,画严丝合缝的被贴上。
  • 2.2 OpenGL中如何做呢?
    跟现实中类似,只要我的纹理四个点的坐标跟矩形的四个顶点对应上,就能保证被贴上了。更方便的是,纹理是可以被拉伸缩放的,我们总能把纹理贴在任意大小的矩形上。
    现在看一下纹理的坐标,区间是[0,1]

与颜色一样,纹理坐标也是顶点的一个属性,所以我们把纹理坐标追加到颜色属性的后面,代码如下:

    float vertices[] = {0.5f, 0.5f, 0.0f,       1.0, 0.0, 0.0,    1.0f, 1.0f,0.5f, -0.5f, 0.0f,      0.0, 1.0, 0.0,    1.0f, 0.0f,-0.5f, -0.5f, 0.0f,     0.0, 0.0, 1.0,    0.0f, 0.0f,-0.5f, 0.5f, 0.0f,      0.9, 0.6, 0.8,     0.0f, 1.0f};

最后的两列就是我们的纹理坐标。内存分布如下图所示:

以一个点为例:(0.5, 0.5) 是顶点的位置坐标,代表矩形右上角的位置,0.5是它在屏幕的坐标系下的位置。对应到纹理上就是 (1,1),因为纹理的右上角要贴在这个矩形的右上角,而不关心矩形到底有多大。参考下图:

  • 2.3 修改我们的顶点着色器:
#version 330 corelayout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTex;uniform mat4 model;out vec3 outColor;
out vec2 outTex;void main()
{gl_Position = model * vec4(aPos, 1.0);outColor = aColor;outTex = aTex;
}

在我们的顶点着色器中,添加了一个新的属性纹理坐标 layout (location = 2) in vec2 aTex;, 并用相同的类型将这个属性的值传递给片段着色器outTex = aTex;
此时还要在C++代码中,告诉OpenGL如何用刚才添加的数据,这与之前颜色的做法相同
注意此时stride要修改为8。

    glEnableVertexAttribArray(2);glVertexAttribPointer(2, 2, GL_FLOAT, false, stride, (void*)(6*sizeof(float)));

紧接着在片段着色器中

#version 330 corein vec3 outColor;
in vec2 outTex;uniform sampler2D image;void main()
{vec4 color = texture(image, outTex);gl_FragColor = color;
}

我们接收到了顶点坐标,并声明了一个uniform的2D的纹理对象。因为纹理对于所有的片段都是一致的,所以是uniform变量。然后用texture函数从纹理上在outTex的位置进行采样,得到最终的颜色值并输出给下一个环节,最终通过各种测试后,显示到屏幕上。

4.加载纹理并使用

万事俱备,只差把图片数据传给OpenGL使用了。

  • 4.1 首先,我们需要将图片加载到内存中来
    因为图片大都是压缩格式保存到硬盘的,jpg,png等格式都是压缩的,需要把他们解压缩,变成RGB或者RGBA或者BGR等排列方式的原始数据,我们这里使用一个库(stb image loader)来做,它已经做了大量格式的兼容,并且使用简单:
    int width, height, channels;unsigned char* data = stbi_load("../resources/images/person.jpg", &width, &height, &channels, 0 );
  • 4.2 像OpenGL申请一块内存,并把数据传过去,完整使用代码如下:
// Gen TextureGLuint texture;glGenTextures(1, &texture);glBindTexture(GL_TEXTURE_2D, texture);glTextureParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);glTextureParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);glTextureParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTextureParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);// Load Imageint width, height, channels;unsigned char* data = stbi_load("../resources/images/person.jpg", &width, &height, &channels, 0 );if (data) {glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);glGenerateMipmap(GL_TEXTURE_2D);}stbi_image_free(data);glBindTexture(GL_TEXTURE_2D, 0);

最后记得要释放内存,因为已经传递给OpenGL了,显存上已经存在,那么主内存的数据就不需要了。

  • 4.3 直接运行绘制

    从运行效果看,我们的确将图片显示了出来,但却是上下翻转的。这是因为OpenGL的Y轴 0 从底部开始,而图像的 Y轴 0 基本从 上面开始。这也很常见,比如Android的屏幕坐标就是左上角开始的,向下Y轴增加。
    直接使用stb_image.h这个头文件里的一个方法翻转图片就可以了:
stbi_set_flip_vertically_on_load(true);

以上是使用单张图片的全部内容,如何使用多张图片,我们下一章再看。

OpenGL开发-第6章-纹理,让物体更漂亮相关推荐

  1. 计算机图形学与opengl C++版 学习笔记 第5章 纹理贴图

    目录 5.1 加载纹理图像文件 5.2 纹理坐标 5.3 创建纹理对象 5.4 构建纹理坐标 5.5 将纹理坐标载入缓冲区 5.6 在着色器中使用纹理:采样器变量和纹理单元 5.7 纹理贴图:示例程序 ...

  2. OpenGL开发之旅基础知识介绍

    最近由于手机项目中需要用到OpenGL ES的知识,所以这段时间正在研究OpenGL的相关知识.因为OpenGL ES是OpenGL的剪裁版本,所以我直接从OpenGL入手,然后再去看OpenGL E ...

  3. 【转载】【《Real-Time Rendering 3rd》 提炼总结】(五) 第六章 · 纹理贴图及相关技术 The Texturing

    本文由@浅墨_毛星云 出品,转载请注明出处.   文章链接: http://blog.csdn.net/poem_qianmo/article/details/73718109 在计算机图形学中,纹理 ...

  4. OpenGL开发库的详细介绍

    OpenGL开发库的组成 开发基于OpenGL的应用程序,必须先了解OpenGL的库函数.它采用C语言风格,提供大量的函数来进行图形的处理和显示.OpenGL库函数的命名方式非常有规律.所有OpenG ...

  5. OPENGL ES 2.0 知识串讲 (10) ——OPENGL ES 详解IV(纹理优化)

    上节回顾 上一节学习了如何从一张原始图片中,获取生成纹理所需要的信息,然后根据这些信息,通过OpenGL ES API在GPU内存中生成了一张纹理,并且还介绍了纹理属性,知道了如何通过纹理坐标将纹理映 ...

  6. Learn OpenGL (四):纹理

    为了能够把纹理映射(Map)到三角形上,我们需要指定三角形的每个顶点各自对应纹理的哪个部分.这样每个顶点就会关联着一个纹理坐标(Texture Coordinate),用来标明该从纹理图像的哪个部分采 ...

  7. 转贴: OpenGL开发库的组成

    OpenGL开发库的组成 开发基于OpenGL的应用程序,必须先了解OpenGL的库函数.它采用C语言风格,提供大量的函数来进行图形的处理和显示.OpenGL库函数的命名方式非常有规律.所有OpenG ...

  8. OpenGL 开发环境配置(Windows) - Visual Studio 2017 + GLFW + GLAD 详细图文教程

    OpenGL 开发环境配置(Windows) - Visual Studio 2017 + GLFW + GLAD 详细图文教程 大部分 OpenGL 是直接面向设备商的,如果开发者需要使用 Open ...

  9. OpenGL开发学习指南二(glfw+glad)

    在上一篇文章中博主介绍了freeglut+glew的环境配置,本文介绍glfw+glad的环境配置 本系列教程将使用本文的opengl开发库 开发工具 VS2017 glfw源码:源码地址 CMake ...

  10. 《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第六章 纹理 Texturing

    写在前面的话:因为英语不好,所以看得慢,所以还不如索性按自己的理解简单粗糙翻译一遍,就当是自己的读书笔记了.不对之处甚多,以后理解深刻了,英语好了再回来修改.相信花在本书上的时间和精力是值得的. -- ...

最新文章

  1. centos7 lvm管理 把/home空间转移给/
  2. mysql添加临时索引_mysql创建索引/删除索引操作
  3. 三十三、分治算法---汉诺塔问题
  4. 计算机jsp外文文献,计算机 JSP web 外文翻译 外文文献 英文文献
  5. hdu4416 Good Article Good sentence (后缀数组)
  6. BZOJ2815: [ZJOI2012]灾难
  7. python requests发送websocket_Pywss - 用python实现WebSocket服务端
  8. 微信扫一扫识物的技术揭秘:抠图与检索
  9. [深度学习] Keras 如何使用fit和fit_generator
  10. 最有效的萨克斯弱音器_1990到2016年全球自杀数据公布,中国自杀死亡率下降最显著!...
  11. 公交查询系统 c语言,城市公交查询系统的设计任务书
  12. 用简单的语言描述C++ 是什么?
  13. Mac开发环境搭建_zsh替换bash_2
  14. CLion + OpenCV 开发
  15. graphpad7.04多组比较p值_R语言缺失值处理(MICE/Amelia/missForest/Hmisc/mi)
  16. ul列表中包含input时line-height属性失效的解决办法
  17. 欧姆龙PLC数据读写工具。 支持FinsTCP实测好用打开欧姆龙PLC读写软件,输入IP地址和端口号
  18. 【软件需求工程】北理的恶龙们——软件需求规格说明
  19. 【限流算法】java实现滑动时间窗口算法
  20. Mac:小米手机刷机

热门文章

  1. RabbitMQ入门教程(十四):RabbitMQ单机集群搭建
  2. 个人java学习路线-Spring
  3. 用u盘安装win7系统教程
  4. The SDK seems invalid 问题处理
  5. 安卓手机远程连接linux系统,电脑(Linux/Windows)使用SSH远程登录安卓(Android)手机实现无线传输和管理文件(图文详解)-Go语言中文社区...
  6. 基于ZEGO SDK实现多人视频通话功能
  7. Python print() 函数,在同一行打印
  8. Thread多线程-(最容易被问到的面试题)
  9. JS框架_(JQuery.js)夜晚天空满天星星闪烁动画
  10. 短线交易有这些特点的,慎做短线交易