QTOpenGL development (1) Drawing plane graphics

Article directory

  • draw triangle
  • Anti-aliasing configuration
  • Draw points, lines, quadrilaterals, polygons
    • Draw a single point
    • Plot multiple points
    • Draw a straight line
    • Draw a quadrilateral
    • Draw polygon
  • Draw picture texture

There are many ways to implement OpenGL development in QT. The simplest and most direct way is to implement OpenGL drawing and rendering by subclassing QOpenGLWindow. QOpenGLWindow is the window used to display OpenGL and is no different from other standard windows in QT. We can combine it with other QT controls for use.

QT uses the QOpenGLContext class to save the context state of OpenGL to control the rendering and drawing process of OpenGL. All our drawing and rendering operations are for the current context, and switching between different states can be achieved by switching context. When rendering and drawing, you must set the corresponding context to the current context state before drawing.

When subclassing QOpenGLWindow, we mainly implement the following three interfaces:

//This function is called once before calling paintGL() and resizeGL(), and is responsible for initializing drawing resources and status values.
//This function sets the context activation to current, which means that there is no need to call the makeCurrent() function separately.
void QOpenGLWindow::initializeGL();

//paintGL() is equivalent to QWidget's paintEvent() function, which is responsible for drawing OpenGL graphics when it needs to be updated or redrawn.
//This function will also be called during the interface
void QOpenGLWindow::paintGL();

//Called when the window size changes. By implementing this function, we can
//Adjust the drawn image
void QOpenGLWindow::resizeGL();

After the OpenGL window is built, we can draw the rendered image. OpenGL provides a series of interfaces for drawing graphics. When developing cross-platform applications, it is quite cumbersome to deal with the differences in these interfaces on different platforms. In order to solve the cross-platform problem, QT encapsulates these drawing interfaces to facilitate cross-platform operations. In QT applications we access OpenGL functions through the QOpenGLFunctions class.

The QOpenGLFunctions class only provides some APIs of the OpenGL ES 2.0 standard. These interfaces can be directly called in most desktop systems and embedded systems that support the QT framework. However, these interfaces are only older interfaces and may not be very convenient to use. If you want to use some newer interfaces, you can also reference the fixed version of the OpenGL function library separately. For example, the QOpenGLFunctions_3_3_Core class contains the OpenGL3.3 version of the function interface. The advantage of using the new version of the function library class is that it has rich interfaces and is easy to call. The disadvantage is that it may support fewer platforms and may not be usable on some platforms. Therefore, developers need to make trade-offs based on their specific business scenarios.

In order to use the OpenGL drawing functions provided by QT in the application, we can inherit both the QOpenGLWindow class and the QOpenGLFunctions class when implementing a custom window. When developing OpenGL applications using this method, you must distinguish between the drawing interface provided by the QOpenGLFunctions function and the native OpenGL drawing interface, because if QOpenGLFunctions does not have a corresponding interface, it is very likely that you are calling the native interface in the application. Native interfaces may cause problems when cross-platform. Therefore, if your application has cross-platform requirements, be sure to call the encapsulation interface provided by QOpenGLFunctions. In order to solve this problem, we can also use the QOpenGLFunctions class as a private member variable of the window, and then access it through the member variable, so as to ensure that the drawing interface provided by QT is called.

Before calling the OpenGL function interface provided by QT, you must call the initializeOpenGLFunctions() function in the current OpenGL context to initialize the function interface. Generally, this initialization operation is performed in the QOpenGLWindow::initializeGL() interface.

Draw a triangle

Triangles are the basic primitives drawn by OpenGL. Complex primitives will be split into small triangles for rendering. Here is an example of drawing triangle primitives to illustrate how QT OpenGL implements custom drawing:

//simpleglwindow.h
#ifndef SIMPLEGLWINDOW_H
#define SIMPLEGLWINDOW_H
#include <QOpenGLWindow>
#include <QOpenGLFunctions_1_1>

