[Plan du site]
Vous êtes ici ---
> Le Site du Zéro
> Les tutoriels
> Non-Officiels
> Programmation
> Bibliothèques
> OpenGL
> Les shaders en GLSL > Les bases du GLSL > Implémentation du GLSL côté API
> Lecture du tutoriel
Implémentation du GLSL côté API
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)
Vous savez normalement comment fonctionne un shader et quel est son rôle, mais vous ne savez pas encore comment
l'intégrer dans un programme avec OpenGL.
Comment diable va-t-on réussir à utiliser une telle chose dans nos programmes OpenGL ? Comment ça marche ? C'est ce que nous allons étudier dans ce chapitre, nous apprendrons à manier les shaders avec OpenGL.
L'implémentation des shaders se décompose en plusieurs parties :
- premièrement, la création d'un shader. Nous verrons comment créer un shader en OpenGL;
- nous verrons ensuite comment attribuer un contenu à ce shader. Souvenez-vous : un shader est un programme, il possède donc un code source. Nous verrons comment envoyer un code source à notre shader précédemment créé;
- une fois le code source spécifié, il nous faudra le compiler. Il faudra également gérer les erreurs, car comme toute compilation, la compilation d'un shader peut échouer;
- notre shader est fin prêt, mais il n'est pas encore utilisable. Il nous faudra pour cela créer un program, car celui-ci peut être exécuté directement par la carte graphique.
Ici nous allons uniquement programmer en C avec l'API OpenGL, ne confondez pas ce que nous allons écrire maintenant avec le langage GLSL, celui-ci ne sera étudié qu'à partir du prochain chapitre.
Nous y voilà
Vous vous demandez probablement comment fonctionnent ces tant convoités shaders ? En réalité, ils ne sont pas difficiles à manier rassurez-vous
Nous allons voir ici la simple opération qu'est la création d'un shader, mais avant cela, il est important de savoir si votre implémentation supporte les shaders GLSL.
Comment ça ?
Les extensions des shaders
Si vous possédez une ancienne carte graphique, vous risquez de ne pas pouvoir utiliser les shaders ou alors leur utilisation conduirait à une erreur indéterminée (erreur de segmentation la plupart du temps

), il est donc important de tester si l'extension correspondante aux shaders GLSL est disponible.
C'est quoi les extensions ?
Je vous invite à lire
mon tutoriel sur les extensions d'OpenGL. Il vous expliquera dans le détail ce que sont les extensions, et comment on les utilise.
Bon OK, et c'est quoi le nom de l'extension des shaders GLSL ?
Il y en a en fait 4
Voici la liste des noms des extensions nécessaires à l'utilisation de shaders GLSL dans un programme OpenGL :
- GL_ARB_shading_language_100 : support du langage GLSL;
- GL_ARB_shader_objects : support des objets de shader (cf plus bas);
- GL_ARB_vertex_shader : support des vertex shaders;
- GL_ARB_fragment_shader : support des pixel shaders.
Globalement, on peut dire que les shaders GLSL sont supportés à partir des GeForceFX chez nVidia, et à partir du Radeon 9500 chez ATI.
Création d'un shader
Vous vous souvenez de ce que je vous ai dit dans l'introduction ? Le maniement des shaders ressemble à celui des textures

Et c'est quoi une texture ? ... (réflexion intense...) un
GLuint !
Code : C
Bon, et bien voilà, ça c'est fait...
Hep hep hep, une minute ! J'ai rien compris moi...
Eh bien un shader est un objet qui ne se manie qu'à partir de son identifiant, tout comme les textures.
Cependant, les shaders ne se manipulent pas avec les habituelles fonctions glGen*, glIs*, glBind* et compagnie, il va donc falloir que je vous fasse découvrir dans un premier temps la fonction qui va venir remplacer glGen*, autrement dit : la fonction qui va créer un shader et nous renvoyer son identifiant.
Il s'agit de la fonction glCreateShader, dont voici le prototype :
Code : C1 | GLuint glCreateShader(GLenum type);
|
- type : attend une constante qui peut être GL_VERTEX_SHADER ou GL_FRAGMENT_SHADER. Chacune de ces constantes représente respectivement un shader de sommet et un shader de pixel. Souvenez-vous lors de l'introduction je vous avait dit qu'il existait deux types de shader, et bien c'est ici que vous allez spécifier ce type

