纹理映射的坐标对应关系

 在纹理映射过程中,二维纹理图像上的像素位置(即纹理坐标)会被映射到三维模型的顶点上。

过程解析

  1. 纹理ID (textureID)

    • textureID 是由 glGenTextures 创建的纹理对象的 ID。这个 ID 用于标识和操作纹理对象。
    • textureID 会被传递给 glBindTexture(GL_TEXTURE_2D, textureID),将纹理绑定到当前上下文中,后续的纹理操作都会作用于该纹理。
  2. 纹理单元 (GL_TEXTURE0)

    • GL_TEXTURE0 是 OpenGL 中的一个纹理单元。它是通过 glActiveTexture(GL_TEXTURE0) 激活的,表示接下来的纹理绑定操作会作用于纹理单元 0。
    • 然后通过 glBindTexture(GL_TEXTURE_2D, textureID) 将纹理绑定到当前纹理单元 0 上。
  3. 纹理坐标 (TexCoords)

    • 顶点着色器通过 in vec2 TexCoords 接收每个顶点的纹理坐标,并传递给片段着色器(out vec2 TexCoords)。
    • 在片段着色器中,TexCoords 被用来从纹理中采样颜色值:FragColor = texture(texture1, TexCoords);
  4. 纹理采样器 (texture1)

    • texture1 是一个 uniform sampler2D 类型的变量,代表片段着色器中的纹理采样器。
    • 在渲染循环中,glUniform1i 被用来将 texture1 采样器与纹理单元 0 关联:glUniform1i(glGetUniformLocation(shaderProgram, "texture1"), 0)
    • 这样,片段着色器会从纹理单元 0 绑定的纹理中采样颜色。

完整代码:

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <stb_image.h> // 用于加载图片
#include <iostream>

// 着色器代码
const char* vertexShaderSource = R"(
#version 330 core
layout (location = 0) in vec3 aPos;       // 顶点位置
layout (location = 1) in vec2 aTexCoords; // 纹理坐标

out vec2 TexCoords; // 传递给片段着色器的纹理坐标

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main() {
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    TexCoords = aTexCoords; // 将纹理坐标传递给片段着色器
}
)";

const char* fragmentShaderSource = R"(
#version 330 core
out vec4 FragColor;

in vec2 TexCoords;  // 从顶点着色器传递的纹理坐标

uniform sampler2D texture1; // 纹理采样器

void main() {
    FragColor = texture(texture1, TexCoords); // 根据纹理坐标从纹理中采样
}
)";

// 创建着色器程序的辅助函数
GLuint createShaderProgram(const char* vertexShaderSource, const char* fragmentShaderSource) {
    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, nullptr);
    glCompileShader(vertexShader);
    checkShaderCompile(vertexShader);

    GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr);
    glCompileShader(fragmentShader);
    checkShaderCompile(fragmentShader);

    GLuint shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    checkProgramLink(shaderProgram);

    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    return shaderProgram;
}

// 检查着色器编译错误
void checkShaderCompile(GLuint shader) {
    GLint success;
    GLchar infoLog[512];
    glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
    if (!success) {
        glGetShaderInfoLog(shader, 512, nullptr, infoLog);
        std::cout << "Shader Compilation Failed: " << infoLog << std::endl;
    }
}

// 检查着色器程序链接错误
void checkProgramLink(GLuint program) {
    GLint success;
    GLchar infoLog[512];
    glGetProgramiv(program, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(program, 512, nullptr, infoLog);
        std::cout << "Program Linking Failed: " << infoLog << std::endl;
    }
}

// 加载纹理的函数
GLuint loadTexture(const char* path) {
    GLuint textureID;
    glGenTextures(1, &textureID);
    glBindTexture(GL_TEXTURE_2D, textureID); // 绑定纹理对象

    // 设置纹理的环绕和过滤方式
    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(path, &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
        stbi_image_free(data);
    } else {
        std::cout << "Failed to load texture!" << std::endl;
    }
    return textureID;
}

