计算机系统应用教程网站

网站首页 > 技术文章 正文

【LearnOpenGL】07.纹理基础

btikc 2024-09-02 17:12:30 技术文章 10 ℃ 0 评论

开发环境

  • Windows 10
  • Visual Studio Community 2022

准备项目

1.新建一个空的C++项目:07_Texture,并设置为启动项目。


2.配置GLEW和GLFW。根据【LearnOpenGL】01.创建窗口【LearnOpenGL】03.配置GLEW,对这2项进行配置,保证编译通过。

3.将【LearnOpenGL】06.着色器基础 中 Shader.h 和 Shader.cpp 文件拷贝到当前项目目录下。

纹理

在OpenGL中,纹理是一种常用的技术,用于将图像或图案映射到3D模型的表面上,以增加图形的细节和真实感。

纹理坐标是对纹理图像(通常是2D)中的像素的引用。纹理图像中的像素被称为纹素(Texel),以便将它们与在屏幕上呈现的像素区分开。

为了能够把纹理映射(Map)到三角形上,我们需要指定三角形的每个顶点各自对应纹理的哪个部分。这样每个顶点就会关联着一个纹理坐标(Texture Coordinate),用来标明该从纹理图像的哪个部分采样。之后在图形的其它片段上进行片段插值(Fragment Interpolation)。

纹理坐标在x和y轴上,范围为0到1之间(注意我们使用的是2D纹理图像)。使用纹理坐标获取纹理颜色叫做采样 (Sampling)。纹理坐标起始于 (0, 0),也就是纹理图片的左下角,终始于 (1, 1),即纹理图片的右上角。

流程

要想在自己的程序中使用纹理映射,需要遵循以下几个步骤:

1.加载纹理图像文件

使用纹理之前要做的第一件事就是把纹理图像文件加载到我们程序中。在这里我们使用一个持多种流行格式的图像加载库来为我们解决这个问题。比如:stb_image.h

  • 在工程目录 src 下创建 vendor 文件夹存放外部代码(非自己编写的)。
  • 在 vendor 文件夹内新建一个文件夹 stb_image 用来存放该库相关的文件。
  • GitHub地址:https://github.com/nothings/stb/blob/master/stb_image.h,把stb_image.h文件下载到 src/vendor/stb_image 目录下。
  • src/vendor/stb_image 目录下新建一个 stb_image.cpp 文件,输入以下代码:
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
  • 加载纹理图像文件:
// 宽度、高度、颜色通道数
int width, height, comp;
unsigned char *data = stbi_load("container.jpg", &width, &height, &comp, 0);


2.创建纹理对象

void glGenTextures(GLsizei n, GLuint *textures);

参数:

  • n

指定要生成的纹理名称的数量。

  • textures

指向存储生成的纹理名称的数组的指针。


3.绑定纹理对象

void glBindTexture(GLenum target, GLuint texture);

参数:

  • target

需要绑定的特定纹理对象类型。必须是 GL_TEXTURE_1D、GL_TEXTURE_2D 或 GL_TEXTURE_3D等之一。

  • texture

绑定完纹理对象后,所有的纹理加载和纹理参数设置只影响当前绑定的纹理对象。


4.设置纹理参数

void glTexParameteri(GLenum target, GLenum pname, GLint param);

参数:

  • target

指定这些参数将要应用到哪个纹理模式上,它可以是 GL_TEXTURE_1D、GL_TEXTURE_2D 或 GL_TEXTURE_3D 等。

  • pname

指定需要设置哪个纹理参数。

  • param

用于设置特定的纹理参数的值。

纹理过滤

纹理坐标不依赖于分辨率(Resolution),它可以是任意浮点值,当你有一个很大的物体但是纹理的分辨率很低的时候这就变得很重要了。

