- 纹理
纹理是一个2D图片,它可以用来添加物体的细节,让物体更加真实。
为了能够把纹理映射到三角形上,我们需要制定三角形的每个顶点各自对应的纹理的哪个部分。这样每个顶点会关联着一个纹理坐标,用来标明该从纹理图像的哪部分采样(采集片段颜色),之后在图形的其他片段上进行片段差值。
纹理坐标在x和y轴上,范围为0-1之间。使用纹理坐标获取纹理颜色叫做采样。
我们需要告诉OpenGL该怎样对纹理采样。 -
纹理环绕方式
纹理坐标范围通常是(0,0)到(1,1)。
1)GL_REPEAT——对纹理的默认行为。重复纹理图像。
2)GL_MIRRORED_REPEAT——和GL_REPEAT一样,但每次重复图片是镜像放置的。
3)GL_CLAMP_TO_EDGE——纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。
4)GL_CLAMP_TO_BORDER——超出的坐标为用户指定的边缘颜色。
前面提到的每个选项都可以使用glTexParameter*函数对单独的一个坐标轴设置(s、t(如果是使用3D纹理那么还有一个r)它们和x、y、z是等价的):
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);//2D纹理目标,S轴,环绕类型
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);//2D纹理目标,T轴,环绕类型
如果选择GL_CLAMP_TO_BORDER选项,我们还需要指定一个边缘的颜色。这需要使用glTexParameter函数的fv后缀形式,用GL_TEXTURE_BORDER_COLOR作为它的选项,并且传递一个float数组作为边缘的颜色值:
float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
-
纹理过滤
纹理坐标不依赖于分辨率,所欲OpenGL不需要知道怎样将纹理像素映射到纹理坐标。OpenGL的纹理过滤种类:
1) GL_NEAREST(临近过滤):选择OpenGL会选择中心点最接近的纹理坐标的那个像素。
2)GL_LINEAR(线性过滤):基于纹理坐标附近的纹理像素,计算出一个插值,近似出这些纹理像素之间的颜色。(混合色)
在纹理被缩小的时候使用临近过滤,被放大时使用线性过滤。
- 多级渐远纹理
距观察者的距离超过一定的阈值,OpenGL会使用不同的多级渐远纹理,即最适合物体的距离的那个。多级渐远纹理过滤方式:
1)GL_NEAREST_MIPMAP_NEAREST:使用最邻近的多级渐远纹理来匹配像素大小,并使用邻近插值进行纹理采样
2)GL_LINEAR_MIPMAP_NEAREST:使用最邻近的多级渐远纹理级别,并使用线性插值进行采样
3)GL_NEAREST_MIPMAP_LINEAR:在两个最匹配像素大小的多级渐远纹理之间进行线性插值,使用邻近插值进行采样
4)GL_LINEAR_MIPMAP_LINEAR:在两个邻近的多级渐远纹理之间使用线性插值,并使用线性插值进行采样 - 加载与创建纹理
用stb_image.h库进行加载与创建纹理。 - 单头文件图像加载库:stb_image.h
头文件
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
int width, height, nrChannels;
unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);//图像宽度、高度和颜色通道的个数
- 生成纹理
纹理也是使用ID引用的。
unsigned int texture;
glGenTextures(1,&texture);//第一个参数:生成纹理的数量,把他们存储在第二个参数unsigned int数组中
glBindTexture(GL_TEXTURE_2D, texture);//绑定
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);//glTexImage2D生成纹理
glGenerateMipmap(GL_TEXTURE_2D);//自动生成做需要的多级渐远纹理
stbi_image_free(data);//释放图像内存
glTexImage2D参数说明:
1)第一个参数指定了纹理目标(Target)。设置为GL_TEXTURE_2D意味着会生成与当前绑定的纹理对象在同一个目标上的纹理(任何绑定到GL_TEXTURE_1D和GL_TEXTURE_3D的纹理不会受到影响)。
2)第二个参数为纹理指定多级渐远纹理的级别,如果你希望单独手动设置每个多级渐远纹理的级别的话。这里我们填0,也就是基本级别。
3)第三个参数告诉OpenGL我们希望把纹理储存为何种格式。我们的图像只有RGB值,因此我们也把纹理储存为RGB值。
4)第四个和第五个参数设置最终的纹理的宽度和高度。我们之前加载图像的时候储存了它们,所以我们使用对应的变量。
5)下个参数应该总是被设为0(历史遗留的问题)。
6)第七第八个参数定义了源图的格式和数据类型。我们使用RGB值加载这个图像,并把它们储存为char(byte)数组,我们将会传入对应值。
7)最后一个参数是真正的图像数据。
- 应用纹理
glDrawElements
1)告诉OpenGL 如何采样纹理,使用纹理坐标更新顶点数据
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 // 左上
};
2)由于添加了一个额外的顶点属性,所以必须告诉OpenGL新的顶点格式
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);//步长
3)调整顶点着色器使其能够接受顶点坐标为一个顶点属性,并把坐标传给片段着色器
#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;
}
片段着色器应该接下来会把输出变量TexCoord作为输入变量。
片段着色器也应该能访问纹理对象,但是我们怎样能把纹理对象传给片段着色器呢?GLSL有一个供纹理对象使用的内建数据类型,叫做采样器(Sampler),它以纹理类型作为后缀,比如sampler1D、sampler3D,或在我们的例子中的sampler2D。我们可以简单声明一个uniform sampler2D把一个纹理添加到片段着色器中,稍后我们会把纹理赋值给这个uniform。
#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);
- 纹理单元
使用glUniform1i,我们可以给纹理采样器分配一个位置值,这样的话我们能够在一个片段着色器中设置多个纹理。一个纹理的位置值通常称为一个纹理单元。一个纹理的默认纹理单元是0,它是默认的激活纹单元,纹理单元的主要目的的让我们在着色器中可以使用多于一个的纹理。
通过把纹理单元赋值给采样器,我们可以一次绑定多个纹理,只要我们首先激活对应的纹理单元。
glActiveTexture(GL_TEXTURE0); // 在绑定纹理之前先激活纹理单元
glBindTexture(GL_TEXTURE_2D, texture);
mix函数
glActiveTexture(GL_TEXTURE0); // 在绑定纹理之前先激活纹理单元
glBindTexture(GL_TEXTURE_2D, texture);
具体:https://learnopengl-cn.github.io/01%20Getting%20started/06%20Textures/#_7