Cette fonction renvoie l'identifiant de l'objet de shader créé.
Allez, un exemple complet :
Code : C1
2 | GLuint shader;
shader = glCreateShader(GL_VERTEX_SHADER);
|
Et voilà, nous avons à présent un shader de sommet prêt à être utilisé
Et quand on ne voudra plus l'utiliser ?
Alors il faudra le supprimer.
Voici le prototype de la fonction qui permet de supprimer un shader :
Code : C1 | void glDeleteShader(GLuint shader);
|
Nous l'utiliserons comme ceci au sein de notre programme :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | GLuint shader;
/* creation */
shader = glCreateShader(GL_VERTEX_SHADER);
if(shader == 0)
{
/* erreur de creation :( */
return;
}
/* utilisation ... */
/* suppression */
glDeleteShader(shader);
shader = 0;
|
Notez l'ajout de la dernière ligne de code, ainsi que de la vérification de la valeur de l'identifiant. Tout comme tout autre identifiant d'objet OpenGL, 0 n'est pas un identifiant de shader valide. Bien que cela soit rare, il n'est pas impossible qu'à priori OpenGL vous renvoie 0, il est donc conseillé de tester la valeur de retour de glCreateShader, mais ce n'est pas obligatoire comme avec malloc.
Pour finir, je vais vous présenter une fonction dont la syntaxe devrait vous être familière : glIsShader.
A l'instar des autres fonctions glIs* d'OpenGL, cette fonction permet de savoir si l'identifiant qu'elle reçoit en paramètre est un identifiant de shader valide. Voici le prototype de cette fonction :
Code : C1 | GLboolean glIsShader(GLuint shader);
|
Bien, nous avons réussi à créer un shader... mais quelles sont les fonctions permettant de le manipuler afin de l'utiliser à notre guise ? Eh bien suivez-moi, c'est par là
Nous voici parvenus à la seconde étape de la création d'un shader fonctionnel.
Nous allons voir ici comment charger notre code source afin de le donner à manger à OpenGL.
Comment ça ?
Je vous l'ai déjà dit lors de l'introduction, les shaders sont compilés lors de l'exécution de votre application, il va donc falloir charger leur code source dynamiquement, à partir d'un fichier par exemple
Pourquoi un shader doit se compiler à l'exécution ? Ça doit être lent non ?
Ce n'est pas lent du tout rassurez-vous

