文章目录
- 【OpenGL学习】(六)图形添加纹理
- 纹理环绕
- 纹理过滤
- 纹理颜色与顶点颜色混合
OpenGL纹理介绍:https://learnopengl-cn.github.io/01%20Getting%20started/06%20Textures/
【OpenGL学习】(六)图形添加纹理
项目链接:https://github.com/BinaryAI-1024/OpenGLPorjects/blob/master/textures
filesystem.h用于获取图像路径;stb_image.h用于加载图像。
顶点着色器texture.vs:
#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 = vec2(aTexCoord.x, aTexCoord.y);
}
片段着色器texture.fs:
#version 330 core
out vec4 FragColor;in vec3 ourColor;
in vec2 TexCoord;// texture sampler
uniform sampler2D texture1;void main()
{FragColor = texture(texture1, TexCoord);
}
textures.cpp实现纹理图映射到正方形上:
// textures.cpp
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h" // 包含 STB 图像加载库,用于加载纹理图像
#include <filesystem.h>
#include <shader_s.h>
#include <iostream> void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
const unsigned int WRAP_TYPE = 1;
const unsigned int FILTER_TYPE = 2;int main()
{glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); #ifdef __APPLE__glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif// 创建 GLFW 窗口GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL); if (window == NULL){std::cout << "Failed to create GLFW window" << std::endl; glfwTerminate(); return -1;}glfwMakeContextCurrent(window); glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);// 初始化 GLADif (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {std::cout << "Failed to initialize GLAD" << std::endl; return -1;}Shader ourShader("texture.vs", "texture.fs"); // 创建着色器对象// 设置顶点数据(和缓冲区)并配置顶点属性//float vertices[] = {// // 位置坐标 // 颜色 // 纹理坐标// 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 2.0f, 2.0f, // 右上角// 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 2.0f, -1.0f, // 右下角// -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, -1.0f, -1.0f, // 左下角// -0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, -1.0f, 2.0f // 左上角 //};float vertices[] = {// 位置坐标 // 颜色 // 纹理坐标(翻转y轴)0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // 右上角0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, // 右下角-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, // 左下角-0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f // 左上角 };unsigned int indices[] = {0, 1, 3, // 第一个三角形1, 2, 3 // 第二个三角形};// 定义顶点缓冲对象、顶点数组对象和元素缓冲对象unsigned int VBO, VAO, EBO; glGenVertexArrays(1, &VAO); // 生成顶点数组对象glGenBuffers(1, &VBO); // 生成顶点缓冲对象glGenBuffers(1, &EBO); // 生成元素缓冲对象glBindVertexArray(VAO); // 绑定顶点数组对象glBindBuffer(GL_ARRAY_BUFFER, VBO); // 绑定顶点缓冲对象glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 将顶点数据复制到缓冲区glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); // 绑定元素缓冲对象glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); // 将索引数据复制到缓冲区// 设置顶点属性指针:xyz(顶点坐标)rgb(顶点颜色)st(纹理坐标)glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); // 设置位置属性指针glEnableVertexAttribArray(0); // 启用位置属性glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); // 设置颜色属性指针glEnableVertexAttribArray(1); // 启用颜色属性// 纹理坐标属性glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float))); // 设置纹理坐标属性指针glEnableVertexAttribArray(2); // 启用纹理坐标属性// 加载并创建纹理unsigned int texture; // 定义纹理对象glGenTextures(1, &texture); // 生成纹理对象glBindTexture(GL_TEXTURE_2D, texture); // 绑定纹理对象,所有 GL_TEXTURE_2D 操作对该纹理生效// 设置 S 轴和T轴的纹理环绕方式if (WRAP_TYPE == 1) {glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);}else if (WRAP_TYPE == 2) {glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);}else if (WRAP_TYPE == 3) {glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);}else if (WRAP_TYPE == 4) {// 如果选择GL_CLAMP_TO_BORDER纹理环绕,需要设置边缘颜色float borderColor[] = { 1.0f, 0.0f, 0.0f, 1.0f }; // 红色glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);}if (FILTER_TYPE == 1) {glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // 设置纹理缩小过滤方式glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // 设置纹理放大过滤方式}else if (FILTER_TYPE == 2) {glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); }else if (FILTER_TYPE == 3) {glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);}else if (FILTER_TYPE == 4) {glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);}else if (FILTER_TYPE == 5) {glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);}else if (FILTER_TYPE == 6) {glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);}// 加载图像,创建纹理并生成 Mipmapint width, height, nrChannels; // 定义图像宽度、高度和通道数std::string imagePath = FileSystem::getPath("resources/grid2.jpg");std::cout << "Attempting to load texture from: " << imagePath << std::endl; unsigned char* data = stbi_load(imagePath.c_str(), &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); // 生成 Mipmap}else{std::cout << "Failed to load texture" << std::endl; }stbi_image_free(data); // 释放图像数据// 渲染循环while (!glfwWindowShouldClose(window)) {processInput(window); // 渲染glClearColor(0.2f, 0.3f, 0.3f, 1.0f); // 设置清屏颜色glClear(GL_COLOR_BUFFER_BIT); // 清空颜色缓冲// 绑定纹理对象glBindTexture(GL_TEXTURE_2D, texture); // 渲染容器ourShader.use(); // 使用着色器程序glBindVertexArray(VAO); // 绑定顶点数组对象glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); // 绘制三角形glfwSwapBuffers(window);glfwPollEvents();}glDeleteVertexArrays(1, &VAO);glDeleteBuffers(1, &VBO); glDeleteBuffers(1, &EBO); glfwTerminate(); return 0;
}void processInput(GLFWwindow* window)
{if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) glfwSetWindowShouldClose(window, true);
}void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{glViewport(0, 0, width, height);
}
纹理环绕
纹理图像(纹理像素组成):
图形的纹理坐标的范围通常是从(0, 0)到(1, 1),如果把纹理坐标设置在这个范围之外,那么就需要指定纹理环绕方式对超过的部分进行填充。OpenGL提供以下四种方式:
- GL_REPEAT :重复纹理图像,为默认方法。
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, 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之间,超出的部分会重复纹理的边缘。
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:边界颜色填充 ,超出的坐标为指定的边缘颜色。
// 如果选择GL_CLAMP_TO_BORDER纹理环绕,需要设置边缘颜色float borderColor[] = { 1.0f, 0.0f, 0.0f, 1.0f }; // 红色glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
纹理过滤
图形和纹理图像的形状和大小不一定完全匹配,因此需要对纹理图像的像素进行过滤。OpenGL的纹理过滤是一种通过插值或采样技术优化纹理映射效果的方法,用于解决纹理在放大、缩小或斜视角渲染时出现的失真、锯齿或模糊问题,提升渲染质量。一般通过最近邻和线性插值实现。
以对下面的纹理图进行放大为例,
- GL_NEAREST: 直接选取与纹理坐标最邻近的纹理像素颜色。
GL_LINEAR:对纹理坐标附近的纹理像素进行插值,近似出这些纹理像素之间的颜色。
在缩小过滤时,由于远处的物体在屏幕上的投影面积较小,可能仅占用极少的片段(屏幕像素)。此时,OpenGL需要从高分辨率纹理中为这些片段获取颜色值,但由于单个片段需要覆盖纹理中的一大片区域(多个Texel),直接采样高分辨率纹理会导致严重的采样不足问题。
具体表现为:GPU可能仅从该区域中选取一个或少数几个Texel的颜色值,从而丢失了纹理的高频细节,最终在屏幕上产生锯齿、闪烁或摩尔纹等视觉伪影。
以下面的纹理图为例,
对其进行缩小:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
可以看到,视角中远处的像素出现较大失真。
多级渐远纹理(MIPMAP)通过预生成一系列不同分辨率的纹理副本(从原始纹理到最低分辨率),使GPU能够根据片段覆盖的纹理区域大小,动态择最合适的分辨率级别进行采样,从而避免采样不足的问题。OpenGL提供了以下四种基于多级渐远纹理的过滤方法:
-
GL_NEAREST_MIPMAP_NEAREST:首先,根据当前片段(屏幕像素)覆盖的纹理区域大小,选择最接近的MIPMAP级别(即分辨率最匹配的那一级)。然后,在该MIPMAP级别中,直接使用最邻近的纹理像素(Texel)颜色(不进行插值)。
-
GL_LINEAR_MIPMAP_NEAREST:首先,选择最接近的MIPMAP级别。然后,在该MIPMAP级别中,对纹理坐标进行线性插值(即取周围4个纹理像素的加权平均值)。
-
GL_NEAREST_MIPMAP_LINEAR:首先,找到两个最匹配当前片段覆盖大小的MIPMAP级别(例如,一个略大,一个略小)。然后,在这两个MIPMAP级别中,分别使用最邻近的纹理像素颜色。最后,对这两个级别的采样结果进行线性插值(即加权平均)。
-
GL_LINEAR_MIPMAP_LINEAR:首先,找到两个最匹配当前片段覆盖大小的MIPMAP级别。然后,在这两个MIPMAP级别中,分别对纹理坐标进行线性插值。最后,对这两个级别的线性插值结果再进行线性插值(即双重加权平均)。
以第四种方法为例:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // 放大不需要采用MIPMAP
可以看到,渲染效果比普通的方法更好。
纹理颜色与顶点颜色混合
修改片段着色器:
#version 330 core
out vec4 FragColor;in vec3 ourColor;
in vec2 TexCoord;// texture sampler
uniform sampler2D texture1;void main()
{//FragColor = texture(texture1, TexCoord);FragColor = texture(texture1, TexCoord) * vec4(ourColor, 1.0);
}