根据一个拉伸或收缩的纹理贴图计算颜色片段的过程称为纹理过滤(Texture Filtering)。

  • 放大过滤器参数名:GL_TEXTURE_MAG_FILTER
  • 缩小过滤器参数名:GL_TEXTURE_MIN_FILTER

纹理过滤有很多个选项,但有两种是最重要:

  • 邻近过滤(GL_NEAREST):OpenGL会选择中心点最接近纹理坐标的那个像素。
  • 线性过滤(GL_LINEAR):基于纹理坐标附近的纹理像素,计算出一个插值,近似得出这些纹理像素之间的颜色。

纹理环绕

纹理坐标的范围通常是从(0, 0)到(1, 1),那如果我们把纹理坐标设置在范围之外会发生什么?OpenGL 会根据当前纹理环绕模式(Wrapping Mode)进行处理。

GL_REPEAT

重复纹理图像。默认行为。

GL_CLAMP_TO_EDGE

超出的部分会重复纹理坐标的边缘

GL_CLAMP_TO_BORDER

超出的坐标为用户指定的边缘颜色。


5.附加纹理图像

void glTexImage2D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid * data);

参数:

  • target

指定了纹理目标类型。必须是 GL_TEXTURE_1D、GL_TEXTURE_2D 或 GL_TEXTURE_3D等之一。

  • level

指定多级渐远纹理的基本。如果你希望单独手动设置每个多级渐远纹理的级别的话。这里我们填0,也就是基本级别。

  • internalFormat

告诉OpenGL我们希望把纹理储存为何种格式。

  • width

纹理的宽度

  • height

纹理的高度

  • border

默认设置为0(历史遗留问题)。

  • format

源图的格式。

  • type

源图的数据类型。

  • data

源图的图像数据。


6.生成多级渐远纹理

调用 glTexImage2D 后,当前绑定的纹理对象就会附加纹理图像。然而,目前只有基本级别(Base-level)的纹理图像被加载。

void glGenerateMipmap(GLenum target);

参数:

  • target

指定纹理对象绑定到的目标。必须是 GL_TEXTURE_1D、GL_TEXTURE_2D 或 GL_TEXTURE_3D 等之一。


7.顶点数据增加纹理坐标

// 顶点数据
float vertices[] = {
  // --- position ---     --- color ---   - texture -
  -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, // 左下角
  -0.5f,  0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.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, 0.0f, 0.0f, 1.0f, 0.0f  // 右下角
};
// --- 位置属性 ---
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GL_FLOAT), 0);
glEnableVertexAttribArray(0);

// --- 颜色属性 ---
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GL_FLOAT), (void*)(3 * sizeof(GL_FLOAT)));
glEnableVertexAttribArray(1);   

// --- 纹理属性 ---
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GL_FLOAT), (void*)(6 * sizeof(GL_FLOAT)));
glEnableVertexAttribArray(2);


8.激活纹理单元

在OpenGL中,一个纹理的位置值通常称为一个纹理单元(Texture Unit)。

可用纹理单元的数量取决于图形卡上提供的数量。OpenGL至少保证有16个纹理单元供你使用,也就是说你可以激活从 GL_TEXTURE0 到 GL_TEXTRUE15。

一个纹理的默认纹理单元是0,它是默认的激活纹理单元。纹理单元的主要目的是让我们在着色器中可以使用多于一个的纹理。

void glActiveTexture(GLenum texture);

参数:

  • texture

指定激活的纹理单元。


9.绑定纹理

void glBindTexture(GLenum target, GLuint texture);

参数:

  • target

指定纹理对象绑定到的目标。必须是 GL_TEXTURE_1D、GL_TEXTURE_2D 或 GL_TEXTURE_3D 等之一。

  • texture

指定的纹理对象名。


10.在着色器中通过纹理采样器获取纹素数据

顶点shader

#version 330 core
layout(location=0) in vec3 position;
layout(location=1) in vec3 color;
layout(location=2) in vec2 texCoord;

out vec3 v_Color;
out vec2 v_TexCoord;

