OpenGL学习(三)——纹理(texture)

        艺术家和程序员通常喜欢使用纹理。纹理是用于向对象添加细节的 2D 图像(甚至存在 1D 和 3D 纹理);将纹理想象成一张纸,上面有漂亮的砖块图像。因为我们可以在单个图像中插入大量细节,所以我们可以给人一种对象非常详细的错觉,而无需指定额外的顶点。

        为了将纹理映射到三角形,我们需要告诉三角形的每个顶点它对应于纹理的哪个部分。因此,每个顶点都应该有一个与之关联的纹理坐标,该坐标指定要从纹理图像的哪个部分进行采样。然后,片段插值会为其他片段执行其余操作。

        纹理坐标在 x 轴和 y 轴上的范围从 0 到 1(请记住,我们使用 2D 材质)。使用纹理坐标检索纹理颜色称为采样。纹理坐标从纹理图像左下角的 (0,0) 开始,到纹理图像右上角的 (1,1) 开始。例:

纹理包装(texture wrapping)

  • GL_REPEAT:纹理的默认行为。重复纹理图像。
  • GL_MIRRORED_REPEAT:与GL_REPEAT相同,但每次重复都会镜像图像。
  • GL_CLAMP_TO_EDGE:限制 0 到 1 之间的坐标。结果是较高的坐标被钳制到边缘,从而产生拉伸的边缘图案。
  • GL_CLAMP_TO_BORDER:超出范围的坐标现在被赋予用户指定的边框颜色。

当使用默认范围之外的纹理坐标时,每个选项都有不同的视觉输出。

 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
  1. 第一个参数指定纹理目标(使用 2D 纹理),因此纹理目标是 GL_TEXTURE_2D。
  2. 第二个参数要求我们告诉要设置什么选项以及针对哪个纹理轴;我们想为 S 轴和 T 轴配置它。
  3. 第三个参数要求我们传入纹理包装模式。

如果我们选择gl_clamp_to_borderoption,应该指定边界颜色。这是使用GL_TEXTURE_BORDER_COLOR作为它的选项,我们传入一个浮点数组的边界的颜色值。

 float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
 glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

纹理滤波(texture filtering)

  • GL_NEAREST(也称为最近邻或点过滤): OpenGL 的默认纹理过滤方法。当设置为 GL_NEAREST 时,OpenGL 会选择最接近纹理坐标的纹素。
  • GL_LINEAR (也称为 (双)线性筛选): 从纹理坐标的相邻纹素中获取内插值,从而近似纹素之间的颜色。从纹理坐标到纹素中心的距离越小,纹素的颜色对采样颜色的贡献就越大。

纹理滤波可以设置为放大和缩小操作(放大或缩小时),例如,在纹理向下缩放时使用最近邻过滤,对放大的材质使用线性过滤:

 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

贴图(mipmap)

        纹理图像集合

        在距查看器达到一定距离阈值后,OpenGL 将使用最适合与对象距离的不同 mipmap 纹理。由于对象距离较远,因此用户不会注意到较小的分辨率。然后,OpenGL 能够对正确的纹素进行采样,并且在对 mipmap 的该部分进行采样时,涉及的缓存内存更少。

贴图之间的过滤方法
  • GL_NEAREST_MIPMAP_NEAREST:采用最近的 mipmap 来匹配像素大小,并使用最近邻插值进行材质采样
  • GL_LINEAR_MIPMAP_NEAREST:获取最近的 mipmap 级别,并使用线性插值对该级别进行采样
  • GL_NEAREST_MIPMAP_LINEAR:在与像素大小最匹配的两个 mipmap 之间进行线性插值,并通过最近邻插值对插值级别进行采样
  • GL_LINEAR_MIPMAP_LINEAR:在两个最近的 mipmap 之间进行线性插值,并通过线性插值对插值级别进行采样
 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
    GL_LINEAR_MIPMAP_LINEAR);
 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        一个常见的错误是将其中一个 mipmap 筛选选项设置为放大筛选器。这没有任何影响,因为 mipmap 主要用于纹理缩小时,放大不使用 mipmap,并且将生成 OpenGL GL_INVALID_ENUM错误代码。

stb_image.h

        stb_image.h 是 Sean Barrett写的一个非常流行的单头图像加载库,它能够加载最流行的文件格式,并且易于集成到项目中。

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

        通过定义 STB_IMAGE_IMPLEMENTATION 预处理器修改头文件,使其仅包含相关的定义源代码,从而有效地将头文件转换为 .cpp 文件。

生成含有纹理的矩形

shader.h
#ifndef SHADER_H
#define SHADER_H

#include <glad/glad.h>

#include <string>
#include <fstream>
#include <sstream>
#include <iostream>