//The version of OpenGLFunction used is 1.1
class SimpleGLWindow : public QOpenGLWindow, protected QOpenGLFunctions_1_1
{<!-- -->
public:
    SimpleGLWindow(QWindow *parent = 0);
protected:
    //Initialization operation
    void initializeGL();

    //draw function
    void paintGL();

protected:
    //window size change event
    void resizeGL(int w, int h);
};

#endif // SIMPLEGLWINDOW_H
//simpleglwindow.cpp
#include "simpleglwindow.h"
#include <QDebug>

SimpleGLWindow::SimpleGLWindow(QWindow *parent) :
    QOpenGLWindow(NoPartialUpdate, parent)
{<!-- -->}
void SimpleGLWindow::initializeGL()
{<!-- -->
    //Initialize OpenGL function
    if (!initializeOpenGLFunctions())
    {<!-- -->
        qDebug() << "init opengl functions failed";
    }

    //Set the default color when refreshing the display to RGB(255,255,255)
    glClearColor(1, 1, 1, 0);
}

void SimpleGLWindow::paintGL()
{<!-- -->
    //Clear the color cache
    glClear(GL_COLOR_BUFFER_BIT);

    //Keep the OpenGL viewport and window the same
    glViewport(0, 0, width(), height());

    //Start triangle drawing mode
    //The point coordinates here are not absolute values, but the ratio to the boundary. The value range is (-1,1)
    //The value 1 corresponds to the border, and -1 corresponds to the left border.
    //Color values are also proportional values, 1 corresponds to 255 in RGB, 0 corresponds to 0 in RGB
    glBegin(GL_TRIANGLES);
    {<!-- -->
        //Vertex 1 coordinates XYZ (0,1,0) color RGB (255,0,0)
        glColor3f(1, 0, 0);
        glVertex3f(0.0f, 1.0f, 0.0f);

        //Vertex 2 coordinates XYZ (1,-1,0) color RGB (0,255,0)
        glColor3f(0, 1, 0);
        glVertex3f(1.0f,-1.0f, 0.0f);

        //Vertex 3 coordinates XYZ (-1,-1,0) color RGB (0,0,255)
        glColor3f(0, 0, 1);
        glVertex3f(-1.0f,-1.0f, 0.0f);
    }
    //end drawing
    glEnd();
}

void SimpleGLWindow::resizeGL(int w, int h)
{<!-- -->
    Q_UNUSED(w);
    Q_UNUSED(h);
}

The display effect is as follows:

Anti-aliasing configuration

By default, OpenGL’s drawing is very aliased. We can enable OpenGL to draw with higher image quality by turning on multi-sampling configuration to achieve anti-aliasing effects. The corresponding configuration is as follows:

SimpleGLWindow::SimpleGLWindow(QWindow *parent) :
    QOpenGLWindow(NoPartialUpdate, parent)
{<!-- -->
    //Set the value of multi-sampling
    QSurfaceFormat fmt = format();
    fmt.setSamples(18);
    setFormat(fmt);
}

Theoretically, the higher the multisampling value, the better the image quality. However, the higher the multi-sampling value, the greater the burden on hardware performance. If the value exceeds the hardware threshold, the program will also fail. Therefore, developers should select the corresponding value based on the performance of their own hardware platform, and cannot blindly select a large value.
The multi-sampling drawing effect is as follows:

Draw points, lines, quadrilaterals, polygons

The following introduces how some other primitives are drawn in OpenGL.

Draw a single point

void SimpleGLWindow::paintGL()
{<!-- -->
    //Clear the color cache
    glClear(GL_COLOR_BUFFER_BIT);

    //Keep the OpenGL viewport and window the same
    glViewport(0, 0, width(), height());

    //In order to clearly display the default size of the modified point
    glPointSize(10.0f);

    //Start point drawing mode
    glBegin(GL_POINTS);
    {<!-- -->
        //Vertex coordinates XYZ(0,0,0) color RGB(0,0,255)
        //Display the blue dot in the center of the screen
        glColor3f(0, 0, 1);
        glVertex3f(0.0f, 0.0f, 0.0f);
    }
    //end drawing
    glEnd();
}

The display effect is as follows:

Draw multiple points

