Search code examples
c++qtopengltextures

Qt & OpenGL : render a 2D texture


I'm trying to render my first texture with Qt & OpenGL.

This is the code.

initializeGL function

void OpenGLWidget::initializeGL()
{
    initializeOpenGLFunctions();

    glEnable(GL_DEBUG_OUTPUT);
    glDebugMessageCallback(debugCallback, nullptr);

    program = genProgram("../06_HelloTexture/vert.glsl", "../06_HelloTexture/frag.glsl");
    glUseProgram(program);

    glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
    glEnable(GL_DEPTH_TEST);

    // vertices is std::vector<QVector3D>
    vertices = {
        {-0.5f, -0.5f, 0.0f}, {0.0f, 0.0f, 0.0f},
        {-0.5f, +0.5f, 0.0f}, {0.0f, 1.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},
    };

    // indices is std::vector<GLuint>
    indices = {
        0, 1, 2,
        1, 2, 3,
    };

    GLuint VAO, VBO, EBO;

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

    glBindVertexArray(VAO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, vertices.size()*sizeof(QVector3D), &vertices[0], GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size()*sizeof(GLuint), &indices[0], GL_STATIC_DRAW);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 2*sizeof(QVector3D), static_cast<void*>(nullptr));
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 2*sizeof(QVector3D), (void*)sizeof(QVector3D));
    glEnableVertexAttribArray(1);

    glBindBuffer(GL_ARRAY_BUFFER, 0);

    QImage image("../lightbulb-solid.svg");
    // QImage image("../test.png");
    qDebug() << image;
    // this outputs
    // QImage(QSize(352, 512),format=6,depth=32,devicePixelRatio=1,bytesPerLine=1408,sizeInBytes=720896)

    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, image.constBits());
    // glGenerateMipmap(GL_TEXTURE_2D);

    lightbulbSolidLocation = glGetUniformLocation(program, "lightbulbSolid");
    glUniform1i(lightbulbSolidLocation, 0);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture);
}

paintGL function

void OpenGLWidget::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(indices.size()), GL_UNSIGNED_INT, nullptr);
}

vertex shader

#version 450 core
layout (location = 0) in vec3 vertPosition;
layout (location = 1) in vec3 vertTexCoord;

out vec3 fragTexCoord;

void main()
{
    gl_Position = vec4(vertPosition, 1.0);
    fragTexCoord = vertTexCoord;
}

fragment shader

#version 450 core
in vec3 fragTexCoord;
out vec4 pixelColor;

uniform sampler2D lightbulbSolid;

void main()
{
    // pixelColor = vec4(fragColor, 1.0f);
    // pixelColor = texture(lightbulbSolid, vec2(fragTexCoord.x, fragTexCoord.y));

    vec4 temp = texture(lightbulbSolid, vec2(fragTexCoord.x, fragTexCoord.y));

    if (!all(equal(temp.xyz, vec3(0.0f))))
    {
        pixelColor = temp;
    }
    else
    {
        pixelColor = vec4(fragTexCoord, 1.0f);
    }
}

And this is the result

enter image description here

As you can see, all the colors generated by the texture function in the fragment shader are black, so the if statement choose the texture coordinate as color rather than the real texture color.

I tried to catch some error, but the callback function didn't find something wrong in the code. I also tried with another image (of PNG format rather than SVG), but the result is the same.


Solution

  • If you want to load a PNG file, then you can load it to a QImage directly,

    QImage image("../test.png");
    

    but if you want to render SVG file, then you have to use the QSvgRenderer and QPainter to paint the content to an QImage. You have to choose the format, the resolution and the background color of the target bitmap:

    e.g.:

    #include <Qtsvg/QSvgRenderer>
    

    QSvgRenderer renderer(QString("../lightbulb-solid.svg"));
    
    QImage image(512, 512, QImage::Format_RGBA8888);  // 512x512 RGBA
    image.fill(0x00ffffff);                           // white background
    
    QPainter painter(&image);
    renderer.render(&painter);
    
    const uchar *image_bits = image.constBits();
    int          width      = image.width();
    int          height     = image.height();
    

    Note, you have to link qt5svgd.lib (debug) respectively qt5svg.lib (release).


    The default minifying function parameter (GL_TEXTURE_MIN_FILTER) is GL_NEAREST_MIPMAP_LINEAR. See glTexParameteri

    This means either you have to change the GL_TEXTURE_MIN_FILTER parameter to GL_LINEAR

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
                 GL_RGBA, GL_UNSIGNED_BYTE, image_bits);
    

    or you have to create mipmaps

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
                 GL_RGBA, GL_UNSIGNED_BYTE, image_bits);
    
    glGenerateMipmap(GL_TEXTURE_2D);
    

    In the initializeGL method the texture is bound to texture unit 0, but it is not guaranteed, that this is still the current state in the paintGL method. You have to bind the texture in the paintGL method:

    void OpenGLWidget::paintGL()
    {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture);
        glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(indices.size()), GL_UNSIGNED_INT, nullptr);
    }