> 文章列表 > OpenGL入门教程之 纹理

OpenGL入门教程之 纹理

OpenGL入门教程之 纹理

引言

 我们已经了解到,我们可以为每个顶点添加颜色来增加图形的细节,从而创建出有趣的图像但是,如果想让图形看起来更真实,我们就必须有足够多的顶点,从而指定足够多的颜色。这将会产生很多额外开销,因为每个模型都会需求更多的顶点,每个顶点又需求一个颜色属性
 艺术家和程序员更喜欢使用纹理(Texture)纹理是一个2D图片(甚至也有1D和3D的纹理),它可以用来添加物体的细节;你可以想象纹理是一张绘有砖块的纸,无缝折叠贴合到你的3D的房子上,这样你的房子看起来就像有砖墙外表了。因为我们可以在一张图片上插入非常多的细节,这样就可以让物体非常精细而不用指定额外的顶点
 除了图像以外,纹理也可以被用来储存大量的数据,这些数据可以发送到着色器上

纹理映射

自注:为顶点指定纹理坐标,这样顶点就可以根据纹理坐标对纹理的像素进行采样,于是顶点就拥有了纹理对应位置的颜色。二图形其他的片段会依据纹理进行相应插值。
 为了能够把纹理映射(Map)到三角形上,我们需要指定三角形的每个顶点各自对应纹理的哪个部分。这样每个顶点就会关联着一个纹理坐标(Texture Coordinate),用来标明该从纹理图像的哪个部分采样(译注:采集片段颜色)之后在图形的其它片段上进行片段插值(Fragment Interpolation)

纹理环绕方式

自注:纹理坐标在x和y轴上,且范围为0到1之间(对于2D纹理图形)。纹理坐标起始于(0,0)为纹理图片左下角,终止于(1,1)为纹理图片的右上角。纹理环绕就是指当纹理坐标超出范围时,采取纹理重复方式去平铺物体的表面。
OpenGL入门教程之 纹理
使用函数:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,GL_MIRRORED_REPEAT);
float borderColor[] = {1.0f, 1.0f , 0.0f, 1.0f };
glTexParametefv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

OpenGL入门教程之 纹理

纹理过滤

自注:当你拥有一张纹理图片时,其实它其中包含很多纹理像素,就是它拥有很多像素点。而我们的纹理坐标是浮点数,我们需要根据纹理坐标去纹理上进行采样获取颜色。
OpenGL入门教程之 纹理
使用函数:

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

多级渐远纹理
OpenGL入门教程之 纹理
涉及函数:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN)FILTER, GL_LINEAR_MIPMAP_LINEAR);

加载纹理

OpenGL入门教程之 纹理
 加载纹理的代码:

int width, height;
unsigned char* image = SOIL_load_image("container,jpg", &width, &height, 0 ,SOIL_LOAD_RGB);

创建纹理

OpenGL入门教程之 纹理
OpenGL入门教程之 纹理
生成纹理的过程:

GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
// 为当前绑定的纹理对象色湖之环绕、过滤方式// 加载并生成纹理
int width, height;
unsigned char* image = SOIL_load_image("container.jpg", &width, &height, 0 ,SOIL_LOAD_RGB);

应用纹理

 后面的这部分我们会使用glDrawElements绘制「你好,三角形」教程最后一部分的矩形。我们需要告知OpenGL如何采样纹理,所以我们必须使用纹理坐标更新顶点数据:

GLfloat 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    // 左上
};

 新顶点数据的配置:

glVertexAttribPointer(2, 2, GL_FLOAT,GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
glEnableVertexAttribArray(2);

 顶点着色器:

#version 330 core// 输入为顶点数据中的位置、颜色、纹理坐标
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color;
layout (location = 2) in vec2 texCoord;// 输出为顶点颜色、纹理坐标
out vec3 ourColor;
out vec2 TexCoord;void main()
{gl_Position = vec4(position, 1.0f);ourColor = color;TexCoord = texCoord;
}

OpenGL入门教程之 纹理

 片段着色器:

#version 330 core// 输入为顶点颜色、纹理坐标
in vec3 ourColor;
in vec2 TexCoord;// 输出为颜色
out vec4 color;// 采样器
uniform sampler2D ourTexture;void main()
{// 使用texture函数来采样纹理颜色(采样器,纹理坐标)color = texture(ourTexture, TexCoord);
}

OpenGL入门教程之 纹理

glBindTexture(GL_TEXTURE_2D, texture);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);

完整代码

