C-4:对正方体加载纹理

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张贴图。

C-4:对正方体加载纹理图1
C-4:对正方体加载纹理图2
C-4:对正方体加载纹理图3
C-4:对正方体加载纹理图4
C-4:对正方体加载纹理图5
C-4:对正方体加载纹理图6

使用stb_image.h库加载纹理。通过glTexParameteri设置纹理的wrapfilter参数。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);
}

效果图

整体效果图如下所示。

C-4:对正方体加载纹理图1
C-4:对正方体加载纹理图2

在放大到超出原有图片分辨率的情况下,观察不同纹理过滤方式的效果,如下图所示。

  • 线性采样
    C-4:对正方体加载纹理

  • mipmap
    C-4:对正方体加载纹理

  • 最近点采样
    C-4:对正方体加载纹理

上一篇:一文详解 纹理采样与Mipmap纹理——构建山地渲染效果


下一篇:在DirectX 12中使用cubemap