VAO (Vertex Array Object) and VBO (Vertex Buffer Object) were introduced to help programmers since OpenGL 3.0.
So it’s not a recent features but if you just started learning OpenGL it could be very complex to understand.
Furthermore if you are learning OpenGL and want to use it with Qt together, it becomes a real nightmare.
So what are these vertex objects and how to use them to manage two different objects on the scene?
Let’s see that in this VAO and VBO tutorial for Qt.
First of all
We are going to use the QWindow class (to display a window) and the QOpenGLFunctions class (to retrieve the OpenGL classic functions).
The BadprogOpenGLWindow will inherit from these two classes.
So all the basics Qt and OpenGL mechanisms will be do in this class.
Then, in the BadprogDisplay class, we are going to use our famous VAO and VBO through only two methods:
The former will be called only once (it’s an "init" method).
The latter will be called for each new frame displayed (so all the time).
In the initialize() we’ll create:
- 1 program;
- 2 VAOs;
- 4 VBOs (2 for each VAO).
And we'll allocate the memory (in this method) for the VAO1's VBO1 and VBO2 and the VAO2's VBO1 only.
Indeed the VBO2 (of the VAO2) is the color array and we want to change this color each second, so it'll be allocated in the render() method.
So one of the most important thing to understand is that a VAO is a manager.
What does it mean?
It means that a VAO will take a global value and set it as the current value.
This is hidden to the user, don’t worry about that, OpenGL knows how it works.
But before that we have to create a program then 2 shaders.
These shaders are then linked by the program.
Once again don't worry about that it's the OpenGL intrinsec mechanism.
So after the program created, with the QOpenGLShaderProgram class and the shaders linked, we'll be ready to create our first VAO.
Using Qt classes with OpenGL is quite different from the classic OpenGL functions but the final is the same.
With Qt, a VAO is a QOpenGLVertexArrayObject class.
And to create it simply call the create() method like this :
It’s also necessary to call the bind() one:
This bind() method is a way to say to OpenGL "Hey OpenGL, I’m a VAO and I’m taking the lead, so all the next VBOs will be mine".
It means that the VBOs, that we are setting just after the VAO1 binding, will be part of this VAO1.
Indeed we don’t need to specify for each VBO which VAO they belong to.
Why?
Because OpenGL works as a state machine, you can’t use several VAO a the same time, it’s only one by one.
So if you want ot use the VAO1, you have to bind it.
If you want ot use the VAO2 you’ll have to bind it (but at this moment VAO1 won't be available anymore).
To clarify this we use the release() method at the end of each VAO:
But this release() method isn’t really necessary because each time we call a bind() the last bind() is automatically released.
It’s somehow clearer to use it.
Regarding shaders, we have a Vertex shader and a Fragment shader simpliest as we could do.
Nothing special about that.
Except maybe the size of the point that we set it to 40.
So what are the final display?
We should have a first object (our VAO1) represented by 3 points of 3 different colors on the left of the scene.
Then on the right a triangle with a changing color that will be our VAO2.
Note that VAO1 shows 3 points, it’s because we set the glDrawArrays() function to GL_POINTS.
Changing this parameter with GL_TRIANGLES will display a triangle instead.
Let’s code a bit.
BadprogOpenGLWithQt_1.pro
###############################################################################
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
# badprog.com
CONFIG += c++11
DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += \
badprogDisplay.cpp \
badprogOpenGLWindow.cpp \
main.cpp
HEADERS += \
badprogDisplay.h \
badprogOpenGLWindow.h
RESOURCES += \
badprog.qrc
badprog.qrc
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource>
<file alias="shader_vertex">shader/vertex1.glsl</file>
<file alias="shader_fragment">shader/fragment1.glsl</file>
</qresource>
</RCC>
badprogDisplay.cpp
#include "badprogDisplay.h"
#include <QtGui/QMatrix4x4>
#include <QtGui/QOpenGLShaderProgram>
#include <QtGui/QScreen>
#include <QtCore/qmath.h>
#include <chrono>
#include <ctime>
// BadproG.com
// ============================================================================
//
// ============================================================================
BadprogDisplay::BadprogDisplay()
: _program(nullptr)
, _frame(0)
{
setTitle("Badprog: VAO and VBO with 2 different objects with OpenGL and Qt");
}
// ============================================================================
//
// ============================================================================
void BadprogDisplay::initialize()
{
// Program.
_program = new QOpenGLShaderProgram(this);
_program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":shader_vertex");
_program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":shader_fragment");
// Linking.
_program->link();
// Attributes.
_attribute1_position = _program->attributeLocation("layoutPosition");
_attribute2_position = _program->attributeLocation("layoutPosition");
_attribute1_color = _program->attributeLocation("layoutColor");
_attribute2_color = _program->attributeLocation("layoutColor");
// Uniforms.
_uniformForShader1_matrix = _program->uniformLocation("shaderMatrix");
_uniformForShader2_matrix = _program->uniformLocation("shaderMatrix");
// Enabling the point size modification.
glEnable(GL_PROGRAM_POINT_SIZE);
// Array of vertex
GLfloat vertices[] = {
// First triangle
-0.6f, 0.6f, 0.0f,
-0.9f, -0.5f, 0.0f,
-0.3f, -0.5f, 0.0f,
// Second triangle (not used in this tutorial)
0.6f, 0.6f, 0.0f,
0.9f, -1.0f, 0.0f,
0.3f, 0.0f, 0.0f
};
GLfloat arrayOfVertex2[] = {
0.6f, 0.6f, 0.0f,
0.9f, -1.0f, 0.0f,
0.3f, 0.0f, 0.0f
};
// Array of colors
GLfloat arrayOfColors1[] = {
// First triangle
0.0f, 1.0f, 0.0f, // G
0.0f, 0.0f, 1.0f, // B
1.0f, 0.0f, 0.0f, // R
};
// ------------------------------------------
// VAO 1
// ------------------------------------------
// One VAO for 1 object to render (but multi VBOs possible like 1 for the
// position and 1 for the color as array for both).
//
_vao1.create();
_vao1.bind();
//
// VBO 1
//
// Buffer 1
_vbo1_position.create();
_vbo1_position.bind();
_vbo1_position.setUsagePattern(QOpenGLBuffer::StreamDraw);
_vbo1_position.allocate(vertices, sizeof (vertices));
// program
_program->enableAttributeArray(_attribute1_position);
_program->setAttributeBuffer(_attribute1_position, GL_FLOAT, 0, 3);
// _program->release();
// Buffer 2
_vbo1_color.create();
_vbo1_color.bind();
_vbo1_color.setUsagePattern(QOpenGLBuffer::StreamDraw);
_vbo1_color.allocate(arrayOfColors1, sizeof(arrayOfColors1));
// program
_program->enableAttributeArray(_attribute1_color);
_program->setAttributeBuffer(_attribute1_color, GL_FLOAT, 0, 3);
// _program->release();
// ------------------------------------------
// VAO 2
// ------------------------------------------
_vao2.create();
_vao2.bind();
//
// VBO 2
//
// Buffer 1
_vbo2_position.create();
_vbo2_position.bind();
_vbo2_position.setUsagePattern(QOpenGLBuffer::StreamDraw);
_vbo2_position.allocate(arrayOfVertex2, sizeof (arrayOfVertex2));
// program
_program->enableAttributeArray(_attribute2_position);
_program->setAttributeBuffer(_attribute2_position, GL_FLOAT, 0, 3);
// _program->release();
// Buffer 2
_vbo2_color.create();
_vbo2_color.bind();
_vbo2_color.setUsagePattern(QOpenGLBuffer::StreamDraw);
// program
_program->enableAttributeArray(_attribute2_color);
_program->setAttributeBuffer(_attribute2_color, GL_FLOAT, 0, 3);
// _program->release();
}
// ============================================================================
//
// ============================================================================
void BadprogDisplay::render()
{
const qreal retinaScale = devicePixelRatio();
// Viewport
glViewport(0, 0, width() * retinaScale, height() * retinaScale);
// Clear whole buffer
glClear(GL_COLOR_BUFFER_BIT);
//
_program->bind();
// Binding the VAO 1, here there is only one VAO but with
// many VAOs we'd have to call it for each VAO to render their content.
_vao1.bind();
//
QMatrix4x4 matrixFromCpp1;
matrixFromCpp1.perspective(60.0f, 4.0f / 3.0f, 0.1f, 100.0f);
matrixFromCpp1.translate(0, 0, -2);
matrixFromCpp1.rotate(100.0f * _frame / screen()->refreshRate(), 1, 0, 0);
//
_program->setUniformValue(_uniformForShader1_matrix, matrixFromCpp1);
//
auto now = std::chrono::system_clock::now();
//
std::time_t currentTime = std::chrono::system_clock::to_time_t(now);
// Another array of colors
const GLfloat color[] = {
(float)sin(currentTime) * 0.5f + 0.5f, (float)cos(currentTime) * 0.5f + 0.5f, (float)tan(currentTime) * 0.5f + 0.5f,
(float)sin(currentTime) * 0.5f + 0.3f, (float)cos(currentTime) * 0.5f + 0.3f, (float)tan(currentTime) * 0.5f + 0.3f,
(float)sin(currentTime) * 0.5f + 0.1f, (float)cos(currentTime) * 0.5f + 0.1f, (float)tan(currentTime) * 0.5f + 0.1f
};
//
glDrawArrays(GL_POINTS, 0, 3);
//
_vao1.release();
//
_vao2.bind();
_vbo2_color.allocate(color, sizeof(color)); // Needed to display colors of triangle 2
// Matrix 2
QMatrix4x4 matrixFromCpp2;
matrixFromCpp2.perspective(60.0f, 4.0f / 3.0f, 0.1f, 100.0f);
matrixFromCpp2.translate(0, 0, -2);
matrixFromCpp2.rotate(110.0f * _frame / screen()->refreshRate(), 0, 1, 0);
//
_program->setUniformValue(_uniformForShader1_matrix, matrixFromCpp2);
//
glDrawArrays(GL_TRIANGLES, 0, 3);
//
_vao2.release();
//
++_frame;
//
// _program->release();
}
badprogDisplay.h
#ifndef BADPROG_DISPLAY_H
#define BADPROG_DISPLAY_H
// BadproG.com
#include "badprogOpenGLWindow.h"
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
class QOpenGLShaderProgram;
class BadprogDisplay : public BadprogOpenGLWindow
{
public:
BadprogDisplay();
void initialize() override;
void render() override;
private:
GLuint _attribute1_position;
GLuint _attribute1_color;
GLuint _attribute2_position;
GLuint _attribute2_color;
GLuint _uniformForShader1_matrix;
GLuint _uniformForShader2_matrix;
QOpenGLShaderProgram *_program;
int _frame;
QOpenGLVertexArrayObject _vao1;
QOpenGLVertexArrayObject _vao2;
QOpenGLBuffer _vbo1_position;
QOpenGLBuffer _vbo1_color;
QOpenGLBuffer _vbo2_position;
QOpenGLBuffer _vbo2_color;
};
#endif // !BADPROG_DISPLAY_H
badprogOpenGLWindow.cpp
#include "badprogOpenGLWindow.h"
#include <QtCore/QCoreApplication>
#include <QtGui/QOpenGLContext>
// BadproG.com
// ============================================================================
//
// ============================================================================
BadprogOpenGLWindow::BadprogOpenGLWindow(QWindow *parent)
: QWindow(parent)
, _animating(false)
, _context(0)
{
setSurfaceType(QWindow::OpenGLSurface);
}
// ============================================================================
//
// ============================================================================
BadprogOpenGLWindow::~BadprogOpenGLWindow() {
}
// ============================================================================
//
// ============================================================================
void BadprogOpenGLWindow::initialize() {
}
// ============================================================================
//
// ============================================================================
void BadprogOpenGLWindow::render() {
//
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
// ============================================================================
//
// ============================================================================
void BadprogOpenGLWindow::renderWhenReady() {
//
requestUpdate();
}
// ============================================================================
//
// ============================================================================
bool BadprogOpenGLWindow::event(QEvent *event) {
//
switch (event->type()) {
case QEvent::UpdateRequest:
manageBasics();
return true;
default:
return QWindow::event(event);
}
}
// ============================================================================
//
// ============================================================================
void BadprogOpenGLWindow::exposeEvent(QExposeEvent *event) {
//
Q_UNUSED(event);
//
if (isExposed()) {
manageBasics();
}
}
// ============================================================================
//
// ============================================================================
void BadprogOpenGLWindow::manageBasics() {
//
if (!isExposed()) {
return;
}
//
bool needsInitialize = false;
//
if (!_context) {
_context = new QOpenGLContext(this);
_context->setFormat(requestedFormat());
_context->create();
needsInitialize = true;
}
//
_context->makeCurrent(this);
//
if (needsInitialize) {
initializeOpenGLFunctions();
initialize();
}
//
render();
//
_context->swapBuffers(this);
//
if (_animating) {
renderWhenReady();
}
}
// ============================================================================
//
// ============================================================================
void BadprogOpenGLWindow::setAnimating(bool state) {
//
_animating = state;
//
if (state) {
renderWhenReady();
}
}
badprogOpenGLWindow.h
#ifndef BADPROG_OPENGL_WINDOW_H
#define BADPROG_OPENGL_WINDOW_H
// BadproG.com
#include <QtGui/QWindow>
#include <QtGui/QOpenGLFunctions>
QT_BEGIN_NAMESPACE
class QOpenGLContext;
QT_END_NAMESPACE
class BadprogOpenGLWindow : public QWindow, protected QOpenGLFunctions
{
Q_OBJECT
public:
explicit BadprogOpenGLWindow(QWindow *parent = Q_NULLPTR);
~BadprogOpenGLWindow() override;
virtual void render();
virtual void initialize();
void setAnimating(bool state);
void renderWhenReady();
void manageBasics();
protected:
bool event(QEvent *event) override;
void exposeEvent(QExposeEvent *event) override;
private:
bool _animating;
QOpenGLContext *_context;
};
#endif // !BADPROG_OPENGL_WINDOW_H
main.cpp
#include <QApplication>
#include "badprogDisplay.h"
// BadproG.com
// ============================================================================
//
// ============================================================================
int main(int ac, char *av[]) {
//
QApplication app(ac, av);
//
BadprogDisplay bpDisplay;
bpDisplay.resize(1080, 720);
bpDisplay.show();
bpDisplay.setAnimating(true);
//
return app.exec();
}
For the shaders, just create a shader/ directory at the project root.
shader/fragment1.glsl
#version 330 core
// BadproG.com
// out
out vec4 FragColor;
// in
in vec3 shaderColor;
// ----------------------------------------------------------------------------
// main
// ----------------------------------------------------------------------------
void main() {
//
FragColor = vec4(shaderColor, 1.0f);
}
shader/vertex1.glsl
#version 330 core
// BadproG.com
// layout
layout (location = 0) in vec3 layoutPosition;
layout (location = 1) in vec3 layoutColor;
// out
out vec3 shaderColor;
// uniform
uniform mat4 shaderMatrix;
// ----------------------------------------------------------------------------
// main
// ----------------------------------------------------------------------------
void main() {
//
gl_PointSize = 40;
gl_Position = shaderMatrix * vec4(layoutPosition, 1.0);
shaderColor = layoutColor;
}
Conclusion
Setting up Qt, OpenGL, VAO, VBO and shaders wasn't an easy task.
You are now ready to create plenty of new objects on your scene.
Congratulations if you succeeded.
Add new comment