✠OpenGL-5-纹理贴图

Posted itzyjr

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了✠OpenGL-5-纹理贴图相关的知识,希望对你有一定的参考价值。


纹理贴图是在光栅化的模型表面上覆盖图像的技术。
纹理贴图非常重要,因此硬件也为它提供了支持,使得它具备了实现实时的照片级真实感的超高性能。 纹理单元是专为纹理设计的硬件组件,现代显卡通常带有数个纹理单元。

加载纹理图像文件

为了在 OpenGL/GLSL 中有效地完成纹理贴图,需要协调好以下几个不同的数据集和机制:

  • 用于保存纹理图像的纹理对象(在本章中我们仅考虑 2D 图像);
  • 一个特殊的统一采样器变量,以便顶点着色器可以访问纹理;
  • 用于保存纹理坐标的缓冲区;
  • 用于将纹理坐标传递给管线的顶点属性;
  • 显卡上的纹理单元

为了使纹理图像可以被用于 OpenGL 管线中的着色器,我们需要从图像中提取颜色并将它们放入 OpenGL 纹理对象(用于保存纹理图像的内置 OpenGL 结构)中。

许多 C++库可用于读取和处理图像文件, 常见的选择包括 Cimg、 BoostGIL 和 Magick++。我们选择使用专为 OpenGL 设计的名为 SOIL2 的库。

将纹理加载到 OpenGL 应用程序的步骤是:
(1)使用 SOIL2 实例化 OpenGL 纹理对象并从图像文件中读入数据;
(2)调用 glBindTexture()以使新创建的纹理对象处于激活状态;
(3)使用 glTexParameter()函数调整纹理设置。
最终获得的结果就是现在可用的OpenGL纹理对象的整型 ID。

SOIL_load_OGL_texture()函数接受图像文件名作为其参数之一(稍后将描述一些其他参数)。这些步骤在以下函数中实现:

GLuint loadTexture(const char* textImagePath) {
	GLuint textureID;
	// 通过指定SOIL_FLAG_INVERT_Y参数,垂直翻转图像来重新定向,使其与OpenGL的预期格式相对应。
	textureID = SOIL_load_OGL_texture(textImagePath, SOIL_LOAD_AUTO, SOIL_CREATE_NEW_ID, SOIL_FLAG_INVERT_Y);
	if (textureID == 0)
		cout << "could not find texture file" << textImagePath << endl;
	return textureID;
}
纹理坐标

通过为模型中的每个顶点指定纹理坐标来完成将纹理应用于对象的渲染表面。

纹理坐标是对纹理图像(通常是 2D)中的像素的引用。
纹理图像中的像素被称为纹素(Texel),以便将它们与在屏幕上呈现的像素区分开。

纹理坐标用于将 3D 模型上的点映射到纹理中的位置。除了将它定位在 3D 空间中的(x,y,z)坐标之外,模型表面上的每个点还具有纹理坐标(s,t),用来指定纹理图像中的哪个纹素为它提供颜色。这样,物体的表面被按照纹理图像“涂画”。纹理在对象表面上的朝向由分配给对象顶点的纹理坐标来确定。

为了确保渲染模型中的每个像素都使用纹理图像中的适当纹素进行绘制,纹理坐标也需要被放入顶点属性中,以便它们也由光栅着色器进行插值。以这种方式,纹理图像与模型顶点一起被插值或者填充。

我们将设置两个缓冲区,一个用于顶点(每个条目中有3个分量 x、y和z),另一个用于相应的纹理坐标(每个条目中有两个分量 s 和 t)。这样,每次顶点着色器的调用接收到一个顶点的数据,现在包括了其空间坐标和相应的纹理坐标。

2D 纹理图像被设定为矩形,左下角的位置坐标为(0,0),右上角的位置坐标为(1,1)。理想情况下,纹理坐标应该在[0…1]范围内取值。

顶点之间的所有中间像素都已使用图像中间插值的纹素进行绘制。这正是因为纹理坐标在顶点属性中被发送到片段着色器,因此也像顶点本身一样被插值。

3D模型上的每个顶点与纹理坐标一一对应,由纹素给顶点提供颜色。

上图可以看到图像看起来略微拉伸——这是因为纹理图像的长宽比与立方体面相关的给定纹理坐标的长宽比不匹配。

