You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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));
}

正确的实现方法

问题分析

  1. 错误创建多个独立的2D数组纹理:GL_TEXTURE_2D_ARRAY是包含多个2D纹理层的单一纹理对象,无需创建多个独立数组纹理。
  2. 顶点着色器输入location重复:原顶点着色器中a_texindex的location设为0,与a_pos冲突,导致纹理索引无法正确传递。
  3. 未正确初始化数组纹理存储:数组纹理需指定层数,应使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 02:36:59