纹理
纹理坐标
每个顶点需要有一个对应的纹理坐标,指定三角形的每个顶点各自对应纹理的哪个部分
原点位于纹理的左下角,横纵坐标均为(0,1)
1 2 3 4 5 | float texCoords[] = { 0.0f, 0.0f, // 左下角 1.0f, 0.0f, // 右下角 0.5f, 1.0f // 上中 }; |
纹理环绕方式
GL_REPEAT对纹理的默认行为。重复纹理图像。GL_MIRRORED_REPEAT和GL_REPEAT一样,但每次重复图片是镜像放置的。GL_CLAMP_TO_EDGE纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。GL_CLAMP_TO_BORDER超出的坐标为用户指定的边缘颜色。
设置纹理环绕方式
glTexParameter*()其中*为变量类型
1 2 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT); |
S, T轴对应X, Y轴,若使用3D纹理还有一个R轴
如果我们选择GL_CLAMP_TO_BORDER选项,我们还需要指定一个边缘的颜色。这需要使用glTexParameter函数的
fv后缀形式,用GL_TEXTURE_BORDER_COLOR作为它的选项,并且传递一个float数组作为边缘的颜色值:
1 2 | float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f }; glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); |
纹理过滤
GL_NEAREST邻近过滤,取纹理图像中最靠近纹理坐标的那个像素GL_LINEAR线性过滤,会基于纹理坐标附近的像素计算出一个插值
Mipmap
对远近程度不同的纹理使用分辨率不同的图像
可使用
glGenerateMipmap函数生成一个Mipmap图像有多种过滤方式:
GL_NEAREST_MIPMAP_NEAREST使用最邻近的多级渐远纹理来匹配像素大小,并使用邻近插值进行纹理采样GL_LINEAR_MIPMAP_NEAREST使用最邻近的多级渐远纹理级别,并使用线性插值进行采样GL_NEAREST_MIPMAP_LINEAR在两个最匹配像素大小的多级渐远纹理之间进行线性插值,使用邻近插值进行采样GL_LINEAR_MIPMAP_LINEAR在两个邻近的多级渐远纹理之间使用线性插值,并使用线性插值进行采样
1 2 3 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
加载纹理图像
与生成OpenGL对象类似,通过ID引用来管理
1 2
unsigned int texture; glGenTextures(1, &texture);
绑定纹理,此后任何关于纹理的设置都会应用到当前绑定的纹理上
1glBindTexture(GL_TEXTURE_2D, texture);
之后即可使用函数生成纹理
1 2
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D);//生成Mipmap
参数:
- 第一个参数指定了纹理目标(Target)。设置为GL_TEXTURE_2D意味着会生成与当前绑定的纹理对象在同一个目标上的纹理(任何绑定到GL_TEXTURE_1D和GL_TEXTURE_3D的纹理不会受到影响)。
- 第二个参数为纹理指定多级渐远纹理的级别,如果你希望单独手动设置每个多级渐远纹理的级别的话。这里我们填0,也就是基本级别。
- 第三个参数告诉OpenGL我们希望把纹理储存为何种格式。我们的图像只有
RGB值,因此我们也把纹理储存为RGB值。 - 第四个和第五个参数设置最终的纹理的宽度和高度。我们之前加载图像的时候储存了它们,所以我们使用对应的变量。
- 下个参数应该总是被设为
0(历史遗留的问题)。 - 第七第八个参数定义了源图的格式和数据类型。我们使用RGB值加载这个图像,并把它们储存为
char(byte)数组,我们将会传入对应值。 - 最后一个参数是真正的图像数据。
别忘了释放内存!
stbi_image_free(data);总过程应该看起来像这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
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);
int width, height, nrChannels;中nrChannels代表颜色通道数。
应用纹理
为每个顶点带上两个纹理坐标数据
1 2 3 4 5 6 7
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 // 左上 };
此后在顶点着色器内增加一个顶点属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14
#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; }
将纹理对象传递给片段着色器使用uniform,类型为sampler2D
1 2 3 4 5 6 7 8 9 10 11 12
#version 330 core out vec4 FragColor; in vec3 ourColor; in vec2 TexCoord; uniform sampler2D ourTexture; void main() { FragColor = texture(ourTexture, TexCoord); }
在主程序中使用
glBindTexture()绑定纹理,默认绑定到GL_TEXTURE0纹理单元上使用多个texture时,需要分别激活不同的纹理单元
1 2 3 4 5 6 7
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每个着色器采样器属于哪个纹理单元。我们只需要设置一次即可,所以这个会放在渲染循环的前面
1 2 3 4 5 6 7 8
ourShader.use(); // 不要忘记在设置uniform变量之前激活着色器程序! glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0); // 手动设置 ourShader.setInt("texture2", 1); // 或者使用着色器类设置 while(...) { [...] }
stb_image.h能够在图像加载时帮助我们翻转y轴,使用如下函数设置:stbi_set_flip_vertically_on_load(true);