OpenGL入门——多个纹理

发布时间 2023-09-25 21:58:33作者: 一只小瓶子

上一节OpenGL入门——纹理 - 一只小瓶子 - 博客园 (cnblogs.com)中介绍了怎么使用纹理,实际使用过程中可能会用到多个纹理。跟顶点属性一样(顶点对象可以有多个属性,每个属性都有一个位置值(layout)),纹理采样器也有一个位置值(纹理单元),OpenGL中至少有16个纹理单元,从GL_TEXTURE0到GL_TEXTURE15,可以通过GL_TEXTURE0+8的方式获得GL_TEXTURE8(循环中很有用)。

 

多个纹理采样器在片段着色器中的使用如下,声明多个采样器

//fragment shader source
#version 330 core
in vec3 vertexColor;    //顶点颜色
in vec2 textureCoord;    //顶点对应纹理坐标

out vec4 fragColor;        //像素的最终颜色

uniform sampler2D ourTexture0;    //纹理采样器,通过源码中绑定纹理glBindTexture赋值
uniform sampler2D ourTexture1;    

void main()
{
    //fragColor = texture(ourTexture1, textureCoord);//赋值为ourTexture1
    //fragColor = texture(ourTexture0, textureCoord)*vec4(vertexColor, 1.0);//纹理与颜色混合
    //如果第三个值是0.0,它会返回第一个输入;如果是1.0,会返回第二个输入值。0.2会返回80%的第一个输入颜色和20%的第二个输入颜色
    fragColor = mix(texture(ourTexture0, textureCoord), texture(ourTexture1, textureCoord), 0.2);//混合纹理
}

 

怎么将采样器和纹理对应起来呢?片段着色器中的采样器类型是uniform,说明是在程序中定义赋值。

    shader.run();//设置uniform值之前必须激活程序
    //设置纹理采样器对应哪个纹理单元,如果只有一个纹理单元无须设置,默认为0单元
    shader.setUniformInt("ourTexture0", 0);
    shader.setUniformInt("ourTexture1", 1);

为什么上一节我们没有在程序中设置这个纹理单元呢?因为当只有一个纹理的时候默认纹理单元是0,它是默认的激活纹理单元,所以上一节我们没有给它分配一个位置值。

 

最后在绘制前绑定所需的纹理即可,记住绑定前应先激活对应纹理单元

//绘制前,激活纹理单元0和1,如果只有一个纹理单元默认自动激活
        glActiveTexture(GL_TEXTURE0);//先激活对应的纹理单元
        glBindTexture(GL_TEXTURE_2D, texture0);//再绑定对应的纹理
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, texture1);

 

 

完整示例

int texture_triangle()
{
    ///初始化窗口
    GLFWwindow* window = init_window();

    ///定义着色器
    CShader shader("texture_triangle.vs", "texture_triangle.fs");

    ///定义顶点对象
    float vertices[] = {
        // 顶点坐标X,Y,Z      // 顶点颜色R,G,B    // 纹理坐标S,T
         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  // 左上
    };
    unsigned int indices[] = {
        0, 1, 3, // 第一个三角形
        1, 2, 3  // 第二个三角形
    };

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

    //创建纹理,和之前生成的OpenGL对象一样,纹理也是使用ID引用的
    unsigned int texture0;
    glGenTextures(1, &texture0);//生成纹理的数量1,然后把它们储存在第二个参数的unsigned int数组中

    glBindTexture(GL_TEXTURE_2D, texture0);//绑定纹理

    //为当前绑定的纹理对象设置环绕、过滤方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);//X轴环绕方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);//Y轴环绕方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);//被缩小时的过滤选项
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);//被放大时的过滤选项

    //加载图像前进行翻转,因为图像的原点在左上角,OpenGL的原点在左下角
    stbi_set_flip_vertically_on_load(true);

    //加载纹理图像0
    int width, height, nrChannels;
    unsigned char* data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
    if (data)
    {
        //生成纹理,当调用glTexImage2D时,当前绑定的纹理对象就会被附加上纹理图像
        /*
        第一个参数指定了纹理目标(Target)。设置为GL_TEXTURE_2D意味着会生成与当前绑定的纹理对象在同一个目标上的纹理(任何绑定到GL_TEXTURE_1D和GL_TEXTURE_3D的纹理不会受到影响)。
        第二个参数为纹理指定多级渐远纹理的级别,如果你希望单独手动设置每个多级渐远纹理的级别的话。这里我们填0,也就是基本级别。
        第三个参数告诉OpenGL我们希望把纹理储存为何种格式。我们的图像只有RGB值,因此我们也把纹理储存为RGB值。
        第四个和第五个参数设置最终的纹理的宽度和高度。我们之前加载图像的时候储存了它们,所以我们使用对应的变量。
        下个参数应该总是被设为0(历史遗留的问题)。
        第七第八个参数定义了源图的格式和数据类型。我们使用RGB值加载这个图像,并把它们储存为char(byte)数组,我们将会传入对应值。
        最后一个参数是真正的图像数据。
        */
        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);

    //创建纹理
    unsigned int texture1;
    glGenTextures(1, &texture1);//生成纹理的数量1,然后把它们储存在第二个参数的unsigned int数组中

    glBindTexture(GL_TEXTURE_2D, texture1);//绑定纹理

    //为当前绑定的纹理对象设置环绕、过滤方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);//X轴环绕方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);//Y轴环绕方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);//被缩小时的过滤选项
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);//被放大时的过滤选项

    //加载纹理图像1
    data = stbi_load("awesomeface.png", &width, &height, &nrChannels, 0);
    if (data)
    {
        //生成纹理,当调用glTexImage2D时,当前绑定的纹理对象就会被附加上纹理图像
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 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);

    shader.run();//设置uniform值之前必须激活程序
    //设置纹理采样器对应哪个纹理单元,如果只有一个纹理单元无须设置,默认为0单元
    shader.setUniformInt("ourTexture0", 0);
    shader.setUniformInt("ourTexture1", 1);

    while (!glfwWindowShouldClose(window))
    {
        processInput(window);


        //清空屏幕
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        //绘制前,激活纹理单元0和1,如果只有一个纹理单元默认自动激活
        glActiveTexture(GL_TEXTURE0);//先激活对应的纹理单元
        glBindTexture(GL_TEXTURE_2D, texture0);//再绑定对应的纹理
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, texture1);

        ///绘制物体
        shader.run();

        glBindVertexArray(VAO);
        //使用VEO绘制
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);//绘制图元为三角形,绘制顶点数量6,索引类型uint,偏移量0

        glfwSwapBuffers(window);//交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲)
        glfwPollEvents();//检查有没有触发什么事件
    }

    //释放资源
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &EBO);

    glfwTerminate();//释放/删除之前的分配的所有资源
    return 0;
}

 

运行效果

 附上纹理图像下载地址container.jpg (512×512) (learnopengl-cn.github.io) 和awesomeface.png (512×512) (learnopengl-cn.github.io)