milanHrabos
milanHrabos

Reputation: 1965

How to use GL_TEXTURE_2D_ARRAY for binding multiple textures (as array)?

According to this answer: OpenGL sampler2D array

You cannot use a fragment shader input variable to index an array of texture samplers. You have to use a sampler2DArray (GL_TEXTURE_2D_ARRAY) instead of an array of sampler2D (GL_TEXTURE_2D)

So I tried to do exactly that - generate GL_TEXTURE_2D_ARRAY:

#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;
}

// https://stackoverflow.com/questions/60513272/alternative-for-glbindtextureunit-for-opengl-versions-less-than-4-5
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();
    }
}

and shader:

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

Where the Shader class, which is not shown simply parses the shader file with both, vertex and fragment part. Now both textures are distinguished via last attrib pointer, one float 0.0f for first texture, 1.0f for second one. But in the output, I only see 2 black squares (which should have the textures in it), so how to use the GL_TEXTURE_2D_ARRAY to load both textures (and thus calling 2 times Bind2DArrayTexture, since that is the target, I have no way how to make an actual array of both textures).

Now please do not mark this as duplicate to the referenced answer (provided at the beginning of this question). Because there is only shown the usage of the shader (which I did), but no actual usage of C++ side (using the GL_TEXTURE_2D_ARRAY for 2 distinct textures). So it won't answer my question and thus my question cannot be duplicate

PS: for completness, here is the shader class:

header:

#ifndef TEST_SHADER_HPP
#define TEST_SHADER_HPP

#include <string>

#include <glm/glm.hpp>

class Shader {
public:
    explicit Shader(const std::string& filepath);

    void Bind() const;

    void SetInt(const std::string& name, int value) const;

    void SetIntArray(const std::string& name, int *values, int count);

    void SetFloat(const std::string& name, float value) const;

    void SetVec3(const std::string& name, const glm::vec3& vec) const;

    void SetVec4(const std::string& name, const glm::vec4& vec) const;

    void SetMat4(const std::string& name, const glm::mat4& mat) const;

private:
    static void CheckCompileErrors(uint32_t shader_id, const std::string& type, const std::string& filepath);

    uint32_t id_;
};

#endif //TEST_SHADER_HP

source:

#include <test/main/shader.hpp>

#include <iostream>
#include <fstream>
#include <sstream>

#include <glad/glad.h>

void Shader::CheckCompileErrors(uint32_t shader_id, const std::string& type, const std::string& filepath) {
    int success;
    char infoLog[1024];
    if (type != "PROGRAM") {
        glGetShaderiv(shader_id, GL_COMPILE_STATUS, &success);
        if (!success) {
            glGetShaderInfoLog(shader_id, 1024, nullptr, infoLog);
            std::cout << "ERROR::SHADER::COMPILATION_ERROR of type: " << type << "\n" << infoLog << "filepath: "
                      << filepath;
        }
    } else {
        glGetProgramiv(shader_id, GL_LINK_STATUS, &success);
        if (!success) {
            glGetProgramInfoLog(shader_id, 1024, nullptr, infoLog);
            std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "filepath: "
                      << filepath;
        }
    }
}

Shader::Shader(const std::string& filepath) {
    // 1. divide vertex and fragment part
    enum class ShaderType {
        NONE = -1, VERTEX = 0, FRAGMENT = 1
    };
    std::ifstream stream(filepath);
    std::string line;
    std::stringstream ss[2];
    ShaderType type = ShaderType::NONE;
    while (getline(stream, line)) {
        if (line.find("#type") != std::string::npos) {
            if (line.find("vertex") != std::string::npos) {
                type = ShaderType::VERTEX;
            } else if (line.find("fragment") != std::string::npos) {
                type = ShaderType::FRAGMENT;
            } else {
                std::cout << "Unknown shader: " << line << '\n';
            }
        } else {
            if (type == ShaderType::NONE) {
                std::cout << "No shader defined" << '\n';
            } else {
                ss[(int) type] << line << '\n';
            }
        }
    }
    std::string vertex_code = ss[(int) ShaderType::VERTEX].str();
    std::string fragment_code = ss[(int) ShaderType::FRAGMENT].str();

    // 2. convert, compile and link sources to shader program
    // 2.1 convert string to c str
    const char *vertex_str = vertex_code.c_str();
    const char *fragment_str = fragment_code.c_str();
    // 2.2.1 vertex shader
    uint32_t vertex_id = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertex_id, 1, &vertex_str, nullptr);
    glCompileShader(vertex_id);
    CheckCompileErrors(vertex_id, "VERTEX", filepath);
    // 2.2.2 fragment shader
    uint32_t fragment_id = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment_id, 1, &fragment_str, nullptr);
    glCompileShader(fragment_id);
    CheckCompileErrors(fragment_id, "FRAGMENT", filepath);
    // 2.3 shader program
    id_ = glCreateProgram();
    glAttachShader(id_, vertex_id);
    glAttachShader(id_, fragment_id);
    glLinkProgram(id_);
    CheckCompileErrors(id_, "PROGRAM", filepath);
    // 2.4 delete the shaders as they're linked into program and no longer necessary
    glDeleteShader(vertex_id);
    glDeleteShader(fragment_id);
}

void Shader::Bind() const {
    glUseProgram(id_);
}

void Shader::SetInt(const std::string& name, int value) const {
    glUniform1i(glGetUniformLocation(id_, name.c_str()), value);
}

void Shader::SetIntArray(const std::string& name, int *values, int count) {
    glUniform1iv(glGetUniformLocation(id_, name.c_str()), count, values);
}

void Shader::SetFloat(const std::string& name, float value) const {
    glUniform1f(glGetUniformLocation(id_, name.c_str()), value);
}

void Shader::SetVec4(const std::string& name, const glm::vec4& vec) const {
    glUniform4f(glGetUniformLocation(id_, name.c_str()), vec.x, vec.y, vec.z, vec.w);
}

void Shader::SetVec3(const std::string& name, const glm::vec3& vec) const {
    glUniform3f(glGetUniformLocation(id_, name.c_str()), vec.x, vec.y, vec.z);
}

void Shader::SetMat4(const std::string& name, const glm::mat4& mat) const {
    glUniformMatrix4fv(glGetUniformLocation(id_, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}

Upvotes: 2

Views: 933

Answers (1)

Rabbid76
Rabbid76

Reputation: 210968

glTexImage2D with target GL_TEXTURE_2D_ARRAY generates a INVALID_ENUM error. Actually a GL_TEXTURE_2D_ARRAY are multiple GL_TEXTURE_2D textures. You have to specify the size of the 2D texture and the number of textures in the array. Therefore the texture needs to be specified with glTexImage3D. e.g.: for array size 1:

glTexImage2D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);

glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, w, h, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);

You also need to set GL_TEXTURE_MAG_FILTER because the initial value of GL_TEXTURE_MIN_FILTER is GL_NEAREST_MIPMAP_LINEAR. If you don't change it and don't create mipmaps, the texture will not be "complete" and will not be "shown".

glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

For more than one 2D texture, I suggest creating the 3D texture image for the desired number of 2D textures (noOfTextures)

glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, w, h, noOfTextures, 0, GL_RGBA, GL_UNSIGNED_BYTE, null);

and specify the individual 2D textures with glTexSubImage3D. You have to call glTexSubImage3D for each individual 2D image, the zoffset (textureIndex) is the index of the texture in the array and has to be different for each 2D texture.

glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, textureIndex, w, h, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels)

Of course, the individual 2D images must be of the same size.

Upvotes: 3

Related Questions