Hello GLES3 Example▲
Overview▲
This example demonstrates easy, cross-platform usage of OpenGL ES 3.0 functions via QOpenGLExtraFunctions in an application that works identically on desktop platforms with OpenGL 3.3 and mobile/embedded devices with OpenGL ES 3.0.
This example has no QWidget dependencies, it uses QOpenGLWindow, a convenience subclass of QWindow that allows easy implementation of windows that contain OpenGL-rendered content. In this sense it complements the OpenGL Window Example, which shows the implementation of an OpenGL-based QWindow without using the convenience subclass.
The Qt logo shape implementation is included from the Hello GL2 Example.
In other aspects pertaining to using OpenGL there are the following differences.
-
The OpenGL context creation has to have a sufficiently high version number for the features that are in use.
-
The shader's version directive is different.
Setting up in main.cpp▲
Here we instantiate our QGuiApplication, QSurfaceformat and set its depth buffer size:
int
main(int
argc, char
*
argv[])
{
QGuiApplication app(argc, argv);
QSurfaceFormat fmt;
fmt.setDepthBufferSize(24
);
We request an OpenGL 3.3 core or OpenGL ES 3.0 context, depending on QOpenGLContext::openGLModuleType():
if
(QOpenGLContext::
openGLModuleType() ==
QOpenGLContext::
LibGL) {
qDebug("Requesting 3.3 core context"
);
fmt.setVersion(3
, 3
);
fmt.setProfile(QSurfaceFormat::
CoreProfile);
}
else
{
qDebug("Requesting 3.0 context"
);
fmt.setVersion(3
, 0
);
}
QSurfaceFormat::
setDefaultFormat(fmt);
We set the default surface format and instantiate our GLWindow glWindow.
Implementing GLWindow▲
This class delivers the features of the example application.
To start, GLWindow is declared by implementing a subclass of QOpenGLWindow:
class
GLWindow : public
QOpenGLWindow
The following properties are declared using Q_PROPERTY:
{
Q_OBJECT
Q_PROPERTY(float
z READ z WRITE setZ)
Q_PROPERTY(float
r READ r WRITE setR)
Q_PROPERTY(float
r2 READ r2 WRITE setR2)
The following public functions are declared:
public
:
GLWindow();
~
GLWindow();
void
initializeGL();
void
resizeGL(int
w, int
h);
void
paintGL();
float
z() const
{
return
m_eye.z(); }
void
setZ(float
v);
float
r() const
{
return
m_r; }
void
setR(float
v);
float
r2() const
{
return
m_r2; }
void
setR2(float
v);
The following private objects are declared:
private
slots:
void
startSecondStage();
private
:
QOpenGLTexture *
m_texture =
nullptr
;
QOpenGLShaderProgram *
m_program =
nullptr
;
QOpenGLBuffer *
m_vbo =
nullptr
;
QOpenGLVertexArrayObject *
m_vao =
nullptr
;
Logo m_logo;
int
m_projMatrixLoc =
0
;
int
m_camMatrixLoc =
0
;
int
m_worldMatrixLoc =
0
;
int
m_myMatrixLoc =
0
;
int
m_lightPosLoc =
0
;
QMatrix4x4 m_proj;
QMatrix4x4 m_world;
QVector3D m_eye;
On the implementation side, those functions that are not declared inline are implemented (or re-implemented) in glwindow.cpp. The following selections will cover implementation particulars pertaining to the use of OpenGL ES 3.0.
Animations▲
The following code pertains to the animations, and won't be explored here:
GLWindow::
GLWindow()
{
m_world.setToIdentity();
m_world.translate(0
, 0
, -
1
);
m_world.rotate(180
, 1
, 0
, 0
);
QSequentialAnimationGroup *
animGroup =
new
QSequentialAnimationGroup(this
);
animGroup-&
gt;setLoopCount(-
1
);
QPropertyAnimation *
zAnim0 =
new
QPropertyAnimation(this
, QByteArrayLiteral("z"
));
zAnim0-&
gt;setStartValue(1.5
f);
zAnim0-&
gt;setEndValue(10.0
f);
zAnim0-&
gt;setDuration(2000
);
animGroup-&
gt;addAnimation(zAnim0);
QPropertyAnimation *
zAnim1 =
new
QPropertyAnimation(this
, QByteArrayLiteral("z"
));
zAnim1-&
gt;setStartValue(10.0
f);
zAnim1-&
gt;setEndValue(50.0
f);
zAnim1-&
gt;setDuration(4000
);
zAnim1-&
gt;setEasingCurve(QEasingCurve::
OutElastic);
animGroup-&
gt;addAnimation(zAnim1);
QPropertyAnimation *
zAnim2 =
new
QPropertyAnimation(this
, QByteArrayLiteral("z"
));
zAnim2-&
gt;setStartValue(50.0
f);
zAnim2-&
gt;setEndValue(1.5
f);
zAnim2-&
gt;setDuration(2000
);
animGroup-&
gt;addAnimation(zAnim2);
animGroup-&
gt;start();
QPropertyAnimation*
rAnim =
new
QPropertyAnimation(this
, QByteArrayLiteral("r"
));
rAnim-&
gt;setStartValue(0.0
f);
rAnim-&
gt;setEndValue(360.0
f);
rAnim-&
gt;setDuration(2000
);
rAnim-&
gt;setLoopCount(-
1
);
rAnim-&
gt;start();
QTimer::
singleShot(4000
, this
, &
amp;GLWindow::
startSecondStage);
}
GLWindow::
~
GLWindow()
{
makeCurrent();
delete
m_texture;
delete
m_program;
delete
m_vbo;
delete
m_vao;
}
void
GLWindow::
startSecondStage()
{
QPropertyAnimation*
r2Anim =
new
QPropertyAnimation(this
, QByteArrayLiteral("r2"
));
r2Anim-&
gt;setStartValue(0.0
f);
r2Anim-&
gt;setEndValue(360.0
f);
r2Anim-&
gt;setDuration(20000
);
r2Anim-&
gt;setLoopCount(-
1
);
r2Anim-&
gt;start();
}
void
GLWindow::
setZ(float
v)
{
m_eye.setZ(v);
m_uniformsDirty =
true
;
update();
}
void
GLWindow::
setR(float
v)
{
m_r =
v;
m_uniformsDirty =
true
;
update();
}
void
GLWindow::
setR2(float
v)
{
m_r2 =
v;
m_uniformsDirty =
true
;
update();
}
For more information see the documentation for QPropertyAnimation, QSequentialAnimationGroup.
Shaders▲
The shaders are defined like so:
static
const
char
*
vertexShaderSource =
"layout(location = 0) in vec4 vertex;
\n
"
"layout(location = 1) in vec3 normal;
\n
"
"out vec3 vert;
\n
"
"out vec3 vertNormal;
\n
"
"out vec3 color;
\n
"
"uniform mat4 projMatrix;
\n
"
"uniform mat4 camMatrix;
\n
"
"uniform mat4 worldMatrix;
\n
"
"uniform mat4 myMatrix;
\n
"
"uniform sampler2D sampler;
\n
"
"void main() {
\n
"
" ivec2 pos = ivec2(gl_InstanceID % 32, gl_InstanceID / 32);
\n
"
" vec2 t = vec2(float(-16 + pos.x) * 0.8, float(-18 + pos.y) * 0.6);
\n
"
" float val = 2.0 * length(texelFetch(sampler, pos, 0).rgb);
\n
"
" mat4 wm = myMatrix * mat4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, t.x, t.y, val, 1) * worldMatrix;
\n
"
" color = texelFetch(sampler, pos, 0).rgb * vec3(0.4, 1.0, 0.0);
\n
"
" vert = vec3(wm * vertex);
\n
"
" vertNormal = mat3(transpose(inverse(wm))) * normal;
\n
"
" gl_Position = projMatrix * camMatrix * wm * vertex;
\n
"
"}
\n
"
;
static
const
char
*
fragmentShaderSource =
"in highp vec3 vert;
\n
"
"in highp vec3 vertNormal;
\n
"
"in highp vec3 color;
\n
"
"out highp vec4 fragColor;
\n
"
"uniform highp vec3 lightPos;
\n
"
"void main() {
\n
"
" highp vec3 L = normalize(lightPos - vert);
\n
"
" highp float NL = max(dot(normalize(vertNormal), L), 0.0);
\n
"
" highp vec3 col = clamp(color * 0.2 + color * 0.8 * NL, 0.0, 1.0);
\n
"
" fragColor = vec4(col, 1.0);
\n
"
"}
\n
"
;
These are OpenGL version agnostic. We take this and append the version like so:
QByteArray versionedShaderCode(const
char
*
src)
{
QByteArray versionedSrc;
if
(QOpenGLContext::
currentContext()-&
gt;isOpenGLES())
versionedSrc.append(QByteArrayLiteral("#version 300 es
\n
"
));
else
versionedSrc.append(QByteArrayLiteral("#version 330
\n
"
));
versionedSrc.append(src);
return
versionedSrc;
}
Initializing OpenGL▲
Initializing the shader program in handled by initializeGL():
void
GLWindow::
initializeGL()
{
QOpenGLFunctions *
f =
QOpenGLContext::
currentContext()-&
gt;functions();
QImage img(":/qtlogo.png"
);
Q_ASSERT(!
img.isNull());
delete
m_texture;
m_texture =
new
QOpenGLTexture(img.scaled(32
, 36
).mirrored());
delete
m_program;
m_program =
new
QOpenGLShaderProgram;
Now the OpenGL version is prepended and the various matrices and light position is set:
m_program-&
gt;addShaderFromSourceCode(QOpenGLShader::
Vertex, versionedShaderCode(vertexShaderSource));
m_program-&
gt;addShaderFromSourceCode(QOpenGLShader::
Fragment, versionedShaderCode(fragmentShaderSource));
m_program-&
gt;link();
m_projMatrixLoc =
m_program-&
gt;uniformLocation("projMatrix"
);
m_camMatrixLoc =
m_program-&
gt;uniformLocation("camMatrix"
);
m_worldMatrixLoc =
m_program-&
gt;uniformLocation("worldMatrix"
);
m_myMatrixLoc =
m_program-&
gt;uniformLocation("myMatrix"
);
m_lightPosLoc =
m_program-&
gt;uniformLocation("lightPos"
);
While not strictly required for ES 3, a vertex array object is created.
delete
m_vao;
m_vao =
new
QOpenGLVertexArrayObject;
if
(m_vao-&
gt;create())
m_vao-&
gt;bind();
m_program-&
gt;bind();
delete
m_vbo;
m_vbo =
new
QOpenGLBuffer;
m_vbo-&
gt;create();
m_vbo-&
gt;bind();
m_vbo-&
gt;allocate(m_logo.constData(), m_logo.count() *
sizeof
(GLfloat));
f-&
gt;glEnableVertexAttribArray(0
);
f-&
gt;glEnableVertexAttribArray(1
);
f-&
gt;glVertexAttribPointer(0
, 3
, GL_FLOAT, GL_FALSE, 6
*
sizeof
(GLfloat),
nullptr
);
f-&
gt;glVertexAttribPointer(1
, 3
, GL_FLOAT, GL_FALSE, 6
*
sizeof
(GLfloat),
reinterpret_cast
&
lt;void
*&
gt;(3
*
sizeof
(GLfloat)));
m_vbo-&
gt;release();
f-&
gt;glEnable(GL_DEPTH_TEST);
f-&
gt;glEnable(GL_CULL_FACE);
Resizing the window▲
The perspective needs to be aligned with the new window size as so:
void
GLWindow::
resizeGL(int
w, int
h)
{
m_proj.setToIdentity();
m_proj.perspective(45.0
f, GLfloat(w) /
h, 0.01
f, 100.0
f);
m_uniformsDirty =
true
;
}
Painting▲
We use QOpenGLExtraFunctions instead of QOpenGLFunctions as we want to do more than what GL(ES) 2.0 offers:
void
GLWindow::
paintGL()
{
// Now use QOpenGLExtraFunctions instead of QOpenGLFunctions as we want to
// do more than what GL(ES) 2.0 offers.
QOpenGLExtraFunctions *
f =
QOpenGLContext::
currentContext()-&
gt;extraFunctions();
We clear the screen and buffers and bind our shader program and texture:
f-&
gt;glClearColor(0
, 0
, 0
, 1
);
f-&
gt;glClear(GL_COLOR_BUFFER_BIT |
GL_DEPTH_BUFFER_BIT);
m_program-&
gt;bind();
m_texture-&
gt;bind();
Logic for handling an initial paintGL() call or a call after a resizeGL() call is implemented like so:
if
(m_uniformsDirty) {
m_uniformsDirty =
false
;
QMatrix4x4 camera;
camera.lookAt(m_eye, m_eye +
m_target, QVector3D(0
, 1
, 0
));
m_program-&
gt;setUniformValue(m_projMatrixLoc, m_proj);
m_program-&
gt;setUniformValue(m_camMatrixLoc, camera);
QMatrix4x4 wm =
m_world;
wm.rotate(m_r, 1
, 1
, 0
);
m_program-&
gt;setUniformValue(m_worldMatrixLoc, wm);
QMatrix4x4 mm;
mm.setToIdentity();
mm.rotate(-
m_r2, 1
, 0
, 0
);
m_program-&
gt;setUniformValue(m_myMatrixLoc, mm);
m_program-&
gt;setUniformValue(m_lightPosLoc, QVector3D(0
, 0
, 70
));
}
Last, we demonstrate a function introduced in OpenGL 3.1 or OpenGL ES 3.0:
f-&
gt;glDrawArraysInstanced(GL_TRIANGLES, 0
, m_logo.vertexCount(), 32
*
36
);
}
This works because we earlier requested 3.3 or 3.0 context.