Aller au menu - Aller au contenu

[Plan du site] Vous êtes ici --- > Le Site du Zér0 > Les tutoriels > Non-Officiels > Programmation > C > Lecture du tutoriel

Les bases du langage

Avatar
Auteur : Yno
Créé : le 06/04/2007 03:23:36
Modifié : le 20/07/2007 12:31:57
Noter et commenter ce tutoriel
Imprimer ce tutoriel
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 :



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.
Sommaire du chapitre :
Chapitre précédent Sommaire Chapitre suivant

Une forte ressemblance avec le C

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
1
float variable;

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
1
float a, b;

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 :


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 : Autre
1
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 : Autre
1
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 o_O

Vous connaissez probablement la multiplication matricielle ? Et bien effectuer ce genre de multiplication en GLSL est un jeu d'enfant :

Code : Autre
1
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 : Autre
1
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 : Autre
1
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 : C
1
float flottant = (float)entier;

En GLSL, le cast d'une variable se fait ainsi :

Code : Autre
1
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 : Autre
1
2
3
vec3 v = ...;
 
vec2 v2 = vec2(v);


Ce code est simple et pourrait se traduire de la façon suivante :

Code : Autre
1
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 : Autre
1
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 : Autre
1
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.

Ce que vous avez sous les yeux est le résultat d'une compilation d'un shader ayant échouée, c'est le message d'erreur que nous a retourné OpenGL. La première phrase est de moi, je l'ai placée dans le code source C du programme. Les deux autres lignes contiennent un message mais avant cela, entre parenthèses, la ligne de code de notre shader qui a été refusée (5). C'est très important, retenez cela ;)

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 : Autre
1
2
vec2 v2 = ...;
vec3 v3 = vec3(v2, 0.0);

Ce code est équivalent à :

Code : Autre
1
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 : Autre
1
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 : Autre
1
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 : Autre
1
vec3 v = vec3(0.0, 1.0, 0.5);

Allez cherchez un peu :-°

...

Alors vous trouvez ? :D

...

Bon, je sens que vous avez fait bouillonner votre cerveau, c'est l'heure de votre récompense :

Code : Autre
1
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
1
vec3 v = vec3(0.0);

Ce code place toutes les composantes du vecteur v à 0.


Et les matrices... ?



Vous savez quoi ? On peut mettre des vecteurs dans des matrices :D Si si je vous assure :) Ainsi, ce code est tout à fait correct :

Code : Autre
1
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
1
mat3 m = mat3(1.0);

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 : Autre
1
2
/* commentaire sur
   plusieurs lignes */

Le GLSL accepte également les commentaires commençant par // comme en C99 :

Code : Autre
1
// 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 : Autre
1
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 : Autre
1
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 : Autre
1
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 : Autre
1
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 :D
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 : Autre
1
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 :


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 : Autre
1
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.


Un vertex shader

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


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 : C
1
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 :D


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 : Autre
1
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 GLSLTypeFonction OpenGL appropriéeDescription
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 GLSLTypeDescription
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 ? :D
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 : Autre
1
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 :D

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 variableTypeDescription
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 ? :D
Allez, que diriez-vous d'un petit...

Exercice



Fini d'rire ! :diable:

Bien, voici le vertex shader que nous avons précédemment écrit :

Code : Autre
1
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 : Autre
1
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 ;)

Un pixel shader

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 ! :D

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

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 : Autre
1
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 GLSLTypeDescription
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 variableTypeDescription
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 : Autre
1
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 : Autre
1
2
3
4
5
6
7
8
9
void main(void)

{

    gl_FrontColor = gl_Color;

    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;

}


Q.C.M.

Quel est le nom de la matrice du vertex shader permettant d'appliquer les modifications de la matrice GL_MODELVIEW ?
Le code de ce vertex shader est-il correct ?

Code : Autre
1
2
3
4
5
6
7
void main(void)

{

    gl_FrontColor = gl_Color;

}

Ces instructions sont-elle correctes ?



Code : Autre
vec2 v2 = vec2(0.0, 1.0);


vec3 v3 = vec3(v2);



La variable de sortie gl_Position du vertex shader doit représenter la position finale en coordonnées écran du vertex, comment dois-je alors procéder pour lui affecter correctement la bonne valeur ?
Quel est le nom de la variable de sortie d'un pixel shader permettant d'affecter la couleur finale du pixel ?


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

Les tableaux énumératifs de variables de ce chapitre sont inspirés du livre OpenGL 2.0 Guide Officiel,
mais certains sont également disponibles dans les spécifications du langage.
Chapitre précédent Sommaire Chapitre suivant
Auteur : Yno
Noter et commenter ce tutoriel
Imprimer ce tutoriel

Changer de design | En savoir plus | Plan du site | Politique d'accessibilité | Règles | Fil RSS | XHTML 1.0 | CSS 2.0
Édité par Simple IT SARL : Nous contacter | Revue de presse | Publicité

Y'a plus rien à lire, faut remonter maintenant !

Hébergement web - Correction de tutoriels - Créer un site
Vous souhaitez apparaître ici ? Contactez-nous.

Nombre de connectés 93 Zéros connectés | Requêtes SQL 10 requêtes | Temps de génération de la page : Total (SQL) 0.1146s (0.1019s)