Sans être affirmatif, je pense que les principales raisons à cela sont :
- plus simple à gérer, s'il fallait procéder à la compilation séparée des shaders on ne s'en sortirait plus;
- je pense qu'une compilation à l'exécution permet à OpenGL d'optimiser le shader en fonction du matériel de la machine qui exécute le programme OpenGL.
Charger un code source à partir d'un fichier
Voilà une étape des plus enfantines, pour peu que vous sachiez utiliser les fichiers dans le langage que vous utilisez pour faire vos programmes OpenGL.
Je vais ici vous présenter un exemple de code en langage C (langage qui est et sera d'ailleurs utilisé pour tous les exemples). Le but est donc d'obtenir un code source, et qu'est-ce qu'un code source ? Une chaîne de caractères ! Bingo

Voyons voir... :
Code : C1 | char* LoadSource(const char *filename);
|
Ceci pourrait être un bon prototype pour une fonction ayant pour but de charger le code source de nos shaders

C'est une fonction simple et générique, je l'adopte !
Il faut à présent la programmer, rien de très sorcier :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 | char* LoadSource(const char *filename)
{
char *src = NULL; /* code source de notre shader */
FILE *fp = NULL; /* fichier */
long size; /* taille du fichier */
long i; /* compteur */
/* on ouvre le fichier */
fp = fopen(filename, "r");
/* on verifie si l'ouverture a echoue */
if(fp == NULL)
{
fprintf(stderr, "impossible d'ouvrir le fichier '%s'\n", filename);
return NULL;
}
/* on recupere la longueur du fichier */
fseek(fp, 0, SEEK_END);
size = ftell(fp);
/* on se replace au debut du fichier */
rewind(fp);
/* on alloue de la memoire pour y placer notre code source */
src = malloc(size+1); /* +1 pour le caractere de fin de chaine '\0' */
if(src == NULL)
{
fclose(fp);
fprintf(stderr, "erreur d'allocation de memoire!\n");
return NULL;
}
/* lecture du fichier */
for(i=0; i<size; i++)
src[i] = fgetc(fp);
/* on place le dernier caractere a '\0' */
src[size] = '\0';
fclose(fp);
return src;
}
|
Ceci étant fait, nous devons voir à présent comment envoyer notre code source à notre objet
shader.
Effectivement, OpenGL nous propose de spécifier le code source de notre shader via un
char*. Ça tombe bien, nous l'avons celui-ci
Voici la fonction qui permet d'envoyer le code source d'un shader :
Code : C1 | void glShaderSource(GLuint shader, GLsizei nombre, const GLchar **sources, const GLint *longueur);
|
- shader : c'est l'identifiant de notre shader, afin que la fonction adresse notre code source à ce shader et pas un autre.
- nombre : c'est le nombre de chaînes contenues dans sources.
- sources : c'est notre code source, décomposé en plusieurs chaînes.
- longueur : un paramètre bizarre autant que compliqué
nous le laisserons à NULL, c'est tout à fait autorisé 
Hé mais cette fonction attend plusieurs chaînes, mais nous on en a qu'une seule !
Ce fut également la réaction que j'ai eu lorsque j'ai vu le prototype de la fonction pour la première fois

J'ai vite compris que ça n'avait aucune importance, nous avons une seule chaîne, donc nous allons positionner
nombre à 1 et nous enverrons l'adresse de notre pointeur sur notre code source.
Un exemple ? Ok :
Code : C1
2 | char *src = LoadSource(...);
glShaderSource(shader, 1, &src, NULL);
|
Voilà, notre shader est à présent prêt à être compilé
C'est quoi l'intérêt d'envoyer un code source à OpenGL si ce n'est pas pour qu'il le compile ? Pourquoi ne l'a-t-il pas déjà compilé ?
Vous restez ainsi maître de votre application et c'est réellement
vous qui choisissez
quand il doit se passer telle ou telle chose.
Notez qu'il n'est pas possible de procéder à plusieurs appels de la fonction glShaderSource() afin "d'entasser" les codes sources, seul le dernier code envoyé sera pris en compte !
Bien, passons à présent à la compilation.
Elle a droit à toute une sous-partie et ce n'est pas pour rien, comme nous allons le voir
Vous avez sans doute l'habitude de compiler vos programmes en C ou dans un autre langage, et en général la procédure se déroule ainsi :
- vous enregistrez votre code source dans un fichier;
- vous demandez la compilation de ce code source;
- vous tombez sur une erreur de compilation

C'est la dernière étape la plus intéressante

En effet, imaginez que vous ayez fait une faute dans l'écriture du code de votre shader, OpenGL va alors refuser la compilation et générera une erreur accompagnée d'un message, vous indiquant où vous vous êtes plantés

Il est très important de récupérer les messages d'erreur de compilation des shaders, vous ferez probablement des erreurs parfois en programmant vos shaders, ou bien des fautes de frappe.
Récupérer une erreur de compilation c'est aussi et surtout récupérer une ligne de code, c'est grâce à elle que vous pourrez localiser votre erreur.
Nous allons dans un premier temps voir comment compiler un shader, puis nous verrons comment vérifier le succès de cette compilation en récupérant un code d'erreur, en l'analysant, et en agissant en fonction de celui-ci.
Compiler un shader
Pour compiler un shader GLSL, rien de compliqué, il existe une fonction dédiée à cela.
La fonction permettant de compiler un shader est glCompileShader, tout simplement. Voici son prototype :
Code : C1 | void glCompileShader(GLuint shader);
|
- shader : c'est l'identifiant de notre shader à compiler.
Voici un exemple de code complet, reprenant les exemples précédents (le contenu de la fonction
LoadSource() a été enlevé afin d'alléger le code) :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 | GLuint shader;
char *src = NULL;
/* creation d'un shader de sommet */
shader = glCreateShader(GL_VERTEX_SHADER);
if(shader == 0)
{
fprintf(stderr, "impossible de creer le shader\n");
return -1;
}
/* chargement du code source */
src = LoadSource("test.vert");
if(src == NULL)
{
/* theoriquement, la fonction LoadSource a deja affiche un message
d'erreur, nous nous contenterons de supprimer notre shader
et de retourner 0 */
glDeleteShader(shader);
return 0;
}
/* assignation du code source */
glShaderSource(shader, 1, (const GLchar**)&src, NULL);
/* compilation du shader */
glCompileShader(shader);
/* liberation de la memoire du code source */
free(src);
/* verification du succes de la compilation ... */
...
/* utilisation ... */
...
/* suppression */
glDeleteShader(shader);
|
Ne pas oublier de libérer le pointeur src après la compilation, même si celle-ci a échoué, le code source ne nous sera de toute façon d'aucune utilité.
Vérifier le succès d'une compilation
Comme pour toute compilation, des erreurs sont possibles et leur nature doit être connue. Pour cela, il nous faut savoir si il y a eu une erreur, et s'il y en a une, on récupère le message qu'elle contient et on affiche ce dernier à l'écran.
Il existe une fonction pour récupérer l'état de la compilation. Plus globalement, il s'agit d'une fonction permettant de récupérer un entier relatif à une information spécifique d'un shader.
Code : C1 | void glGetShaderiv(GLuint shader, GLenum type, GLint *result);
|
- shader : nous placerons ici l'identifiant de notre shader.
- type : il s'agit du type d'état demandé. Nous recherchons l'état de la compilation du shader, nous placerons donc ici la constante GL_COMPILE_STATUS.
- result : il s'agit d'un pointeur sur un entier dans lequel OpenGL écrira la valeur de l'état demandé.
Cette fonction va donc placer dans
result la valeur de l'état demandé, en l'occurrence, l'état de la compilation du shader.
Nous allons procéder ainsi :
Code : C 1
2
3
4
5
6
7
8
9
10 | GLint compile_status = GL_TRUE;
/* verification du succes de la compilation */
glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status);
if(compile_status != GL_TRUE)
{
/* erreur a la compilation
recuperation du log d'erreur */
...
}
|
Si
compile_status est différent de la constante GL_TRUE, une erreur est survenue et il nous faut donc récupérer le message qu'elle contient. Attention, cette procédure n'est à effectuer que si la compilation a échoué, sinon elle est inutile et ne ferait que ralentir l'application. C'est pour cela que nous allons à présent remplir de quelques lignes de code le bloc de ce
if.
Récupérer les messages d'erreur de la compilation
Bien, supposons qu'une erreur soit intervenue, nous nous trouvons à présent dans le bloc du
if du code précédent, et il nous faut agir.
Le message d'erreur se trouve sous la forme d'une chaîne de caractères, mais attention, OpenGL n'allouera aucune mémoire pour nous, il va donc falloir lui demander quelle est la taille du message d'erreur, allouer une chaîne de cette taille puis demander à OpenGL d'écrire le message d'erreur dans notre mémoire fraîchement allouée.
Nous allons reprendre notre fonction glGetShaderiv, mais cette fois-ci en lui envoyant comme second paramètre (
type) la constante GL_INFO_LOG_LENGTH, afin d'obtenir la longueur du message d'erreur :
Code : C1
2
3
4
5 | GLint logsize;
...
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logsize);
|
A présent, il nous faut allouer un espace mémoire de la taille de
logsize :
Code : C 1
2
3
4
5
6
7
8
9
10 | char *log = NULL;
...
log = malloc(logsize+1); /* +1 pour le caractere de fin de chaine '\0' */
if(log == NULL)
{
fprintf(stderr, "erreur d'allocation memoire !\n");
return -1; /* ou autre code approprie */
}
|
Et pour finir, nous allons récupérer le message d'erreur en envoyant notre pointeur
log à une fonction d'OpenGL appelée glGetShaderInfoLog.
Voici le prototype de cette fonction :
Code : C1 | void glGetShaderInfoLog(GLuint shader, GLsizei max_size, Glsizei *longueur, char *info_log);
|
- shader : c'est l'identifiant de notre shader.
- max_size : c'est le nombre maximal de bytes qu'OpenGL écrira dans notre mémoire. OpenGL ne dépassera pas cette valeur.
- longueur : c'est la longueur de notre chaîne. C'est un paramètre un peu bizarre je vous l'accorde, nous placerons ici l'adresse de notre variable logsize.
- info_log : c'est l'adresse mémoire dans laquelle OpenGL écrira le message d'erreur.
Il ne faudra évidemment pas oublier de libérer notre mémoire
log après avoir affiché son contenu.
Voyons à présent si vous le voulez bien, un exemple de code complet

