Shader does not render texture with OpenGL ES version 3.2 and OpenGL EGL

Good afternoon,

I have been working with OpenGL ES version 3.2 and OpenGL EGL to try and render some simple geometries and have been successful. Now I am trying to move to some more advance feature(s), the use of shaders to render a 2D texture.

However, I have only been able to get an image file that is black (my clear color value) when I try and use a texture that I am attaching to the framebuffer. There is a lot of code so I am posting what I hope are the critical points where it may be failing.

I have no idea what is going on with this and any help/hints would be greatly appreciated.

The fragment shader follows:

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

layout(binding = 0) uniform highp sampler2D uTexture;

layout(location = 0) out highp vec4 color;
layout(location = 0) in highp vec2 vTexcoord;

void main()
{
    color = texture(uTexture, vTexcoord);
}

The vertex shader follows:

#version 320 es

layout(location = 0) in vec3 position;
layout(location = 0) out vec2 vTexcoord;
layout(location = 1) in vec2 texcoord;

void main()
{
    gl_Position = vec4(position, 1.0);
    vTexcoord = texcoord;
}

The relevant portion of the C++ code that is called after the successful setting of EGL and GLES preliminaries such as setting context, etc., follows:

    // Where shader program has already been successfully compiled
    // and program defines the uint32_t identifier of this

    constexpr int32_t WIDTH  = 480;
    constexpr int32_t HEIGHT = 620;

    GLfloat textureData[4 * WIDTH * HEIGHT];
    for (int32_t y = 0; y < HEIGHT; y++) {
        for (int32_t x = 0; x < WIDTH; x++) {
            int32_t index = 4 * (y * WIDTH + x);
            float value = static_cast<float>(x) / WIDTH;
            textureData[index]     = value;    // red component
            textureData[index + 1] = value;    // green component
            textureData[index + 2] = value;    // blue component
            textureData[index + 3] = 1.0f;     // alpha component
        }
    }

    GLfloat 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,
    };

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

    ...

    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, WIDTH, HEIGHT, 0, GL_RGBA, GL_FLOAT, textureData);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    // Set uniform for texture
    GLint textureLocation = glGetUniformLocation(program, "uTexture");
    glUniform1i(textureLocation, 0);

    // Create and bind vertex buffer
    GLuint vertexBuffer;
    glGenBuffers(1, &vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // Create and bind index buffer
    GLuint indexBuffer;
    glGenBuffers(1, &indexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    // Set up vertex attribute pointers
    GLint positionLocation = glGetAttribLocation(program, "position");
    glEnableVertexAttribArray(positionLocation);
    glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), 0);

    GLint texcoordLocation = glGetAttribLocation(program, "texcoord");
    glEnableVertexAttribArray(texcoordLocation);
    glVertexAttribPointer(texcoordLocation, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (void*)(3 * sizeof(GLfloat)));

    // Clear the screen
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // Use the shader program
    glUseProgram(program);

    // Bind the texture
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture);

    // Draw the quad
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);

    // Read from framebuffer as floating point values
    std::vector<float> framebufferData(WIDTH * HEIGHT * 4);
    glReadPixels(0, 0, WIDTH, HEIGHT, GL_RGBA, GL_FLOAT, framebufferData.data());

    // Write the framebuffer to a ppm file
    std::ofstream ppmFile(out_file.c_str(), std::ios::out | std::ios::binary);
    ppmFile << "P6\n" << WIDTH << " " << HEIGHT << "\n255\n";
    for (size_t i = 0; i < WIDTH * HEIGHT * 4; i += 4) {
        unsigned char r = static_cast<unsigned char>(std::min(framebufferData[i] * 255.0f, 255.0f));
        unsigned char g = static_cast<unsigned char>(std::min(framebufferData[i + 1] * 255.0f, 255.0f));
        unsigned char b = static_cast<unsigned char>(std::min(framebufferData[i + 2] * 255.0f, 255.0f));
        ppmFile << r << g << b;
    }

    // Clean up
    glDeleteTextures(1, &texture);
    glDeleteBuffers(1, &vertexBuffer);
    glDeleteBuffers(1, &indexBuffer);
    glDeleteProgram(program);

The textureData array should define a gradient from black to white, however the output ppm file is a solid black rectangle.

Apologies for the long message and if I am unclear in my question, but this has got me stumped and I am hoping one of you GLES experts will know where I am messing up.

Thanks in advance for any help.

In the code you’ve posted you are not using any framebuffer objects. You are drawing textured triangles.

Your vertices do not have UV coordinates, each vertex should be a 5-tuple instead of a triple of floats - at least that is what your glVertexAttribPointer call later on say. As written some values will be read from past the end of your vertex buffer.

Thank you @carsten_neumann for the reply.

I will add the framebuffer as well as adding the UV coords to the vertex so that there is a 5-tuple of triple floats.

Question. Is it possible to add these UV coords to a separate buffer or is it always the case that texture coords need to be appended to vertices resulting in a 5-tuple of triple floats?

You don’t have to use a framebuffer object (only if you want to render to something else than the default framebuffer, aka the application window). I was just trying to point out that the way you had phrased things made it sound like you were doing something that isn’t reflected in the code you posted.