渲染金字塔模型,只是这次用砖的图像添加纹理。我们需要指定:
(a)引用纹理图像的整型ID;
(b)模型顶点的纹理坐标;
(c)用于保存纹理坐标的缓冲区;
(d)顶点属性,以便顶点着色器可以接收并通过管线转发纹理坐标;
(e)显卡上用于保存纹理对象的纹理单元
(f)用于访问 GLSL 中纹理单元的统一采样器变量

创建纹理对象

GLuint brickTexture = Utils::loadTexture("brick1.jpg");
纹理对象由整型ID标识,因此brickTexture的类型为GLuint。

构建纹理坐标

我们要定位纹理坐标,以便将砖纹理绘制到金字塔上。


相应的顶点和纹理坐标数据组如图:

将纹理坐标载入缓冲区

先找出与金字塔模型的每个顶点一一对应的纹理坐标值。
每个三元顶点坐标vec3对应一个二元纹理坐标vec2。
金字塔有18个顶点,对应有18个(s, t)值,如下:

float pyrTexCoords[36] = {
	0.0f,0.0f, 1.0f,0.0f, 0.5f,1.0f,// 前侧面
	0.0f,0.0f, 1.0f,0.0f, 0.5f,1.0f,// 右侧面
	0.0f,0.0f, 1.0f,0.0f, 0.5f,1.0f,// 后侧面
	0.0f,0.0f, 1.0f,0.0f, 0.5f,1.0f,// 左侧面
	0.0f,0.0f, 1.0f,1.0f, 0.0f,1.0f,// 底面三角形I
	1.0f,1.0f, 0.0f,0.0f, 1.0f,0.0f // 底面三角形II
}

然后,在创建至少两个VBO(一个用于顶点,一个用于纹理坐标)之后,我们添加以下代码行以将纹理坐标加载到 VBO #1 中:

glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(pyrTexCoords), pyrTexCoords, GL_STATIC_DRAW);
在着色器中使用纹理:采样器变量和纹理单元

为了最大限度地提高性能,我们希望在硬件中执行纹理处理。这意味着我们的片段着色器需要一种访问我们在 C++/OpenGL 应用程序中创建的纹理对象的方法。它的实现机制是通过一个叫作统一采样器变量的特殊 GLSL 工具。这是一个变量,用于指示[ 显卡 ]上的纹理单元,从加载的纹理对象中提取或“采样”哪个纹素。

在着色器中声明一个采样器变量:

layout(binding = 0) uniform sampler2D samp;

声明的变量samp是sampler2D类型的统一采样器变量,“binding=【0】”即指定此采样器与纹理单元【0】相关联。
由于display()函数需要指定纹理单元要为当前帧采样的纹理对象,所以每次绘制对象时,都需要激活纹理单元并将其绑定到特定的纹理对象。例如:

glActiveTexture(GL_TEXTURE0);// 其中“GL_TEXTURE”的后缀【0】与着色器中声明采样器变量中“binding=【0】”相对应
glBindTexture(GL_TEXTURE_2D, brickTexture);
glActiveTexture(GLenum texture)
texture-指定激活哪个纹理单元。
它必须是GL_TEXTUREi中的一个,其中i∈[0, GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS-1]
作用:选择后续纹理状态调用将影响的纹理单元。

void glBindTexture(GLenum target, GLuint texture)
target-指定纹理绑定到的目标。必须是下列之一:
GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D、
GL_TEXTURE_1D_ARRAY、GL_TEXTURE_2D_ARRAY、
GL_TEXTURE_RECTANGLE、
GL_TEXTURE_CUBE_MAP、GL_TEXTURE_CUBE_MAP_ARRAY、
GL_TEXTURE_BUFFER、
GL_TEXTURE_2D_MULTISAMPLE、GL_TEXTURE_2D_MULTISAMPLE_ARRAY
texture-指定纹理的名称。
作用:将命名纹理绑定到纹理目标。

要实际执行纹理处理,我们需要修改片段着色器输出颜色的方式。
我们需要使用从顶点着色器(通过光栅着色器)接收的插值纹理坐标来对纹理对象进行采样,像这样调用 texture() 函数:

// 片段着色器程序
layout(binding = 0) uniform sampler2D samp;
in vec2 tc;// 纹理坐标
...
color = texture(samp, tc);
纹理贴图:示例程序
#include <SOIL2/soil2.h>
// 其他#include和以前一样
...
#define numVAOs 1
#define numVBOs 2
// 摄像机和对象位置、渲染程序、VAO和VBO的变量和以前一样
...
// display()函数的变量分配和以前一样
...
GLuint brickTexture;

