[Plan du site]
Vous êtes ici ---
> Le Site du Zér0
> Les tutoriels
> Non-Officiels
> Programmation
> C
> Lecture du tutoriel
Les bases du langage
Vous vous apprêtez à lire un tutoriel rédigé par un membre de ce site. Malgré tout le soin que ce membre a pu apporter au tutoriel, nous ne pouvons pas garantir que les informations contenues sur cette page sont exactes à 100%. Merci de garder cela en tête lorsque vous lirez cette page ;o)
Nous y voilà enfin, nous allons ici commencer à apprendre le langage GLSL qui est un langage de programmation de shaders conçu pour OpenGL.
Nous étudierons d'abord les bases du langage :
- les variables et leur types ;
- la surcharge des opérateurs ;
- le cast (très important et très employé en GLSL).
Nous programmerons ensuite de simples vertex et pixel shaders, afin de voir comment ils fonctionnent

Vous verrez qu'ils utilisent tout deux des
variables d'entrée et des
variables de sortie, qui nous permettent de
recevoir et de
renvoyer des données. Les données reçues seront traitées par les instructions contenues dans le code source du shader, pour obtenir un résultat qui sera ensuite renvoyé. Ces résultats représentent généralement des informations de
position à l'écran pour les vertex shaders, et de
couleur pour les pixel shaders.
Le langage GLSL a beaucoup de points communs avec le langage C. Syntaxiquement, les deux langages sont quasiment identiques.
Les variables
Tout comme en C, il est possible de créer des variables en GLSL. Il existe une multitude de types de variables, chacun ayant une utilité bien précise.
Par exemple, si vous souhaitez créer une variable capable de stocker des nombres flottants, faites ceci :
Code : Autre
Et oui, comme en C

Notez également la présence d'un point-virgule, qui se place aux mêmes endroits qu'en C.
La création de deux variables à la fois est également autorisée, avec l'ajout d'une virgule entre les deux noms de variable :
Code : Autre
Les variables préfixées gl_ sont réservées au langage lui-même, il est donc interdit de créer une variable dont le nom commence par gl_ !
Il est possible de créer des variables des types suivants :
- int : entier ;
- float : flottant ;
- bool : booléen, peut valoir true ou false ;
- vec2 : vecteur à 2 composantes flottantes ;
- vec3 : vecteur à 3 composantes flottantes ;
- vec4 : vecteur à 4 composantes flottantes ;
- mat2 : matrice 2 * 2 de flottants ;
- mat3 : matrice 3 * 3 de flottants ;
- mat4 : matrice 4 * 4 de flottants ;
- ivec2 : vecteur à 2 composantes entières ;
- ivec3 : vecteur à 3 composantes entières ;
- ivec4 : vecteur à 4 composantes entières ;
- bvec2 : vecteur à 2 composantes booléennes ;
- bvec3 : vecteur à 3 composantes booléennes ;
- bvec4 : vecteur à 4 composantes booléennes.
Soudainement, les ressemblances avec le C s'arrêtent

Effectivement, le GLSL possède beaucoup de types de variables... qui ne sont finalement plus des variables mais des ensembles de variables. Nous pouvons toutefois les comparer à des structures, et nous allons voir pourquoi.
Lorsque vous créez un vecteur à 3 composantes par exemple (
vec3), GLSL vous permet d'accéder à une seule de ses composante de cette façon :
Code : Autre1
2
| vec3 direction;
direction.x = 0.2; |
La seconde instruction place la valeur 0.2 dans la composante X du vecteur
direction grâce à l'opérateur d'affectation =, qui fonctionne de la même façon qu'en C... ou presque.
Les opérateurs
Il est bien sûr possible de multiplier une variable par une autre, ou bien encore de soustraire une valeur à une variable. Les opérateurs en GLSL s'utilisent comme en C, voici un exemple :
Code : Autre1
2
| float var1 = 0.2, var2 = 3.0;
float resultat = (var1 - 0.1) * var2; |
Ici,
resultat vaudra (0.2 - 0.1) * 3.0 soit 0.3.
Et si je vous apprenais qu'on peut multiplier un vecteur par un vecteur, vous me répondriez quoi ?
La surcharge des opérateurs
Premier point commun avec le langage C++ : les opérateurs en GLSL sont surchargés.
Euh, et ça veut dire quoi ?
Je ne vais pas vous faire une description avancée de ce qu'est la surcharge des opérateurs, mais pour vous expliquer en deux mots, ça veut dire qu'on peut additionner, soustraire, multiplier et diviser tous les types de variable par tous les types de variable !
J'y comprend rien

