Tarnyko's website
Tarnyko's website
about

Programmation 3D à travers les âges : OpenGL 1.1 (1997-2003)

2025-09-16

Si vous avez suivi mon article précédent, vous avez maintenant les éléments de contexte pour programmer en 3D !

opengl-logo

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 :

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 !

sdl-logo

Elle n'est par contre 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.

repere1

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/C/opengl-vulkan/
editor sdl3-gl1.c

Point et 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 :

repere3

on choisit 0.8,-0.8 comme coordonnées symétriques :

repere4

ce qui 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 :

Il nous reste à les associer via 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 aussitôt 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);

repere5

Compiler & exécuter

C'est le moment de tester !

gcc -std=c11 sdl3-gl1.c `pkg-config --cflags --libs sdl3` -lGL
./a.out

cross

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é :

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.

Nous verrons par la suite, en avancant dans le temps, comment y remédier 😉.