void setupVertices() {
	float pyramidPositions[54] = {/* 和以前一样 */}
	float pyrTexCoords[36] = {
		0.0f,0.0f, 1.0f,0.0f, 0.5f,1.0f,// 前侧面
		0.0f,0.0f, 1.0f,0.0f, 0.5f,1.0f,// 右侧面
		0.0f,0.0f, 1.0f,0.0f, 0.5f,1.0f,// 后侧面
		0.0f,0.0f, 1.0f,0.0f, 0.5f,1.0f,// 左侧面
		0.0f,0.0f, 1.0f,1.0f, 0.0f,1.0f,// 底面三角形I
		1.0f,1.0f, 0.0f,0.0f, 1.0f,0.0f // 底面三角形II
	}
	
	// ... 和以前一样生成VAO和至少两个VBO,并加载两个缓冲区:
	glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
	glBufferData(GL_ARRAY_BUFFER, sizeof(pyramidPositions), pyramidPositions, GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
	glBufferData(GL_ARRAY_BUFFER, sizeof(pyrTexCoords), pyrTexCoords, GL_STATIC_DRAW);
}

void init(GLFWwindow* window) {
	// 渲染程序配置、摄像机和对象位置没有改变
	...
	brickTexture = Utils::loadTexture("brick1.jpg");
}

void display(GLFWwindow* window, double currentTime) {
	...
	// 背景颜色、深度缓冲区、渲染程序,以及M、V、MV、PROJ矩阵没变化
	...
	glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
	glVertexAttriPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(0);

	glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
	glVertexAttriPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(1);

	glActiveTexture(GL_TEXTURE0);// 激活【纹理单元】#0
	glBindTexture(GL_TEXTURE_2D, brickTexture);// 绑定目标纹理

	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);

	glDrawArrays(TL_TRIANGLES, 0, 18);
}
int main() { /* 和以前一样 */ }
// 顶点着色器
#version 430
layout(location = 0) in vec3 pos;
layout(location = 1) in vec2 texCoord;
out vec2 tc;// 纹理坐标输出到【光栅着色器】用于插值
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
void main() {
	gl_Position = proj_matrix * mv_matrix * vec4(pos, 1.0);
	tc = texCoord;
}

// 片段着色器
#version 430
in vec2 tc;// 输入【插值过的】材质坐标
out vec4 color;
layout(binding = 0) uniform sampler2D samp;
void main() {
	color = texture(samp, tc);
}

运行效果如下:

多级渐远纹理贴图

叠影伪影产生原理:
当图像分辨率小于所绘制区域的分辨率时,会出现一种很常见的伪影。在这种情况下,需要拉伸图像以覆盖整个区域,就会变得模糊(并且可能变形)。根据纹理的性质,有时可以通过改变纹理坐标分配方式来对抗这种情况,使得纹理需要较少的拉伸。另一种解决方案是使用更高分辨率的纹理图像。
相反的情况是当图像纹理的分辨率大于被绘制区域的分辨率时。可能并不是很容易理解为什么这会造成问题,但确实如此!在这种情况下,可能会出现明显的叠影伪影,从而产生奇怪的错误图案,或移动物体中的“闪烁”效果。

叠影是由采样错误引起的。它通常与信号处理有关,不充分采样的信号被重建时,看起来会具有和实际不同的特性(例如波长)。如下图所示,原始波形显示为红色,沿波形的黄点代表采样点。如果采样点被用于重建波形,并且采样频率不足,则可能会定义出不同的波形(以蓝色显示)。

类似地,在纹理贴图中,当稀疏地采样高分辨率(和高细节)图像时(例如使用统一采样器变量时),提取到的颜色将不足以反映图像中的实际细节,而是可能看起来很随机。如果纹理图像具有重复图案,则叠影可能导致生成与原始图像不同的图案。如果被纹理贴图的对象正在移动,则纹素查找中的舍入误差可能导致给定纹理坐标处的采样像素的不断变化,从而在被绘制对象的表面上产生不希望的闪烁效果。

下图显示了一个立方体顶部的倾斜渲染特写, 该立方体使用大尺寸高分辨率棋盘图像进行纹理贴图。

在图像顶部附近明显发生了混叠,棋盘的欠采样产生了“条纹”效果。虽然我们无法在静止图像中展示,但如果这是一个动画场景,则看起来的图案可能会在各种不正确的图案(包括图示的这一个在内)之间波动。

