Sommaire
Salut 'nal,
Si tu suivi mon journal précédent, tu as maintenant les éléments de contexte pour programmer en 3D !
On va passer à la pratique avec un exemple OpenGL 1.1 (cf. spécification), qui correspond au code typique des années fin 90-début 2000. Mais qui, magie de la rétrocompatibilité, tourne encore très bien aujourd'hui.
Installer une "glu" de fenêtrage : SDL3
OpenGL s'occupe très peu du système de fenêtrage. Eh oui, ce n'est qu'une API de dessin : gérer les événements clavier-souris-fenêtre de l'OS est en dehors de son périmètre.
C'est ainsi que chaque système fournit sa bibliothèque maison pour ça :
- sous X11 : Mesa fournit l'API GLX directement dans la bibliothèque principale libGL. Cette fusion entre les deux est d'ailleurs une erreur de conception, qui sera évitée dans la future bibliothèque OpenGL ES pour l'embarqué ;
- sous Wayland (et récemment X11) : le Khronos Group fournit l'API "universelle" EGL ;
[…]
- les autres OS : ne supportent en réalité pas nativement EGL, on y utilise p.ex. ça ou ça ;
- et en général avec Vulkan : universel mais rien à voir 😛.
Vous l'avez compris, ce fatras nous éloigne du sujet… on va donc faire comme des milliers de codeurs avant nous : utiliser une bibliothèque de "glu" multi-plateforme ! (Free)GLUT et GLFW sont encore très utilisées…
Mais nous préférerons SDL 3 car elle supporte toutes les combinaisons ET nous aidera plus tard pour Vulkan.
Elle n'est juste pas encore empaquetée dans les distributions les plus courantes, alors installons-la rapidement :
Prérequis
Debian/Ubuntu :
sudo apt install build-essential git make \
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxtst-dev \
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev
Fedora/RHEL :
sudo dnf install gcc git-core make cmake \
alsa-lib-devel pulseaudio-libs-devel pipewire-devel \
libX11-devel libXext-devel libXrandr-devel libXcursor-devel libXfixes-devel \
libXi-devel libXScrnSaver-devel dbus-devel ibus-devel \
systemd-devel mesa-libGL-devel libxkbcommon-devel mesa-libGLES-devel \
mesa-libEGL-devel vulkan-devel wayland-devel wayland-protocols-devel \
libdrm-devel mesa-libgbm-devel libusb1-devel libdecor-devel \
pipewire-jack-audio-connection-kit-devel
Compiler
git clone https://github.com/libsdl-org/SDL --branch release-3.2.20
cd SDL
cmake -S . -B build
cmake --build build -j `nproc`
Installer
cmake --install build --prefix $HOME/sdl3220
# configurer
sed -i 's!/usr/local!'"$HOME"'/sdl3220!g' $HOME/sdl3220/lib/pkgconfig/sdl3.pc
echo 'export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$HOME/sdl3220/lib"' >> $HOME/.profile
echo 'export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$HOME/sdl3220/lib/pkgconfig"' >> $HOME/.profile
# (pour éviter un redémarrage)
source $HOME/.profile
Tester
pkg-config --modversion sdl3
# (devrait vous renvoyer :)
3.2.20
La base de tout : la croix
Nous allons afficher une croix plate interpolée en 2D, composée de 4 points reliés : rouge, vert, bleu, blanc.
et utilisant ce code source C.
Pour le récupérer et me suivre pendant que je détaille, faites sur votre Linux :
git clone https://github.com/Tarnyko/suave_code_samples
cd suave_code_samples/tree/master/C/opengl-vulkan/
editor sdl3-gl1.c
Points & Coordonnées
Le repère orthonormé par défaut d'OpenGL s'étend de -1.0 à +1.0 sur tous les axes. Cela se change, mais nous conviendra très bien pour commencer 😉.
Si on place les 4 points plutôt vers le bord :
on choisit 0.8,-0.8 comme coordonnées symétriques :
ça se matérialise facilement en code :
#define LINES 2
static const GLfloat vertex_arr[LINES * 4] = { // 2 * 4 = 8 valeurs flottantes
-0.8f, 0.8f, 0.8f, -0.8f, // point ROUGE, point VERT
-0.8f, -0.8f, 0.8f, 0.8f, // point BLEU, point BLANC
};
(chacune des 2 lignes étant constituée de 2 points eux-mêmes définis par 2 coordonnées [X,Y], cela nous donne 2 x 2 x 2 = 8 coordonnées)
Couleurs
Pour les couleurs, bien qu'on puisse utiliser des flottants également, j'ai préféré la définition RGBA32/RGBA8888 beaucoup plus usitée (y compris chez les artistes).
Si un canal couleur est défini par un entier 8-bits, c'est-à-dire "2 exposant 8", c'est-à-dire une fourchette de 0 à 255…
…et qu'une couleur complète est définie par 4 entiers "ROUGE-VERT-BLEU-OPACITÉ" dans cet ordre précis…
… cela donne le code :
static const GLubyte color_arr[LINES * 8] = { // 2 * 8 = 16 valeurs entières
255, 0, 0, 255, 0, 255, 0, 255, // ROUGE, VERT
0, 0, 255, 255, 255, 255, 255, 255, // BLEU, BLANC (= tout au max)
};
Index
Nous venons donc de définir 2 tableaux ci-dessus :
- vertex_arr : "points", alias vertex
- color_arr : couleurs
Il nous à les associer avec un tableau d'index associatifs nommé index_arr :
static const GLuint index_arr[LINES * 2] = { // 2 * 2 = 4 points
0, 1, // point 1, point 2
2, 3, // point 3, point 4
};
Code
Voici la partie réellement technique de l'API !
On crée d'abord une fenêtre de taille 800x600 à l'aide de SDL :
window = SDL_CreateWindow([...], _width, _height, SDL_WINDOW_OPENGL); // _width = 800 ; _height = 600
puis à chaque itération se produisant aussi que possible (on n'a pas encore de limiteur de FPS, ce qui n'est pas si grave sans mouvement), on redessine sur l'intégralité de la fenêtre via ce bloc :
glViewport(0, 0, _width, _height); // contexte (au départ : 800x600)
[...]
case SDL_EVENT_WINDOW_RESIZED: { _width = e.window.data1; // mis à jour si redimensionnement !
_height = e.window.data2; }
_width et _height sont des variables globales qu'on a déjà utilisées pour créer la fenêtre ; on les refournit ici à glViewport() pour dessiner sur exactement la même taille, c'est-à-dire l'intégralité du fond de la fenêtre
(ce n'est pas obligatoire : un contexte OpenGL peut cohabiter avec d'autres éléments sur la même fenêtre, typiquement des widgets comme ici par exemple).
Elles sont globales ici, car on les autorise à être modifiées par un événement externe : SDL_EVENT_WINDOW_RESIZED déclenché par la souris de l'utilisateur, qui mettra à jour _width et _height avant la prochaine itération.
De cette manière, notre croix sera redimensionnée en même temps que la fenêtre (sans SDL, on devrait écrire un code beaucoup plus long et spécifique pour gérer cette partie "système". Vous voyez l'intérêt de la "glu" ?)
Finalement le dessin commence ! D'abord on dessine un fond noir sans rien :
glClearColor(0, 0, 0, 255); // NOIR
glClear(GL_COLOR_BUFFER_BIT); // ràz avec la couleur de glClearColor()
puis on fournit nos 2 tableaux vertex_arr & color_arr à OpenGL côté CPU/RAM, en indiquant comment les interpréter :
glEnableClientState(GL_VERTEX_ARRAY); // active fonction : "vertices CPU/RAM"
glVertexPointer(2, GL_FLOAT, 0, vertex_arr); // POINTS : groupes de 2 flottants (= [x,y])
glEnableClientState(GL_COLOR_ARRAY); // active fonction : "couleurs CPU/RAM"
glColorPointer(4, GL_UNSIGNED_BYTE, 0, color_arr); // COULEURS : groupes de 4 entiers 0-255 (= RGBA32)
Finalement, on fournit le dernier tableau index_arr pour lancer le dessin des 2 lignes :
glDrawElements(GL_LINES, 4, GL_UNSIGNED_INT, index_arr);
et on rafraîchit simultanément le contexte et la fenêtre :
SDL_GL_SwapWindow(window);
Compiler & exécuter
C'est le moment de tester !
gcc -std=c11 sdl3-gl1.c `pkg-config --cflags --libs sdl3` -lGL
./a.out
Remarques
Ce code fonctionne partout ; même sur Windows 95 ou RedHat 5/Mandrake 6 (1998) en mode logiciel 😉.
Il est par contre extrêmement rétro. Et pas juste dans sa forme, mais sa fonctionnalité :
- plutôt que stocker les informations de vertices et couleurs sur le GPU (qui sait le faire depuis environ 30 ans avec les VBOs), il les stocke uniquement en RAM centrale et les transfère à chaque itération au GPU ;
- il effectue un dessin côté CPU en mode immédiat plutôt que d'utiliser un shader stocké sur le GPU ;
Ces innovations apparaîtront respectivement dans OpenGL 1.5 (2003) et 2.0 (2004).
Concrètement en les utilisant pas, nous empêchons les GPU récents d'optimiser/paralléliser en retenant beaucoup d'état et de données dans notre programme. La programmation 3D récente utilise beaucoup plus d'appels et d'extensions dédiés au GPU lui-même.
Voilà ! Pour élargir ce tutoriel, seriez-vous plutôt intéressés par :
1. faire bouger la croix (ce qui nécessitera de créer un limiteur de FPS) ;
2. afficher 3 triangles formant une pyramide en 3D (ce qui nécessitera d'ajouter le repère [Z]) ?
… ou directement de passer à du code plus moderne avec shader et VBO 😉?
# Les trois mon capitaine...
Posté par RoyalPanda . Évalué à 4 (+3/-0).
… mais je suis pas bégueule, tu as tout ton temps :).
Intéressant en tout cas, merci !
Envoyer un commentaire
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.