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▲
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.