Vous connaissez probablement la multiplication matricielle ? Et bien effectuer ce genre de multiplication en GLSL est un jeu d'enfant :
Code : Autre1
2
| mat4 a = ..., b = ...;
mat4 resultat = a * b; |
resultat vaut maintenant le résultat de la multiplication matricielle de
a par
b.
Et ça marche aussi pour les vecteurs !

:
Code : Autre1
2
3
4
| vec4 position = ...;
mat4 m = ...;
vec4 resultat = m * position; |
resultat vaut à présent la position que représente le vecteur
position transformé par la matrice
m.
Notez qu'ici l'ordre de la multiplication est important ! C'est d'abord la matrice, puis le vecteur. Si cela vous semble flou, n'hésitez pas à aller lire
le tutoriel de Kayl sur les matrices.
Allez, encore un exemple :
Code : Autre1
2
| vec3 vecteur = ...;
vecteur *= 2.0; |
La seconde ligne de ce code a pour effet de multiplier chaque composante du vecteur
vecteur par 2.
Les limites de la surcharge
Hé oui malheureusement cette surcharge a des limites, on ne peut pas réellement
tout faire comme je l'ai dit avant, il existe des exceptions. Ces exceptions sont toutefois logiques et n'ont rien de mystérieux comme nous allons le voir.
Par exemple, il est impossible de multiplier une matrice 3*3 par une matrice 4*4, si vous vous y risquez, OpenGL lèvera une erreur lors de la compilation de votre shader.
Autre cas typique : les vecteurs, il est impossible d'effectuer une quelconque opération entre deux vecteurs de type différents.
Enfin, il est également impossible de multiplier un vecteur à 2 composantes (
vec2) par une matrice autre qu'une 2*2 (
mat2), et il en va de même pour tous les autres types,
vec3 avec
mat3 et
vec4 avec
mat4.
Maintenant que je vous ai exposé pleins d'inconvénients dûs à la surcharge, je vais vous proposer
des une solution
Le cast
Tout comme en C, il est possible de forcer la conversion d'un type vers un autre. Ici, la syntaxe est différente que celle du C où l'on fait comme ceci :
Code : C1 | float flottant = (float)entier;
|
En GLSL, le cast d'une variable se fait ainsi :
Code : Autre1
| float flottant = float(entier); |
La conversion explicite de int vers float ou de float vers int n'est pas obligatoire, en revanche, si vous souhaitez convertir un
vec3 en
vec2, là il va falloir le demander explicitement.
Comment on peut convertir un vecteur à trois dimensions en un vecteur à deux dimensions ?
Je ne vais rien vous cacher, et une conversion de ce type amène forcément à une perte de donnée(s), nous allons uniquement conserver dans le vecteur à 2 dimensions deux composantes du vecteur à 3 dimensions. Mais avec le cast, c'est vous qui allez choisir quelles données vous souhaiterez supprimer et quelles données vous souhaiterez garder. (par donnée je sous-entend
composante d'un vecteur)
Étant donné qu'il existe un trop grand nombre de conversions possible (chaque type peut être converti en chaque type), je ne vais pas vous faire une démonstration pour chacune d'entre elles, je vais juste vous fournir la technique à utiliser, elle est logique et fonctionne de la même façon (ou presque) pour tous les types de conversion.
Nous allons prendre l'exemple de la conversion d'un
vec3 vers un
vec2, puis nous prendrons ensuite l'exemple inverse, à savoir
vec2 ->
vec3.
Pour convertir un vec3 en vec2, une méthode simple existe :
Code : Autre1
2
3
| vec3 v = ...;
vec2 v2 = vec2(v); |
Ce code est simple et pourrait se traduire de la façon suivante :
Code : Autre1
2
3
4
5
| vec3 v = ...;
vec2 v2;
v2.x = v.x;
v2.y = v.y; |
En réalité, le cast du premier code prend les
n premières composantes de la variable à caster et les places dans la variable finale, où
n représente le nombre maximal de variables stockables dans le type de la variable finale, dans notre exemple,
n vaut 2.
Vous pouvez également prendre d'autres composantes de
v pour les placer dans
v2 sans avoir à spécifier manuellement les composantes de
v qui recevrons la valeur. L'instruction suivante place les composantes de
v demandées et les places respectivement dans
v2.x et
v2.y :
Code : Autre1
| vec2 v2 = vec2(v.z, v.x); |
Voyons à présent comment convertir un
vec2 en
vec3, nous verrons cette voici qu'à l'inverse d'une conversion
vec3 ->
vec2, il y a un manque de données.
À priori, on pourrait se dire qu'une conversion comme ceci est correct :
Code : Autre1
2
| vec2 v2 = ...;
vec3 v3 = vec3(v2); |
Mais...
Code : Console | impossible de compiler le shader 'test.vert' :
(5) : error C1033: cast not allowed
(5) : error C1056: invalid initialization |
En effet, c'est une instruction non valide, refusée par OpenGL à la compilation du shader.
Afin de pouvoir effectuer un cast sans encombre, il va falloir donner à notre shader ce qu'il attend : la composante manquante. (en l'occurrence il s'agit de la composante z)
Il nous faut donc la spécifier explicitement, comme dans l'exemple qui suit :
Code : Autre1
2
| vec2 v2 = ...;
vec3 v3 = vec3(v2, 0.0); |
Ce code est équivalent à :
Code : Autre1
2
3
4
5
6
| vec2 v2 = ...;
vec3 v3;
v3.x = v2.x;
v3.y = v2.y;
v3.z = 0.0; |
Si vous le souhaitez, vous pouvez aussi faire ceci :
Code : Autre1
2
| vec2 v2 = ...;
vec3 v3 = vec3(0.0, v2); |
Mais ce code n'aura pas le même effet que le précédent, voici ce que l'on obtient en l'appelant :
Code : Autre1
2
3
4
5
6
| vec2 v2 = ...;
vec3 v3;
v3.x = 0.0;
v3.y = v2.x;
v3.z = v2.y; |
Initialiser le contenu d'un vecteur
Maintenant que vous savez comment fonctionne l'opérateur de cast, vous devriez comprendre facilement ce que fait ce bout de code :
Code : Autre1
| vec3 v = vec3(0.0, 1.0, 0.5); |
Allez cherchez un peu
...
Alors vous trouvez ?
...
Bon, je sens que vous avez fait bouillonner votre cerveau, c'est l'heure de votre récompense :
Code : Autre1
2
3
4
5
| vec3 v;
v.x = 0.0;
v.y = 1.0;
v.z = 0.5; |
Et voilà l'travail
Il est également possible d'initialiser toutes les composantes d'un vecteur d'un seul coup :
Code : Autre
Ce code place toutes les composantes du vecteur
v à 0.
Et les matrices... ?
Vous savez quoi ? On peut mettre des vecteurs dans des matrices