另一个例子如下图所示,其中的立方体已经使用月球表面的图像进行纹理贴图。乍一看,这张图片显得清晰而细节丰富。然而,图像右上部分的某些细节是错误的,并且当立方体对象(或相机)移动时会导致“闪烁”。(不幸的是,我们无法在静止图像中清楚地显示闪烁效果。)

使用多级渐远纹理贴图来校正:
使用多级渐远纹理贴图(Mipmapping)技术可以在很大程度上校正这一类的采样误差伪影,它需要用各种分辨率创建纹理图像的不同版本。然后, OpenGL 使用最适合正在处理的这一点处的分辨率的纹理图像进行纹理贴图。更好的是,可以为被贴图的区域使用最适合的分辨率的纹理图像的平均颜色。多级渐远纹理贴图应用于上面两图像的结果如下图所示。

多级渐远纹理贴图的校正原理:
多级渐远纹理贴图通过一种巧妙的机制来工作,它在纹理图像中存储相同图像的连续的一系列较低分辨率的副本,所用的纹理图像比原始图像大 1/3。这是通过将图像的 R、G 和B 分量分别存储在纹理图像空间的 3 个 1/4 中来实现的,然后在剩余的 1/4 图像空间中对于同一图像重复相当于原始分辨率 1/4 的处理。 重复该细分直到剩余象限太小而不包含任何有用的图像数据。

这种将几个图像填充到一个小空间中的方法(只比存储原始图像所需的空间大一点)是 Mipmapping 得名的原因。MIP 代表拉丁语Multum In Parvo,意思是“在很小的空间里有很多东西”。

多级渐远纹理对应的OpenGL选项:
实际给对象添加纹理时,可以通过多种方式对多级渐远纹理进行采样。在 OpenGL 中,可以通过将 GL_TEXTURE_MIN_FILTER 参数设置为所需的缩小方法来选择多级渐远纹理的采样方式,可以选取以下方法之一:

  • GL_NEAREST_MIPMAP_NEAREST
    选择具有与纹素区域最相似的分辨率的多级渐远纹理。然后,它获得所需纹理坐标的最近纹素。
  • GL_LINEAR_MIPMAP_NEAREST
    选择具有与纹素区域最相似的分辨率的多级渐远纹理。 然后它取最接近纹理坐标的4 个纹素的插值。这被称为“线性过滤”。
  • GL_NEAREST_MIPMAP_LINEAR
    选择具有与纹素区域最相似的分辨率的 2 个多级渐远纹理。然后,它从每个多级渐远纹理获取纹理坐标的最近纹素并对其进行插值。这被称为“双线性过滤”。
  • GL_LINEAR_MIPMAP_LINEAR
    选择具有与纹素区域最相似的分辨率的 2 个多级渐远纹理。然后,它取各自最接近纹理坐标的 4 个纹素,并计算插值。这被称为“三线性过滤”。

三线性过滤通常是比较好的选择,因为较低的混合级别通常会产生伪影,例如多级渐远纹理级别之间的可见分离。左下图显示了只启用了线性过滤的使用多级渐远纹理的棋盘的特写。请注意在多级渐远纹理的边界处垂直线突然从粗变为细(图中圈出的位置的伪影)。相比之下,右下图中的示例使用了三线性过滤。

OpenGL 提供了丰富的多级渐远纹理支持。有一些机制可用于构建你自己的多级渐远纹理级别,或者让 OpenGL 为你构建它们。在大多数情况下, OpenGL 自动构建的多级渐远纹理已足够。这是通过将以下代码行添加进 getTextureObject()函数之后立即执行的 Utils::loadTexture()函数中实现的:

GLuint getTextureObject(GLuint textureID) {
	glBindTexture(GL_TEXTURE_2D, textureID);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	glGenerateMipmap(GL_TEXTURE_2D);
	return textureID;
}

这通知 OpenGL 生成多级渐远纹理。使用 glBindTexture()调用激活砖纹理,然后glTexParameteri() 函数调用启用前面列出的缩小方法之一 ,例如上面调用中显示的GL_LINEAR_MIPMAP_LINEAR,它启用三线性过滤。

构建多级渐远纹理后,可以通过再次调用 glTexParameteri()来更改过滤选项(尽管这很少需要),例如在 display 函数中。甚至可以通过选择 GL_NEAREST 或GL_LINEAR 来禁用多级渐远纹理。

对于关键应用程序,可以使用您喜欢的任何图像编辑软件自行构建多级渐远纹理。然后可以通过为每个多级渐远纹理级别重复调用 OpenGL 的 glTexImage2D()函数来创建纹理对象,并将它们添加为多级渐远纹理级别。这里不做过多讨论。

