C-4. 对正方体加载纹理
作业要求:
1、使用三种纹理过滤方式加载纹理(线性采样、mipmap 和最近点采样);
2、加载纹理的图片可以自己设定;
3、各个面的纹理不同;
4、鼠标或者键盘控制纹理过滤方式的切换。
初始化OpenGL
同之前C-2和C-3的报告。
准备数据
建立顶点数组对象。同时注册顶点的位置属性和纹理坐标属性。属性的序号值指明了数据的存放位置,以在着色器中通过对应位置找到位置和纹理坐标数据。
顶点的position属性满足题设的立方体要求。在后面渲染时使用glDrawArrays(GL_TRIANGLES, 0, 36)
来绘制每个三角形,即不同的三角形面片之间不复用顶点数据。
float vertices[] = {
// positions // texture coords
-0.5f, -0.5f, -0.5f, 1.0f, 0.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, 1.0f, 0.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, 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, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 1.0f, 0.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, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 0.0f, 1.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, 0.0f, 1.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, 0.0f, 1.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, 0.0f, 1.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, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f
};
// first, configure the pyramid's VAO, VBO, EBO
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// position attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// texture attribute
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3*sizeof(float)));
glEnableVertexAttribArray(1);
glBindVertexArray(0);//unbind
纹理图片准备
根据作者本人喜好,采用了如下的6张贴图。
|
|
|
|
|
|
使用stb_image.h
库加载纹理。通过glTexParameteri
设置纹理的wrap
和filter
参数。wrap
参数选择GL_REPEAT
使得纹理坐标超出[0, 1]范围时按周期性重复采样,另外有GL_CLAMP_TO_EDGE
选项使超出范围的采样延拓到边缘。初始化加载纹理时filter
参数都先采用GL_LINEAR的过滤方式。具体根据用户输入而切换的控制放在渲染循环中。stbi_load
读入图片的数据后,根据图片的分量通道数通过glTexImage2D
生成纹理。
GLuint textures[6];
const char * textPaths[] = {
"../resources/gantayipao.jpg",
"../resources/jingrui.jpg",
"../resources/kaipao.jpg",
"../resources/kongerlengzi.jpg",
"../resources/tiancai.jpg",
"../resources/yidalipao.jpg"
};
// glGenTextures(6, textures);
for (int i = 0; i < 6; i++) {//初始化加载纹理时都采用GL_LINEAR的过滤方式
glGenTextures(1, &textures[i]);
glBindTexture(GL_TEXTURE_2D, textures[i]);
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);
stbi_set_flip_vertically_on_load(true);
int width, height, nrChannels;
unsigned char *data = stbi_load(textPaths[i], &width, &height, &nrChannels, 0);
if (data) {
GLenum format;
if (nrChannels == 1)
format = GL_RED;
else if (nrChannels == 3)
format = GL_RGB;
else if (nrChannels == 4)
format = GL_RGBA;
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
stbi_image_free(data);
} else {
std::cout << "Texture failed to load at path: " << textPaths[i] << std::endl;
stbi_image_free(data);
}
}
建立着色器
由于使用纹理贴图,不涉及任何光照,因此顶点着色器和片段着色器都非常简单。只需要在顶点着色器中读入纹理坐标并传送给片段着色器即可。
顶点着色器
#version 460 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;
out vec2 TexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
TexCoord = vec2(aTexCoord.x, aTexCoord.y);
}
片段着色器
#version 460 core
out vec4 FragColor;
in vec2 TexCoord;
// texture samplers
uniform sampler2D texture;
void main()
{
FragColor = texture(texture, TexCoord);
}
纹理过滤方式的切换
通过在process_input
函数中接受键盘输入的信号而改变变量filter_mode
的值,
void processInput(GLFWwindow *window) {
... ...
if (glfwGetKey(window, GLFW_KEY_0) == GLFW_PRESS)
filter_mode = LINEAR;
else if (glfwGetKey(window, GLFW_KEY_1) == GLFW_PRESS)
filter_mode = MIPMAP;
else if (glfwGetKey(window, GLFW_KEY_2) == GLFW_PRESS)
filter_mode = NEAREAST;
}
进而在渲染循环中,对每张纹理图修改filter
参数。需要注意的是,利用glTexParameteri
修改时需要先用glBindTexture
绑定到GL_TEXTURE_2D
纹理的位置上。需要注意的是,LearnOpenGL指出:
一个常见的错误是,将放大过滤的选项设置为多级渐远纹理过滤选项之一。这样没有任何效果,因为多级渐远纹理主要是使用在纹理被缩小的情况下的:纹理放大不会使用多级渐远纹理,为放大过滤设置多级渐远纹理的选项会产生一个GL_INVALID_ENUM错误代码。
因此选择MIPMAP选项时,只将缩小过滤设置为多级,而放大过滤仍采用GL_LINEAR
switch (filter_mode)
{
case LINEAR:
for (int i = 0; i < 6; i++) {
glBindTexture(GL_TEXTURE_2D, textures[i]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
break;
case NEAREAST:
for (int i = 0; i < 6; i++) {
glBindTexture(GL_TEXTURE_2D, textures[i]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
break;
case MIPMAP:
for (int i = 0; i < 6; i++) {
glBindTexture(GL_TEXTURE_2D, textures[i]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
break;
default:
std::cout << "invalid filter_mode of " << filter_mode << std::endl;
break;
}
渲染纹理
在渲染循环中,除了需要设定着色器的三个坐标变换的矩阵,还要通过setInt("texture", 0)
设定片段着色器中的sampler2D值:uniform sampler2D texture
,这个数需要与后续激活的纹理单元号保持一致glActiveTexture(GL_TEXTURE0)
(不显式激活时默认为0),片段着色器才能正确地采样到纹理。
ourShader.use();
ourShader.setInt("texture", 0);
ourShader.setMat4("model", glm::mat4(1.0f));
ourShader.setMat4("view", camera.GetViewMatrix());
ourShader.setMat4("projection", glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f));
glBindVertexArray(VAO);
//依次画两个三角形(六个顶点)
for (int i = 0; i < 6; i++) {
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textures[i]);// bind corresponding texture
glDrawArrays(GL_TRIANGLES, 6*i, 6);
}
效果图
整体效果图如下所示。
|
|
在放大到超出原有图片分辨率的情况下,观察不同纹理过滤方式的效果,如下图所示。
-
线性采样
-
mipmap
-
最近点采样