5. Hello window▲
Nous allons mettre en place GLFW et le faire fonctionner. Commençons par créer un fichier .cpp et ajoutons les fichiers d’en-tête :
#include
<glad/glad.h>
#include
<GLFW/glfw3.h>
Il faut inclure glad.h avant glfw3.h car le fichier glad.h inclut les fichiers d'en-tête adéquats d’OpenGL (GL/gl.h) nécessaires pour les autres dépendances.
Nous créons ensuite la fonction main() qui va instancier la fenêtre GLFW :
int
main()
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3
);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3
);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
return
0
;
}
Dans la fonction main(), on initialise GLFW et on le configure avec glfwWindowHint. Le premier argument indique quelle option nous configurons parmi toutes les possibilités offertes, le second argument spécifie la valeur de cette option. Une liste de toutes ces options est disponible ici : GLFW's window handling. Si l’on essaie de lancer l’application maintenant, on obtient des erreurs de type undefined reference signifiant que l’on n’est pas lié correctement à la librairie GLFW. Il faut préciser que nous travaillons avec la version 3.3 d'OpenGL. En indiquant la version majeure (3) et la version mineure (3), GLFW créera le contexte correct. Si l'ordinateur de l'utilisateur ne supporte pas cette version, le lancement de GLFW échouera. On doit aussi préciser que nous utilisons le core-profile. Cela signifie que nous aurons accès à un sous-ensemble plus petit des caractéristiques d’OpenGL (sans les fonctionnalités de compatibilité avec les anciennes versions).
Notons que sur Mac OS X vous aurez aussi besoin d’ajouter glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); pour un bon fonctionnement.
Assurez-vous d’avoir au moins la version 3.3 d’OpenGL sur votre machine sinon ça va planter ou produire un résultat non voulu. Pour trouver la version d’OpenGL installée sur votre machine, utiliser glxinfo sur Linux ou un utilitaire comme OpenGL Extension Viewer pour Windows. Si votre version n’est pas assez récente, vérifiez que votre carte graphique supporte OpenGL 3.3+ (sinon c’est vraiment ancien) et mettez à jour vos pilotes.
Ensuite, nous créons un objet fenêtre, laquelle contiendra toutes les données de fenêtrage et qui sera très souvent utilisée par les autres fonctions GLFW :
GLFWwindow*
window =
glfwCreateWindow(800
, 600
, "LearnOpenGL"
, NULL
, NULL
);
if
(window ==
NULL
)
{
std::
cout <<
"Failed to create GLFW window"
<<
std::
endl;
glfwTerminate();
return
-
1
;
}
glfwMakeContextCurrent(window);
Cette fonction requiert de passer la largeur et la hauteur de la fenêtre, le nom de cette fenêtre, et l’on peut ignorer le reste. Elle retourne un objet dont nous aurons besoin par la suite. Après cela, on indique à GLFW que le contexte de notre application sera le contexte de notre fenêtre.
5-1. GLAD▲
Nous avons indiqué précédemment que GLAD gère les pointeurs des fonctions OpenGL, il faut donc initialiser GLAD avant d’appeler la première fonction OpenGL :
if
(!
gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::
cout <<
"Failed to initialize GLAD"
<<
std::
endl;
return
-
1
;
}
Nous passons à GLAD la fonction permettant de charger les adresses des pointeurs de fonctions OpenGL, lesquelles dépendent du système d’exploitation. GLFW offre une fonction générique glfwGetProcAddress() à cet effet.
5-2. Viewport▲
Avant d’effectuer le premier rendu, une chose reste à faire. Nous devons donner à OpenGL la taille de la fenêtre de rendu pour qu’OpenGL effectue ce rendu conformément à cette fenêtre. On spécifie ces dimensions avec la fonction glViewport() :
glViewport(0
, 0
, 800
, 600
);
Les deux premiers paramètres définissent la place du coin en bas à gauche de la fenêtre. On donne ensuite les dimensions de la fenêtre.
Nous pourrions définir des dimensions plus petites pour la zone de rendu (viewport), et OpenGL effectuerait ses rendus dans une zone plus petite, laissant la place restante pour d’autres affichages indépendants d’OpenGL.
En interne, OpenGL utilise les données spécifiées avec glViewport() pour transformer les coordonnées 2D qu’il calcule en coordonnées écran. Par exemple, un point situé en (-0.5, 0.5) serait affiché en (200, 450) en coordonnées écran. Notons que les coordonnées OpenGL sont entre -1 et +1, et ainsi on projette l’intervalle [-1, +1] sur [800, 600].
Cependant, si l’utilisateur redimensionne la fenêtre, la zone de rendu doit être ajustée à une nouvelle valeur. On réalise cela au moyen d’une fonction callback qui sera appelée à chaque fois que la fenêtre est redimensionnée. Le prototype de cette fonction est le suivant :
void
framebuffer_size_callback(GLFWwindow*
window, int
width, int
height);
Cette fonction prend en premier argument une fenêtre de type GLFWwindow et ensuite deux valeurs pour les nouvelles dimensions. À chaque redimensionnement de la fenêtre, GLFW appelle cette fonction callback pour ajuster la taille du port d’affichage.
void
framebuffer_size_callback(GLFWwindow*
window, int
width, int
height)
{
glViewport(0
, 0
, width, height);
}
Il faut indiquer cette fonction callback pour qu’elle soit appelée à chaque redimensionnement de la fenêtre :
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
Lorsque la fenêtre est affichée une première fois, framebuffer_size_callback() est exécutée, produisant ainsi les bonnes dimensions. Notons que pour les écrans Retina, largeur et hauteur donneront des valeurs plus grandes que prévu.
Il existe beaucoup de fonctions callback pour enregistrer le comportement de l’application. Par exemple, pour traiter les entrées joystick, gérer les messages d’erreur, etc. Nous installerons ces fonctions après avoir créé la fenêtre, mais avant que la boucle de rendu ne soit initiée.
5-3. Prêts pour le départ▲
Nous ne voulons pas que l’application affiche une seule image et se termine aussitôt, mais plutôt qu’elle continue à afficher des images en tenant compte des entrées utilisateur, ceci jusqu’à ce qu’on la stoppe. Pour cela, il faut créer une boucle, que nous appellerons la boucle de rendu, qui tourne tant que l’on ne l’arrêtera pas. Voici une boucle de rendu très simple :
while
(!
glfwWindowShouldClose(window))
{
glfwSwapBuffers(window);
glfwPollEvents();
}
La fonction glfwWindowShouldClose() vérifie à chaque passage que l’on ne souhaite pas fermer la fenêtre, auquel cas il nous resterait à fermer l’application. La fonction glfwPollEvents() regarde si un événement est survenu (comme une entrée clavier ou un mouvement de la souris), met à jour l’état de la fenêtre et appelle les fonctions callback correspondantes. La fonction glfwSwapBuffers() change le tampon (buffer) des couleurs (un tampon qui contient la couleur de chaque pixel de la fenêtre), pour afficher le contenu du tampon calculé pendant cette itération, selon la méthode du double buffer.
Double buffer : Lorsqu’une application n’utilise qu’un seul tampon, l’image résultante peut donner un effet de scintillement (flickering), car l’image n’est pas affichée d’un seul coup mais pixel par pixel. Si l’image, du fait des calculs, est modifiée pendant l’affichage, on constate des effets indésirables. Pour résoudre cela, les applications graphiques utilisent deux tampons. Le tampon avant (front buffer) contient l’image finale affichée sur l’écran, tandis que la construction de l’image est réalisée dans le tampon arrière (back buffer). Lorsque l’image est prête, on intervertit (swap) les deux buffers, affichant ainsi une image cohérente.
5-4. Une dernière chose▲
Lorsque l’on quitte la boucle de rendu, on souhaite fermer l’application proprement en effaçant ou supprimant les ressources que nous avons allouées :
glfwTerminate();
return
0
;
Essayez maintenant de compiler votre application et vous devriez voir apparaître votre première fenêtre :
Si cette image noire est réellement ennuyeuse, vous avez réussi ! Sinon, vous pouvez consulter le code ici.
En cas de souci, vérifiez vos options de compilation et notamment celles de l'éditeur de lien, et aussi que vous avez correctement inclus les fichiers d'en-tête. Vérifiez votre code en le comparant au code ci-dessus. Sinon, utilisez le fil de discussion pour poser une question ou voir les problèmes des autres lecteurs, et quelqu’un vous aidera.
5-5. Les entrées▲
Nous voulons aussi un minimum de contrôle au moyen des entrées utilisateur dans GLFW et, dans un premier temps, nous réaliserons cela avec glfwGetKey() qui prend en paramètre la fenêtre et la touche clavier utilisée. Cette fonction nous permet de savoir si cette touche est actuellement appuyée. Nous créons une fonction processInput() pour gérer cela :
void
processInput(GLFWwindow *
window)
{
if
(glfwGetKey(window, GLFW_KEY_ESCAPE) ==
GLFW_PRESS)
glfwSetWindowShouldClose(window, true
);
}
Ici, nous regardons si la touche Échap est utilisée (sinon, la fonction retourne GLFW_RELEASE). Si c'est le cas, nous fermons la fenêtre en positionnant la propriété WindowShouldClose à true grâce à la fonction glfwSetWindowShouldClose(). Le test de la boucle de rendu va échouer et ainsi l’application se fermera.
Ce test est effectué à chaque passage dans la boucle de rendu :
while
(!
glfwWindowShouldClose(window))
{
processInput(window);
glfwSwapBuffers(window);
glfwPollEvents();
}
Cela nous donne un moyen simple de tester des touches du clavier et de réagir en conséquence.
5-6. Le rendu▲
Nous allons placer toutes les commandes de rendu dans la boucle, car nous souhaitons les exécuter à chaque passage :
// boucle de rendu
while
(!
glfwWindowShouldClose(window))
{
// input
processInput(window);
// rendering commands here
...
// check and call events and swap the buffers
glfwPollEvents();
glfwSwapBuffers(window);
}
Juste pour tester que les choses fonctionnent bien, nous allons colorer la fenêtre avec une couleur de notre choix. Au début de chaque rendu, nous effacerons l’écran, sinon nous verrions encore le résultat de l’itération précédente (cela peut être un effet voulu, mais souvent ce n'est pas le cas). On peut effacer le tampon de la couleur écran en utilisant la fonction glClear(), en spécifiant quels tampons nous voulons effacer. Les tampons possibles sont GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT et GL_STENCIL_BUFFER_BIT. Pour l’instant nous nous intéressons seulement aux couleurs :
glClearColor(0.2
f, 0.3
f, 0.3
f, 1.0
f);
glClear(GL_COLOR_BUFFER_BIT);
Nous avons choisi la couleur avec glClearColor(). À chaque appel de glClear(), le tampon sera entièrement rempli avec la couleur définie. On aura donc un fond bleu-gris ici.
Rappelez-vous le tutoriel OpenGL, la fonction glClearColor() est une fonction modifiant l’état et glClear() est une fonction pour mettre en service cet état.
Le code de cette application se trouve ici.
On a désormais tout ce qu’il faut pour enrichir notre boucle de rendu, mais ce sera pour le tutoriel suivant.
5-7. Remerciements▲
Ce tutoriel est une traduction réalisée par Jean-Michel Fray dont l’original a été écrit par Joey de Vries et qui est disponible sur le site Learn OpenGL.