Search code examples
copengltextures

Textured Triangle Loads as Solid Color


I'm trying to render a textured triangle. I'm using STB image to load a png. The triangle is displayed as a single color for a host of different images that I've tried. The color seems to be an average of the total colors displayed in the image.

I've looked into many solutions for similar questions and have ensured (or so I think) that I've not fallen into the common 'gotchas': glEnable(GL_TEXTURE_2D) is called. My calls to glVertexAttribPoitners() are set up correctly. I've tried to tell OpenGl that I don't want to use mipmapping with glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR), and I've enabled the relevant vertex arrays via glEnableVertexAttribArray(). My shaders compile with no errors.

#include <practice/practice.h>
#include <test_dep/stb_image.h>


void
initRenderer_practice()
{
    const char* vertex_shader = GLSL(450 core,

                                     layout (location = 0) in vec2 vCoords;
                                     layout (location = 1) in vec2 tCoords;

                                     out vec2 ftCoords;

                                     void main()
                                     {
                                         gl_Position = vec4(vCoords.x, vCoords.y, 0.0, 1.0);
                                         ftCoords = tCoords;
                                     }

        );

    const char* fragment_shader = GLSL(450 core,

                                       uniform sampler2D texture_data;
                                       in vec2 ftCoords;
                                       out vec4 fragColor;

                                       void main()
                                       {
                                           fragColor = texture(texture_data, ftCoords.st);
                                       }
        );


    GLfloat vCoordsLocal[] =
        {
            //
            // POSITION COORDS
            //
            // bottom left
            -0.5, -0.5,

            // bottom right
            0.5, -0.5,

            // top center
            0.0, 0.5,

            //
            // TEXTURE COORDS
            //
            // left bottom
            0.0, 0.0,

            // right bottom
            1.0, 0.0,

            // center top
            0.5, 1.0,
        };

    if (!practice_target)
    {
        practice_target = (uGLRenderTarget*)malloc(sizeof(uGLRenderTarget));
        practice_target->shader_program = 0;
        practice_target->vertex_array_object = (GLuint) -1;
        practice_target->vertex_buffer_object = (GLuint) -1;
    }

    practice_target->shader_program = uGLCreateShaderProgram_vf(&vertex_shader,
                                                                &fragment_shader);
    assert(practice_target->shader_program);
    glUseProgram(practice_target->shader_program);
    glError;

    glGenVertexArrays(1, &practice_target->vertex_array_object);
    glBindVertexArray(practice_target->vertex_array_object);
    glError;

    glGenBuffers(1, &practice_target->vertex_buffer_object);
    glBindBuffer(GL_ARRAY_BUFFER, practice_target->vertex_buffer_object);
    glBufferData(GL_ARRAY_BUFFER,
                 sizeof(vCoordsLocal),
                 vCoordsLocal,
                 GL_STATIC_DRAW);
    glError;

    glGenTextures(1, &practice_target->texture_id);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, practice_target->texture_id);
    practice_target->shdr_texture_2d_location = glGetUniformLocation(practice_target->shader_program, "texture_data");
    glUniform1i(practice_target->shdr_texture_2d_location, 0);
    assert(practice_target->shdr_texture_2d_location != -1);
    glError;

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glError;


    stbi_set_flip_vertically_on_load(true);
    u8* texture_data = NULL;
    GLint width, height, channels = 0;
    texture_data = stbi_load("./assets/index.png", &width, &height, &channels, 0);
    if (texture_data)
    {
        /*
        void glTexImage2D(GLenum target,
                          GLint level,
                          GLint internalFormat,
                          GLsizei width,
                          GLsizei height,
                          GLint border,
                          GLenum format,
                          GLenum type,
                          const GLvoid * data);
        */
        glTexImage2D(GL_TEXTURE_2D,
                     0,
                     GL_RGB,
                     width,
                     height,
                     0,
                     GL_RGB,
                     GL_UNSIGNED_BYTE,
                     texture_data);
        glGenerateMipmap(GL_TEXTURE_2D);
        glError;

        printf("\n\n numchannels: %d\n\n", channels);
    }
    else
    {
        puts("[ debug ] Load texture failed\n");
        assert(0);
    }

    stbi_image_free(texture_data);

    /*
      void glVertexAttribPointer(GLuint index,
      GLint size,
      GLenum type,
      GLboolean normalized,
      GLsizei stride,
      const GLvoid * pointer);
    */

    // Vertex coordinate attribute
    glVertexAttribPointer(0,
                          2,
                          GL_FLOAT,
                          GL_FALSE,
                          0,
                          (void*) 0);
    glEnableVertexAttribArray(0);

    // Texture coordinate attribute
    glVertexAttribPointer(1,
                          2,
                          GL_FLOAT,
                          GL_FALSE,
                          0,
                          (void*) 6);
    glEnableVertexAttribArray(1);
    glError;

    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glUseProgram(0);
    glError;
}

void
render_practice()
{
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(practice_target->shader_program);

    glEnable(GL_TEXTURE_2D);
    glBindVertexArray(practice_target->vertex_array_object);
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, practice_target->texture_id);
    glDrawArrays(GL_TRIANGLES, 0, 3);

    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glUseProgram(0);
    glError;
}

The expected result is to have the texture mapped onto the triangle, rather than a single color.


Solution

  • The vertex attribute specification for the texture coordinates is misaligned.
    The memory layout of the attributes is

    x0 y0, x1, y1, x2, y2,   u0 v0, u1, v1, u2, v2
    

    So the first texture coordinate is 6th element in the buffer.

    But, the last parameter of glVertexAttribPointer is a byte offset into the buffer object's data store.
    This mean the offset has to be 6*sizeof(GLfloat) rather than 6

    glVertexAttribPointer(1,
                          2,
                          GL_FLOAT,
                          GL_FALSE,
                          0,
                          (void*)(6*sizeof(GLfloat)); // <-----
    

    I recommend to change the vertex attribute arrangement, to an extendable layout:

    x0 y0 u0 v0    x1 y1 u1 v1    x2 y2 u2 v2   ...
    

    and to use the corresponding vertex attribute specification:

    GLsizei stride     = (GLsizei)(4*sizeof(GLfloat)); // 4 because of (x, y, u ,v)
    void*   vertOffset = (void*)(0);                   // 0 because the vertex coords are 1st
    void*   texOffset  = (void*)(2*sizeof(GLfloat));   // 2 because u, v are after x, y
    
    // Vertex coordinate attribute
    glVertexAttribPointer(0,
                          2,
                          GL_FLOAT,
                          GL_FALSE,
                          stride,
                          vertOffset);
    glEnableVertexAttribArray(0);
    
    // Texture coordinate attribute
    glVertexAttribPointer(1,
                          2,
                          GL_FLOAT,
                          GL_FALSE,
                          stride,
                          texOffset;
    glEnableVertexAttribArray(1);