纹理
纹理是一个2D图片(甚至也有1D和3D的纹理),它可以用来添加物体的细节,
除了图像以外,纹理也可以被用来储存大量的数据,这些数据可以发送到着色器上
纹理环绕方式
纹理坐标的范围通常是从(0, 0)到(1, 1)
环绕方式 | 描述 |
---|---|
GL_REPEAT | 对纹理的默认行为。重复纹理图像 |
GL_MIRRORED_REPEAT | 和GL_REPEAT一样,但每次重复图片是镜像放置的 |
GL_CLAMP_TO_EDGE | 纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。 |
GL_CLAMP_TO_BORDER | 超出的坐标为用户指定的边缘颜色 |
每个选项都可以使用glTexParameter*函数对单独的一个坐标轴设置(s、t(如果是使用3D纹理那么还有一个r)它们和x、y、z是等价的)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
第一个 参数指定了 纹理目标 ;我们使用的是2D纹理,因此纹理目标是 GL_TEXTURE_2D 。 第二个参数 需要我们指定设置的选项与应用的 纹理轴 。我们打算配置的是WRAP选项,并且指定S和T轴。最后一个参数 需要我们传递一个 环绕方式(Wrapping)
纹理过滤
** GL_NEAREST(也叫邻近过滤,Nearest Neighbor Filtering)**
OpenGL默认的纹理过滤方式。当设置为GL_NEAREST的时候,OpenGL会选择中心点最接近纹理坐标的那个像素。
效果
GL_NEAREST产生了颗粒状的图案,我们能够清晰看到组成纹理的像素
GL_LINEAR(也叫线性过滤,(Bi)linear Filtering)
它会基于纹理 坐标附近 的纹理像素,计算出一个 插值 ,近似出这些纹理像素之间的颜色。一个纹理像素的 中心距离纹理坐标越近 ,那么这个纹理像素的颜色对最终的样本颜色的 贡献越大。
效果
L_LINEAR能够产生更平滑的图案,很难看出单个的纹理像素
当进行 放大(Magnify) 和 缩小(Minify) 操作的时候可以设置纹理过滤的选项,比如你可以在纹理被 缩小 的时候使用 邻近过滤,被 放大 时使用 线性过滤。我们需要使用glTexParameter*函数为放大和缩小指定过滤方式。这段代码看起来会和纹理环绕方式的设置很相似:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
多级渐远纹理
简单来说就是 一系列的纹理图像,后一个纹理图像是前一个的 二分之一。多级渐远纹理背后的理念很简单:距观察者的距离 超过一定的阈值,OpenGL会 使用不同的多级渐远纹理,即最适合物体的距离的那个。由于距离远,解析度不高也不会被用户注意到。同时,多级渐远纹理另一加分之处是它的 性能非常好
** 过滤方式 **
加载与创建纹理
加载与创建纹理
使用stb_image.h
库
int width, height, nrChannels;
unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
这个函数首先接受一个图像文件的 位置 作为输入。接下来它需要三个int作为它的第二、第三和第四个参数,stb_image.h将会用图像的 宽度、高度和颜色通道 的个数填充这三个变量。我们之后生成纹理的时候会用到的图像的宽度和高度的。
生成纹理
unsigned int texture;
glGenTextures(1, &texture);
绑定纹理
glBindTexture(GL_TEXTURE_2D, texture);
生成纹理
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
// 生成所有需要的多级渐远纹理
glGenerateMipmap(GL_TEXTURE_2D);
** 过程 **
unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
// 为当前绑定的纹理对象设置环绕、过滤方式
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);
// 加载并生成纹理
int width, height, nrChannels;
unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
if (data)
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);
应用纹理
float vertices[] = {
// ---- 位置 ---- ---- 颜色 ---- - 纹理坐标 -
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // 右上
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // 右下
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // 左下
-0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // 左上
};
** 顶点着色器 **
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;
out vec3 ourColor;
out vec2 TexCoord;
void main()
{
gl_Position = vec4(aPos, 1.0);
ourColor = aColor;
TexCoord = aTexCoord;
}
** 采样器(Sampler) **
把纹理对象传给片段着色器
** 片段着色器 **
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 TexCoord;
uniform sampler2D ourTexture;
void main()
{
FragColor = texture(ourTexture, TexCoord);
}
GLSL内建的texture
函数来采样纹理的颜色,它第一个参数是纹理采样器``
,第二个参数是对应的纹理坐标
。texture函数会使用之前设置的纹理参数对相应的颜色值进行采样。这个片段着色器的输出就是纹理的(插值)纹理坐标上的(过滤后的)颜色。
** 调用glDrawElements之前绑定纹理了,它会自动把纹理赋值给片段着色器的采样器**
glBindTexture(GL_TEXTURE_2D, texture);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
纹理单元
一个 纹理的位置 值通常称为一个纹理单元(Texture Unit)。一个纹理的默认纹理单元是0,它是默认的激活纹理单元
激活纹理单元
纹理单元的主要目的是让我们在着色器中可以 使用多于一个的纹理 。通过把纹理单元赋值给采样器,我们可以一次绑定多个纹理,只要我们首先激活对应的纹理单元。就像glBindTexture一样,我们可以使用glActiveTexture激活纹理单元,传入我们需要使用的纹理单元:
glActiveTexture(GL_TEXTURE0); // 在绑定纹理之前先激活纹理单元
glBindTexture(GL_TEXTURE_2D, texture);
激活纹理单元之后,接下来的glBindTexture函数调用会绑定这个纹理到当前激活的纹理单元,纹理单元 GL_TEXTURE0默认总是被激活,所以我们在前面的例子里当我们使用glBindTexture的时候,无需激活任何纹理单元。
OpenGL至少保证有16个纹理单元供你使用,也就是说你可以激活从
GL_TEXTURE0到GL_TEXTRUE15。它们都是按顺序定义的,所以我们
也可以通过GL_TEXTURE0 + 8的方式获得GL_TEXTURE8,这在当我
们需要循环一些纹理单元的时候会很有用。
多个纹理
#version 330 core
...
uniform sampler2D texture1;
uniform sampler2D texture2;
void main()
{
// 0.2会返回80%的第一个输入颜色和20%的第二个输入颜色,
//即返回两个纹理的混合色
FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}
使用
先绑定两个纹理到对应的纹理单元,然后定义哪个uniform采样器对应哪个纹理单元
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
我们还要通过使用glUniform1i
设置每个采样器的方式告诉OpenGL每个着色器采样器属于哪个纹理单元
ourShader.use(); // 别忘记在激活着色器前先设置uniform!
glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0); // 手动设置
ourShader.setInt("texture2", 1); // 或者使用着色器类设置
while(...)
{
[...]
}