void SimpleGLWindow::paintGL()
{<!-- -->
    //Clear the color cache
    glClear(GL_COLOR_BUFFER_BIT);

    //Keep the OpenGL viewport and window the same
    glViewport(0, 0, width(), height());

    //In order to clearly display the default size of the modified point
    glPointSize(10.0f);

    //Start point drawing mode
    glBegin(GL_POINTS);
    {<!-- -->
        //Vertex coordinates XYZ(0,0,0) color RGB(0,0,255)
        glColor3f(0, 0, 1);
        glVertex3f(0.0f, 0.0f, 0.0f);

        //Vertex coordinates XYZ(0.5,0,0) color RGB(255,0,0)
        glColor3f(1, 0, 0);
        glVertex3f(0.5f, 0.0f, 0.0f);

        //Vertex coordinates XYZ(0,0.5,0) color RGB(0,255,0)
        glColor3f(0, 1, 0);
        glVertex3f(0.0f, 0.5f, 0.0f);

        //Vertex coordinates XYZ (-0.5,0,0) color RGB (0,255,255)
        glColor3f(0, 1, 1);
        glVertex3f(-0.5f, 0.0f, 0.0f);

    }
    //end drawing
    glEnd();
}

The display effect is as follows:

Draw a straight line

void SimpleGLWindow::paintGL()
{<!-- -->
    //Clear the color cache
    glClear(GL_COLOR_BUFFER_BIT);

    //Keep the OpenGL viewport and window the same
    glViewport(0, 0, width(), height());
    
    //Start straight line drawing mode
    glBegin(GL_LINES);
    {<!-- -->
        //The starting point and end point of straight line 1
        //Start point coordinates XYZ(1,0,0) color RGB(255,0,0)
        glColor3f(1, 0, 0);
        glVertex3f(1.0f, 0.0f, 0.0f);

        //End point coordinates XYZ (-1,0,0) color RGB (0,255,0)
        glColor3f(0, 1, 0);
        glVertex3f( -1.0f, 0.0f, 0.0f);

        //The starting point and end point of straight line 2
        //Start point coordinates XYZ(0,-1,0) color RGB(255,0,0)
        glColor3f(1, 0, 0);
        glVertex3f(0.0f, -1.0f, 0.0f);

        //End point coordinates XYZ (0,1,0) color RGB (0,255,0)
        glColor3f(0, 1, 0);
        glVertex3f(0.0f, 1.0f, 0.0f);

    }
    //end drawing
    glEnd();
}

The display effect is as follows:

Draw a quadrilateral

When drawing a quadrilateral, you must pay attention to the order of drawing points in the program. The same four points may appear in different quadrilaterals if they are drawn in a different order. So be sure to pay attention to the order in which the points are drawn.

void SimpleGLWindow::paintGL()
{<!-- -->
    //Clear the color cache
    glClear(GL_COLOR_BUFFER_BIT);

    //Keep the OpenGL viewport and window the same
    glViewport(0, 0, width(), height());
    
    //Start quadrilateral drawing mode
    glBegin(GL_QUADS);
    {<!-- -->
        //Vertex 1 coordinates XYZ (1,0,0) color RGB (255,0,0)
        glColor3f(1, 0, 0);
        glVertex3f(1.0f, 0.0f, 0.0f);

        //Vertex 2 coordinates XYZ(0,1,0) color RGB(0,255,0)
        glColor3f(0, 1, 0);
        glVertex3f(0.0f, 1.0f, 0.0f);

        //Vertex 3 coordinates XYZ (-1,0,0) color RGB (0,255,0)
        glColor3f(0, 1, 0);
        glVertex3f( -1.0f, 0.0f, 0.0f);

        //Vertex 4 label XYZ(0,-1,0) color RGB(255,0,0)
        glColor3f(1, 0, 0);
        glVertex3f(0.0f, -1.0f, 0.0f);
    }
    //end drawing
    glEnd();
}

The display effect is as shown below:

Draw polygon

When drawing polygons, you should also pay attention to the order of drawing points, otherwise the result will be greatly affected.