Si si je vous assure

Ainsi, ce code est tout à fait correct :
Code : Autre1
2
3
4
5
6
7
8
| vec4 a, b, c, d;
a = vec4(1.0, 0.0, 0.0, 0.0);
b = vec4(0.0, 1.0, 0.0, 0.0);
c = vec4(0.0, 0.0, 1.0, 0.0);
d = vec4(0.0, 0.0, 0.0, 1.0);
mat4 m = mat4(a, b, c, d); |
Ce code a pour effet de charger dans
m la matrice d'identité. Bien évidemment, il est inutilement lourd à cause de la création de 4 vecteurs, il n'est là qu'à titre indicatif
Il est important de noter ici que chaque vecteur représente une ligne de la matrice, c'est-à-dire que le premier vecteur ira se loger de m1,1 à m1,4
Tout comme pour les vecteurs, il est possible d'initialiser une matrice ainsi :
Code : Autre
Toutefois il y a une différence ici avec les vecteurs. L'initialisation d'une matrice comme nous l'avons fait place toutes les composantes de
la diagonale de la matrice à 1, et toutes les autres à 0. Avec 1, la matrice chargée est celle d'identité, avec une autre valeur, la matrice est une matrice de mise à l'échelle de la valeur envoyée.
Les commentaires
Tout comme en C, il est possible d'intégrer des commentaires en GLSL.
Ils ont la même forme ainsi que le même comportement :
Code : Autre1
2
| /* commentaire sur
plusieurs lignes */ |
Le GLSL accepte également les commentaires commençant par // comme en C99 :
Code : Autre1
| // ceci est un commentaire sur une seule ligne |
Les tableaux
Encore une similitude avec le C : les tableaux. Ils se définissent et s'utilisent comme en C.
Il est interdit de fournir une variable comme taille de tableau lors de sa déclaration, seules les constantes sont acceptées :
Code : Autre1
| float tab[3] = {0.0, 0.5, 1.0}; |
En revanche l'accès aux valeurs contenues dans un tableau est des plus simples, cette fois-ci les variables sont bien sûr acceptées :
Code : Autre1
2
| int case = 2;
float val = tab[case]; // val = 1 |
Les tableaux en GLSL commencent également à 0 : tab[0]
Vous souvenez-vous de la manière dont on accède à une composante d'un vecteur ? Nous faisions comme ceci :
Code : Autre1
2
| vec2 v = vec2(1.0, 0.0);
float vx = v.x; |
Et bien sachez qu'il est possible de considérer un vecteur comme un tableau !
Ainsi, ce code est
strictement identique :
Code : Autre1
2
| vec2 v = vec2(1.0, 0.0);
float vx = v[0]; |
Notez que l'on préfèrera la première méthode car elle est beaucoup plus légère et beaucoup plus lisible

