OpenGL入门——多个纹理
上一节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)