OpenGL Intro

Wednesday | Friday
Fall 2018

QT and OpenGL

The class QOpenGLWidget creates an OpenGL context inside QT. OpenGL functions are called within this context. Additionally, QT has some OpenGL wrapper classes beginning with the prefix QOpenGL.

Creating a QT OpenGL application

We store the vertices of a triangle in a VBO in GPU memory. The next step is to define, create, load, and compile shaders. The vertex shader runs first and takes vertex data from the VBO and outputs geometry in clip coordinates. This geometry is then clipped, rasterized, and fed to the fragment shader which runs on each fragmenet, or potential output pixel. The output of the fragment shader is written to a framebuffer and displayed in the viewport. Once each shader is compiled, we define, create, and link a shader program, which is the combination of a vertex shader and a fragment shader. Finally, we are ready to draw. The main steps are: Once the geometric data are copied to GPU memory, almost everything else happens on the GPU. paintGL is just issuing commands to the GPU. The GPU itself will process those commands.

Shaders, GLSL

Modifying Shaders

Adding uniform values

A uniform variable in a shader is a value that does not vary with different input values. Vertex shaders run in parallel on multiple geometric objects with different in data values, and fragment shaders run in parallel with different pixel coordinates, but sometimes, we want these programs to have access to variables that are the same for each vertex/fragment. In our first example, suppose we want to modify the color of the triangle through the application. We first modify the fragment shader fshader.glsl to use a uniform value for the color.
#version 410

uniform vec4 color;
out vec4 fragColor;

void main() {
    fragColor = color;
}

Next, we must set the value of this uniform somewhere in mypanelopengl.cpp. The general syntax is to use the setUniformValue method of the QOpenGLShaderProgram class. If we only want to set the color once, we can run this method when we are creating the VBOs

void MyPanelOpenGL::createVBOs() {
  ...
  shaderProgram->bind();
  shaderProgram->enableAttributeArray("vPosition");
  shaderProgram->setAttributeBuffer("vPosition", GL_FLOAT, 0, 4, 0);

  /* New! Set color in MyPanelOpenGL */
  shaderProgram->setUniformValue("color",QVector4D(0.,0.7,0.,1.));

  vao->release();
  ...
}

Compile and run. Your triangle should now be green.

Being able to change the values of uniforms through our program gives us more flexibility. And we don't have to be limited to setting a uniform just once. For our next exercise, we will set up a new uniform to eventually an animated triangle. First modify the vertex shader vshader.glsl to have a new uniform called displacement

#version 410

uniform vec4 displacement;
in vec4 vPosition;

void main(){
    gl_Position =  vPosition + displacement;
}

Next, add a new member variable called displacement in mypanelopengl.h

...
private:
  ...
  void destroyShaders();

  unsigned int numVertices;
  QVector4D *vertices;

  /* New! */
  QVector4D displacement;

Initialize the displacement variable in the constructor, and set the uniform value, this time in paintGL in mypanelopengl.cpp

MyPanelOpenGL::MyPanelOpenGL(QWidget *parent) : QOpenGLWidget(parent) {
  ...
  displacement = QVector4D(0,0.2,0,0);
}

...

void MyPanelOpenGL::paintGL() {
  ...
  shaderProgram->bind();
  vao->bind();

  /* New! */
  shaderProgram->setUniformValue("displacement",displacement);

  glDrawArrays(GL_TRIANGLES, 0, numVertices);
  ...
}

Save, compile, and run. The triangle should be shifted from before, but the shift is not animated.

Adding animation

Next we will animate our triangle by adjusting the displacement value over time. For this will will need a few tools
  1. Add a QTimer object that will periodically trigger refresh events.
  2. Handle refresh events with a step() slot that updates the displacement and repaints the canvas.
To start, add the following member variables to mypanelopengl.h

 ...

 private:

    unsigned int numVertices;
    QVector4D *vertices;
    QVector4D displacement;

    /* add the vars below */
    QTimer timer;
    float phi;

  ...

Also add a public slot to the same file
  public slots:
    /* called every time timer fires */
    void step();

Next, hop over to the implementation in mypanelopengl.cpp

It's probably a good idea to initialize our new variables in the constructor.

MyPanelOpenGL::MyPanelOpenGL(QWidget *parent) :
    QOpenGLWidget(parent) {

   ...

  phi = 0;
  displacement = QVector4D(0,0.2,0,0);

  ...
}
void MyPanelOpenGL::initializeGL()
{
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    createShaders();
    createVBOs();
    /* manually connect QTimer::timeout() signal to
        MyPanelOpenGL::step() slot without using QtCreator UI */
    connect(&timer, &QTimer::timeout, this, &MyPanelOpenGL::step );
    timer.start(30); /*fire every 30ms */
}

Implement the step() slot as follows

void MyPanelOpenGL::step()
{
    phi += 0.01;
    displacement.setY(phi);
    update(); /* call paint */
}

Since we are modifying the displacement variable, remember to change paintGL() to use the value of the variable and not a fixed QVector4D

void MyPanelOpenGL::paintGL(){

    ...

  shaderProgram->bind();
  vao->bind();
  shaderProgram->setUniformValue("displacement",displacement);
  //shaderProgram->setUniformValue("color",QVector4D(0.,0.7,0.,1.));
  glDrawArrays(GL_TRIANGLES, 0, numVertices);


    ...

}
If everything is working, your triangle should gradually move off the top of the screen.
Bouncing

If we want to have the triangle smoothly bounce up and down while staying on the screen, we can modulate the displacement with a sine wave.

void MyPanelOpenGL::step()
{
    phi += 0.1;
    if(phi > 2*M_PI){
        phi -= 2*M_PI;
    }
    displacement.setY(0.2*sin(phi));
    update(); /* call paint */
}

Exercise

Linear Algebra Point and Vector Tools

For additional reading on this topic in the context of computer graphics, I recommend Immersive Linear Algebra, particularly the first four chapters. Some content covered at the site we will not be using now, but will likely revisit later when we move to 3D. For now, it is sufficient to review the topics we cover in class and skip the rest.