#version 330 core// 输入为顶点数据中的位置、颜色、纹理坐标
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color;
layout (location = 2) in vec2 texCoord;// 输出为顶点颜色、纹理坐标
out vec3 ourColor;
out vec2 TexCoord;void main()
{gl_Position = vec4(position, 1.0f);ourColor = color;TexCoord = texCoord;
}
#version 330 core// 输入为顶点颜色、纹理坐标
in vec3 ourColor;
in vec2 TexCoord;// 输出为颜色
out vec4 color;// 采样器
uniform sampler2D ourTexture;void main()
{// 使用texture函数来采样纹理颜色(采样器,纹理坐标)color = texture(ourTexture, TexCoord);
}
#include <iostream>// GLEW
#define GLEW_STATIC
#include <GL/glew.h>// GLFW
#include <GLFW/glfw3.h>// Other Libs
#include "SOIL.h"// Other includes
#include "Shader.h"// Function prototypes
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);// Window dimensions
const GLuint WIDTH = 800, HEIGHT = 600;// The MAIN function, from here we start the application and run the game loop
int main()
{// Init GLFWglfwInit();// Set all the required options for GLFWglfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);// Create a GLFWwindow object that we can use for GLFW's functionsGLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);glfwMakeContextCurrent(window);// Set the required callback functionsglfwSetKeyCallback(window, key_callback);// Set this to true so GLEW knows to use a modern approach to retrieving function pointers and extensionsglewExperimental = GL_TRUE;// Initialize GLEW to setup the OpenGL Function pointersglewInit();// Define the viewport dimensionsglViewport(0, 0, WIDTH, HEIGHT);// Build and compile our shader programShader ourShader("C:\\\\Users\\\\32156\\\\source\\\\repos\\\\LearnOpenGL\\\\Shader\\\\vertexShader.txt", "C:\\\\Users\\\\32156\\\\source\\\\repos\\\\LearnOpenGL\\\\Shader\\\\fragmentShader.txt");// 设置顶点数据GLfloat vertices[] = {// 顶点位置           // 顶点颜色         // 纹理坐标0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f, // Top Right0.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 };// 设置索引GLuint indices[] = {  0, 1, 3, // 第一个三角形1, 2, 3  // 第二个三角形};// 创建VAO、VBO、EBO,绑定并设置VBO、EBO、VAOGLuint 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(GLfloat), (GLvoid*)0);glEnableVertexAttribArray(0);// 颜色数据解析glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));glEnableVertexAttribArray(1);// 纹理坐标数据解析glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));glEnableVertexAttribArray(2);glBindVertexArray(0); // 解绑VAO(这时VAO中包含VBO和EBO)// 加载纹理GLuint texture;glGenTextures(1, &texture);// 将纹理对象texture绑定到GL_TEXTURE_2D上,往后对GL_TEXTURE_2D的操作都将对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;unsigned char* image = SOIL_load_image("C:\\\\Users\\\\32156\\\\source\\\\repos\\\\LearnOpenGL\\\\Resource\\\\container.jpg", &width, &height, 0, SOIL_LOAD_RGB);// 将纹理图片保存到绑定GL_TEXTURE_2D的对象上,以字符数组形式glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);// 生成GL_TEXTURE_2D绑定对象的多级渐远纹理glGenerateMipmap(GL_TEXTURE_2D);// 释放纹理图片内存,解绑纹理textureSOIL_free_image_data(image);glBindTexture(GL_TEXTURE_2D, 0); // 游戏循环while (!glfwWindowShouldClose(window)){// 事件检测glfwPollEvents();// 清除屏幕颜色缓存glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);// 绑定纹理glBindTexture(GL_TEXTURE_2D, texture);// 使用着色器ourShader.Use();// 绑定VAO 并使用glDrawElements绘图glBindVertexArray(VAO);//glDrawElements的第一个参数为绘制的图元类型//第二个参数为模型的总顶点数,第三个参数为索引值的类型,最后一个值是指向索引存贮位置的指针glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);// 解绑VAOglBindVertexArray(0);// 交换前后缓冲区glfwSwapBuffers(window);}// 释放VAO、VBO、EBO占用的的显存glDeleteVertexArrays(1, &VAO);glDeleteBuffers(1, &VBO);glDeleteBuffers(1, &EBO);// 释放GLFW申请的内存glfwTerminate();return 0;
}// Is called whenever a key is pressed/released via GLFW
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)glfwSetWindowShouldClose(window, GL_TRUE);
}

 顶点颜色的使用:
OpenGL入门教程之 纹理

color = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0f);