class Shader
{
public:
    unsigned int ID;
    Shader(const char* vertexPath, const char* fragmentPath)
    {
        std::string vertexCode;
        std::string fragmentCode;
        std::ifstream vShaderFile;
        std::ifstream fShaderFile;
        vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
        fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
        try
        {
            vShaderFile.open(vertexPath);
            fShaderFile.open(fragmentPath);
            std::stringstream vShaderStream, fShaderStream;
            vShaderStream << vShaderFile.rdbuf();
            fShaderStream << fShaderFile.rdbuf();
            vShaderFile.close();
            fShaderFile.close();
            vertexCode = vShaderStream.str();
            fragmentCode = fShaderStream.str();
        }
        catch (std::ifstream::failure& e)
        {
            std::cout << "ERROR::SHADER::FILE_NOT_SUCCESSFULLY_READ: " << e.what() << std::endl;
        }
        const char* vShaderCode = vertexCode.c_str();
        const char* fShaderCode = fragmentCode.c_str();

        unsigned int vertex, fragment;

        vertex = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertex, 1, &vShaderCode, NULL);
        glCompileShader(vertex);
        checkCompileErrors(vertex, "VERTEX");

        fragment = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragment, 1, &fShaderCode, NULL);
        glCompileShader(fragment);
        checkCompileErrors(fragment, "FRAGMENT");

        ID = glCreateProgram();
        glAttachShader(ID, vertex);
        glAttachShader(ID, fragment);
        glLinkProgram(ID);
        checkCompileErrors(ID, "PROGRAM");
     
        glDeleteShader(vertex);
        glDeleteShader(fragment);
    }
  
    void use()
    {
        glUseProgram(ID);
    }
   
    void setBool(const std::string& name, bool value) const
    {
        glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
    }
   
    void setInt(const std::string& name, int value) const
    {
        glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
    }
   
    void setFloat(const std::string& name, float value) const
    {
        glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
    }

private:
    void checkCompileErrors(unsigned int shader, std::string type)
    {
        int success;
        char infoLog[1024];
        if (type != "PROGRAM")
        {
            glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
            if (!success)
            {
                glGetShaderInfoLog(shader, 1024, NULL, infoLog);
                std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
            }
        }
        else
        {
            glGetProgramiv(shader, GL_LINK_STATUS, &success);
            if (!success)
            {
                glGetProgramInfoLog(shader, 1024, NULL, infoLog);
                std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
            }
        }
    }
};
#endif
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);
}
cpp
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

#include "shader.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;

int main()
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    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);

    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    Shader ourShader("texture.vs", "texture.fs");

    float vertices[] = {
        // positions          // colors           // texture coords
         0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f, // top right
         0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f, // bottom right
        -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f, // bottom left
        -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f  // top left 
    };
    unsigned int indices[] = {
        0, 1, 3, // first triangle
        1, 2, 3  // second triangle
    };
    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);

    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); 
    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_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    
    int width, height, nrChannels;
    unsigned char* data = stbi_load("../resources/textures/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);

    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);
}
项目结构

图片位置

运行结果

修改片段着色器

FragColor = texture(texture1, TexCoord) * vec4(ourColor, 1.0);

得到结果

纹理单元(texture unit)

        一个纹理的位置值通常称为一个纹理单元(Texture Unit)。一个纹理的默认纹理单元是0,它是默认的激活纹理单元。纹理单元的主要目的是让我们在着色器中可以使用多于一个的纹理。通过把纹理单元赋值给采样器,我们可以一次绑定多个纹理,只要我们首先激活对应的纹理单元。

        OpenGL至少保证有16个纹理单元供你使用,也就是说你可以激活从GL_TEXTURE0到GL_TEXTRUE15。它们都是按顺序定义的,所以我们也可以通过GL_TEXTURE0 + 8的方式获得GL_TEXTURE8,这在当我们需要循环一些纹理单元的时候会很有用。

生成下图:

可对代码进行部分修改

texture.fs

#version 330 core
out vec4 FragColor;

in vec3 ourColor;
in vec2 TexCoord;

uniform sampler2D texture1;
uniform sampler2D texture2;
//最终输出颜色现在是两个纹理的结合。
//GLSL内建的mix函数需要接受两个值作为参数,并对它们根据第三个参数进行线性插值。
//如果第三个值是0.0,它会返回第一个输入;
//如果是1.0,会返回第二个输入值。
//0.2会返回80%的第一个输入颜色和20%的第二个输入颜色,即返回两个纹理的混合色。
void main()
{
    FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}

 cpp文件中的main函数

unsigned int texture1, texture2;
glGenTextures(1, &texture1);
glBindTexture(GL_TEXTURE_2D, texture1);
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;
stbi_set_flip_vertically_on_load(true); 
unsigned char* data = stbi_load("../resources/textures/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);

glGenTextures(1, &texture2);
glBindTexture(GL_TEXTURE_2D, texture2);
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);
data = stbi_load("../resources/textures/awesomeface.png", &width, &height, &nrChannels, 0);
if (data)
{
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
    glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
    std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);

ourShader.use(); 
//设置每个采样器的方式告诉OpenGL每个着色器采样器属于哪个纹理单元
glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0);
ourShader.setInt("texture2", 1);
stbi_set_flip_vertically_on_load(true);

while (!glfwWindowShouldClose(window))
{

    processInput(window);

    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture1);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, texture2);

    ourShader.use();
    glBindVertexArray(VAO);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);


    glfwSwapBuffers(window);
    glfwPollEvents();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值