各向异性过滤

多级渐远纹理贴图有时看起来比非多级渐远纹理贴图更模糊(下图比较可知),尤其是当被贴图对象以严重倾斜的视角渲染时。我们在左下图中看到了一个这样的例子,右下图使用多级渐远纹理减少伪影的同时也减少了图像细节。

这种细节的丢失是因为当物体倾斜时,其基元看起来沿一个轴(即宽度或高度)比沿另一个轴更小。当 OpenGL 为图元贴图时,它选择适合两个轴中较小的轴的多级渐远纹理(以避免“闪烁”伪影)。在右上图中,表面远离观察者倾斜,因此每个渲染图元将使用适合其更小的高度的多级渐远纹理,这可能对其宽度来说分辨率太小了。

一种恢复一些丢失细节的方法是使用各向异性过滤(AF)。标准的多级渐远纹理贴图以各种正方形分辨率(例如 256 像素× 256 像素、 128 像素× 128 像素等)对纹理图像进行采样,而 AF 却以多种矩形分辨率对纹理进行采样,例如 256 像素× 128 像素、 64 像素× 128像素等。这使得从各种角度观看并同时在纹理中保留尽可能多的细节成为可能。

各向异性过滤比标准多级渐远纹理贴图在计算上代价更高, 并且不是 OpenGL 的必需部分。但是,大多数显卡都支持 AF(这被称为 OpenGL 扩展),而 OpenGL 确实提供了一种查询显卡是否支持 AF 的方法,以及一种访问 AF 的方法。生成多级渐远纹理贴图后立即添加代码:

// 如果启用【多级渐远纹理贴图】
glBindTexture(GL_TEXTURE_2D, textureID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glGenerateMipmap(GL_TEXTURE_2D);

// 如果还启用【各向异性过滤】
if (glewIsSupported("GL_EXT_texture_filter_anisotropic")) {
	GLfloat anisoSetting = 0.0f;
	glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisoSetting);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisoSetting);
}

glewIsSupported()的调用测试显卡是否支持 AF。如果支持,我们将其设置为支持的最大采样程度,这个最大值使用glGetFloatv()获取。然后使用 glTexParameterf()将其应用于激活纹理对象。

启用【多级渐远纹理贴图】,并启用【各向异性过滤】,效果如下:

从上图并对比之上的两图,可以看到,大部分丢失细节已经恢复,同时仍然消除了闪烁的伪影。

这时,我们的loadTexture函数,可以改成:

GLuint Utils::loadTexture(const char* texImagePath) {
	GLuint textureRef;
	textureRef = SOIL_load_OGL_texture(texImagePath, SOIL_LOAD_AUTO, SOIL_CREATE_NEW_ID, SOIL_FLAG_INVERT_Y);
	if (textureRef == 0) 
		cout << "didnt find texture file " << texImagePath << endl;
	// ----- mipmap/anisotropic section
	glBindTexture(GL_TEXTURE_2D, textureRef);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);// 三线性过滤
	glGenerateMipmap(GL_TEXTURE_2D);
	if (glewIsSupported("GL_EXT_texture_filter_anisotropic")) {
		GLfloat anisoset = 0.0f;
		glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisoset);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisoset);// 各种异性过滤
	}
	// ----- end of mipmap/anisotropic section
	return textureRef;
}
环绕和平铺

到目前为止,我们假设纹理坐标都落在[0…1]范围内。但是, OpenGL 实际上支持任何取值的纹理坐标。有几个选项可以用来指定当纹理坐标超出范围[0…1]时会发生什么。使用glTexParameteri()设置所需的行为,选项如下。

  • GL_REPEAT:忽略纹理坐标的整数部分,生成重复或“平铺”图案。这是默认行为。
  • GL_MIRRORED_REPEAT:忽略整数部分,但是当整数部分为奇数时坐标反转,因此重复的图案在正常和镜像之间交替。
  • GL_CLAMP_TO_EDGE:小于 0 或大于 1 的坐标分别设置为 0 和 1。
  • GL_CLAMP_TO_BORDER:将[0…1]以外的纹素设置成指定的边框颜色。

例如,考虑一个金字塔,其纹理坐标已在[0…5]范围,而不是通常的[0…1]范围内定义。默认行为(GL_REPEAT),使用前面图中显示的纹理图像,将导致纹理在表面上重复五次(有时称为“平铺”),如下图所示。

