OpenGL learning essay - January 22, 2022

Posted by soupy127 on Wed, 26 Jan 2022 18:13:46 +0100

Last time, we drew a 30 pixel point with vertex shader and fragment shader. This time, we mainly briefly introduced the functions of each shader, the template for detecting OpenGL and GLSL errors, and the template for reading GLSL source code from the file. Finally, we drew a simple two-dimensional animation.

1, Various shader functions

Vertex shader: all vertex data will be passed into the vertex shader, and the vertices will be processed one by one, that is, the vertex shader will execute once for each vertex. For a large complex scale with many vertices, vertex shaders perform hundreds or even tens of thousands of times in parallel. Vertex shaders process only one vertex at a time.

Surface subdivision shader: it is used to generate a large number of triangles, usually in the form of mesh. It also provides some tools that can manipulate these triangles in various ways. Surface subdivision shaders work well when many vertices are needed on a simple shape.

Geometric shaders: vertex shaders give programmers the ability to operate one vertex at a time, while geometric shaders give programmers the ability to process one entity at a time (the most commonly used entity is a triangle). When the geometric shading stage is reached, the pipeline must have completed the process of combining vertices into triangles (primitive assembly). Next, the geometric shader will allow the programmer to access all vertices of each triangle at the same time. Processing by primitives has many uses, such as deforming primitives and deleting primitives. At the same time, geometric shaders also provide a method to generate additional primitives, and geometric shaders can also add surface textures to objects.

Rasterization: all the points, triangles and colors in the 3D model should be displayed on a 2D display. This 2D screen consists of a raster (matrix pixel array). When a 3D object is rasterized, OpenGL converts the primitives (usually triangles) in the object into fragments. Clips have information about pixels. The rasterization process determines where all pixels of the triangle determined by the three vertices need to be drawn.

Clip shaders: clip shaders are used to assign colors to rasterized pixels. Clip shaders also provide other ways to calculate color, such as determining the output color based on pixel position. The clip shader operates one pixel at a time.

2, Detect OpenGL and GLSL errors

The process of compiling and running GLSL code is different from that of ordinary code. GLSL compilation always occurs when C + + is running, and GLSL runs on GPU. Therefore, the operating system can not always catch the error of OpenGL runtime (GLSL error will not cause C + + program crash). At this time, we usually need to print out the log about GLSL, where glGetShaderiv() and glGetProgramiv() are used to provide information about the compiled GLSL shaders and programs. Three modules for capturing and displaying GLSL errors are provided below.

checkOpenGLError: check the OpenGL error flag, that is, whether an OpenGL error occurs. It can be used to detect both GLSL compilation errors and OpenGL runtime errors.

paintShaderLog: displays the OpenGL log content when GLSL compilation fails.

paintProgramLog: displays the OpenGL log content when the GLSL link fails.

