I. Introduction▲
Les shaders sont des programmes exécutés par la carte graphique afin de calculer le rendu des pixels à l'écran. Ce tutoriel a pour but d'expliquer le fonctionnement des shaders et leur utilisation dans un programme OpenGL. Les seuls prérequis dont vous avez besoin sont de savoir ouvrir une fenêtre OpenGL (avec WinAPI ou encore SDL ou Glut) et de connaître le fonctionnement des extensions (notamment si vous travaillez sous Windows). Il est aussi conseillé de savoir ce qu'est le pipeline graphique utilisé dans OpenGL, mais je reviendrai dessus un peu plus tard.
Vous pouvez télécharger le projet de base (Code::Blocks / Windows) permettant l'affichage d'un cube rotatif en 3D utilisant des shaders en cliquant iciProgramme OpenGL basique pour travailler avec les shaders.
I-A. Historique▲
I-A-1. Pipeline fixe▲
Durant les années 1990, les premières cartes graphiques dédiées capables de générer des images 3D en temps réel ont vu le jour. Par la même occasion, les bibliothèques OpenGL et DirectX ont été créées. À cette époque, le pipeline graphique permettant d'afficher le rendu était fixe. Le diagramme suivant décrit ce pipeline :
Avec le pipeline fixe, les constructeurs de cartes graphiques peuvent optimiser le calcul de toutes ces étapes en créant un matériel spécifiquement conçu pour cette tâche.
Par contre, comme son nom l'indique, le pipeline fixe est statique, l'utilisateur ne peut pas modifier un des algorithmes utilisés.
I-A-2. Pipeline dynamique▲
Au fur et à mesure des besoins, notamment dans les productions professionnelles telles que les films d'animation, le pipeline fixe se trouve très limitant. La solution était soit d'utiliser le CPU pour appliquer les algorithmes voulus, processus très lent sachant que le CPU n'est pas spécialement optimisé pour de telles tâches, soit d'utiliser des cartes graphiques professionnelles ayant de nouveaux algorithmes ou même des algorithmes pouvant être modifiés. Avec la forte demande des professionnels tels que les sociétés de films d'animation, mais aussi les développeurs de jeux vidéo, les cartes configurables sont devenues monnaie courante. Ce nouveau pipeline graphique est présenté dans le diagramme suivant :
Les blocs vertex shader et fragment shader sont ceux que nous pouvons programmer.
L'avantage de ce nouveau pipeline est de pouvoir intégrer notre propre algorithme pour définir le rendu voulu. Grâce à cela, une multitude d'effets ont pu voir le jour, tels que le Cell Shading, le bump mapping, mais aussi l'implémentation de nouvelles techniques pour le calcul des lumières et des ombres.
La carte graphique est moins optimisée en comparaison de celles conçues pour un pipeline fixe, mais le fait de pouvoir définir un algorithme précis à la place d'un algorithme plus généraliste est un avantage.
Souvent les cartes graphiques supportant les shaders et le pipeline fixe, utilisent simplement des shaders pour reproduire le comportement du pipeline fixe.
I-A-3. Évolution du pipeline dynamique▲
Du point de vue des bibliothèques graphiques, les shaders ont été intégrés à partir d'OpenGL 2.0 (OpenGL 1.4 par le biais des extensions) et dans DirectX 8. Depuis ce temps, les langages des shaders ne font qu'évoluer pour devenir de plus en plus puissants et performants.
Pour chaque version différente de shader, nous parlons de modèles de shader (shader models (SM)). Il est possible de préciser la version que nous voulons utiliser dans notre shader. Nous verrons comment faire cela un peu plus loin.
Rapide historique (je ne citerai que les cartes des constructeurs ATI et NVidia) :
Année |
Version des shaders |
DirectX |
OpenGL |
ATI |
Nvidia |
Info |
2001 |
SM 1.x |
DirectX8 |
OpenGL 2.0 |
Radeon R200 |
Geforce séries 3 |
|
2003 |
SM 2.x |
DirectX9.0 |
Radeon R300 / R420 |
Geforce FX |
||
2004 |
SM 3.0 |
DirectX9.0c |
Radeon R520 |
Geforce séries 6 / 7 |
||
2006 |
SM 4.0 |
DirectX10 |
OpenGL 3.2 |
Radeon R600 |
Geforce séries 200 / 300 |
Apparition des Geometry Shader |
2009 |
SM 5.0 |
DirectX11 |
OpenGL 4.1 |
Radeon R800 |
Geforce séries 400 |
Tesselation |
I-B. Explication▲
Un shader est un programme que l'on exécute directement dans le cœur même de la carte graphique. La carte est optimisée pour tous les calculs nécessaires à la 3D (tels que la multiplication de matrices). Grâce aux shaders, certaines parties de la carte graphique sont programmables et nous pouvons en faire ce que nous voulons. Par exemple, dans le pipeline fixe, nous n'avions que huit sources de lumière. En reprogrammant l'algorithme utilisé pour le calcul des lumières, nous pouvons imaginer l'intégration d'une neuvième lumière. De plus, nous pouvons même changer l'algorithme afin de créer un effet totalement différent de ce que nous forçait le constructeur.
- Le vertex shader est exécuté pour chaque sommet.
- Le fragment shader est exécuté sur chaque pixel dessiné.
I-C. Fonctionnement▲
Comme il a été dit, nous allons pouvoir programmer directement nos cartes graphiques. Pour cela de nouveaux langages ont été inventés. Au tout début, c'était un assembleur spécialisé pour les cartes graphiques. Pour OpenGL, le langage s'appelle le GLSL pour OpenGL Shading Language (langage de shading pour OpenGL) et pour DirectX, le HLSL pour High Level Shading Language (langage de shading de haut niveau).
Comme nous l'avons vu dans le diagramme du pipeline dynamique, nous avons besoin des blocs Vertex Shader et Fragment Shader pour reconstituer le processus complet de rendu. Cela signifie qu'il va falloir développer deux programmes et que ceux-ci vont suivre un processus similaire à la création d'un programme, qui est composé de :
- deux compilations ;
- une édition de liens.
Il y a deux compilations, car nous devons compiler le Vertex Shader et le Fragment Shader. Par contre, ils sont liés ensemble pour constituer un programme (Program Shader), ce qui reconstituera l'ensemble de notre pipeline graphique.
Il est tout à fait possible de charger plusieurs Program Shader dans un programme OpenGL, mais il n'est pas possible d'en utiliser plusieurs pour le même objet à dessiner.