OpenGL ES2 avec Qt 5, Qt Designer en C++

Pierre Bénet

Cet article explique comment mettre en place OpenGL ES2 avec shader sur Qt 5 avec une interface réalisée avec Qt Designer.

Cette configuration est particulièrement adaptée pour réaliser un logiciel de CAO, de visualisation ou un jeu vidéo simplement et efficacement avec Qt.

Le but est de pouvoir d’un côté utiliser toutes les possibilités qu’offrent les shaders pour obtenir des graphismes personnalisables et performants tout en ayant la possibilité de construire facilement une interface utilisateur avec Qt Designer.

Cette solution est plus flexible que Qt 3D, car on garde la main sur l’intégralité du processus de rendu. Elle permet aussi d’intégrer du code OpenGL existant à une application Qt.

Commentez

- Titre : OpenGL ES2 avec Qt 5, Qt Designer en C++

- Auteur : Pierre Bénet

- Parution : 9 mai 2020

- Mise à jour : 11 mai 2020

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2020 pierre-benet. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Création du projet

- II. Création de l’interface

- II-A. Disposition

- III. Ajout d’OpenGL

- III-A. Héritage

- IV. Gestion des évènements

- IV-A. Connexions automatiques

- IV-B. Connexions semi-automatiques

- IV-C. Fonction override

- V. Sources

- V-A. Pour aller plus loin

- VI. Remerciements

I - Création du projet

Lancez Qt Creator et allez dans :

Accueil → Projets → New → Application → Qt Widgets Application.

Choisissez un emplacement et un nom pour le projet : par exemple, « qtgl2 ».

Laissez par défaut la configuration, à savoir Base class « QMainWindow » et l’option « Generate form » cochée.

Ensuite, choisissez le langage et la version de Qt que vous souhaitez utiliser (Qt 5.4 minimum(1)). Une fois terminé, vous pouvez cliquer sur l’icône verte Play pour compiler et exécuter le programme. Celui-ci affiche une fenêtre blanche « MainWindow » dans le cas où tout fonctionne bien.

Dans l’arborescence, cinq fichiers ont été créés :

- qtgl2.pro : le fichier de configuration du projet, il liste les fichiers du projet pour que Qt Creator puisse les compiler ;

- mainwindow.h : la classe qui contient notre fenêtre ;

- mainwindow.cpp : l’implémentation de cette classe ;

- mainwindow.ui : un fichier XML utilisé pour stocker l’interface que vous aurez créée avec l’interface de design. Ce fichier va être utilisé par Qt Creator pour générer automatiquement une classe qui sera compilée avec le reste du projet ;

- main.cpp : le lanceur du programme.

II - Création de l’interface

Tout d’abord nous allons réaliser la disposition des éléments de notre interface. Nous voulons :

- un bouton « fullscreen » à l’intérieur du menu ;

- un rendu OpenGL qui prend presque toute la place ;

- un curseur pour zoomer ;

- une zone de texte en dessous du curseur.

Pour créer cette interface dans Qt Designer, il faut double-cliquer dans l’arborescence sur mainwindow.ui, cela ouvrira automatiquement l’interface de Qt Designer. Nous pouvons alors glisser-déposer où bon nous semble les boutons et autres widgets.

Pour le plein écran : cliquez sur « Taper ici », écrivez « show », retour à la ligne puis « fullscreen » et retour à la ligne.

Pour le reste, il faut glisser déposer les « Widgets », unQOpenGLWidget, unQLabelet un QSlider.

II-A - Disposition

Nous voulons que notreQOpenGLWidgetprenne toute la place possible en fonction que l’on agrandit ou rétrécit la fenêtre. Pour cela, on applique une disposition à notre « centralwidget » :

- clic droit sur MainWindow→ mettre en page→ mettre en page dans une grille ;

- clic gauche sur QLabel → sizePolicy → politique verticale → Fixed.

Nous voulons aussi supprimer les espaces vides en bordure :

- clic gauche sur centralwidget → Layout → mettre toutes les marges à 0.

Une fois les modifications faites, le widget OpenGL (en noir) prend toute la place possible.

III - Ajout d’OpenGL

Nous allons récupérer les classes de l’exemple cube openglES 2 de Qt. Nous allons ici récupérer sept fichiers :

- geometryengine.cpp et geometryengine.h : une classe pour générer un cube avec la position des sommets du cube, la coordonnée des textures, l’association des sommets pour former les faces, et le chargement en mémoire du cube dans le processeur graphique ;

- mainwidget.cpp et mainwidget.h : une classe héritée deQOpenGLWidgetqui permet de charger les shaders et créer un contexte OpenGL ;

- vshader.glsl et fshader.glsl : le vertex et le fragment shader. Ils sont très simples ;

- cube.png : une image contenant la texture d’un dé à six faces.

On déplace donc ces fichiers et on les met dans le répertoire du projet à côté des autres sources, puis on les intègre au projet :

- dans l’arborescence du projet, clic droit sur qtgl2 → Ajouter des fichiers existants.

