Exemple de cube OpenGL ES 2.0
Montre comment faire pivoter manuellement un cube 3D texturé à partir des entrées de l'utilisateur.
L'exemple « Cube OpenGL ES 2.0 » montre comment faire pivoter manuellement un cube 3D texturé à partir des entrées de l'utilisateur, à l'aide d'OpenGL ES 2.0 avec Qt. Il explique comment gérer efficacement les géométries polygonales et comment écrire un shader de sommets et un shader de fragments simples pour un pipeline graphique programmable. De plus, il montre comment utiliser les quaternions pour représenter l'orientation d'un objet 3D.
Cet exemple a été écrit pour OpenGL ES 2.0, mais il fonctionne également avec OpenGL pour ordinateur de bureau, car il est suffisamment simple et que l’API OpenGL pour ordinateur de bureau est, pour l’essentiel, identique. Il se compile également sans prise en charge d’OpenGL, mais affiche alors simplement un message indiquant que la prise en charge d’OpenGL est requise.

L’exemple se compose de deux classes :
MainWidgetqui étend la classe ` QOpenGLWidget ` et contient l’initialisation et le dessin OpenGL ES 2.0, ainsi que la gestion des événements de souris et de minuterieGeometryEnginegère les géométries polygonales. Elle transfère la géométrie polygonale vers des objets de tampon de sommets et dessine les géométries à partir de ces objets.
Nous commencerons par initialiser OpenGL ES 2.0 dans ` MainWidget`.
Initialisation d’OpenGL ES 2.0
Comme OpenGL ES 2.0 ne prend plus en charge le pipeline graphique fixe, nous devons l’implémenter nous-mêmes. Cela rend le pipeline graphique très flexible, mais en même temps, cela complique les choses, car l’utilisateur doit implémenter le pipeline graphique pour faire fonctionner même l’exemple le plus simple. Cela rend également le pipeline graphique plus efficace, car l’utilisateur peut décider du type de pipeline nécessaire à l’application.
Nous devons d’abord implémenter le shader de sommets. Celui-ci reçoit comme paramètres les données de sommets et la matrice modèle-vue-projection (MVP). Il transforme la position des sommets à l’aide de la matrice MVP dans l’espace écran et transmet les coordonnées de texture au shader de fragments. Les coordonnées de texture seront automatiquement interpolées sur les faces des polygones.
void main()
{
// Calculate vertex position in screen space
gl_Position = mvp_matrix * a_position;
// Pass texture coordinate to fragment shader
// Value will be automatically interpolated to fragments inside polygon faces
v_texcoord = a_texcoord;
}Ensuite, nous devons implémenter la deuxième partie du pipeline graphique : le shader de fragment. Pour cet exercice, nous devons implémenter un shader de fragment chargé de la texturation. Il reçoit en paramètre les coordonnées de texture interpolées et recherche la couleur du fragment dans la texture fournie.
void main()
{
// Set fragment color from texture
gl_FragColor = texture2D(texture, v_texcoord);
}À l’aide de QOpenGLShaderProgram, nous pouvons compiler, lier et associer le code du shader au pipeline graphique. Ce code utilise des fichiers de ressources Qt pour accéder au code source du shader.
void MainWidget::initShaders()
{
// Compile vertex shader
if (!program.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/vshader.glsl"))
close();
// Compile fragment shader
if (!program.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/fshader.glsl"))
close();
// Link shader pipeline
if (!program.link())
close();
// Bind shader pipeline for use
if (!program.bind())
close();
}Le code suivant active la mémoire tampon de profondeur et le culling des faces arrière.
// Enable depth buffer
glEnable(GL_DEPTH_TEST);
// Enable back face culling
glEnable(GL_CULL_FACE);Chargement des textures à partir des fichiers de ressources Qt
L’interface ` QOpenGLWidget ` implémente des méthodes permettant de charger des textures depuis QImage vers la mémoire de textures OpenGL. Nous devons toutefois utiliser les fonctions fournies par OpenGL pour spécifier l’unité de texture OpenGL et configurer les options de filtrage des textures.
void MainWidget::initTextures()
{
// Load cube.png image
texture = new QOpenGLTexture(QImage(":/cube.png").flipped());
// Set nearest filtering mode for texture minification
texture->setMinificationFilter(QOpenGLTexture::Nearest);
// Set bilinear filtering mode for texture magnification
texture->setMagnificationFilter(QOpenGLTexture::Linear);
// Wrap texture coordinates by repeating
// f.ex. texture coordinate (1.1, 1.2) is same as (0.1, 0.2)
texture->setWrapMode(QOpenGLTexture::Repeat);
}Géométrie cubique
Il existe de nombreuses façons de rendre des polygones dans OpenGL, mais la plus efficace consiste à n’utiliser que des primitives de type « triangle strip » et à rendre les sommets à partir de la mémoire du matériel graphique. OpenGL dispose d’un mécanisme permettant de créer des objets tampons (buffer objects) dans cette zone de mémoire et de transférer les données de sommets vers ces tampons. Dans la terminologie OpenGL, on parle d’objets tampons de sommets (Vertex Buffer Objects, VBO).

Voici comment les faces d’un cube se décomposent en triangles. Les sommets sont ordonnés de cette manière afin d’assurer un ordre correct des sommets lors de l’utilisation de bandes de triangles. OpenGL détermine la face avant et la face arrière d’un triangle en fonction de l’ordre des sommets. Par défaut, OpenGL utilise un ordre antihoraire pour les faces avant. Cette information est utilisée par le culling des faces arrière, qui améliore les performances de rendu en omettant de rendre les faces arrière des triangles. De cette manière, le pipeline graphique peut omettre de rendre les côtés du triangle qui ne sont pas tournés vers l'écran.
La création d’objets de tampon de sommets et le transfert de données vers ceux-ci sont assez simples à l’aide de ` QOpenGLBuffer`. `MainWidget` s’assure que l’instance de `GeometryEngine` est créée et détruite avec le contexte OpenGL actif. De cette manière, nous pouvons utiliser les ressources OpenGL dans le constructeur et effectuer un nettoyage approprié dans le destructeur.
GeometryEngine::GeometryEngine()
: indexBuf(QOpenGLBuffer::IndexBuffer)
{
initializeOpenGLFunctions();
// Generate 2 VBOs
arrayBuf.create();
indexBuf.create();
// Initializes cube geometry and transfers it to VBOs
initCubeGeometry();
}
GeometryEngine::~GeometryEngine()
{
arrayBuf.destroy();
indexBuf.destroy();
}
// Transfer vertex data to VBO 0
arrayBuf.bind();
arrayBuf.allocate(vertices, 24 * sizeof(VertexData));
// Transfer index data to VBO 1
indexBuf.bind();
indexBuf.allocate(indices, 34 * sizeof(GLushort));Le dessin de primitives à partir de VBO et l’indication au pipeline graphique programmable de la manière de localiser les données de sommets nécessitent quelques étapes. Nous devons d’abord lier les VBO à utiliser. Ensuite, nous lions les noms d’attributs du programme de shader et configurons le type de données contenues dans le VBO lié. Enfin, nous dessinerons des primitives de bandes de triangles en utilisant les indices provenant de l’autre VBO.
void GeometryEngine::drawCubeGeometry(QOpenGLShaderProgram *program)
{
// Tell OpenGL which VBOs to use
arrayBuf.bind();
indexBuf.bind();
// Offset for position
quintptr offset = 0;
// Tell OpenGL programmable pipeline how to locate vertex position data
int vertexLocation = program->attributeLocation("a_position");
program->enableAttributeArray(vertexLocation);
program->setAttributeBuffer(vertexLocation, GL_FLOAT, offset, 3, sizeof(VertexData));
// Offset for texture coordinate
offset += sizeof(QVector3D);
// Tell OpenGL programmable pipeline how to locate vertex texture coordinate data
int texcoordLocation = program->attributeLocation("a_texcoord");
program->enableAttributeArray(texcoordLocation);
program->setAttributeBuffer(texcoordLocation, GL_FLOAT, offset, 2, sizeof(VertexData));
// Draw cube geometry using indices from VBO 1
glDrawElements(GL_TRIANGLE_STRIP, 34, GL_UNSIGNED_SHORT, nullptr);
}Projection en perspective
Grâce aux méthodes d’aide d’ QMatrix4x4, il est très facile de calculer la matrice de projection en perspective. Cette matrice sert à projeter les sommets dans l’espace écran.
void MainWidget::resizeGL(int w, int h)
{
// Calculate aspect ratio
qreal aspect = qreal(w) / qreal(h ? h : 1);
// Set near plane to 3.0, far plane to 7.0, field of view 45 degrees
const qreal zNear = 3.0, zFar = 7.0, fov = 45.0;
// Reset projection
projection.setToIdentity();
// Set perspective projection
projection.perspective(fov, aspect, zNear, zFar);
}Orientation de l’objet 3D
Les quaternions constituent un moyen pratique de représenter l’orientation d’un objet 3D. Bien qu’ils impliquent des calculs mathématiques assez complexes, toutes les opérations nécessaires sont heureusement implémentées dans QQuaternion. Cela nous permet de stocker l’orientation d’un cube sous forme de quaternion, et la rotation du cube autour d’un axe donné est alors très simple.
Le code suivant calcule l’axe de rotation et la vitesse angulaire à partir des événements de la souris.
void MainWidget::mousePressEvent(QMouseEvent *e)
{
// Save mouse press position
mousePressPosition = QVector2D(e->position());
}
void MainWidget::mouseReleaseEvent(QMouseEvent *e)
{
// Mouse release position - mouse press position
QVector2D diff = QVector2D(e->position()) - mousePressPosition;
// Rotation axis is perpendicular to the mouse position difference
// vector
QVector3D n = QVector3D(diff.y(), diff.x(), 0.0).normalized();
// Accelerate angular speed relative to the length of the mouse sweep
qreal acc = diff.length() / 100.0;
// Calculate new rotation axis as weighted sum
rotationAxis = (rotationAxis * angularSpeed + n * acc).normalized();
// Increase angular speed
angularSpeed += acc;
}QBasicTimer Il est utilisé pour animer la scène et mettre à jour l’orientation du cube. Les rotations peuvent être enchaînées simplement en multipliant les quaternions.
void MainWidget::timerEvent(QTimerEvent *)
{
// Decrease angular speed (friction)
angularSpeed *= 0.99;
// Stop rotation when speed goes below threshold
if (angularSpeed < 0.01) {
angularSpeed = 0.0;
} else {
// Update rotation
rotation = QQuaternion::fromAxisAndAngle(rotationAxis, angularSpeed) * rotation;
// Request an update
update();
}
}La matrice modèle-vue est calculée à l’aide du quaternion et en déplaçant le monde le long de l’axe Z. Cette matrice est multipliée par la matrice de projection afin d’obtenir la matrice MVP pour le programme de shader.
// Calculate model view transformation
QMatrix4x4 matrix;
matrix.translate(0.0, 0.0, -5.0);
matrix.rotate(rotation);
// Set modelview-projection matrix
program.setUniformValue("mvp_matrix", projection * matrix);© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.