Black image when rendering texture on Quad

Good afternoon,

I know this is probably not the best way to word a question as it seems like “something is wrong” and I apologize ahead of time, but I have been going over this code for a while now and cannot seem to make any headway. I am using OpenGL ES version 3.2 with OpenGL EGL. I’m hoping it is something stupid simple that I am overlooking because I have been staring at the code too long :slight_smile:

I am trying to apply a texture to a quad geometry but all I get is a black PPM file image when I render. Maybe I am not correctly using textures? I just don’t know at this point, so if anyone could take a quick look at the sample code and let me know if I am doing something obviously wrong that would be greatly appreciated.

The vertex shader follows:

#version 430 core

layout (location = 0) in vec3 aPos;
layout (location = 1) out vec2 TexCoord;

void main()
{
     gl_Position = vec4(aPos, 1.0);
     TexCoord = (aPos.xy + vec2(1.0)) / 2.0;
}

The fragment shader follows:

#version 430 core

layout (location = 0) in vec2 TexCoord; 
layout (location = 1) out vec4 FragColor;

layout (binding = 0) uniform sampler2D uTexture;

void main()
{
    FragColor = texture(uTexture, TexCoord);
}

I will spare all the gory details of properly accessing EGL display and setting of context as well as creation of shader program, herein defined as shaderProgram, that is done before the code shown in the following C++ code snippet.

I know that the EGL and shader function calls are working as no errors via glGetProgramiv for linking or validation of shader program (I also was able to properly render the standard hello triangle from OpenGL tutorial with these operations).

Also, the value of width and height is 640 and 480, respectively.

 ...
    // Use the previously compiled shader program
    glUseProgram(shaderProgram);

    // Create a texture from a floating point array
    const int textureWidth = 512;
    const int textureHeight = 512;
    float* textureData = new float[textureWidth * textureHeight * 4];

    for (int i = 0; i < textureWidth * textureHeight * 4; ++i)
    {
        textureData[i] = i / 100000.0f;
    }

    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, textureWidth, textureHeight, 0, GL_RGBA, GL_FLOAT, textureData);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    delete[] textureData;

    // Create a frame buffer and attach the texture to it
    GLuint framebuffer;
    glGenFramebuffers(1, &framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);

    // Create a vertex buffer and index buffer for a quad
    float vertices[] = {
        -1.0f, -1.0f, 0.0f,
         1.0f, -1.0f, 0.0f,
         1.0f,  1.0f, 0.0f,
        -1.0f,  1.0f, 0.0f
    };

    GLfloat texCoords[] = {
        0.0f, 1.0f,
        1.0f, 1.0f,
        0.0f, 0.0f,
        1.0f, 0.0f
    };

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

    GLuint VBO, VAO, EBO, textureCoordLoc;

    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &textureCoordLoc);
    glGenBuffers(1, &VBO);
    glGenBuffers(1, &EBO);

    glBindVertexArray(VAO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

    glBindBuffer(GL_ARRAY_BUFFER, textureCoordLoc);
    glBufferData(GL_ARRAY_BUFFER, sizeof(texCoords), texCoords, GL_STATIC_DRAW);
    glVertexAttribPointer(textureCoordLoc, 2, GL_FLOAT, GL_FALSE, 0, texCoords);
    glEnableVertexAttribArray(textureCoordLoc);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    // Render the offscreen quad and apply the texture
    glViewport(0, 0, width, height);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

    glClear(GL_COLOR_BUFFER_BIT);

    glUseProgram(shaderProgram);
    glBindVertexArray(VAO);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

    std::vector<float> framebufferData(width * height * 4);
    glReadPixels(0, 0, width, height, GL_RGBA, GL_FLOAT, framebufferData.data());

    // The following method writes out PPM file and works with the "hello triangle" example of
    // OpenGL tutorial
    saveImage(out_file, framebufferData, width, height);

    // Render the quad to the default framebuffer
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    // Cleanup
    glDeleteTextures(1, &texture);
    glDeleteFramebuffers(1, &framebuffer);
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &EBO);
    glDeleteVertexArrays(1, &VAO);
...

Once again, I apologize for the ambiguous “something isn’t working” kind of question, but I have no absolutely idea why there is no image shown except black and am looking for a potential starting point if anything.

Thanks for your time and any help.

I haven’t looked closely at your code, but does it work when your fragment shader outputs a constant color?
What about when the fragment shader outputs the texture coordinate as color (this only really works for texture coordinates in [0, 1] and for 2D texture coordinates produces some sort of black to red/green gradient). Hopefully that can help you narrow down a bit where things go wrong.
Have you tried running the code under RenderDoc and inspected the state, e.g. if the texture is bound when you make your draw call(s).

1 Like

Thank you @carsten_neumann for the response.

No, unfortunately it doesn’t work for outputting a constant color, which is strange because when I used the fragment shader for the “hello triangle” example of the OpenGL tutorial (LearnOpenGL - Hello Triangle) it worked fine.

I have tried outputting the texture coordinate as a color, given my texture coordinates are in in [0,1] and for 2D texture coordinates - either option produced nothing but a black image.

I am hoping it is something simple in my code.

Ok, until that works it is not worth it looking at anything related to textures and you should first try to get a solid color quad to show up.
If I read it correctly the triangles for your quad are specified in clockwise order. Do you enable face culling (glEnable(GL_CULL_FACE))? Since the default front face (glFrontFace) is GL_CCW, i.e. counter-clockwise face culling would cause the triangles not to be rasterized because we are looking at the back faces (the clockwise faces).