纹理单元

 一个纹理的位置值通常称为一个纹理单元(Texture Unit)。一个纹理的默认纹理单元是0,它是默认的激活纹理单元,所以教程前面部分我们没有分配一个位置值。

 纹理单元的主要目的是让我们在着色器中可以使用多于一个的纹理。通过把纹理单元赋值给采样器,我们可以一次绑定多个纹理,只要我们首先激活对应的纹理单元。就像glBindTexture一样,我们可以使用glActiveTexture激活纹理单元,传入我们需要使用的纹理单元:

glActiveTexture(GL_TEXTURE0); //在绑定纹理之前先激活纹理单元
glBindTexture(GL_TEXTURE_2D, texture);

 激活纹理单元之后,接下来的glBindTexture函数调用会绑定这个纹理到当前激活的纹理单元,纹理单元GL_TEXTURE0默认总是被激活,所以我们在前面的例子里当我们使用glBindTexture的时候,无需激活任何纹理单元。

 片段着色器代码:

#version 330 coreuniform sampler2D ourTexture1;
uniform sampler2D ourTexture2;void main()
{color = mix(texture(ourTexture1, TexCoord), texture(ourTexture2, TexCoord), 0.2);
}

 最终输出颜色现在是两个纹理的结合。GLSL内建的mix函数需要接受两个值作为参数,并对它们根据第三个参数进行线性插值。。如果第三个值是0.0,它会返回第一个输入;如果是1.0,会返回第二个输入值。0.2会返回80%的第一个输入颜色和20%的第二个输入颜色,即返回两个纹理的混合色。我们现在需要载入并创建另一个纹理;你应该对这些步骤很熟悉了。记得创建另一个纹理对象,载入图片,使用glTexImage2D生成最终纹理。对于第二个纹理我们使用一张你学习OpenGL时的面部表情图片。为了使用第二个纹理(以及第一个),我们必须改变一点渲染流程,先绑定两个纹理到对应的纹理单元,然后定义哪个uniform采样器对应哪个纹理单元:

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture1"), 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture2"), 1);glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);

 注意,我们使用glUniform1i设置uniform采样器的位置值,或者说纹理单元。通过glUniform1i的设置,我们保证每个uniform采样器对应着正确的纹理单元。

 OpenGL要求y轴0.0坐标是在图片的底部的,但是图片的y轴0.0坐标通常在顶部。一些图片加载器比如DevIL在加载的时候有选项重置y原点,但是SOIL没有。SOIL却有一个叫做SOIL_load_OGL_texture函数可以使用一个叫做SOIL_FLAG_INVERT_Y的标记加载并生成纹理,这可以解决我们的问题。不过这个函数用了一些在现代OpenGL中失效的特性,所以现在我们仍需坚持使用SOIL_load_image,自己做纹理的生成。
OpenGL入门教程之 纹理