.
Et pour les matrices ? Il existe aussi un tour de passe-passe ?
Les tableaux à deux dimensions
Rien qu'à la vue de ce titre, j'imagine que vous prévoyez déjà ce que je vais vous dire

Allons-y franchement : les matrices sont des tableaux à deux dimensions !
Si vous connaîssez les tableaux à deux dimensions en C, alors vous ne devriez pas avoir de problèmes.
Allez, un exemple de code vaudra sûrement mieux qu'un long discours :
Code : Autre1
2
3
4
5
6
7
8
9
| mat4 m = mat4(
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 5.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
float var = m[2][1];
// ici, var = 5.0 |
Dans
m[i][j] on a :
- i : la ligne (position en hauteur)
- j : la colonne (position en largeur)
Et voilà, rien de très compliqué, encore faut-il le savoir

.
Et comment on crée un tableau à deux dimensions ?
Comme en C :
Code : Autre1
2
3
4
5
6
7
8
9
10
| float tab[3][3] =
{
{0.0, 0.0, 0.0},
{0.0, 0.0, 2.0},
{0.0, 0.0, 0.0}
};
float var = tab[1][2];
// var = 2.0 |
Notez qu'il est impératif de définir la taille d'un tableau lors de sa déclaration en GLSL, contrairement au langage C qui est capable de déduire tout seul de la taille d'un tableau rien que par son contenu. Cette règle s'applique aussi bien aux tableaux 1D que 2D.
Maintenant que vous avez acquis les bases du langage, ça vous dirait de programmer votre premier vertex shader ? Oui ? Pas de problème, allons-y
Un vertex shader : ça fait quoi ?
Un shader est donc un code source qui, une fois compilé, est exécutable par la carte graphique. Quand vous écrivez ce code source en OpenGL :
Code : C1
2
3
4
5 | glBegin(GL_TRIANGLES);
glColor3f(1.0, 0.0, 0.0); glVertex2f(0.9, -0.9);
glColor3f(0.0, 1.0, 0.0); glVertex2f(-0.9, -0.9);
glColor3f(0.0, 0.0, 1.0); glVertex2f(0.0, 0.9);
glEnd();
|
vous envoyez exactement 3 sommets (ou vertices, nom au choix) à la carte graphique. Avant d'être affichés à l'écran ils sont tout d'abord traités; ils subissent de nombreuses transformations, puis les données sont interpolées afin de donner naissance à un triangle plein.
Et qu'est-ce qu'il fait le vertex shader là dedans ?
Le vertex shader effectue entre autres la première opération : il fait subir des transformations (matricielles) aux sommets. Il vous permet en fait de toucher à toutes les composantes (coordonnées de texture, normale, couleur, etc...) d'un sommet et de les modifier à votre guise

Nous allons voir qu'il y a des règles pour le codage d'un vertex shader, mais passé celles-ci vous êtes libres de faire tout ce dont vous avez envie.
Avec l'exemple ci-dessus, le vertex shader sera invoqué exactement 3 fois, ce qui est très peu. Le nombre d'appel au vertex shader dépend donc du nombre de sommet que comporte votre scène, plus elle en comporte, et plus il y aura d'appels au vertex shader, et donc plus le traitement sera lourd et consommateur de ressources.
Il est important de savoir : qu'un vertex shader activé sera actif sur tous les futurs sommets qui seront envoyés à la carte graphique, jusqu'à ce que les shaders de sommet soient désactivés. Quand vous activez un vertex shader, il vient remplacer une partie du FFP, donc tous les sommets envoyés après l'activation du shader seront traités par le vertex shader que vous avez activé.
Ça a l'air d'être un vrai chantier...
C'est une réflexion normale

Vous vous apercevrez vite que ça n'a rien de sorcier et que le langage a été bien pensé, ce n'est pas si difficile que ça en a l'air rassurez-vous, et puis, je suis là pour vous guider
Quelques règles de programmation
Tout comme en C, la programmation d'un shader ne se fait pas à "l'arrache", il y a des règles à respecter.
La fonction main
En GLSL une fonction principale appelée
main() est nécessaire. Cette dernière se différencie des habituelles formes du
main() du langage C par sa valeur de retour et ses arguments : la fonction main en GLSL ne renvoie rien et ne prend aucun paramètre.
Nous l'invoquerons comme ceci :
Code : Autre1
2
3
4
5
6
7
| void main(void)
{
// notre code ici
} |
Comme en C, la fonction main représente la première fonction qui sera exécutée.
Contrairement au C, les codes sources GLSL ne réclament pas de retour chariot en fin de fichier.
C'est quoi un retour chariot ?
C'est un retour à la ligne (entrée) tout à la fin du fichier.
Enfin, une dernière chose à retenir : un
vertex shader doit
toujours placer une valeur dans la variable de sortie
gl_Position.
Hein ? C'est quoi ce truc ?
Les variables d'entrée/sortie
Il est ici important de se rappeler ce qu'est en gros un vertex shader : son objectif est d'agir sur le traitement de chaque vertex qu'on lui enverra.
Nous allons à présent voir quel est le rôle précis d'un vertex shader, et ce qu'il permet de faire.
Les variables d'entrée
Les variables d'entrée sont généralement destinées à être lues puis traitées.
Lorsque vous programmerez un
vertex shader (et uniquement un vertex shader), OpenGL aura créé pour vous quelques variables utiles, dont voici justement la liste :
| Nom de la variable GLSL | Type | Fonction OpenGL appropriée | Description |
|---|
| gl_Vertex |
vec4 |
glVertex*() |
Position du sommet |
| gl_Color |
vec4 |
glColor*() |
Couleur du sommet |
| gl_Normal |
vec3 |
glNormal*() |
Normale du sommet. |
| gl_MultiTexCoordn |
vec4 |
glMultiTexCoord*() ou glTexCoord*() |
Coordonnées de l'unité de texture n |
| gl_SecondaryColor |
vec4 |
glSecondaryColor*() |
Couleur secondaire du sommet |
| gl_FogCoord |
float |
glFogCoord*() |
Coordonnées de brouillard |
J'espère qu'à la vue de cette liste vous y voyez déjà plus clair sur la tâche que remplie un vertex shader. Mais je pense que vous y verrez d'autant plus clair lorsque vous aurez vu la liste des variables de sortie
Les variables de sortie
Après avoir traité les variables d'entrées à notre guise, nous pourrons écrire dans les variables de sortie. Les variables de sortie d'un
vertex shader (uniquement) représentent la position finale du sommet (position écran, ou presque) sa couleur finale, etc...
Voici la liste des variables de sortie disponibles :
| Nom de la variable GLSL | Type | Description |
|---|
| gl_Position |
vec4 |
Position en coordonnées écran du sommet |
| gl_FrontColor |
vec4 |
Couleur du côté "avant" de la face à laquelle
est rattaché le sommet |
| gl_BackColor |
vec4 |
Couleur du côté "arrière" de la face à laquelle
est rattaché le sommet |
| gl_FrontSecondaryColor |
vec4 |
Couleur secondaire du côté "avant" de la face
à laquelle est rattaché le sommet |
| gl_BackSecondaryColor |
vec4 |
Couleur secondaire du côté "arrière" de la face
à laquelle est rattaché le sommet |
| gl_TexCoord[n] |
tableau de vec4 |
Coordonnées de l'unité de texture n |
| gl_FogFragCoord |
float |
Coordonnée de fog |
| gl_PointSize |
float |
Taille du point du sommet |
| gl_ClipVertex |
vec4 |
Vecteur utilisé pour les plans de clipping |
On y voit tout de suite plus clair n'est-ce pas ?

Vous pouvez déjà vous faire une petite idée de la fonction d'un vertex shader à la vue de ces deux tableaux.
Vous vous souvenez de la règle de base pour un vertex shader ? C'est que la variable
gl_Position doit être affectée à une valeur à la fin du vertex shader, sinon le vertex shader est invalide. Nous pouvons donc construire un vertex shader de base, tout simple, comme ceci :
Code : Autre1
2
3
4
5
6
7
| void main(void)
{
gl_Position = gl_Vertex;
} |
Ce vertex shader est tout à fait correct, mais sa fonctionnalité laisse à désirer

Je vous propose tout de même de l'étudier, afin de mettre les choses au clair pour la gestion des vertex shaders.
Si vous appliquez ce vertex shader à vos rendus, les données envoyées à la fonction
glVertex*() seront les coordonnées
écran de vos sommets, même si vous utilisez une transformation quelconque (
glTranslate*() et compagnie)
elle ne sera pas appliquée au sommet ! Idem pour la matrice de projection, elle n'affectera pas la position que vous aurez envoyé à la fonction
glVertex*().
C'est un peu débile non ?
Non ! C'est ce qui fait la flexibilité des shaders, c'est
vous qui décidez exactement
comment vos sommets seront rendus,
vous êtes le maître absolu de votre machine
Et alors comment on fait pour que notre vertex soit au bon endroit en subissant les transformations de nos matrices modelview et de projection ?
Les matrices, quelques variables d'entrée supplémentaires
Si vous avez à peu près compris ce que sont les variables d'entrée, vous devriez sauter au plafond à la vue de ce titre
Ah bon ? Moi ça ne fait que m'embrouiller encore plus... Qu'est-ce qu'elles peuvent nous faire ces matrices ?
Chers Zér0s, vous devriez savoir comment fonctionne le rendu d'un sommet et quelles sont les transformations qui lui sont appliquées, sinon c'est que vous n'êtes pas totalement prêts à lire ce tutoriel. Bien sûr je pourrais vous faire gober des principes tout cuits, mais ça ne serait pas très pédagogique en plus du fait que vous risqueriez d'être un peu bloqué par la suite.
Sur ce, je vous renvoie sur
cet excellent lien qui vous expliquera comment on passe des coordonnées 3D aux coordonnées écran.
Vous avez dit "variable d'entrée" ?
Parfaitement

Ces variables ne sont rien d'autre que des matrices, et ô combien utiles.
Je vous propose de voir sans plus tarder la listes des matrices disponibles au sein d'un vertex shader
uniquement :
| Nom de la variable | Type | Description |
|---|
| gl_ModelViewMatrix |
mat4 |
C'est la matrice de modélisation/visualisation,
celle que l'on manipule avec GL_MODELVIEW en C |
| gl_ModelViewMatrixInverse |
mat4 |
C'est l'inverse de la matrice gl_ModelViewMatrix |
| gl_ModelViewMatrixTranspose |
mat4 |
C'est la transposée de la matrice gl_ModelViewMatrix |
| gl_ModelViewMatrixInverseTranspose |
mat4 |
C'est la transposée de la matrice gl_ModelViewMatrixInverse |
| --- |
--- |
--- |
| gl_ProjectionMatrix |
mat4 |
C'est la matrice de projection GL_PROJECTION, maniable
entre autres avec gluPerspective() dans le code C |
| gl_ProjectionMatrixInverse |
mat4 |
C'est l'inverse de la matrice gl_ProjectionMatrix |
| gl_ProjectionMatrixTranspose |
mat4 |
C'est la transposée de la matrice gl_ProjectionMatrix |
| gl_ProjectionMatrixInverseTranspose |
mat4 |
C'est la transposée de la matrice gl_ProjectionMatrixInverse |
| --- |
--- |
--- |
| gl_ModelViewProjectionMatrix |
mat4 |
C'est la matrice gl_ModelViewMatrix multipliée
par la matrice gl_ProjectionMatrix |
| gl_ModelViewProjectionMatrixInverse |
mat4 |
C'est l'inverse de la matrice gl_ModelViewProjectionMatrix |
| gl_ModelViewProjectionMatrixTranspose |
mat4 |
C'est la transposée de la matrice gl_ModelViewProjectionMatrix |
| gl_ModelViewProjectionMatrixInverseTranspose |
mat4 |
C'est la transposée de la matrice gl_ModelViewProjectionMatrixInverse |
| --- |
--- |
--- |
| gl_TextureMatrix[n] |
tableau de mat4 |
C'est la matrice de l'unité de texturage n,
maniable en C avec GL_TEXTURE |
| gl_TextureMatrixInverse[n] |
tableau de mat4 |
C'est l'inverse de la matrice gl_TextureMatrix[n] |
| gl_TextureMatrixTranspose[n] |
tableau de mat4 |
C'est la transposée de la matrice gl_TextureMatrix[n] |
| gl_TextureMatrixInverseTranspose[n] |
tableau de mat4 |
C'est la transposée de la matrice gl_TextureMatrixInverse[n] |
| --- |
--- |
--- |
| gl_NormalMatrix |
mat3 |
C'est la transposée inverse de la partie 3*3 de la matrice gl_ModelViewMatrix
(matrice généralement appliquée à la variable gl_Normal
pour les transformations de normales) |
Pffiouuu, ça fait du monde hein ?

Allez, que diriez-vous d'un petit...
Exercice
Fini d'rire !
Bien, voici le vertex shader que nous avons précédemment écrit :
Code : Autre1
2
3
4
5
6
7
| void main(void)
{
gl_Position = gl_Vertex;
} |
J'aimerai que vous le modifiez afin que la position finale du sommet (
gl_Position) soit affectée par la matrice de modélisation
et la matrice de projection, comme ça notre sommet aura la bonne position à l'écran si par exemple nous avons configuré une projection 3D

