2/17
2. Création d’une fenêtre graphique
Fenêtre graphique
La première étape essentielle d’un programme graphique est d’être capable d’afficher une fenêtre dans laquelle les résultats visuels vont pouvoir être affichés. Cette étape est, dans une très large part, indépendante de d’OpenGL, ou plus généralement, de l’approche mis en place pour réaliser le rendu. Cependant, la fenêtre doit être capable d’interagir avec les données graphiques et de se synchroniser avec une application OpenGL. Cette fenêtre doit ainsi être compatible avec ce que l’on appelle un contexte OpenGL.
La création d’une fenêtre sans bibliothèque externe est une opération complexe qui dépend du système d’exploitation. Dans la plupart des cas, une bibliothèque dédiée à la gestion d’une fenêtre est utilisée pour gérer cette étape. Dans notre cas, nous utilisons la bibliothèque GLFW. GLFW est une bibliothèque C légère capable de créer des fenêtres compatibles avec un contexte OpenGL. GLFW permet également de gérer des évènements utilisateur tels que les clics/déplacement souris, ou l’appui sur les touches du clavier.
On notera qu’il existe plusieurs bibliothèques (Window Toolkits) capables de gérer des fenêtres compatibles avec un contexte OpenGL.
-
GLFW possède l’avantage d’être simple et légère à mettre en place et utiliser. GLFW se focalise sur la création de fenêtres et la gestion d’évènements et laisse libre le programmeur pour adapter la manière dont il organise son code.
-
A l’opposé, Qt, ou GTK+ sont des bibliothèques de plus grande envergure. Elles proposent des widgets graphiques (bouttons, sliders, etc) et des outils de développement supplémentaires. Cependant, utiliser ces bibliothèques vient généralement forcer le code à entrer dans un framework spécifique à la bibliothèque (ex. boucles d’animation gérée par la bibliothèque, dérivation des classes Qt, etc.).
Le code GLFW associé à la création d’une fenêtre graphique est le suivant
[ code ]
// Include for GLFW headers - library handling the window
#include <GLFW/glfw3.h>
int main()
{
// Initialize GLFW
// This step is required before creating the window
glfwInit();
// Create a new Window of size 500x500 with title "My Window"
// The two last parameters are
// - the monitor for full screen mode (or nullptr for windowed mode in this case)
// - another pointer to a window in the case where ressources are shared (or nullptr to not share ressources).
// The function returns a pointer to the newly created window (or nullptr in case of error).
GLFWwindow* window = glfwCreateWindow(500, 500, "My Window", nullptr, nullptr);
// User defined loop
// This loop will be active until the user close the window
while( !glfwWindowShouldClose(window) ) {
glfwSwapBuffers(window); // Double buffering (will be used to avoid flickering when animating a scene)
glfwPollEvents(); // Handle GLFW events (ex. mouse clicks, etc)
}
glfwTerminate(); // Close the GLFW Window
return 0;
}
On supposera dans la suite que GLFW3 est installé sur votre système, ce qui est le cas pour les ordinateurs de l’école (ex. sous une distribution Ubuntu apt-get install libglfw3-dev)
Rem.
-
Il n’est pas nécessaire de compiler spécifiquement ce code, vous pouvez passer à la suite.
-
Si vous n’avez pas l’habitude de compiler du code sur votre machine personnelle (Windows (Visual Studio), Mac), il est préférable de réaliser les tutoriaux de cette séance sur les machines de l’école. Vous n’aurez pas d’étapes supplémentaires à réaliser. Le code fourni à partir de la séance prochaine contiendra des dépendances externes et un fichier de setup (CMakeLists.txt) compatible avec Windows (sous Visual Studio).
Chargement des fonctions OpenGL
La seconde étape consiste à mettre en place un contexte OpenGL et à donner accès aux fonctions d’OpenGL. En effet, par défaut, le système n’a pas accès aux fonctions OpenGL récentes, celles-ci doivent être chargées spécifiquement en fonction de la version d’OpenGL souhaitée.
Pour cela, il est possible d’utiliser une bibliothèque de chargement d’OpenGL (OpenGL loader library). Nous utiliserons dans notre cas la librairie Glad dans le cas de l’utilisation d’OpenGL 3.3 (core profile).
-
OpenGL 3.3 est une version compatible avec une large gamme de systèmes, même avec une carte graphique d’ancienne génération. Et propose tout de même une approche de programmation OpenGL moderne. À titre d’information, la dernière version d’OpenGL est 4.6. Celle-ci est accessible aux cartes graphiques plus récentes. Dans le cadre de ces travaux pratiques, il n’y aurait pas de différence majeure de code entre une version 3.3 et 4.6.
-
La notion de core profile indique que l’on restreint l’utilisation d’OpenGL aux fonctions récentes, et n’utilisons plus les fonctions dépréciées des anciennes versions. Bien qu’il soit possible de le faire, une bonne pratique consiste à ne pas mélanger l’utilisation de fonctions dépréciées des versions antérieures d’OpenGL (ex. OpenGL 1) avec les versions récentes.
Dans le cas des TD, le code généré par GLAD est directement inclus dans les sources que vous allez télécharger. A titre d’information, le code généré par GLAD permettant le chargement des fonctions OpenGL est obtenu par ce service web.
Code
-
Téléchargez l’archive contenant le code suivant Lien vers l’archive
-
Décompressez l’archive dans le répertoire de votre choix
-
Compilez le code (le fichier README rappelle les instructions sur un système Linux)
-
Par exemple, en utilisant CMake (outil de compilation)
-
Ouvrez une ligne de commande dans le répertoire
create_window_opengl_context/puis tapez les lignes de commandes suivantes
-
$ cd cmake # Place la ligne de commande dans le répertoire cmake/
$ mkdir build # Création d'un répertoire pour les fichiers de compilation temporaires
$ cd build
$ cmake .. # Lancement de l'outil CMake dans ce répertoire
$ cd ../.. # Place la ligne de commande dans le dossier racine
$ make -C cmake/build # Lance la compilation depuis le dossier racine
$ cmake/build/pgm # Execution du programme
-
Si tout fonctionne correctement, au moment de l’exécution, une fenêtre devrait apparaitre, et les informations relatives à votre carte graphique et driver OpenGL installé sur votre système devrait être affiché sur ligne de commande.
Note: Il est normal que rien ne s’affiche dans la fenêtre (potentiellement noire, texture du bureau ou couleur aléatoire en fonction du système) car aucune demande d’affichage n’est réalisée.
[ code ]
#include "../external/glad/include/glad/glad.hpp" // glad.h should be included before glfw or any OpenGL related include
#include <GLFW/glfw3.h>
#include <iostream>
int main()
{
glfwInit();
// Indicate to GLFW to setup context compatible with OpenGL 3.3 core profile
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, 1);
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, 1);
glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(500, 500, "My Window", nullptr, nullptr);
if( window==nullptr ) {
std::cerr<<"Error creating Window"<<std::endl;
exit(1);
}
// Enable the window to handle OpenGL Context
glfwMakeContextCurrent(window);
// Load OpenGL Functions
gladLoadGL();
// Print OpenGL Information
std::cout << "OpenGL information: VENDOR : " << glGetString(GL_VENDOR) <<std::endl;
std::cout << " RENDERDER : " << glGetString(GL_RENDERER) <<std::endl;
std::cout << " VERSION : " << glGetString(GL_VERSION) <<std::endl;
std::cout << " GLSL VERSION: " << glGetString(GL_SHADING_LANGUAGE_VERSION)<<std::endl;
while( !glfwWindowShouldClose(window) ) {
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwTerminate();
return 0;
}
Rem. Ne pas copier/coller le code de cette fonction main dans l’archive de l’exemple précédent (qui n’incluait pas les dépendances à glad), mais téléchargez entièrement la nouvelle archive.
Modification de la couleur de fond
Dans une scène OpenGL classique,contenant une animation ou une interaction utilisation, l’affichage de l’image est réactualisé plusieurs fois par secondes. Cette demande régulière d’affichage est réalisée par la boucle contenue dans l’instruction.
while( !glfwWindowShouldClose(window) ){ ... }
Cette boucle est ainsi exécutée tant que l’utilisateur ne ferme pas la fenêtre. A chaque itération de cette boucle, il est courant d’effacer entièrement l’écran pour réafficher la scène dans sa totalité. Pour obtenir une impression d’animation fluide, chaque nouvelle image doit être recalculée en, au plus, 40ms (soit 25 images par secondes - fps).
Modifiez le code de la boucle de la manière suivante
while( !glfwWindowShouldClose(window) ) {
// Set the (R,G,B,A) color to clear the screen
glClearColor(1.0f, 1.0f, 0.5f, 1.0f);
// Clear the screen (designated by the color buffer)
glClear(GL_COLOR_BUFFER_BIT);
glfwSwapBuffers(window);
glfwPollEvents();
}
Vérifiez que la fenêtre s’affiche désormais en jaune.
Explications
-
Premièrement, une couleur (clearColor) est choisie d’après ces composantes RGBA (Red-Green-Blue-Alpha) en utilisant la fonction glClearColor. Chaque composante est comprise entre 0 (plus sombre) et 1 (le plus clair). Les valeurs RGB ont un impact sur la couleur obtenue. La composante A n’est pas utilisée ici et n’a pas d’impact (la composante A peut être utilisée, en particulier, pour gérer de la transparence dans certaines configurations).
-
Dans un second temps, le buffer de couleur, c’est-à-dire l’image visualisée dans la fenêtre, est effacé par la fonction glClear avec l’argument GL_COLOR_BUFFER_BIT. Plus précisément, glClear permets ici d’écrire sur l’intégralité de l’image la couleur préalable définie par glClearColor.
Rem. Les fonctions glClearColor et glClear sont des fonctions OpenGL. On retrouvera la même convention de nom pour l’ensemble des fonctions et variables OpenGL.
-
Les fonctions OpenGL sont dénommés glFunctionName
-
Les valeurs constantes prédéfinies par OpenGL sont des mots en majuscules, séparées par underscore (ex. GL_COLOR_BUFFER_BIT).
Exercice
Adaptez le code de manière à faire varier la couleur du fond de manière continue entre différentes couleurs.
Aide
-
Il est possible de créer une valeur flottante variant de manière périodique dans [0,1] à l’intérieur de la boucle d’animation.
-
Il est également possible d’obtenir différentes variations en utilisant des fonctions mathématiques (
#include <cmath>pour donner accès aux fonctions) telles que std::cos, std::abs, etc. Notez qu’il est également possible de créer une valeur périodique à l’aide de l’opérateur modulo % appliqué à un entier.