URL: https://linuxfr.org/users/tarnyko/journaux/programmation-3d-a-travers-les-ages-opengl-1-1-1997-2003 Title: Programmation 3D à travers les âges : OpenGL 1.1 (1997-2003) Authors: Tarnyko Date: 2025年09月16日T10:31:23+02:00 License: CC By-SA Tags: opengl, mesa, x11, wayland, moteur3d, gpu et langage_c Score: 52 Salut 'nal, Si tu suivi mon [journal précédent](https://linuxfr.org/users/tarnyko/journaux/programmation-3d-a-travers-les-ages-les-debuts-1992-1999), 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](https://registry.khronos.org/OpenGL/specs/gl/glspec11.pdf))_, 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. ![logo OpenGL](https://tarnyko.net/articles/03b-3dprog_origins-gl1/opengl-logo.png) 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](https://fr.wikipedia.org/wiki/X_Window_System)** : [Mesa](https://fr.wikipedia.org/wiki/Mesa_3D) fournit l'API *[GLX](https://fr.wikipedia.org/wiki/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](https://fr.wikipedia.org/wiki/OpenGL_ES) pour l'embarqué ; - sous **[Wayland](https://fr.wikipedia.org/wiki/Wayland)** (et _récemment_ **X11**) : le Khronos Group fournit l'API "universelle" *[EGL](https://fr.wikipedia.org/wiki/EGL_(API))* ; [...] - les **autres OS** : ne supportent en réalité pas nativement EGL, on y utilise p.ex. [ça](https://en.wikipedia.org/wiki/WGL_(API)) ou [ça](https://en.wikipedia.org/wiki/Core_OpenGL) ; - et en général avec [Vulkan](https://fr.wikipedia.org/wiki/Vulkan_(API)) : 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](https://freeglut.sourceforge.net/) et [GLFW](https://www.glfw.org/) sont encore très utilisées... Mais nous préférerons **[SDL 3](https://wiki.libsdl.org/SDL3/FrontPage)** car elle supporte toutes les combinaisons ET nous aidera plus tard pour Vulkan. ![logo SDL](https://tarnyko.net/articles/03b-3dprog_origins-gl1/SDL_logo.png) Elle n'est juste pas encore empaquetée dans les distributions les plus courantes, alors installons-la rapidement : Prérequis ----------- Debian/Ubuntu : ```bash 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 : ```bash 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 -------- ```bash 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 --------- ```bash 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 ------ ```bash 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. ![croix](https://tarnyko.net/articles/03b-3dprog_origins-gl1/repere1.png) et utilisant ce **[code source C](https://github.com/Tarnyko/suave_code_samples/blob/master/C/opengl-vulkan/1-cross/sdl3-gl1.c)**. Pour le récupérer et me suivre pendant que je détaille, faites sur votre Linux : ```bash 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 : ![Repère avec points](https://tarnyko.net/articles/03b-3dprog_origins-gl1/repere3.png) on choisit 0.8,-0.8 comme coordonnées symétriques : ![Repère avec points marqués](https://tarnyko.net/articles/03b-3dprog_origins-gl1/repere4.png) ç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](https://en.wikipedia.org/wiki/RGBA_color_model#RGBA32) 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](https://fr.wikipedia.org/wiki/Sommet_(g%C3%A9om%C3%A9trie)) - **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](https://fr.wikipedia.org/wiki/Images_par_seconde), 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](https://fr.wikipedia.org/wiki/Widget_interactif) comme [ici par exemple](https://tarnyko.net/articles/03b-3dprog_origins-gl1/glarea.png))_. 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); ![dessin final](https://tarnyko.net/articles/03b-3dprog_origins-gl1/repere5.png) Compiler & exécuter ---- C'est le moment de tester ! gcc -std=c11 sdl3-gl1.c `pkg-config --cflags --libs sdl3` -lGL ./a.out ![a.out](https://tarnyko.net/articles/03b-3dprog_origins-gl1/cross.png) 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](https://fr.wikipedia.org/wiki/Vertex_Buffer_Object))_, 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](https://en.wikipedia.org/wiki/Immediate_mode_(computer_graphics)) plutôt que d'utiliser un [shader](https://en.wikipedia.org/wiki/Shading_language) 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](https://fr.wikipedia.org/wiki/Images_par_seconde))_ ; 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_ 😉?

AltStyle によって変換されたページ (->オリジナル) /