void SimpleGLWindow::paintGL()
{<!-- -->
    //Clear the color cache
    glClear(GL_COLOR_BUFFER_BIT);

    //Keep the OpenGL viewport and window the same
    glViewport(0, 0, width(), height());

    //Start polygon drawing mode
    glBegin(GL_POLYGON);
    {<!-- -->
        //Vertex 1 coordinates XYZ(-0.5,0,0)
        //The color is RGB(255,0,0)
        glColor3f(1, 0, 0);
        glVertex3f(-0.5f, 0.0f, 0.0f);

        //Vertex 2 coordinates XYZ(-0.25,0.5,0)
        glVertex3f(-0.25f, 0.5f, 0.0f);

        //Vertex 3 coordinates XYZ(0.25,0.5,0)
        glVertex3f(0.25f, 0.5f, 0.0f);

        //Vertex 4 is labeled XYZ(0.5,0,0)
        glVertex3f(0.5f, 0.0f, 0.0f);

        //Vertex 5 is labeled XYZ(0.25,-0.5,0)
        glVertex3f(0.25f, -0.5f, 0.0f);

        //Vertex 6 is labeled XYZ(-0.25,-0.5,0)
        glVertex3f(-0.25f, -0.5f, 0.0f);
    }
    //end drawing
    glEnd();
}

The display effect is as shown below:

Draw picture texture

Picture texture is to paste a picture onto a specific graphic according to a certain size correspondence. Here we paste an image onto a rectangular primitive. Before drawing the texture, we need to initialize the texture first. The corresponding implementation is as follows:

void SimpleGLWindow::initialTexture()
{<!-- -->
    //Initialize pictures and textures
    m_texture_image = new QImage(":/background.jpg");
    
    //The y-axis of the OpenGL coordinate system is in the opposite direction to the y-axis of the picture coordinate system. Mirroring operation is required to straighten the picture.
    m_texture = new QOpenGLTexture(m_texture_image->mirrored());
    
    //Add filters when zooming in and out
    m_texture->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear);
    m_texture->setMagnificationFilter(QOpenGLTexture::Linear);
}

After adding the initialization operation, we call the texture initialization operation in the OpenGL initialization function.

void SimpleGLWindow::initializeGL()
{<!-- -->
    //Initialize OpenGL function
    if (!initializeOpenGLFunctions())
    {<!-- -->
        qDebug() << "init opengl functions failed";
    }
    //Initialize texture
    initialTexture();
    //Set the default color when refreshing the display to RGB(255,255,255)
    glClearColor(1, 1, 1, 0);
}

After completing the initialization of the texture, we can draw the texture image in the corresponding function. The drawing operation looks like this:

void SimpleGLWindow::paintGL()
{<!-- -->
    //Clear the color cache
    glClear(GL_COLOR_BUFFER_BIT);
    //Keep the OpenGL viewport and window the same
    glViewport(0, 0, width(), height());
    //Reset transformation matrix
    glLoadIdentity();
    //Bind texture
    if(m_texture)
    {<!-- -->
        m_texture->bind();
    }
    //Start texture
    glEnable(GL_TEXTURE_2D);
    //Start rectangle drawing mode
    glBegin(GL_QUADS);
    {<!-- -->
        //The four vertices of the rectangle correspond to the four vertices of the picture.
        //Vertex 1 coordinates XYZ (1,1,0) texture coordinates (1,1)
        glTexCoord2d(1.0f, 1.0f);
        glVertex3f(1.0f, 1.0f, 0.0f);

        //Vertex 2 coordinates XYZ (-1,1,0) texture coordinates (0,1)
        glTexCoord2d(0.0f, 1.0f);
        glVertex3f(-1.0f, 1.0f, 0.0f);

        //Vertex 3 coordinates XYZ (-1,-1,0) texture coordinates (0,0)
        glTexCoord2d(0.0f, 0.0f);
        glVertex3f( -1.0f, -1.0f, 0.0f);

        //Vertex 4 marked XYZ (1,-1,0) texture coordinates (1,0)
        glTexCoord2d(1.0f, 0.0f);
        glVertex3f(1.0f, -1.0f, 0.0f);
    }
    //end drawing
    glEnd();
    //Close texture
    glDisable(GL_TEXTURE_2D);

}