void main()
{
	gl_Position = vec4(position, 1.0f);
	v_Color = color;
	v_TexCoord = texCoord;
}

片元shader

#version 330 core
in vec3 v_Color;
in vec2 v_TexCoord;

uniform sampler2D u_Texture;

void main()
{
	vec4 texColor = texture(u_Texture, v_TexCoord);
	gl_FragColor = texColor * vec4(v_Color, 1.0f);
}

第5行:定义一个 uniform 变量的纹理采样器。

第9行:通过texture()函数获取纹理采样器上的纹素数据。

第10行:将纹理颜色和顶点颜色融合输出。


完整项目

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include "src/Shader.h"
#include "src/Texture2D.h"

unsigned int VBO;
unsigned int IBO;

// !!!使用指针全局变量,为了保证 tex 在执行完 init() 函数后不被析构。
Texture2D* tex;

void init() {
    // 顶点数据
    float vertices[] = {
     // --- position ---     --- color ---   - texture -
       -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, // 左下角
       -0.5f,  0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.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, 0.0f, 0.0f, 1.0f, 0.0f  // 右下角
    };

    // 绘制顶点的顺序
    unsigned int indices[] = {
        0, 1, 2,    // 第一个三角形的顶点数组下标
        2, 0, 3     // 第二个三角形的顶点数组下标
    };

    // 创建一个缓存对象名,并存储到VBO
    glGenBuffers(1, &VBO);
    // 将缓存对象绑定到GL_ARRAY_BUFFER
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    // 把顶点数据复制到当前绑定的缓存对象
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // 创建一个缓存对象名,并存储到IBO
    glGenBuffers(1, &IBO);
    // 将缓存对象绑定到GL_ELEMENT_ARRAY_BUFFER
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
    // 把顶点索引数据复制到当前绑定的缓存对象
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    // --- 位置属性 ---
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GL_FLOAT), 0);
    glEnableVertexAttribArray(0);

    // --- 颜色属性 ---
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GL_FLOAT), (void*)(3 * sizeof(GL_FLOAT)));
    glEnableVertexAttribArray(1);   

    // --- 纹理属性 ---
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GL_FLOAT), (void*)(6 * sizeof(GL_FLOAT)));
    glEnableVertexAttribArray(2);

    // 纹理    
    tex = new Texture2D("res/textures/OpenGL.png");
    // 将纹理绑定到相应的纹理单元上并激活
    tex->Bind();

    // 着色器
    Shader shader("res/shaders/Basic.vert", "res/shaders/Basic.frag");
    shader.Bind(); 
}

void display() {
    // 绘制
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
}

void clear() {
    // 删除缓存对象
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &IBO);
}

// main
int main(void)
{
    GLFWwindow* window;

    /* Initialize the library */
    if (!glfwInit())
        return -1;

    /* Create a windowed mode window and its OpenGL context */
    window = glfwCreateWindow(640, 480, "Texture", NULL, NULL);
    if (!window)
    {
        glfwTerminate();
        return -1;
    }

    /* Make the window's context current */
    glfwMakeContextCurrent(window);

    // glew init
    if (glewInit() != GLEW_OK) {
        std::cout << "glew init error!" << std::endl;
    }

    // print opengl version
    std::cout << "OpenGL version :" << glGetString(GL_VERSION) << std::endl;

    /* init */
    init();


    /* Loop until the user closes the window */
    while (!glfwWindowShouldClose(window))
    {
        /* Render here */
        glClear(GL_COLOR_BUFFER_BIT);

        /* display */
        display();

        /* Swap front and back buffers */
        glfwSwapBuffers(window);

        /* Poll for and process events */
        glfwPollEvents();
    }

    glfwTerminate();

    /* clear */
    clear();
    return 0;
}

运行结果:


GitHub[作揖]

https://github.com/zGameDeveloper/LearnOpenGL/tree/main/Project/07_Texture

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表