2
votes

I am trying to use a 2D texture array as a texture atlas. I have the following code to render a single square. The square renders if I replace the texture lookup in the fragment shader with a simple colour output, and it renders one of the textures properly if I use and set up a simple 2D texture instead of a 2d Texture Array. However, when trying to create and use the 2d Texture array, I simply get a black screen. What gives?

//Shader Sources
const GLchar* vertexsource =
    "#version 330\n"
    "in vec2 position;"
    "in vec2 texcoord;"
    "out vec2 f_texcoord;"
    "void main() {"
    "   gl_Position = vec4(position, 0.0, 1.0);"
    "   f_texcoord = texcoord;"
    "}";
const GLchar* fragmentsource =
    "#version 330\n"
    "in vec2 f_texcoord;"
    "out vec4 color;"
    "uniform float time;"
    "uniform sampler2DArray sample;"
    "void main() {"
    "   float layer = step(sin(time), 0.0);"
    "   color = texture(sample, vec3(f_texcoord, 0.0));\n"
    "   //color = vec4(1.0,1.0,1.0,1.0);\n"
    "}";

//Vertex Data
GLfloat vertices[] = {
    //Verts         Texture verts
    -0.5f, 0.5f,    0.0f, 0.0f,
    0.5f, 0.5f,     1.0f, 0.0f,
    0.5f, -0.5f,    1.0f, 1.0f,
    -0.5f, -0.5f,   0.0f, 1.0f
};

GLuint elements[] = {
    0, 1, 2,
    2, 3, 0
};

Shaders::SetLogger(log_);
VertexShader vertexshader("D3 Vertex Shader", vertexsource);
FragmentShader fragshader("D3 Frag Shader", fragmentsource);
program = new ShaderProgram("D3 Shader Program");
program->AttachShader(vertexshader);
program->AttachShader(fragshader);
program->LinkProgram();
glUseProgram(program->glIdentifier());

GLint posattrib = program->GetAttributeLocation("position");
GLint texattrib = program->GetAttributeLocation("texcoord");
//Dont actually need this because only one Texture unit in use
GLint coloruniform = program->GetUniformLocation("sample");

GLuint vbo;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

GLuint ebo;
glGenBuffers(1, &ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(elements), elements, GL_STATIC_DRAW);

glEnableVertexAttribArray(posattrib);
glVertexAttribPointer(posattrib, 2, GL_FLOAT, GL_FALSE,
                      4 * sizeof(GLfloat),
                      (GLvoid*)0);

glEnableVertexAttribArray(texattrib);
glVertexAttribPointer(texattrib, 2, GL_FLOAT, GL_FALSE,
                      4 * sizeof(GLfloat),
                      (GLvoid*)(2 * sizeof(GLfloat)));

//glUniform1i(coloruniform, 0);

int width, height;
unsigned char* image = SOIL_load_image("sample.png", &width, &height,
                                       0, SOIL_LOAD_RGBA);

int width2, height2;
unsigned char* image2 = SOIL_load_image("sample2.png", &width2, &height2,
                                       0, SOIL_LOAD_RGBA);
errorstream << glGetError() << " ";

glActiveTexture(GL_TEXTURE0);
GLuint texture;
glGenTextures(1, &texture);
errorstream << glGetError() << " ";
glBindTexture(GL_TEXTURE_2D_ARRAY, texture);    
errorstream << glGetError() << " ";

glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 
             width, height, 2,
             0, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)0);
errorstream << glGetError() << " ";
log_->Debug("Requested memory for texture");


glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 
                0, 0, 0, 
                width, height, 1,
                GL_RGBA, GL_UNSIGNED_BYTE, image);
errorstream << glGetError() << " ";
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 
                0, 0, 1, 
                width2, height2, 1, 
                GL_RGBA, GL_UNSIGNED_BYTE, image2);
errorstream << glGetError() << " ";


glUniform1i(coloruniform, 0);
errorstream << glGetError() << " ";
log_->Debug(errorstream.str());

And the actual drawing code:

GLint timeuniform = program->GetUniformLocation("time");
GLint sampleuniform = program->GetUniformLocation("sample");
glActiveTexture(GL_TEXTURE0);
glUniform1f(timeuniform, frametimer_.RunningTime().asSeconds());
glUniform1i(sampleuniform, 0);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
window_->display();

Edit: Edited in suggested changes suggested by jozxyqk

1

1 Answers

3
votes

[EDIT] Possible causes:

  • The texture/sampler isn't bound correctly (see below).

  • The texture is incomplete (also see this).

    The common issue is GL expects mipmaps by default (and most tutorials/example code start without mipmaps). A simple fix is:
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    For details, see GL4.5 specs, sec 8.17 "Texture Completeness ".

    A texture is said to be complete if all the texture images and texture parameters required to utilize the texture for texture application are consistently defined.

    ...

    Using the preceding definitions, a texture is complete unless an of the following conditions hold true:

    • The minification filter requires a mipmap (is neither NEAREST nor LINEAR), and the texture is not mipmap complete.
  • The texture coordinates are wrong (e.g. all zero and the first texel happens to be black). Easily verifiable by using the coords for pixel colour.

  • The above is working and the data in the image is just black.

To bind a texture,

  • Call glActiveTexture, with GL_TEXTURE0 + texIndex

    This isn't needed during setup, but selects the texture unit to bind to before drawing.

    https://www.opengl.org/sdk/docs/man/docbook4/xhtml/glActiveTexture.xml

  • Call glBindTexture

    This should be after glActiveTexture (a related post on opengl.org)

  • Set the sampler uniform glUniform1i(samplerLocation, texIndex) ← the main issue, I think

    texIndex is 0 for GL_TEXTURE0, 1 for GL_TEXTURE1 etc. Using GL_TEXTURE0 + texIndex is a handy shortcut.

    Your code uses GL_TEXTURE0 instead of the index, which is 0x84C0 or 33984.