:
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70 | GLuint LoadShader(GLenum type, const char *filename)
{
GLuint shader = 0;
GLsizei logsize = 0;
GLint compile_status = GL_TRUE;
char *log = NULL;
char *src = NULL;
/* creation d'un shader de sommet */
shader = glCreateShader(type);
if(shader == 0)
{
fprintf(stderr, "impossible de creer le shader\n");
return 0;
}
/* chargement du code source */
src = LoadSource(filename);
if(src == NULL)
{
/* theoriquement, la fonction LoadSource a deja affiche un message
d'erreur, nous nous contenterons de supprimer notre shader
et de retourner 0 */
glDeleteShader(shader);
return 0;
}
/* assignation du code source */
glShaderSource(shader, 1, (const GLchar**)&src, NULL);
/* compilation du shader */
glCompileShader(shader);
/* liberation de la memoire du code source */
free(src);
src = NULL;
/* verification du succes de la compilation */
glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status);
if(compile_status != GL_TRUE)
{
/* erreur a la compilation recuperation du log d'erreur */
/* on recupere la taille du message d'erreur */
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logsize);
/* on alloue un espace memoire dans lequel OpenGL ecrira le message */
log = malloc(logsize + 1);
if(log == NULL)
{
fprintf(stderr, "impossible d'allouer de la memoire !\n");
return 0;
}
/* initialisation du contenu */
memset(log, '\0', logsize + 1);
glGetShaderInfoLog(shader, logsize, &logsize, log);
fprintf(stderr, "impossible de compiler le shader '%s' :\n%s",
filename, log);
/* ne pas oublier de liberer la memoire et notre shader */
free(log);
glDeleteShader(shader);
return 0;
}
return shader;
}
|
Et voilà, nous avons une fonction opérationnelle capable de charger un shader à partir d'un fichier

