如何使用GL_TEXTURE_2D_ARRAY绑定多张纹理为数组?
问题:GL_TEXTURE_2D_ARRAY 无法正确渲染纹理
根据相关结论:
无法使用片段着色器输入变量索引纹理采样器数组,必须使用
sampler2DArray(对应GL_TEXTURE_2D_ARRAY类型)替代sampler2D数组(对应GL_TEXTURE_2D类型)
我尝试实现GL_TEXTURE_2D_ARRAY,但错误地创建了两个独立的GL_TEXTURE_2D_ARRAY纹理并绑定到不同采样单元,导致运行后仅显示黑方块,无法正常渲染纹理。需要正确的C++端实现方法,将多张纹理整合到同一个GL_TEXTURE_2D_ARRAY中。
我的错误代码(C++)
#include <iostream> #include <glad/glad.h> #include <GLFW/glfw3.h> #define STB_IMAGE_IMPLEMENTATION #include <stb/stb_image.h> #include <test/main/shader.hpp> uint32_t LoadTexture(const std::string& filepath) { int w, h, bits; stbi_set_flip_vertically_on_load(1); uint8_t *pixels = stbi_load(filepath.c_str(), &w, &h, &bits, 4); uint32_t texture_id; glGenTextures(1, &texture_id); glBindTexture(GL_TEXTURE_2D_ARRAY, texture_id); glTexImage2D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); // glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); stbi_image_free(pixels); return texture_id; } // 替代OpenGL 4.5以下版本的glBindTextureUnit void Bind2DArrayTexture(int unit, uint32_t texture) { glActiveTexture(GL_TEXTURE0 + unit); glBindTexture(GL_TEXTURE_2D_ARRAY, texture); } int main() { glfwInit(); GLFWwindow *window = glfwCreateWindow(960, 540, "test", nullptr, nullptr); if (window == nullptr) { std::cerr << "failed to create GLFW window\n"; } glfwMakeContextCurrent(window); glfwSetFramebufferSizeCallback(window, [](GLFWwindow *_, int width, int height) { glViewport(0, 0, width, height); }); if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) { std::cerr << "failed to initialize GLAD\n"; } glEnable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); Shader shader("assets/GLSL/test.glsl"); shader.Bind(); int samplers[2] = {0, 1}; shader.SetIntArray("u_textures", samplers, 2); uint32_t quad_va, quad_vb, quad_ib; glGenVertexArrays(1, &quad_va); glBindVertexArray(quad_va); float vertices[] = { // first texture - logo.png -1.5f, -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, -0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, -1.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // second texture - chessboard.png 0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.5f, 0.5f, 0.0f, 1.0f, 1.0f, 1.0f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f }; glGenBuffers(1, &quad_vb); glBindBuffer(GL_ARRAY_BUFFER, quad_vb); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 6, nullptr); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 6, reinterpret_cast<const void *>(sizeof(float) * 3)); glEnableVertexAttribArray(1); glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, sizeof(float) * 6, reinterpret_cast<const void *>(sizeof(float) * 5)); glEnableVertexAttribArray(2); uint32_t indices[] = { 0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4 }; glGenBuffers(1, &quad_ib); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quad_ib); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); uint32_t logo_id = LoadTexture("assets/textures/logo.png"); uint32_t chessboard_id = LoadTexture("assets/textures/chessboard.png"); while (!glfwWindowShouldClose(window)) { glClearColor(0.1f, 0.1f, 0.1f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); shader.Bind(); Bind2DArrayTexture(0, logo_id); Bind2DArrayTexture(1, chessboard_id); glBindVertexArray(quad_va); glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_INT, nullptr); glfwSwapBuffers(window); glfwPollEvents(); } }
对应的着色器代码
#type vertex #version 450 core layout(location = 0) in vec3 a_pos; layout(location = 1) in vec2 a_texcoord; layout(location = 0) in float a_texindex; out vec2 v_texcoord; out float v_texindex; void main() { v_texcoord = a_texcoord; v_texindex = a_texindex; gl_Position = vec4(a_pos, 1.0); } #type fragment #version 450 core in vec2 v_texcoord; in float v_texindex; uniform sampler2DArray u_textures; out vec4 color; void main() { color = texture(u_textures, vec3(v_texcoord.xy, v_texindex)); }
正确的实现方法
问题分析
- 错误创建多个独立的2D数组纹理:
GL_TEXTURE_2D_ARRAY是包含多个2D纹理层的单一纹理对象,无需创建多个独立数组纹理。 - 顶点着色器输入location重复:原顶点着色器中
a_texindex的location设为0,与a_pos冲突,导致纹理索引无法正确传递。 - 未正确初始化数组纹理存储:数组纹理需指定层数,应使用
glTexImage3D而非glTexImage2D。
步骤1:创建单一GL_TEXTURE_2D_ARRAY纹理
所有层的纹理必须尺寸相同,这是GL_TEXTURE_2D_ARRAY的硬性要求。修改加载函数,将多张图片填充到同一数组纹理的不同层:
// 加载多张纹理到同一个GL_TEXTURE_2D_ARRAY中 uint32_t LoadTextureArray(const std::vector<std::string>& filepaths) { if (filepaths.empty()) return 0; // 先加载第一张图片获取尺寸 int w, h, bits; stbi_set_flip_vertically_on_load(1); uint8_t* first_pixels = stbi_load(filepaths[0].c_str(), &w, &h, &bits, 4); if (!first_pixels) return 0; uint32_t texture_id; glGenTextures(1, &texture_id); glBindTexture(GL_TEXTURE_2D_ARRAY, texture_id); // 初始化数组纹理存储:第三维为纹理层数 glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, w, h, filepaths.size(), 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); // 设置纹理参数(必须设置,否则会显示黑块) glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // 填充第一层数据 glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, w, h, 1, GL_RGBA, GL_UNSIGNED_BYTE, first_pixels); stbi_image_free(first_pixels); // 填充剩余层 for (size_t i = 1; i < filepaths.size(); ++i) { uint8_t* pixels = stbi_load(filepaths[i].c_str(), &w, &h, &bits, 4); if (!pixels) continue; // 确保当前图片尺寸与第一张一致,否则OpenGL会静默失败 glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, (GLint)i, w, h, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels); stbi_image_free(pixels); } return texture_id; }
步骤2:修改main函数的纹理加载与绑定逻辑
int main() { // ... 原有初始化代码不变 ... Shader shader("assets/GLSL/test.glsl"); shader.Bind(); // 仅需绑定一个采样单元(单一数组纹理) shader.SetInt("u_textures", 0); // ... 原有VAO/VBO/IBO代码不变 ... // 加载两张纹理到同一数组纹理 std::vector<std::string> texture_paths = { "assets/textures/logo.png", "assets/textures/chessboard.png" }; uint32_t texture_array_id = LoadTextureArray(texture_paths); while (!glfwWindowShouldClose(window)) { glClearColor(0.1f, 0.1f, 0.1f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); shader.Bind(); // 仅绑定单一数组纹理到单元0 Bind2DArrayTexture(0, texture_array_id); glBindVertexArray(quad_va); glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_INT, nullptr); glfwSwapBuffers(window); glfwPollEvents(); } }
步骤3:修复顶点着色器的输入location
修正a_texindex的location,避免与其他输入冲突:
#type vertex #version 450 core layout(location = 0) in vec3 a_pos; layout(location = 1) in vec2 a_texcoord; layout(location = 2) in float a_texindex; // 修正为正确的location out vec2 v_texcoord; out float v_texindex; void main() { v_texcoord = a_texcoord; v_texindex = a_texindex; gl_Position = vec4(a_pos, 1.0); }
关键注意事项
- 统一纹理尺寸:所有层的宽高必须一致,若图片尺寸不同需先缩放。
- 必须设置纹理参数:未设置过滤参数时,OpenGL默认使用需要mipmap的模式,会导致纹理不可用。
- 使用正确的纹理函数:用
glTexImage3D初始化数组纹理,用glTexSubImage3D更新单一层的数据。
内容的提问来源于stack exchange,提问作者milanHrabos
相关产品推荐
相关产品推荐