Pour l’instant, l’ajout de ces fichiers n’a aucun effet sur le programme. D’abord, nous allons créer l’interface, ensuite nous allons intégrer les classes OpenGL pour qu’elles interagissent avec l’interface, c’est la partie héritage.

III-A - Héritage

La classeMainWidgetdoit être à la place de notreQOpenGLWidgetdont elle hérite. Pour cela, on l’indique à Qt Designer :

- clic droit sur QOpenGLWidget→promouvoir en → nom de la classe promue → MainWidget →ajouter → promouvoir.

Après avoir fait attention aux chemins des shaders et textures dans mainwidget.cpp :

"../qtgl2/vshader.glsl"

"../qtgl2/fshader.glsl"

"../qtgl2/cube.png"

Le programme fonctionne et le rendu OpenGL ES2 du dé est bien intégré.

IV - Gestion des évènements

Pour l’instant, nos boutons sont inactifs. Il y a plusieurs façons pour les rendre interactifs, toujours à l’aide des signaux et slots de Qt.

IV-A - Connexions automatiques

Qt peut créer automatiquement une fonction qui s’effectuera seulement lorsque l’évènement se produit. Pour ce faire, dans Qt Designer :

- clic droit sur horizontalSlider →Aller au slot → valueChanged.

À ce moment, Qt creator crée une fonction dans la classe MainWindow avec un nom particulier. Ce nom particulier permet la connexion automatique de l’évènement avec cette fonction grâce à la fonctionQMetaObject::connectSlotsByName(MainWindow);qui est appelée dans le constructeur de la classeMainWindow.

Qt Creator nous envoie directement sur l’implémentation de la fonction afin que nous puissions la personnaliser. C’est une fonction membre deMainWindowque nous allons personnaliser. Il faut savoir que cette classe a automatiquement un membreuiqui possède des pointeurs vers tous les objets créés dans l’interface. Vous pouvez en savoir plus sur cet objetuien allant voir son implémentation dans le fichier « ui_mainwindow.h », généré automatiquement lors de la compilation.

Ici, nous allons utiliser la position du curseur pour gérer la profondeur de l’objet. Au passage, la fonction appelle update, car l’affichage du widget OpenGL aura changé. Nous allons donc remplir deux lignes de code dans cette fonction :

void MainWindow::on_horizontalSlider_valueChanged(int value)
{
// passe le paramètre du slider au widget
ui->openGLWidget->depth=value;
// appelle l'affichage opengl
ui->openGLWidget->update();
}

IV-B - Connexions semi-automatiques

Il arrive que Qt Designer ne propose pas « d’aller au slot » ou que vous n’ayez pas utilisé Qt Designer pour créer vos interfaces. Dans ce cas, il suffit de définir vous-même une fonction avec le bon nom associé. Pour cela, il faut que la fonction soit de la forme :

void MainWindow::on_<object name>_<signal name>(<signal parameters>);

Nous allons en profiter pour créer la fonctionnalité de plein écran. Il suffit de définir la fonction :

void MainWindow::on_actionfullscreen_triggered()
{
ui->label->setVisible(false);
ui->statusbar->setVisible(false);
ui->menubar->setVisible(false);
ui->horizontalSlider->setVisible(false);
showFullScreen();
}

Ici, on désactive tous les widgets différents du widget OpenGL et on passe en plein écran.

IV-C - Fonction override

Une fois en plein écran, aucun bouton n’est accessible. On veut alors utiliser la touche échappe pour revenir à l’affichage en fenêtre.

Pour cela, on redéfinit la fonctionkeyPressEvent:

void MainWindow::keyPressEvent(QKeyEvent* event)
{
if (event->type()==QEvent::KeyPress) {
QKeyEvent* key = static_cast<QKeyEvent*>(event);
if ( (key->key()==Qt::Key_Escape)  ) {
ui->label->setVisible(true);
ui->statusbar->setVisible(true);
ui->menubar->setVisible(true);
ui->horizontalSlider->setVisible(true);
showNormal();
}
}
}

V - Sources

qtgl2.zip

V-A - Pour aller plus loin

L’exemple cube OpenGL de Qt offre une architecture intéressante. C’est une bonne base pour aller plus loin. Le fait que la fonction d’affichage du modèle prenne en paramètre un shader permet de souligner l’importance d’une telle architecture. En pratique, pour des affichages OpenGL plus poussés, le programmeur devra avoir un affichage fortement dynamique et utiliser différents shaders pour un même modèle.

- Si le modèle est loin, un shader moins précis, mais plus léger sera utilisé.

- Si l’on souhaite réaliser des ombres, un shader spécial sera utilisé pour créer une texture de profondeur.

- On peut aussi vouloir d’autres passes de rendu pour l’anticrénelage ou simplement pour l’optimisation (deferred rendering).

VI - Remerciements

Merci à dourouc5 pour sa relecture technique et merci à ClaudeLELOUP pour sa relecture orthographique.

(1)Première version proposant QOpenGLWidget, précédemment QGLWidget, obsolète.