La fonction renvoie 0 en cas d'erreur, ou bien l'identifiant du shader créé si elle a réussi.
"program" ? C'est quoi ce nouveau mot ?
C'est comme cela que l'on appelle les
program de shader en OpenGL. Un peu flou ?

Nous venons de voir comment créer un shader, mais ce shader n'est hélas pas exécutable, la seule chose qui soit exécutable est un
program.
Un program recueille un ou deux shaders, et il est exécutable par la carte graphique.
Créer un objet de program
Et oui, nous revoilà à nouveau avec des objets OpenGL, eux aussi différents de tous ceux que vous aurez pu manipuler jusqu'à maintenant.
Rassurez-vous toutefois, leur fonctionnement est très simple

La base des objets OpenGL est d'ailleurs reprise, un objet de program est un GLuint (entier non signé) :
Code : C
La création d'un program est simple, il suffit d'appeler la fonction
glCreateProgram() qui ne prend aucun paramètre, et retourne simplement l'identifiant du program créé :
Code : C1 | program = glCreateProgram();
|
Jusque là, pas de difficulté majeure
Idem pour la suppression d'un program, cela se fait en toute simplicité avec la fonction
glDeleteProgram(). Un exemple ? :
Code : C1 | glDeleteProgram(program);
|
Pour l'instant, notre program ne fait rien, il va donc falloir lui donner des shaders à exécuter.
Association d'un ou plusieurs shaders à un program
Oui, vous avez bien lu.
Comme je l'ai déjà dit, un program est un recueil de un ou deux shaders. On peut y mettre soit un vertex shader, soit un pixel shader, soit les deux.
Pour clarifier vos idées dès à présent, je vous ai préparé un petit schéma

Voici la procédure à suivre pour créer un program exécutable :
- créer un vertex shader, nous allons l'appeler VS;
- créer un pixel shader, nous allons l'appeler PS;
- créer un program, nous l'appellerons Program;
- incorporer (je fais de la cuisine là
) le vertex et le pixel shader dans le program;
- rendre le program exécutable en le liant (nous verrons plus bas en quoi cela consiste);
- le program étant lié, nous pouvons supprimer nos shaders VS et PS si nous en avons plus besoin;
- utiliser le program à notre guise