Ooops, that was stupid of me, listing the indices in clockwise manner. I changed them to the following so that they are in counter-clockwise as follows:

GLuint indices[] = {
        0, 1, 3,
        1, 2, 3
    }

I also noticed that my UV coordinates are not as I wanted, so I changed them to the following:

GLfloat texCoords[] = {
        1.0f, 1.0f,
        1.0f, 0.0f,
        0.0f, 0.0f,
        0.0f, 1.0f
    };

I also modified the fragment shader to output an orange color:

#version 430 core

layout (location = 0) in vec2 TexCoord; 
layout (location = 1) out vec4 FragColor;

layout (binding = 0) uniform sampler2D uTexture;

void main()
{
    FragColor = texture(uTexture, TexCoord);
    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f); // TESTING output orange
}

To see if I can just output a solid color now. However none of my changes made a difference, still a black screen.

Apparently I also forgot to bind to the shader program uniform variable which is the texture in the fragment shader as well. I made the changes, and also moved the code around to (hopefully) be a little clearer:

...
    // Create a vertex buffer and index buffer for a quad
    float vertices[] = {
        -1.0f, -1.0f, 0.0f,
         1.0f, -1.0f, 0.0f,
         1.0f,  1.0f, 0.0f,
        -1.0f,  1.0f, 0.0f
    };

    GLfloat texCoords[] = {
        0.0f, 1.0f,
        0.0f, 0.0f,
        1.0f, 0.0f,
        1.0f, 1.0f
    };

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

    // Create a texture from a floating point array
    const int textureWidth = 512;
    const int textureHeight = 512;
    float* textureData = new float[textureWidth * textureHeight * 4];

    for(int i = 0; i < textureWidth * textureHeight * 4; ++i) {
        textureData[i] = i / 100000.0f;
    }

    // Generate the shader program
    RenderMethodParams params;
    params.vertex_shader_file_name   = "GlesTextureDisplay.vert";
    params.fragment_shader_file_name = "GlesTextureDisplay.frag";

    uint32_t vert_id = loadESSLShader(GL_VERTEX_SHADER, params.vertex_shader_file_name);
    uint32_t frag_id = loadESSLShader(GL_FRAGMENT_SHADER, params.fragment_shader_file_name);
    uint32_t shaderProgram = createShaderProgram(vert_id, frag_id);
    glUseProgram(shaderProgram);

    glUniform1i(glGetUniformLocation(shaderProgram, "uTexture"), 0);

    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, textureWidth, textureHeight, 0, GL_RGBA, GL_FLOAT, textureData);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    // Create a frame buffer and attach the texture to it
    GLuint framebuffer;
    glGenFramebuffers(1, &framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);

    GLuint VBO, VAO, EBO, textureCoordLoc;

    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &textureCoordLoc);
    glGenBuffers(1, &VBO);
    glGenBuffers(1, &EBO);

    glBindVertexArray(VAO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

    glBindBuffer(GL_ARRAY_BUFFER, textureCoordLoc);
    glBufferData(GL_ARRAY_BUFFER, sizeof(texCoords), texCoords, GL_STATIC_DRAW);
    glVertexAttribPointer(textureCoordLoc, 2, GL_FLOAT, GL_FALSE, 0, texCoords);
    glEnableVertexAttribArray(textureCoordLoc);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    // Render the offscreen quad and apply the texture
    glViewport(0, 0, width, height);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

    glClear(GL_COLOR_BUFFER_BIT);

    glUseProgram(shaderProgram);
    glBindVertexArray(VAO);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

    std::vector<float> framebufferData(width * height * 4);
    glReadPixels(0, 0, width, height, GL_RGBA, GL_FLOAT, framebufferData.data());
    saveImage(out_file, framebufferData, width, height);

    // Render the quad to the default framebuffer
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    // Cleanup
    delete[] textureData;
    glDeleteTextures(1, &texture);
    glDeleteFramebuffers(1, &framebuffer);
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &EBO);
    glDeleteVertexArrays(1, &VAO);
...

The vertex shader:

#version 320 es

layout(location = 0) in vec3 aPos;
layout(location = 1) out vec2 TexCoord;

void main()
{
    gl_Position = vec4(aPos, 1.0);
    TexCoord = (aPos.xy + vec2(1.0)) / vec2(2.0);
}

The fragment shader:

#version 320 es
precision mediump float;
precision highp int;

layout(binding = 0) uniform highp sampler2D uTexture;

layout(location = 1) out highp vec4 FragColor;
layout(location = 0) in highp vec2 TexCoord;

void main()
{
    FragColor = texture(uTexture, TexCoord);
}

Unfortunately, still a black screen :confused:

I fixed the problem :grinning: Thank you again @carsten_neumann for all the help along the way.

Turns out that the eglCreatePbufferSurface(...) function was not being properly called prior to all the C++ code that I posted. I made an incorrect assumption that I had done this successfully. Just one line of code was missing, the following not properly placed prior to the C++ code snippet.

...
EGLSurface eglSurface = eglCreatePbufferSurface(eglDisplay, eglConfig, pbufferAttribs);
...

NOTE to self: ALWAYS check assumptions first