// 主程序
int main() {
    // 初始化 GLFW
    if (!glfwInit()) {
        std::cout << "GLFW Initialization Failed!" << std::endl;
        return -1;
    }

    // 创建窗口
    GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL Texture Mapping", nullptr, nullptr);
    if (!window) {
        std::cout << "Window Creation Failed!" << std::endl;
        glfwTerminate();
        return -1;
    }

    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, [](GLFWwindow* window, int width, int height) {
        glViewport(0, 0, width, height);
    });

    // 初始化 GLEW
    if (glewInit() != GLEW_OK) {
        std::cout << "GLEW Initialization Failed!" << std::endl;
        return -1;
    }

    // 创建着色器程序
    GLuint shaderProgram = createShaderProgram(vertexShaderSource, fragmentShaderSource);

    // 定义顶点数据(位置和纹理坐标)
    float vertices[] = {
        // 位置               // 纹理坐标
        -0.5f, -0.5f, 0.0f,   0.0f, 0.0f,
         0.5f, -0.5f, 0.0f,   1.0f, 0.0f,
         0.5f,  0.5f, 0.0f,   1.0f, 1.0f,
         0.5f,  0.5f, 0.0f,   1.0f, 1.0f,
        -0.5f,  0.5f, 0.0f,   0.0f, 1.0f,
        -0.5f, -0.5f, 0.0f,   0.0f, 0.0f
    };

    // 创建 VAO 和 VBO
    GLuint VAO, VBO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // 设置顶点属性指针
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);

    // 加载纹理
    GLuint textureID = loadTexture("texture.jpg");

    // 渲染循环
    while (!glfwWindowShouldClose(window)) {
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // 激活着色器程序
        glUseProgram(shaderProgram);

        // 激活纹理单元 0 并绑定纹理
        glActiveTexture(GL_TEXTURE0);       // 激活纹理单元 GL_TEXTURE0
        glBindTexture(GL_TEXTURE_2D, textureID); // 绑定纹理

        // 将纹理采样器传递给着色器
        glUniform1i(glGetUniformLocation(shaderProgram, "texture1"), 0); // 将 texture1 与纹理单元 0 关联

        // 绘制物体
        glBindVertexArray(VAO);
        glDrawArrays(GL_TRIANGLES, 0, 6);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // 清理资源
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteProgram(shaderProgram);

    glfwTerminate();
    return 0;
}

纹理坐标和三维坐标的关系

纹理坐标是二维的(通常为 uv 坐标),而三维坐标是三维的(通常为 x, y, z 坐标)。纹理坐标通常范围是 [0, 1],其中:

  • u 表示纹理在水平方向上的位置(即 X 轴方向)。
  • v 表示纹理在垂直方向上的位置(即 Y 轴方向)。

纹理映射的基本过程是通过一个数学关系将三维顶点的纹理坐标(u, v)与纹理图像的二维像素坐标进行映射,进而确定每个三维点在纹理图像中的相应位置。

如何将纹理坐标映射到三维表面

  1. 模型坐标系与纹理坐标系的关系

    在 OpenGL 渲染中,我们通常通过 UV坐标来表示纹理坐标。每个顶点都配有一个纹理坐标,用来告诉 OpenGL 渲染引擎该纹理在该顶点上的具体位置。UV坐标的值通常在 [0, 1] 范围内,分别表示纹理图片的最左边和最下边(u=0, v=0)以及最右边和最上边(u=1, v=1)。

  2. 顶点数据中的纹理坐标

    • 每个顶点除了有 三维位置坐标x, y, z)外,还会有一个纹理坐标(u, v),它决定了该顶点在纹理图像上的位置。
    • 这些纹理坐标通常会在建模时生成,并随模型一起导入到 OpenGL。
  3. 纹理坐标与顶点坐标的映射关系

    • 假设我们有一个三角形网格模型,其中每个顶点都有对应的纹理坐标。OpenGL 会将这些纹理坐标与加载的纹理图像进行关联。
    • 通过 顶点着色器,这些纹理坐标会传递给 片段着色器,在片段着色器中根据纹理坐标从纹理图像中采样颜色。
  4. 纹理坐标插值

    • 在渲染过程中,OpenGL 会根据三角形的顶点坐标进行 插值(interpolation),即对于三角形内的每个片段(像素),它会根据邻近顶点的纹理坐标对纹理坐标进行插值,从而获得该片段的纹理坐标。
    • 这样,片段着色器就可以使用插值后的纹理坐标从纹理图像中采样正确的像素。

详细过程

步骤 1:加载纹理图片

我们可以使用像 stb_image.h 这样的第三方库来加载图像文件。stb_image 是一个非常流行的图像加载库,支持多种图像格式(例如 PNG, JPEG, BMP 等)。

首先,确保包含 stb_image.h 库并在编译时定义 STB_IMAGE_IMPLEMENTATION

GLuint loadTexture(const char* path) {
    int width, height, nrChannels;
    // 加载图像文件
    unsigned char *data = stbi_load(path, &width, &height, &nrChannels, 0);
    if (data) {
        GLuint textureID;
        glGenTextures(1, &textureID); // 生成纹理ID
        glBindTexture(GL_TEXTURE_2D, textureID); // 绑定纹理

        // 设置纹理的环绕和过滤方式
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // x轴重复
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // y轴重复
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // 线性过滤
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // 线性放大

        // 根据图像数据生成纹理
        GLenum format = (nrChannels == 3) ? GL_RGB : GL_RGBA; // 判断是否为RGB或RGBA
        glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D); // 生成纹理的mipmap

        stbi_image_free(data); // 释放加载的图像数据

        return textureID;
    } else {
        std::cout << "Failed to load texture" << std::endl;
        return 0;
    }
}