- détruire les shaders (si ce n'est pas déjà fait) et le program.
Et voici le schéma correspondant

:
Ici, trois notions qui vous sont encore inconnues ont été introduites, il s'agit de "attach", "construction" et "utilisation".
Nous allons sans plus tarder nous intéresser à "attach".
Bon, nous voulons donc associer un ou plusieurs shaders à un program. Il existe pour cela une fonction très simple,
glAttachShader() :
Code : C1 | void glAttachShader(GLuint program, GLuint shader);
|
- program : il s'agit là du program qui recevra le shader.
- shader : c'est le shader à associer à program.
Son utilisation est tellement simple qu'on en pleurerait
Je vous épargne l'exemple de code pour cette fonction, je pense que ça serait inutile.
A l'inverse de
glAttachShader(), il existe une fonction qui a l'effet contraire.
Supposez que vous vouliez mettre à jour un shader, il faudra d'abord que vous le détachiez du program auquel il a été attaché via
glAttachShader(), utilisez pour cela
glDetachShader() :
Code : C1 | void glDetachShader(GLuint program, GLuint shader);
|
Je pense qu'elle se passe de commentaire
Bien, nous savons à présent comment créer un program exécutable, comment lui assigner des shaders, mais nous ne savons toujours pas comment l'utiliser. Toutefois avant de pouvoir utiliser un program, il est important de le rendre opérationnel en le liant.
Liage d'un program de shader
Le liage (ou linking) d'un program peut se comparer à la compilation des shaders car il se décompose également en deux étapes :
- le liage en lui même;
- la vérification du succès de ce liage.
Oui mais qu'est-ce que le liage d'un program ?
Et bien en fait lorsqu'on lie un program de shader on demande à OpenGL de
lier le vertex shader avec le pixel shader. Cette liaison peut échouer dans la mesure où les deux shaders peuvent être incompatibles entre eux.
Nous verrons dans une autre partie de ce tutoriel en quoi consiste réellement le linking et ce qu'il apporte.
Bien, passons à présent au vif du sujet : nous voulons lier un program de shaders.
Pour ce faire, il existe une simple fonction appelée
glLinkProgram() :
Code : C1 | void glLinkProgram(GLuint program);
|
- program : c'est le program que l'on souhaite lier.
Comme je l'ai dit plus haut, l'étape suivante consiste à vérifier que le liage a bien fonctionné. La méthode employée est très similaire à celle des shaders :
- on vérifie si une erreur est présente;
- s'il y en a une, on récupère la taille de la chaîne contenant le message d'erreur;
- on alloue un espace mémoire de cette taille;
- on indique l'adresse de cette mémoire à OpenGL pour qu'il puisse y écrire le message d'erreur.
Voici la fonction permettant (entre autres) de savoir si une erreur a été levée :
Code : C1 | void glGetProgramiv(GLuint program, GLenum type, GLint *result);
|
Son fonctionnement est identique à glGetShaderiv pour les shaders, je vais donc passer les descriptions minutieuses. Tout ce que vous devez savoir ici, c'est la constante à passer à
type pour obtenir ce que l'on cherche. Nous cherchons l'état du précédent liage du program, pour cela nous allons passer la constante GL_LINK_STATUS. La vérification de la valeur de
result fonctionne de la même façon que pour les shaders, si elle est différente de GL_TRUE, alors une erreur a été levée.
Etape suivante, nous voulons récupérer la longueur du message d'erreur, nous allons utiliser pour cela glGetProgramiv avec comme paramètre
type la constante GL_INFO_LOG_LENGTH (comme pour les shaders).
Enfin, pour récupérer le message d'erreur, nous avons à notre disposition la fonction glGetProgramInfoLog :
Code : C1 | void glGetProgramInfoLog(GLuint program, GLsizei max_size, GLsizei *longueur, char *log);
|
Son fonctionnement est lui aussi identique à la fonction glGetShaderInfoLog pour les shaders.
Un exemple de code complet sera disponible à la fin du chapitre.
Utiliser un program de shader
Ah ! Nous voici arrivés à la partie la plus intéressante
Résumons une fois de plus si vous le voulez bien :
- nous savons créer un shader;
- nous savons créer un program exécutable;
- nous savons attribuer à ce program des shaders à exécuter;
- nous ne savons pas comment rendre ce program actif

Utiliser un program, c'est le rendre actif pour tous les prochains rendus jusqu'à ce que l'on demande explicitement l'arrêt de l'utilisation d'un quelconque program de shader, ou jusqu'à ce que l'on active un autre program de shader.
Comme toujours, je vais reprendre mon exemple sur la similitude avec les textures. Vous savez certainement comment on rend une texture active ?
Code : C1 | glBindTexture(GL_TEXTURE_2D, tex_id);
|
Ce code rend la texture
tex_id active pour tous les prochains rendus jusqu'à ce qu'un appel de glBindTexture avec un identifiant de 0 soit effectué :
Code : C1
2 | /* desactive la precedente texture active */
glBindTexture(GL_TEXTURE_2D, 0);
|
Vous noterez que pour les textures, l'emploi de glDisable(GL_TEXTURE_2D) est préférable, mais pour utiliser un program de shader, aucun état OpenGL n'est à activer.
Bien, il est temps de vous présenter la fonction qui permet de rendre un program actif pour le rendu

:
Code : C1 | void glUseProgram(GLuint program);
|
- program : il s'agit là de l'identifiant du program que l'on souhaite activer, si cet identifiant est de 0, OpenGL désactivera l'utilisation des programs de shaders.
Allez, un exemple pour le fun

:
Code : C1
2
3
4
5
6 | glUseProgram(program);
/* super rendus avec des effets de la mort qui tue */
/* desactive l'utilisation des shaders, OpenGL retourne en mode normal (passe par le FFP) */
glUseProgram(0);
|
Tout comme les textures, vous pouvez charger plusieurs shaders en début de programme et ensuite les utiliser simultanément, comme ceci :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | glUsePogram(prog1);
/* rendus utilisant le program prog1 */
glUsePogram(prog2);
/* rendus utilisant le program prog2 */
glUsePogram(prog3);
/* rendus utilisant le program prog3 */
glUseProgram(0);
/* fin des rendus utilisant les shaders */
|
Attention toutefois, comme avec les textures, il est impossible d'utiliser deux programs à la fois. Ici, le second appel à glUseProgram défini prog2 comme étant actif
à la place de prog1.
Voilà, je crois que nous avons abordé toutes les facettes de la gestion des shaders GLSL du côté de l'API

Bien sûr, il reste encore quelques détails, mais je vous les ferai connaître plus tard, pour l'instant ils ne feraient que vous embrouiller croyez-moi
Afin que vous vous fassiez une meilleure idée de la façon dont tout ce bazar s'assemble, je vous ai préparé un programme complet chargeant un simple vertex shader qui a pour effet de transformer l'image affichée en couleurs négatives.
Pour activer le shader, vous devez appuyer sur une touche du clavier.
J'utilise
Glew pour charger les extensions d'OpenGL, vous devez le posséder si vous voulez pouvoir compiler/recompiler le code source. L'exécutable fourni utilise la glibc2.4
Que les utilisateurs de MacOS et Windows m'excusent, je n'utilise pas ces OS et par conséquent je n'ai pas pu vous mijoter quelque chose, il va falloir que vous vous débrouilliez pour compiler les sources
Toutefois je vous recommande fortement de télécharger ces sources pour les étudier afin de bien comprendre comment fonctionne le chargement et l'utilisation des shaders. N'hésitez pas à relire les parties du chapitre que vous n'avez pas comprises, avec l'exemple de code sous les yeux afin de bien faire la relation entre ce que j'explique et la mise en pratique
Un peu à l'instar de Kayl avec son sdlglutils, je vous propose de télécharger cet ensemble de deux fichiers qui proposent quelques fonctions bien pratiques pour charger un shader simplement sans trop se fatiguer :
Ce sont en réalité les fonctions que nous avons construites ensembles, avec quelques petits ajouts toutefois

Dorénavant j'utiliserai ces fonctions pour le chargement des shaders afin d'alléger les exemples de code.
Ouf, nous voici enfin arrivés au terme de ce long chapitre !
Vous savez à présent comment utiliser les shaders dans vos programmes OpenGL, les activer, les désactiver, etc...
Mais (parce qu'il y a un mais

), vous ne savez pas
programmer un shader.
Et d'ailleurs, ça vous dirait de savoir les programmer, ces fameux shaders ? Oui ? Pas de problème, c'est par ici