GL_MIRRORED_REPEAT:使平铺块的外观在正常和镜像之间交替。

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

GL_CLAMP_TO_EDGE:将指定 < 0 或 > 1 的值分别设置为0和1。

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

GL_CLAMP_TO_BORDER:指定 < 0 或 > 1的值输出“边框”颜色。

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
float redColor[4] = { 1.0f, 0.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, redColor);

纹理坐标范围为−2~+3,效果如下(从左往右分别是:镜像重复、夹紧到边缘、夹紧到边框):

-2 ~ +3的纹理坐标范围,使得镜像重复示例,最底下是重复五次平铺。
中间的示例(夹紧到边缘)中,沿纹理图像边缘的像素向外复制。

透视变形

考虑一个由两个三角形组成的矩形,纹理贴图是棋盘图像,面向相机。当矩形围绕 X轴旋转时,矩形的顶部会倾斜并远离相机,而矩形的下半部分则更靠近相机。因此,我们希望顶部的方块变小,底部的方块变大。但是,纹理坐标的线性插值将导致所有正方形的高度相等。沿着构成矩形的两个三角形之间的对角线的失真加剧。产生的失真如图:

幸运的是,存在用于校正透视失真的算法,并且默认情况下, OpenGL 在光栅化期间会应用透视校正算法。下图显示了由 OpenGL 正确呈现的相同的旋转棋盘。

可以禁用OpenGL的透视校正,虽然不常见。
必须在顶点着色器和片段着色器中都添加关键字“noperspective”。

// 顶点着色器
noperspective out vec2 texCoord;

// 片段着色器
noperspective out vec2 texCoord;
材质——更多OpenGL细节

可以使用 C++和 OpenGL 函数直接将纹理图像文件数据加载到 OpenGL 中。 虽然它有点复杂,但并不少见。一般步骤如下:
(1)使用C++工具读取图像文件;
(2)生成OpenGL纹理对象;
(3)将图像文件数据复制到纹理对象中。

步骤(1):可以使用C++函数fopen()和fread()就可以将数据从.bmp图像文件读入unsigned char类型的数组中。

步骤(2):使用OpenGL的glGenTextures()命令创建一个或多个纹理对象。例如,生成单个OpenGL纹理对象:

GLuint textureID;// 或者GLuint类型的数组,如需创建多于一个纹理对象
glGenTextures(1, &textureID);
void glGenTextures(GLsizei n, GLuint* textures)
n-指定要生成的纹理名称的数目。
textures-指定一个数组去存储生成的纹理名称。

步骤(3):将步骤(1)中的图像数据关联到步骤(2)中创建的纹理对象,这里使用glTexImage2D()命令完成——从步骤(1)中描述的 unsigned
char 数组(此处表示为“ data”)加载到步骤(2)中创建的纹理对象中:

glBindTexture(GL_TEXTURE_2D, textureID)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_BGR, GL_UNSIGNED_BYTE, data);
void glTexImage2D( 
  GLenum target,
  GLint level,
  GLint internalformat,
  GLsizei width,
  GLsizei height,
  GLint border,
  GLenum format,
  GLenum type,
  const void * data);
target-指定目标纹理。
level-指定详细级别编号。级别0是基本图像级别。第n级是第n幅mipmap约简图像。
internalformat-指定纹理中颜色组件的数量。如GL_RED、GL_R16、GL_COMPRESSED_RGBA等等。
width-指定纹理图像的宽度。所有的实现都支持至少1024 纹素宽的纹理图像。
height-指定纹理图像的高度,或纹理数组中的层数。。。
border-This value must be 0.
format-指定像素数据的格式。
type-指定像素数据的数据类型。
data-指定一个指向内存中的图像数据的指针。

前面介绍的用于设置多级渐远纹理贴图等的各种 glTexParameteri()调用也可以应用于纹理对象。

生成多个纹理的例子

一个六个面的多纹理贴图立方体示例(伪代码):

GLuint texture[6]; // Storage For Six textures
................
addGLTextures() {
    AUX_RGBImageRec *TextureImage[6]; // 创建纹理的存储空间
    TextureImage[以上是关于✠OpenGL-5-纹理贴图的主要内容,如果未能解决你的问题,请参考以下文章

尝试对立方体贴图纹理进行采样时出现 GL_INVALID_OPERATION

光照贴图

Real - time Rendering 实时计算机图形学

图形学纹理贴图光照计算

应用纹理贴图

关于多站点全景纹理贴图问题