Un indice ? Rappelez-vous la première partie du tutoriel, à l'endroit où je parle des opérateurs, et plus précisément de leur surcharge
Allez-y !
...
Correction
Voici la réponse :
Secret (cliquez pour afficher)Code : Autre1
2
3
4
5
6
7
| void main(void)
{
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
} |
Allez, avouez que c'était pas trop difficile
Voilà, vous savez à présent théoriquement comment faire un vertex shader, évidemment celui-ci est extrêmement simple et n'a que peu d'intérêt, mais vous pouvez déjà essayer de vous amuser à modifier les variables de sortie pour voir le résultat que cela donnera et aussi pour vous familiariser avec le langage GLSL
Nous avons vu en gros quel était la tâche d'un vertex shader, son rôle au sein du rendu 3D. Que diriez-vous de savoir ce que fait un pixel shader à présent ? Vous vous demandez probablement à quoi ils peuvent bien servir, mais croyez-moi : ils servent énormément
Traiter le rendu d'un pixel ?
Un pixel shader agit au niveau du rendu du pixel à l'écran. C'est petit, très petit, et pourtant chaque pixel est traité indépendamment et nécessite des calculs, parfois nombreux, pour obtenir sa couleur exacte à l'écran.
Le code source d'un pixel shader peut être plus ou moins gros, mais une chose est sûre : plus il est conséquent et demande beaucoup de calculs et plus les performances chutent vite. Effectivement, alors que les vertex shaders agissent au niveau de chaque sommet, si vous n'affichez qu'un triangle, le code de votre vertex shader ne sera exécuté qu'une seule fois. Les pixel shaders quant à eux sont exécutés autant de fois qu'il y a de pixels dans ce triangle à l'écran ! Si votre triangle rempli tout l'écran et que votre fenêtre de rendu fait 1024*768 pixels, alors votre pixel shader sera appelé 1024 * 768 = 786432 fois !
C'est beaucoup, très beaucoup !
Une limite en puissance assez restreinte ?
Et pourtant non ! Il est aujourd'hui possible de programmer des pixels shaders très complexes sur des résolutions d'écran de 1600*1024 sans que le frame rate en soit très affecté.
Prenez un exemple simple : les jeux vidéo.
Les jeux vidéo récents utilisent énormément les shaders, vertex et pixel. Les joueurs s'achètent des écrans toujours plus larges et arrivent tout de même à jouer à des jeux gourmands tels que F.E.A.R ou SplinterCell DA en haute résolution et sans lags (saccades).
Pourquoi ?
Pourquoi, avec des résolutions énormes, des shaders complexes et des scènes rendues en plusieurs passes les jeux ne mettent pas à genoux les PC modernes ? (bien que certains se plaignent de jouer à 40 FPS

) Comment est-il possible de traiter plusieurs millions, et parfois même milliard(s), d'appels à un pixel shader par seconde ? La réponse est relativement simple : les pixels shaders sont des shaders, et par conséquent ils sont traités par la
carte graphique.
Ouah je suis super impressionné... En plus je le savais déjà.
Croyez-moi, il y a de quoi être impressionné, si vous demandiez à votre processeur de modifier une image pixel par pixel (vous comprendrez mieux cela lorsque nous parlerons des textures, plus loin dans le tutoriel), cela prendrait un temps énorme. Un exemple simple : les logiciels de dessin 2D (The GIMP, Photoshop, ...) sont très lourds et parfois aussi très lents à rendre un effet sur vos images, essayez pour voir de regarder quelle est la consommation CPU rien que quand vous dessinez un trait avec un effet de flou
Les cartes graphiques sont
conçues pour traiter des pixels, enfin tout du moins les cartes graphiques un minimum récentes. Si vous avez une GeForce 6 ou supérieur, ou une Radeon 9800pro ou supérieur, vous pouvez êtres sûrs que les shaders de sommet tout comme de pixel sont parfaitement supportés
Bon, qu'est-ce qu'on attend pour programmer ça ?
Que vous soyez psychologiquement prêts
Le code source de base
Tout comme le vertex shader, le pixel shader requiert une fonction main. En revanche contrairement aux vertex shaders, les pixel shaders GLSL n'exigent aucun code source de base au sein de la fonction main, ce qui veut dire qu'un pixel shader écrit comme suit :
Code : Autre1
2
3
4
5
6
7
| void main(void)
{
// rien
} |
est tout à fait acceptable et compilera sans broncher. Par contre, son effet est plus que maigre : il ne fait
rien. Et ici, rien signifie rien de rien, autrement dit, rien ne s'affichera à l'écran.
Avant de pouvoir faire afficher quelque chose à notre pixel shader, il est important de connaître ses variables d'entrée ainsi que celles de sortie, car il en possède, tout comme les vertex shaders.
Les variables d'entrée
Comme pour les variables d'entrée de nos vertex shaders, un joli tableau fera l'affaire. Il n'est bien sûr pas important que vous le reteniez par coeur pour l'instant, mais au moins, le jour où vous voudrez une info, vous n'aurez qu'à venir ici