最后的源代码

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color;
layout (location = 2) in vec2 texCoord;out vec3 ourColor;
out vec2 TexCoord;void main()
{gl_Position = vec4(position, 1.0f);ourColor = color;// We swap the y-axis by substracing our coordinates from 1. This is done because most images have the top y-axis inversed with OpenGL's top y-axis.// TexCoord = texCoord;TexCoord = vec2(texCoord.x, 1.0 - texCoord.y);
}
#version 330 core
in vec3 ourColor;
in vec2 TexCoord;out vec4 color;// Texture samplers
uniform sampler2D ourTexture1;
uniform sampler2D ourTexture2;void main()
{// Linearly interpolate between both textures (second texture is only slightly combined)color = mix(texture(ourTexture1, TexCoord), texture(ourTexture2, TexCoord), 0.2);
}
#include <iostream>// GLEW
#define GLEW_STATIC
#include <GL/glew.h>// GLFW
#include <GLFW/glfw3.h>// Other Libs
#include "SOIL.h"// Other includes
#include "Shader.h"// Function prototypes
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);// Window dimensions
const GLuint WIDTH = 800, HEIGHT = 600;// The MAIN function, from here we start the application and run the game loop
int main()
{// Init GLFWglfwInit();// Set all the required options for GLFWglfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);// Create a GLFWwindow object that we can use for GLFW's functionsGLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);glfwMakeContextCurrent(window);// Set the required callback functionsglfwSetKeyCallback(window, key_callback);// Set this to true so GLEW knows to use a modern approach to retrieving function pointers and extensionsglewExperimental = GL_TRUE;// Initialize GLEW to setup the OpenGL Function pointersglewInit();// Define the viewport dimensionsglViewport(0, 0, WIDTH, HEIGHT);// Build and compile our shader programShader ourShader("C:\\\\Users\\\\32156\\\\source\\\\repos\\\\LearnOpenGL\\\\Shader\\\\vertexShader.txt", "C:\\\\Users\\\\32156\\\\source\\\\repos\\\\LearnOpenGL\\\\Shader\\\\fragmentShader.txt");// Set up vertex data (and buffer(s)) and attribute pointersGLfloat vertices[] = {// Positions          // Colors           // Texture Coords0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f, // Top Right0.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 };GLuint indices[] = {  // Note that we start from 0!0, 1, 3, // First Triangle1, 2, 3  // Second Triangle};GLuint 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);// Position attributeglVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0);glEnableVertexAttribArray(0);// Color attributeglVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));glEnableVertexAttribArray(1);// TexCoord attributeglVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));glEnableVertexAttribArray(2);glBindVertexArray(0); // Unbind VAO// Load and create a texture GLuint texture1;GLuint texture2;// ====================// Texture 1// ====================glGenTextures(1, &texture1);glBindTexture(GL_TEXTURE_2D, texture1); // All upcoming GL_TEXTURE_2D operations now have effect on our texture object// Set our texture parametersglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);	// Set texture wrapping to GL_REPEATglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);// Set texture filteringglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);// Load, create texture and generate mipmapsint width, height;unsigned char* image = SOIL_load_image("C:\\\\Users\\\\32156\\\\source\\\\repos\\\\LearnOpenGL\\\\Resource\\\\container.jpg", &width, &height, 0, SOIL_LOAD_RGB);glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);glGenerateMipmap(GL_TEXTURE_2D);SOIL_free_image_data(image);glBindTexture(GL_TEXTURE_2D, 0); // Unbind texture when done, so we won't accidentily mess up our texture.// ===================// Texture 2// ===================glGenTextures(1, &texture2);glBindTexture(GL_TEXTURE_2D, texture2);// Set our texture parametersglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);// Set texture filteringglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);// Load, create texture and generate mipmapsimage = SOIL_load_image("C:\\\\Users\\\\32156\\\\source\\\\repos\\\\LearnOpenGL\\\\Resource\\\\awesomeface.png", &width, &height, 0, SOIL_LOAD_RGB);glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);glGenerateMipmap(GL_TEXTURE_2D);SOIL_free_image_data(image);glBindTexture(GL_TEXTURE_2D, 0);// Game loopwhile (!glfwWindowShouldClose(window)){// Check if any events have been activiated (key pressed, mouse moved etc.) and call corresponding response functionsglfwPollEvents();// Render// Clear the colorbufferglClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);// Activate shaderourShader.Use();// Bind Textures using texture unitsglActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, texture1);glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture1"), 0);glActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D, texture2);glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture2"), 1);// Draw containerglBindVertexArray(VAO);glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);glBindVertexArray(0);// Swap the screen buffersglfwSwapBuffers(window);}// Properly de-allocate all resources once they've outlived their purposeglDeleteVertexArrays(1, &VAO);glDeleteBuffers(1, &VBO);glDeleteBuffers(1, &EBO);// Terminate GLFW, clearing any resources allocated by GLFW.glfwTerminate();return 0;
}// Is called whenever a key is pressed/released via GLFW
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)glfwSetWindowShouldClose(window, GL_TRUE);
}

核心代码

纹理的配置

	// 创建纹理并绑定GL_TEXTURE_2DglGenTextures(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;unsigned char* image = SOIL_load_image("C:\\\\Users\\\\32156\\\\source\\\\repos\\\\LearnOpenGL\\\\Resource\\\\container.jpg", &width, &height, 0, SOIL_LOAD_RGB);// 绑定纹理图片到绑定GL_TEXTURE_2D的对象texture1glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);// 生成多级渐远纹理glGenerateMipmap(GL_TEXTURE_2D);// 释放图片并解绑textureSOIL_free_image_data(image);glBindTexture(GL_TEXTURE_2D, 0); 

纹理单元的激活

    while (!glfwWindowShouldClose(window)){glfwPollEvents();glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);// 使用着色器ourShader.Use();// 激活纹理单元glActiveTexture(GL_TEXTURE0);// 绑定纹理对象到激活的纹理单元glBindTexture(GL_TEXTURE_2D, texture1);// 设置着色器程序中的纹理采样器对应的纹理单元glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture1"), 0);// 同上glActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D, texture2);glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture2"), 1);glBindVertexArray(VAO);glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);glBindVertexArray(0);glfwSwapBuffers(window);}