The four vertices of the drawn rectangle correspond to the four vertices of the image texture, so the texture can completely tile the entire quadrilateral. The corresponding display effect is as shown in the figure below:

The complete code for texture drawing is as follows:

//simpleglwindow.h
#ifndef SIMPLEGLWINDOW_H
#define SIMPLEGLWINDOW_H

#include <QOpenGLWindow>
#include <QOpenGLFunctions_1_1>
#include <QOpenGLTexture>

//The version of OpenGLFunction used is 1.1
class SimpleGLWindow : public QOpenGLWindow, protected QOpenGLFunctions_1_1
{<!-- -->
public:
    SimpleGLWindow(QWindow *parent = 0);
protected:
    //Initialization operation
    void initializeGL();

    //draw function
    void paintGL();

protected:
    //window size change event
    void resizeGL(int w, int h);

private:
    void initialTexture();
    //OpenGL texture
    QOpenGLTexture* m_texture = nullptr;
    //Texture image
    QImage* m_texture_image = nullptr;
};

#endif // SIMPLEGLWINDOW_H
//simpleglwindow.cpp
#include "simpleglwindow.h"
#include <QDebug>

SimpleGLWindow::SimpleGLWindow(QWindow *parent) :
    QOpenGLWindow(NoPartialUpdate, parent)
{<!-- -->
    //Set the value of multi-sampling
    QSurfaceFormat fmt = format();
    fmt.setSamples(25);
    setFormat(fmt);
}

void SimpleGLWindow::initializeGL()
{<!-- -->
    //Initialize OpenGL function
    if (!initializeOpenGLFunctions())
    {<!-- -->
        qDebug() << "init opengl functions failed";
    }
    initialTexture();
    //Set the default color when refreshing the display to RGB(255,255,255)
    glClearColor(1, 1, 1, 0);
}

void SimpleGLWindow::paintGL()
{<!-- -->
    //Clear the color cache
    glClear(GL_COLOR_BUFFER_BIT);
    //Keep the OpenGL viewport and window the same
    glViewport(0, 0, width(), height());
    //Reset transformation matrix
    glLoadIdentity();

    if(m_texture)
    {<!-- -->
        m_texture->bind();
    }
    //Start texture
    glEnable(GL_TEXTURE_2D);
    //Start rectangle drawing mode
    glBegin(GL_QUADS);
    {<!-- -->
        //Vertex 1 coordinates XYZ (1,1,0) texture coordinates (1,1)
        glTexCoord2d(1.0f, 1.0f);
        glVertex3f(1.0f, 1.0f, 0.0f);

        //Vertex 2 coordinates XYZ (-1,1,0) texture coordinates (0,1)
        glTexCoord2d(0.0f, 1.0f);
        glVertex3f(-1.0f, 1.0f, 0.0f);

        //Vertex 3 coordinates XYZ (-1,-1,0) texture coordinates (0,0)
        glTexCoord2d(0.0f, 0.0f);
        glVertex3f( -1.0f, -1.0f, 0.0f);

        //Vertex 4 marked XYZ (1,0,0) texture coordinates (1,0)
        glTexCoord2d(1.0f, 0.0f);
        glVertex3f(1.0f, -1.0f, 0.0f);
    }
    //end drawing
    glEnd();
    //Close texture
    glDisable(GL_TEXTURE_2D);

}

void SimpleGLWindow::resizeGL(int w, int h)
{<!-- -->
    Q_UNUSED(w);
    Q_UNUSED(h);
}

void SimpleGLWindow::initialTexture()
{<!-- -->
    //Initialize pictures and textures
    m_texture_image = new QImage(":/background.jpg");
    m_texture = new QOpenGLTexture(m_texture_image->mirrored());

    //Add filters when zooming in and out
    m_texture->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear);
    m_texture->setMagnificationFilter(QOpenGLTexture::Linear);
}