glTexImage2D 用来将图像数据(像素数据)上传到纹理对象中,并将其存储在 GPU 内存中。

步骤 2:创建纹理对象

在上面的代码中,我们使用 glGenTextures 创建了一个纹理对象textureID,并使用 glTexImage2D 将加载的图像数据绑定到纹理对象中。

步骤 3:定义顶点数据

接下来,我们定义 3D 模型的顶点数据。对于一个简单的立方体,我们需要定义每个顶点的坐标和纹理坐标。

        每个顶点包含 3 个位置坐标(x, y, z)和 2 个纹理坐标(u, v)。纹理坐标 uv 映射到纹理图片的水平和垂直位置。

GLfloat vertices[] = {
    // 位置            // 纹理坐标 (u, v)
    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
     0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
    -0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
    
    -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
    -0.5f,  0.5f, -0.5f,  1.0f, 0.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
};

步骤 4:设置纹理坐标

通过顶点属性指示如何访问纹理坐标。纹理坐标通常在片段着色器中使用。

GLuint VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);

glBindVertexArray(VAO);

// 绑定顶点数据到 VBO
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

// 位置属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);

// 纹理坐标属性
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);

glBindVertexArray(0);

步骤 5:编写着色器

接下来,我们编写顶点着色器和片段着色器,处理纹理映射。

  1. 顶点着色器:传递纹理坐标

    在顶点着色器中,我们将顶点的纹理坐标从模型空间传递到片段着色器:

    #version 330 core
    layout (location = 0) in vec3 aPos;      // 顶点位置
    layout (location = 1) in vec2 aTexCoords; // 纹理坐标
    
    out vec2 TexCoords; // 传递给片段着色器的纹理坐标
    
    uniform mat4 model;
    uniform mat4 view;
    uniform mat4 projection;
    
    void main() {
        gl_Position = projection * view * model * vec4(aPos, 1.0);
        TexCoords = aTexCoords; // 传递纹理坐标
    }
    

  2. 片段着色器:使用纹理坐标进行纹理采样

    在片段着色器中,我们根据传递的纹理坐标从纹理图像中采样对应的像素:

    #version 330 core
    out vec4 FragColor;
    
    in vec2 TexCoords; // 从顶点着色器传递的纹理坐标
    
    uniform sampler2D texture1; // 纹理采样器
    
    void main() {
        FragColor = texture(texture1, TexCoords); // 使用纹理坐标从纹理中采样颜色
    }
    

    texture(texture1, TexCoords) 函数根据传入的纹理坐标从纹理 texture1 中采样出颜色,并将其传递给片段着色器的输出变量 FragColor

  3. 步骤 6:渲染

    在渲染时,绑定纹理并绘制物体。通过 glActiveTextureglBindTexture 激活和绑定纹理,并使用 glDrawArraysglDrawElements 绘制物体。

// 激活纹理单元
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureID); // 绑定加载的纹理

// 使用着色器程序
shader.use();
shader.setMat4("model", model);
shader.setMat4("view", view);
shader.setMat4("projection", projection);

// 绘制物体
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 36); // 渲染立方体
  • glActiveTexture(GL_TEXTURE0) 激活纹理单元 0,以便接下来绑定的纹理会存储在单元 0 中。
  • glBindTexture(GL_TEXTURE_2D, textureID) 将之前加载的纹理绑定到 OpenGL 上下文中的纹理单元 GL_TEXTURE0
  • shader.setInt("texture1", 0) 将着色器中的纹理采样器变量 texture1 与纹理单元 0 关联,告诉片段着色器去纹理单元 0 中查找纹理数据。

        在渲染过程中,OpenGL 会对三角形内部的所有像素执行插值,计算出每个片段的纹理坐标。片段着色器使用这些插值后的纹理坐标从纹理中取样,从而实现纹理映射。

总结

  1. UV坐标映射到3D顶点:每个顶点在3D空间中有一个位置,并且在纹理中有一个对应的UV坐标。纹理坐标是二维的,通常范围是 [0, 1],用来映射到纹理图像中的像素。
  2. 插值过程:在渲染过程中,OpenGL 会根据顶点的纹理坐标对三角形内的每个片段进行插值,最终生成正确的纹理映射。
  3. 纹理采样:在片段着色器中,根据插值后的纹理坐标,使用 texture() 函数从纹理中获取颜色值,将其应用到该片段上,形成最终的纹理效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值