Introduction à OpenGL : afficher son premier triangle
Afficher un triangle à l’écran est le « Hello World » de la programmation graphique. Avec OpenGL moderne, cet exercice apparemment trivial demande de comprendre tout un pipeline : contexte, tampons de données et shaders. C’est justement ce qui en fait un excellent point de départ. Ce tutoriel OpenGL vous guide pas à pas, de la création de la fenêtre au premier triangle coloré, en utilisant l’approche à shaders programmables — la seule pertinente aujourd’hui.
OpenGL ancien contre OpenGL moderne
Beaucoup de vieux tutoriels montrent du code en glBegin/glEnd, où l’on énumère les sommets un par un. Ce pipeline fixe est obsolète. OpenGL moderne (3.3 et au-delà) repose sur un pipeline programmable : vous envoyez les données des sommets à la carte graphique une fois pour toutes, puis vous écrivez de petits programmes — les shaders — qui décrivent comment traiter ces sommets et colorer les pixels.
C’est un peu plus verbeux au début, mais c’est ainsi que fonctionnent tous les moteurs actuels. Apprendre directement la bonne méthode évite de tout réapprendre plus tard.
Les bibliothèques nécessaires
OpenGL ne sait pas créer de fenêtre ni gérer le clavier : ce n’est qu’une API de rendu. Il faut donc deux compléments.
- GLFW crée la fenêtre, le contexte OpenGL et gère les entrées. Léger et multiplateforme, c’est le choix standard.
- GLAD (ou GLEW) charge les pointeurs de fonctions OpenGL modernes, indispensables car le système ne les expose pas directement.
Sous Linux, on installe par exemple libglfw3-dev ; GLAD se génère sur son site web et s’ajoute au projet sous forme d’un fichier source. La compilation lie ensuite le tout avec la bibliothèque OpenGL du système.
Créer une fenêtre et un contexte
Tout commence par l’initialisation de GLFW et la création d’une fenêtre associée à un contexte OpenGL 3.3.
#include <glad/glad.h>
#include <GLFW/glfw3.h>
int main() {
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* fenetre = glfwCreateWindow(800, 600, "Premier triangle",
nullptr, nullptr);
glfwMakeContextCurrent(fenetre);
gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
// ... suite ...
}
Les glfwWindowHint réclament un contexte moderne en profil core. gladLoadGLLoader charge ensuite les fonctions OpenGL : sans cette ligne, tout appel gl… planterait.
Décrire le triangle
Un triangle, c’est trois sommets, chacun défini par ses coordonnées. OpenGL travaille dans un espace normalisé où les coordonnées vont de -1 à +1.
float sommets[] = {
-0.5f, -0.5f, 0.0f, // bas gauche
0.5f, -0.5f, 0.0f, // bas droit
0.0f, 0.5f, 0.0f // sommet
};
Ces données doivent être transférées dans la mémoire de la carte graphique. On utilise pour cela deux objets fondamentaux : le VBO (Vertex Buffer Object), qui stocke les sommets, et le VAO (Vertex Array Object), qui mémorise comment interpréter ce tampon.
unsigned int VAO, VBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(sommets), sommets, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer est la ligne clé : elle explique que chaque sommet occupe trois flottants et se trouve à l’emplacement 0, celui que le shader lira.
Écrire les shaders
Deux shaders minimaux suffisent. Le vertex shader positionne chaque sommet ; le fragment shader décide de la couleur des pixels.
// Vertex shader
#version 330 core
layout (location = 0) in vec3 aPos;
void main() {
gl_Position = vec4(aPos, 1.0);
}
// Fragment shader
#version 330 core
out vec4 FragColor;
void main() {
FragColor = vec4(1.0, 0.5, 0.2, 1.0); // orange
}
Le layout (location = 0) correspond à l’emplacement défini plus haut. Le fragment shader renvoie une couleur constante : notre triangle sera uniformément orange.
Compiler et lier les shaders
Les shaders sont fournis sous forme de texte et compilés à l’exécution, puis liés dans un programme.
unsigned int vs = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vs, 1, &sourceVertex, nullptr);
glCompileShader(vs);
unsigned int fs = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fs, 1, &sourceFragment, nullptr);
glCompileShader(fs);
unsigned int programme = glCreateProgram();
glAttachShader(programme, vs);
glAttachShader(programme, fs);
glLinkProgram(programme);
glDeleteShader(vs);
glDeleteShader(fs);
En pratique, pensez à vérifier la compilation avec glGetShaderiv et à lire les journaux d’erreur : une virgule oubliée dans un shader est une source de bugs très fréquente.
La boucle de rendu
Reste à dessiner en continu jusqu’à la fermeture de la fenêtre.
while (!glfwWindowShouldClose(fenetre)) {
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(programme);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glfwSwapBuffers(fenetre);
glfwPollEvents();
}
glfwTerminate();
À chaque image, on efface l’écran, on active le programme de shaders et le VAO, puis glDrawArrays dessine trois sommets sous forme de triangle. glfwSwapBuffers affiche le résultat et glfwPollEvents traite le clavier et la souris.
Ce que vous avez appris
Ce petit triangle cache l’essentiel d’OpenGL moderne : un contexte créé par GLFW, des données envoyées dans un VBO décrit par un VAO, et deux shaders qui transforment ces données en pixels colorés. Chaque projet graphique, si complexe soit-il, repose sur ces mêmes briques. Les étapes suivantes naturelles sont d’ajouter des couleurs par sommet, des textures, puis les matrices de transformation pour passer à la 3D.
À lire ensuite
- Renforcez le C++ sous-jacent avec notre guide du langage C.
- Gérez proprement vos ressources graphiques grâce au RAII en C++.
- Pour une approche 2D plus douce, essayez une balle rebondissante en OCaml + SDL.