根据教程:ogldev一步步从零开始,记录学习历程
本节的纹理学习还参考了教程: learnopengl和《OpenGL 编程指南》
一、纹理
1.1 纹理映射简介
我们之前的OpenGL学习一直停留在绘制一个彩色的四面体:
而现实世界物体的表面一般都呈现丰富特殊的颜色,并且在很小的图形上呈现多彩的变化,如果我们还是用计算机计算出一个个微小像素上的颜色再拼成特定的“图案”是非常辛苦的工作。
纹理映射就是找到一张图片,然后把它“粘”到物体表面上,就像贴墙纸一样。
1.2 2D纹理
OpenGL支持不同类型的纹理:1D(一维),2D(二维),3D(三维),CUBE(立方体)等等,我们使用的是2D纹理
- 通过之前的学习,我们知道在在渲染管线中,我们会计算出每个要在屏幕上显示的像素点的颜色并显示出来。
- 而我们拿到一张图片,并且要把它“贴”到我们的图形上去,我们要确定把我们图片上哪块部分贴到哪个像素点上
所以我们要指定图片的“某一块”,这个时候就需要指定纹理坐标
上图显示了纹理坐标怎么定义,纹理坐标定义在单位化的正方形内,并且以左下角为(0,0)比如图片像素是480*320,纹理坐标是(0.5,0.5),则在图形中的坐标即为(240,160)
- 当我们为顶点提供一些列的纹理坐标,在光栅化阶段,会对纹理坐标进行插值计算,计算出每个对应像素点需要的纹素(纹理中的一个像素)
- 纹素中包含一个用于跟屏幕上像素点对应的颜色值,许许多多的纹素里的颜色值显示在我们屏幕上的不同像素上就显示出了我们要的纹理
- 并且纹理坐标和我们绘制的图形的位置坐标对应,下图我们绘制了一个三角形,并用我们的纹理贴图里面的一块“贴”在我们的三角形上当图案,再进行缩放和旋转操作:
1.3 使用纹理映射
使用纹理映射,需要遵循以下3个步骤
1.3.1 创建一个纹理对象并加载纹理数据
(1) 在OpenGL中使用纹理的第一步是创建纹理对象,然后将对象绑定到环境的纹理单元
glGenTextures(1, &m_textureObj);
glBindTexture(m_textureTarget, m_textureObj);
正如之前我们学到的创建顶点缓冲区和索引缓冲区用到的GLUT的函数glGenBuffers(),我们创建一个纹理对象需呀用到函数glGenTextures(),函数返回一个没有用到的整数(对象编号)赋值到m_textureObj
并且我们要绑定当前的纹理对象,就像使用glBindBuffer()绑定顶点缓冲对象和索引缓冲对象一样,在对纹理对象进行操作需要先绑定当前的纹理对象
(2) 创建纹理对象并且绑定了当前的纹理对象,我们就需要把我们的贴图加载到我们的纹理数据当中
然而我们的贴图在硬盘中存放,我们OpenGL应用程序无法直接从硬盘中加载纹理数据,所以需要我们先把硬盘中的贴图读取到内存里,再从内存中加载纹理数据,示意图如下:
unsigned char *data = stbi_load(m_fileName.c_str(),&width, &height, &nrChannels, 0);
...
glTexImage2D(m_textureTarget, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
这里我们使用github上一个开源库——stb_image.h来读取存在硬盘里的图像数据
glTexImage2D()是根据指定的参数生成一个2D纹理
glTexImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void* pixels);
- target 指定目标纹理,这个值必须是GL_TEXTURE_2D。
- level 执行细节级别。0是最基本的图像级别,n表示第N级贴图细化级别。
- internalformat 指定纹理中的颜色组件。可选的值有GL_ALPHA,GL_RGB, GL_RGBA, GL_LUMINANCE, GL_LUMINANCE_ALPHA 等几种。
- width 指定纹理图像的宽度,必须是2的n次方。纹理图片至少要支持64个材质元素的宽度
- height 指定纹理图像的高度,必须是2的m次方。纹理图片至少要支持64个材质元素的高度
- border 指定边框的宽度。必须为0。
- format 像素数据的颜色格式, 不需要和internalformatt取值必须相同。可选的值参考internalformat。
- type 指定像素数据的数据类型。可以使用的值有GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT_5_6_5, GL_UNSIGNED_SHORT_4_4_4_4, GL_UNSIGNED_SHORT_5_5_5_1。
- pixels 指定内存中指向图像数据的指针
1.3.2 为顶点数据增加纹理坐标
我们在主程序main.cpp中为顶点数据增加纹理坐标,首先自定义一个数据结构,用来存放三维位置信息(一个长度为3的float类型数组)和二维纹理坐标(长度为2的数组):
struct Vertex
{
Vector3f m_pos;
Vector2f m_tex;
Vertex() {}
Vertex(Vector3f pos, Vector2f tex)
{
CopyVector3(m_pos, pos);
CopyVector2(m_tex, tex);
}
};
随后我们创建顶点缓冲器时不仅包含我们的位置信息,还要包含纹理坐标信息
static void CreateVertexBuffer()
{
Vector3f Vertices3f[4];
Vector2f Vertices2f[4];
LoadVector3(Vertices3f[0], -1.0f, -1.0f, 0.5773f); LoadVector2(Vertices2f[0], 0.0f, 0.0f);
LoadVector3(Vertices3f[1], 0.0f, -1.0f, -1.15475f); LoadVector2(Vertices2f[1], 0.5f, 0.0f);
LoadVector3(Vertices3f[2], 1.0f, -1.0f, 0.5773f); LoadVector2(Vertices2f[2], 1.0f, 0.0f);
LoadVector3(Vertices3f[3], 0.0f, 1.0f, 0.0f); LoadVector2(Vertices2f[3], 0.5f,