Hello Qt, Hello OpenGL, Hello World

Posted on Posted in OpenGL Tutorials, Qt Tutorials, Tutorials

The example project that I am going to share in this post is the absolute beginner guide version of using OpenGL in Qt (Specially Qt5 and latest versions of OpenGL) which to my surprise I could not find anywhere. By checking the existing OpenGL examples in Qt I noticed they all make some assumptions about what you know about OpenGL and then go ahead and describe how to use in in Qt. [I hope, I really do] that is not what you’ll find here. So without further ado here is the most simple example.

First of all you need to add opengl module to your project. Do it by adding the following line to your project:

QT += opengl

And be sure to go over this documentation page to learn about QOpenGLWidget as much as you can because it’s the base class we have used here. And I might have missed some crucial points but the documentation is always the best reference.

The most important trick here is to know that you need to sub-class a public QOpenGLWidget (and protected QOpenGLFunctions) object and override three of its methods:

void initializeGL();

void paintGL();

void resizeGL(int w, int h);





initializeGL is where you do all the required initialization. What you see below is the most simple example of an initializeGL method:

void MyGL::initializeGL()
{
initializeOpenGLFunctions(); // obvious

GLfloat vertices[6][2] =
{
{ -0.90f, -0.90f }, // Triangle 1
{ +0.85f, -0.90f },
{ -0.90f, +0.85f },

{ +0.90f, -0.85f }, // Triangle 2
{ +0.90f, +0.90f },
{ -0.85f, +0.90f }
};

buffer = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
buffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
Q_ASSERT(buffer.create());
Q_ASSERT(buffer.bind());
buffer.allocate(sizeof(vertices));
buffer.write(0, vertices, sizeof(vertices));

shaderProg.addShaderFromSourceFile(QOpenGLShader::Vertex, “path_to_vertex_shader_file”);
shaderProg.addShaderFromSourceFile(QOpenGLShader::Fragment, “path_to_fragment_shader_file”);
Q_ASSERT(shaderProg.link());
Q_ASSERT(shaderProg.bind());

const int vPosition = 0;

glVertexAttribPointer(vPosition, 2, GL_FLOAT, GL_FALSE, 0, NULL);

glEnableVertexAttribArray(vPosition);

glClear(GL_COLOR_BUFFER_BIT);

glClearColor(0.25, 0.45, 0.65, 1.0);

}

I think the first line is as obvious as it can be right?

initializeOpenGLFunctions();

After that comes the vertices we need to draw on screen. In this example we are going to draw two triangles on screen.

GLfloat vertices[6][2] =
{
{ -0.90f, -0.90f }, // Triangle 1
{ +0.85f, -0.90f },
{ -0.90f, +0.85f },

{ +0.90f, -0.85f }, // Triangle 2
{ +0.90f, +0.90f },
{ -0.85f, +0.90f }
};

Then we create a buffer and add those points to the buffer. Remember, buffers will be drawn on screen:

buffer = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
buffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
Q_ASSERT(buffer.create());
Q_ASSERT(buffer.bind());
buffer.allocate(sizeof(vertices));
buffer.write(0, vertices, sizeof(vertices));

buffer is a QOpenGLBuffer which is defined globally. Here QOpenGLBuffer::VertexBuffer in the constructor means that we are providing Vertex data and setUsagePattern(QOpenGLBuffer::StaticDraw) helps make sure data is drawn on the screen as it is. The rest is creating and binding and allocating and filling it with data. Simple!





Next we have to load our shaders. We MUST have a vertex and a fragment shader at the least and I’ll also provide you with the shaders later in this guide but for now let’s say you  have saved your shaders to disk. So we load, link and bind the shaders like this:

shaderProg.addShaderFromSourceFile(QOpenGLShader::Vertex, “path_to_vertex_shader_file”);
shaderProg.addShaderFromSourceFile(QOpenGLShader::Fragment, “path_to_fragment_shader_file”);
Q_ASSERT(shaderProg.link());
Q_ASSERT(shaderProg.bind());

shaderProg is a QOpenGLShaderProgram.

Next, and probably the only part of the code where you’ll probably feel lost if this is the first OpenGL program is this:

const int vPosition = 0;

glVertexAttribPointer(vPosition, 2, GL_FLOAT, GL_FALSE, 0, NULL);

glEnableVertexAttribArray(vPosition);

This part of the code is actually quite simple after you dig a little bit into the world of OpenGL. Here you are enabling a pointer between the shaders and C++ code. It’s like saying: “Points and vertex data from my C++ code will go to the location vPosition, which is zero, in the GLSL shaders.”

The last part of initializeGL is:

glClear(GL_COLOR_BUFFER_BIT);

glClearColor(0.25, 0.45, 0.65, 1.0);

In which you clear the buffer and prepare it for drawing. Note that you also give it bluish color.

After we’re done with initializeGL, we move to paintGL method. It only contains the actual drawing code which is:

void MyGL::paintGL()
{

glDrawArrays(GL_TRIANGLES, 0, 6);

glFlush();

}

First you say I want to draw triangles using 6 points. (Two triangles that is!) And then flush to make sure it’s all drawn as soon as possible.

In my example you can leave out resizeGL function like this:

void MyGL::resizeGL(int w, int h)
{
Q_UNUSED(w); Q_UNUSED(h);
}

Means do nothing on resize 🙂 but don’t worry about it at all for now.

And finally to the shaders. Your vertex shader needs to be like this:

#version 430 core
layout(location = 0) in vec4 vPosition;
void main()
{
gl_Position = vPosition;
}

And fragment shader like this:

#version 430 core
out vec4 fColor;
void main()
{
fColor = vec4(0.0, 0.0, 1.0, 1.0);
}

These are the most simple shaders. Vertex shader here just passes any points you give it and the fragment shader just colors it with a blue color.

Your widget should look like this it is drawn on a Window:

gl example

Good luck!





Leave a Reply

Your email address will not be published. Required fields are marked *