Vertex attributes can be stored in multiple buffers or different regions of one buffer or interleaved in one buffer. Any layout that you can describe with glVertexAttribPointer really. When using different buffers you change the buffer object bound to the GL_ARRAY_BUFFER binding point before calling glVertexAttribPointer, which captures the bound buffer object along with the type, offset, stride information from its arguments.

Sorry about taking so long to get back to this question. I have been very busy lately.

I added the changes, minus the framebuffer object. The C++ code snippet follows:

// Where shader program has already been successfully compiled
// and program defines the uint32_t identifier of this

constexpr int32_t WIDTH  = 501;
constexpr int32_t HEIGHT = 501;

constexpr int32_t TEX_WIDTH  = 23;
constexpr int32_t TEX_HEIGHT = 73;

// Defines texture data as vertical gradient from black to white
GLfloat textureData[4 * TEX_WIDTH * TEX_HEIGHT];
for (int32_t y = 0; y < TEX_HEIGHT; y++) {
    for (int32_t x = 0; x < TEX_WIDTH; x++) {
        int32_t index = 4 * (y * TEX_WIDTH + x);
        float value = static_cast<float>(x) / TEX_WIDTH;
        textureData[index]     = value;    // red component
        textureData[index + 1] = value;    // green component
        textureData[index + 2] = value;    // blue component
        textureData[index + 3] = 1.0f;     // alpha component
    }
}

GLfloat 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,
};

// Added UV coordinates
float texCoords[] = {
    0.0f, 0.0f, // Bottom left
    1.0f, 0.0f, // Bottom right
    1.0f, 1.0f, // Top right
    0.0f, 1.0f  // Top left
};

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

...

GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, TEX_WIDTH, TEX_HEIGHT, 0, GL_RGBA, GL_FLOAT, textureData);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

// Set uniform for texture
GLint textureLocation = glGetUniformLocation(program, "uTexture");
glUniform1i(textureLocation, 0);

// Create and bind vertex buffer
GLuint vertexBuffer;
glGenBuffers(1, &vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

// Create and bind index buffer
GLuint indexBuffer;
glGenBuffers(1, &indexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

// Bind texture coordinate buffer object and load data
GLuint texBuffer;
glGenBuffers(1, &texBuffer);
glBindBuffer(GL_ARRAY_BUFFER, texBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(texCoords), texCoords, GL_STATIC_DRAW);

// Set up vertex attribute pointers
GLint positionLocation = glGetAttribLocation(program, "position");
glEnableVertexAttribArray(positionLocation);
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);

GLint texcoordLocation = glGetAttribLocation(program, "texcoord");
glEnableVertexAttribArray(texcoordLocation);
glVertexAttribPointer(texcoordLocation, 2, GL_FLOAT, GL_FALSE, 0, 0);

glViewport(0, 0, WIDTH, HEIGHT);

// Clear the screen
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);

// Use the shader program
glUseProgram(program);

// Bind the texture
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);

// Draw the quad
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);

// Read from framebuffer as floating point values
std::vector<float> framebufferData(WIDTH * HEIGHT * 4);
glReadPixels(0, 0, WIDTH, HEIGHT, GL_RGBA, GL_FLOAT, framebufferData.data());

// Write the framebuffer to a ppm file
std::ofstream ppmFile(out_file.c_str(), std::ios::out | std::ios::binary);
ppmFile << "P6\n" << WIDTH << " " << HEIGHT << "\n255\n";
for (size_t i = 0; i < WIDTH * HEIGHT * 4; i += 4) {
    unsigned char r = static_cast<unsigned char>(std::min(framebufferData[i] * 255.0f, 255.0f));
    unsigned char g = static_cast<unsigned char>(std::min(framebufferData[i + 1] * 255.0f, 255.0f));
    unsigned char b = static_cast<unsigned char>(std::min(framebufferData[i + 2] * 255.0f, 255.0f));
    ppmFile << r << g << b;
}

// Clean up
glDeleteTextures(1, &texture);
glDeleteBuffers(1, &vertexBuffer);
glDeleteBuffers(1, &indexBuffer);
glDeleteProgram(program);

The texture displays the gradient in the top left corner but not across the entire output image file and I would like it to display across the entire view. Also the texture doesn’t appear to perform any actual texture sampling.

Any ideas/hints would be greatly appreciated.

Thanks

Your code currently has the buffer containing the texture coordinates bound for both calls to glVertexAttributePtr, so OpenGL will happily read positions and texture coordinates from the buffer you are storing the latter in.

Going forward, please consider working on you skills to debug problems; it is a critical skill for any sort of software development and finding and understanding the issue behind a strange bug can be very rewarding. For graphics problems it is often helpful when running into a problem to simplify the program until the problem goes away - e.g. here to remove the use of texture coordinates and only use positions (with a fragment shader that output a solid color). Since that would still not brought you back to where the same area as before was drawn it could have led you to look closely at how you specify your positions.
If you are still stuck include a clear description of what you have done to isolate the problem in your post, that way others can focus on things you haven’t tried yet. That also helps to distinguish your post from the somewhat dreaded “It doesn’t work.” followed by a wall of code. Those posts often don’t result in helpful responses :wink:

Thank you for the advice regarding future skills in debugging. I will try and keep those in mind as it is good advice.