:
| Nom de la variable GLSL | Type | Description |
|---|
| gl_Color |
vec4 |
Couleur du pixel |
| gl_FragCoord |
vec2 |
Coordonnées écran du pixel |
| gl_SecondaryColor |
bool |
Couleur secondaire |
| gl_TexCoord[n] |
tableau de vec4 |
Coordonnées de l'unité de texturage n |
| gl_FogFragCoord |
float |
Coordonnée de fog |
Comme vous le voyez, certaines variables correspondent à des variables de sortie du vertex shader. Cela prouve bien que les vertex et les pixel shaders GLSL sont très
liés entre eux.
Nous avons maintenant une couleur récupérable dans la variable
gl_Color, chouette, on va pouvoir attribuer une couleur à notre pixel. Mais... il nous manque quelque chose... Comment dire à GLSL qu'on souhaite voir notre pixel avec la couleur contenue dans
gl_Color ?
Hé bien l'expérience des vertex shaders devrait vous le dire, il nous faut une variable de sortie à laquelle attribuer cette couleur

.
Les variables de sortie
C'est bon, c'est fini les gros tableaux de la mort ?
Et non, pourtant avec un simple pixel shader on peut se demander ce qu'il peut bien faire à part affecter la couleur finale du pixel. Pourtant, il existe deux autres variables de sortie que celle qui permet de renvoyer la couleur du pixel :
| Nom de la variable | Type | Description |
|---|
| gl_FragColor |
vec4 |
Couleur finale du pixel |
| gl_FragDepth |
float |
Profondeur du pixel dans le depth buffer |
| gl_FragData[n] |
tableau de vec4 |
En rapport avec glDrawBuffers() |
Il n'existe malheureusement encore aucune variable permettant de modifier la position finale du pixel à l'écran.
L'intérêt général des pixel shaders est plutôt mince non ?
Pas du tout ! Bon évidemment pour l'instant il est normal que vous soyez sceptiques, mais vous découvrirez au fur et à mesure l'utilité des pixel shaders, et à la fin vous verrez ; on ne s'en passe plus
Programmer un simple pixel shader
Vous vous souvenez de la façon dont on s'y prend, dans les vertex shaders, pour affecter la variable de sortie
gl_Position afin que notre vertex soit positionné au bon endroit ? Et bien le principe est le même dans les pixel shaders lorsqu'on veut affecter la couleur du pixel sortant par la couleur d'entrée de base.
Contrairement au vertex shader où il faut appliquer des transformations matricielle pour obtenir la position finale, ici rien de particulier n'est à faire, il suffit de transmettre directement la couleur, comme ceci :
Code : Autre1
2
3
4
5
6
7
| void main(void)
{
gl_FragColor = gl_Color; // c'est aussi simple que cela
} |
Ce pixel shader n'est pas très évolué et n'a pour effet que d'affecter à la couleur finale du pixel la couleur interpolée des trois sommets formant le triangle auquel appartient ce pixel. (phrase compliquée je vous l'accorde

) Il n'y a aucun traitement des textures ou autres attributs du vertex, seules les données de couleur (
glColor*()) sont traitées.
Attention : Comme je l'ai déjà dit, le vertex shader est très lié au pixel shader, ainsi donc, si vous n'affectez pas la variable de sortie
gl_FrontColor de votre vertex shader, la variable d'entrée
gl_Color du pixel shader ne contiendra aucune valeur ! La valeur par défaut des variables est 0 généralement, vous aurez donc un écran noir si vous ne faites pas un vertex shader conçu comme ceci :
Code : Autre1
2
3
4
5
6
7
8
9
| void main(void)
{
gl_FrontColor = gl_Color;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
} |
Et voilà, c'est
déjà enfin la fin de ce chapitre, peut-être un peu rebutant, mais très important

.
Maintenant la compréhension de la suite du tutoriel vous sera plus aisée, et nous pourrons donc avancer plus vite dans l'apprentissage du langage, et c'est tant mieux, parce que le GLSL c'est bien joli, mais notre but de base c'est d'apprendre des techniques de rendu pour réaliser de chouettes effets graphiques