void printShaderLog(GLuint shader)
    int len = 0;
    int chWrittn = 0;
    char *log;
    if(len > 0){
        log = (char*)malloc(len);
        cout<<"shader Info Log:"<<log<<endl;

void printProgramLog(int prog)
    int len = 0;
    int chWrittn = 0;
    char *log;
    if(len > 0){
        log = (char*)malloc(len);
        cout<<"Program Info Log:"<<log<<endl;


bool checkOpenGLError()
    bool foundError = false;
    int glErr = glGetError();
    while(glErr != GL_NO_ERROR){
        foundError = true;
        glErr = glGetError();
    return foundError;


Application examples are as follows:

//Catch errors when compiling shaders
glCompileShader(vShader);//Compiling shaders
glGetShaderiv(vShader,GL_COMPILE_STATUS,&vertCompiled);//GLint vertCompiled
if(vertComplied != 1){
    cout<<"vertex compilation failed:"<<endl;

//Shader error while capturing links
glGetProgramiv(vfProgram,GL_LINK_STATUS,&linked);//GLint linked
if(linked != 1){
    cout<<"linking failed"<<endl;

3, Read GLSL source code from file

When the program becomes complex, it is not practical to store the GLSL shader code inline in a string. You should put the GLSL code into a file (. glsl) and read the GLSL code by reading the file. A module for reading shader code is provided below. readShaderSource() reads the shader text and returns an array of strings, each of which is a line of text in the file. Determines the size of the array based on the number of rows read in.

QString MyWidget::readShaderSource(const char*filePath)
    QString content;
    ifstream fileStream(filePath, ios::in);
    QString line = "";
    return content;

IV. simple triangle animation

Similar to the procedure of drawing a point in the last time, you can draw triangles only by slightly modifying the vertex shader code, and then add variables to control the animation. It should be noted that using glUniform1f function requires the introduction of glew library, but Qt Creator itself does not have this library. After adding the library, you need to add it Connect the libraries in the pro file, otherwise the compilation will not succeed.


#ifndef MYWIDGET_H
#define MYWIDGET_H
#define numVAOs 1
#include<GL/glew. h> / / the header file must be at the front

//Inherit the OpenGL widget and rewrite the initializegl(), paintgl(), and resizegl() functions to draw OpenGL primitives
class MyWidget : public QOpenGLWidget,protected QOpenGLFunctions
    MyWidget(QWidget *parent);
    GLuint createShaderProgram();//GLuint is equivalent to the unsigned int type in C + +
public slots://Define slot function
    void animate();

    void initializeGL() override;
    void paintGL() override;
    void resizeGL(int width, int height) override;
    GLuint renderingProgram;
    GLuint vao[numVAOs];
    float x = 0.0f;
    float inc = 0.01f;

#endif // MYWIDGET_H


#include "mywidget.h"
MyWidget::MyWidget(QWidget *parent)
    QSurfaceFormat format = QSurfaceFormat::defaultFormat();
    format.setVersion(4, 3);

void MyWidget::initializeGL()
    glClearColor(1.0,0.0,0.0,1.0);//Set the background color to red
    renderingProgram = createShaderProgram();
void MyWidget::paintGL()
    //Offset per update
    x += inc;
    if(x > 1.0f) inc = -0.02f;
    if(x < -1.0f) inc = 0.02f;
    //Gets the position of the global variable offset of the uniform class in the vertex shader
    GLuint offsetLoc = glGetUniformLocation(renderingProgram,"offset");
    //Assign the value of x to the uniform class variable offset
    //Draw 3 vertices as triangles
void MyWidget::resizeGL(int width, int height)


GLuint MyWidget::createShaderProgram()
    //Modify the vertex shader to make it triangular
    //Use the global variable offset of the unifom class to control the animation
    const char *vshaderSource =
        "#version 430 \n"
         "uniform float offset; \n"
        "void main(void) \n"
        "{ if(gl_VertexID ==0) gl_Position = vec4(0.25+offset , -0.25, 0.0, 1.0);\n"
        "else if(gl_VertexID == 1) gl_Position = vec4(-0.25+offset ,-0.25,0.0,1.0);\n"
        "else gl_Position = vec4(0.0 +offset,0.25,0.0,1.0);}";

    //Vertices move along the pipeline to the raster shader, where they are converted to pixel (fragment) positions, and eventually these pixels (fragments) reach the fragment shader
    //The purpose of the clip shader is to give RGB color to the pixels to be displayed
    //The "out" tag indicates that the color variable is an output variable
    //Here vec4, the first three elements represent RGB color and the fourth element represents opacity
    const char *fshaderSource =
        "#version 430 \n"
        "out vec4 color; \n"
        "void main(void) \n"
        "{ color = vec4(0.0,0.0,1.0,1.0);}";

    //Call glCreateShader (parameter) to create an empty shader of type parameter
    //After each shader object is created, an integer ID is returned as the sequence number that references them later
    GLuint vShader = glCreateShader(GL_VERTEX_SHADER);//Glue is equivalent to "unsigned int"“
    GLuint fShader = glCreateShader(GL_FRAGMENT_SHADER);

    //glShaderSource loads GLSL code from a string into an empty shader object
    //Four parameters: 1. Shader object storing shader, 2. Number of strings in shader source code, 3. String pointer containing source code, 4

    //glCompileShader compile shader

    //Creates a program object and stores an integer ID pointing to it
    //OpenGL's program object contains a series of compiled shaders, which are added to the program object using glAttachShader
    //Then use glLinkProgram to request GLSL compiler to ensure their compatibility.
    GLuint vfProgram  = glCreateProgram();

    return vfProgram;


void MyWidget::animate()
    this->update();//When you call update, you will automatically call PaintGL function to redraw



#include <QMainWindow>
namespace Ui { class MainWindow; }

class MainWindow : public QMainWindow

    MainWindow(QWidget *parent = nullptr);

    Ui::MainWindow *ui;
    MyWidget *my_widget;
#endif // MAINWINDOW_H


#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    //Create OpenGLWidget object
    my_widget = new MyWidget(this);
    //Set the mainWindow interface to OpenGLWidget
    setCentralWidget( my_widget );
    //Set the size of the interface
    resize( 1000, 800 );
    //Set timer and connect slot function
    QTimer *timer = new QTimer(this);
    connect(timer, &QTimer::timeout, my_widget, &MyWidget::animate);
    timer->start(10);//Sets the time interval for redrawing

    delete ui;


#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
    QApplication a(argc, argv);
    MainWindow w;;
    return a.exec();

Operation results:

Topics: C++ OpenGL