Reputation: 1965
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
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