Principe
L'idée est simple : envoyer une variable de l'application au shader. En réalité ce n'est pas un véritable envoi, mais plutôt une copie de valeur. Nous allons pour cela créer une variable dans notre shader, en lui assignant un type particulier, puis, à partir de notre application, localiser cette variable dans notre shader pour y demander la copie d'une valeur.
Vous pouvez bien sûr transmettre différents types de variables, comme des entiers, des flottants, des booléens, des vecteurs et même des matrices.
Le type « uniform »
Dans un shader, pour créer des variables capables de recevoir leur valeur à partir de l'application appelante, il faut les rendre
globales et leur assigner le préfixe
uniform.
Rendre une variable globale ?

Cela veut dire, comme en C, rendre une variable accessible par tout le programme. Créer une variable globale en GLSL revient à faire ceci :
Code : Autre1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| int variable; // 'variable' est globale
void main(void)
{
...
}
... |
Et le type « uniform » dans tout ça ?
Il vient se placer devant le type de la variable, comme ceci :
Code : Autre
Déclarer une variable avec
uniform revient à dire : "je veux que la valeur de cette variable soit indiquée par mon application".
Et voilà, c'est tout ce qu'on aura à faire dans notre shader pour indiquer à OpenGL les variables dont la valeur proviendra de l'application
Implémentation côté API
Une affaire d'identifiants
Dites donc, on dirait qu'ils aiment bien les identifiants chez OpenGL

Avant de pouvoir transmettre une valeur à une variable de notre shader, il est important de préciser à OpenGL quelle variable recevra la valeur.
Ben, c'est celle qu'on a créée avant avec uniform non ?
Eh bien non, pas forcément, car
il est possible de créer plusieurs variables uniform dans un même shader. Si par exemple vous en créez deux, comment OpenGL saura à quelle variable il doit transmettre la valeur ? Il est donc important de récupérer l'
ID de notre variable avant de lui envoyer une valeur.
Récupérer un ID
Il n'existe pas 36 façons de localiser une variable dans un shader (et n'importe où d'ailleurs...), il va falloir donner à OpenGL le nom de notre variable, pour qu'il nous retourne son identifiant, nous nous servirons ensuite de celui-ci pour envoyer une valeur à notre variable.
Voici la fonction OpenGL permettant de récupérer l'ID d'une variable dans un shader :
Code : C1 | GLint glGetUniformLocation(GLuint program, const char *nom);
|
- program : c'est l'identifiant du program tout entier dans lequel on voudra rechercher la variable.
- nom : le nom de la variable dont on veut récupérer l'identifiant.
Attention : si vous créez une variable uniform qui a le même nom dans le vertex et le pixel shader, les deux variables seront affectées par la valeur que vous spécifierez.
La valeur retournée par cette fonction est l'identifiant de votre variable de shader nommée
"nom", si la fonction n'a pas trouvé votre variable, ou qu'elle a échoué pour une raison x ou y, elle renvoie -1.
Dans quelle mesure cette fonction peut "échouer" ?
Si votre program n'a pas été lié par exemple, rappelez-vous dans le second chapitre de la fonction
glLinkProgram().
Assigner une valeur
Nous voici enfin parvenus à l'étape finale : l'envoi d'une valeur à la variable de notre shader
Pour cela, nous avons besoin de 3 choses :
- l'identifiant de notre variable, récupéré avec glGetUniformLocation() ;
- une valeur à attribuer à cette variable ;
- et surtout, que notre program ait été défini comme actif pour le rendu, c'est-à-dire activé via glUseProgram() !
La dernière condition est très importante, si votre program n'a pas été activé, une erreur OpenGL de type GL_INVALID_OPERATION sera levée.
Allez, il est temps que je vous présente la fonction permettant d'envoyer une valeur à une variable de notre shader

:
Code : C1 | void glUniform*(GLint id, TYPE val);
|
- id : c'est l'ID de notre variable, récupéré via glGetUniformLocation().
- val : la valeur que l'on souhaite envoyer à notre variable.
Cette fonction agit directement sur le program actif, d'où la nécessité de l'avoir activé au préalable avec
glUseProgram().
Lorsque
glUniform*() est appelée, la variable désignée par l'identifiant prend alors la valeur demandée, et garde cette valeur jusqu'à ce que le program soit à nouveau lié ou supprimé. Par conséquent, si vous souhaitez envoyer une valeur constante (un paramètre de démarrage par exemple), n'invoquez
glUniform*() qu'une seule fois.
Les différentes formes de glUniform*()
Comme vous l'aurez remarqué, j'ai mis une petite étoile « * » au nom de la fonction, c'est pour dire qu'elle a été définie sous plusieurs formes, comme pour les fonctions
glVertex*(),
glTexCoord*(), etc...
Cela permet d'envoyer différents types de variables, comme je l'ai dit plus haut ; des vecteurs, des matrices, etc...
Envoi d'une simple variable
La fonction
glUniform*() a été définie sous de nombreuses formes. Toutefois, le nombre de types de variable qu'elle supporte est toujours limité à deux :
- les entiers (int) ;
- les flottants (float).
Du côté du GLSL, nous remarquons la présence d'un type supplémentaire : le type
bool. Rassurez-vous cependant, vous pourrez affecter une variable
bool en passant par la forme entière de
glUniform*().
Voici un premier exemple de code illustrant le simple envoi d'une variable à notre program nommé
prog :
Code : Autre1
| uniform int var; // n'oublions pas de declarer 'var' globale |
Code : C1
2
3
4
5
6
7
8 | /* on recupere l'ID */
int id = glGetUniformLocation(prog, "var");
/* on defini notre program actif */
glUseProgram(prog);
/* on envoie notre variable (ici nous envoyons la valeur 2) */
glUniform1i(id, 2);
|
Notez qu'il n'est pas obligatoire de récupérer l'identifiant à chaque fois que vous voudrez envoyer une valeur à une variable de votre shader, l'ID est invariable, sauf si vous liez à nouveau votre program. Donc, dans la mesure où la recherche d'un identifiant est plutôt lourde (analyse d'une chaîne de caractères), on essayera si possible de stocker au préalable tous les identifiants dans des variables.
Envoi d'un simple vecteur
Si vous voulez envoyer un vecteur à 3 dimensions (par exemple) à votre shader, vous pouvez faire comme ceci :
Code : Autre
Code : C1
2
3
4
5
6
7
8 | /* on recupere l'ID */
int id = glGetUniformLocation(program, "vecteur");
/* on defini notre program actif */
glUseProgram(program);
/* on envoie notre vecteur */
glUniform3f(id, 1.7, 5.2, 3.6);
|
Cette méthode peut poser un petit problème ; elle est plutôt "lourde" et peut flexible, il faut spécifier chaque composante du vecteur une par une, et si l'on souhaite subitement envoyer un vecteur à deux dimensions à la place, il faudra non seulement changer le nom de la fonction, mais aussi le nombre de ses paramètres. Pour remédier à ce problème, vous pouvez utiliser la version vectorielle de
glUniform*().
Étude de la version vectorielle de glUniform*()
Afin d'envoyer un groupe de données (comme un vecteur), la fonction
glUniform*v() vous propose de lui spécifier un pointeur vers ces données. De plus, elle bénéficie d'un nouveau paramètre :
Code : C1 | void glUniform*v(GLint id, GLsizei count, TYPE *val);
|
- id : ...
- count : nombre de groupes de données.
- val : ...
Voyons tout d'abord comment modifier le code précédent pour spécifier la valeur de notre vecteur par le biais d'un tableau :
Code : Autre
Code : C 1
2
3
4
5
6
7
8
9
10
11 | /* le vecteur que l'on veut envoyer en parametre */
float vec[3] = {1.7, 5.2, 3.6};
/* on recupere l'ID */
int id = glGetUniformLocation(program, "vecteur");
/* on defini notre program actif */
glUseProgram(program);
/* on envoie notre vecteur */
glUniform3fv(id, 1, vec);
|
Vous pouvez voir ici que j'ai spécifié le paramètre
count de
glUniform*v() à la valeur 1. Cela veut dire que j'ai souhaité envoyer
un vecteur, un vecteur à
3 composantes (
glUniform3fv() ).
En effet, le chiffre contenu dans le nom de la fonction
glUniform*() (qui peut varier de 1 à 4 inclus) nous informe du type de la variable GLSL (nombre de composantes du vecteur), et par conséquent du nombre de données que la fonction
glUniform*() va aller chercher dans notre pointeur.
Donc : faites bien attention aux débordements mémoire.
Mais attendez, vous n'avez pas encore tout vu. Eh bien oui ; qu'advient-il de notre paramètre
count ? Il ne faut pas l'oublier.
Le paramètre
count vient complexifier la chose en vous permettant d'envoyer des
tableaux de vecteurs. Avec ce paramètre, vous allez pouvoir spécifier le nombre de vecteurs que contient votre tableau.
Prenons tout de suite un exemple :
Code : Autre1
| uniform vec3 vecteurs[2]; // tableau de deux vecteurs |
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | /* les vecteurs que l'on veut envoyer en parametre */
float vecs[2][3] =
{
{0.8, 2.1, 1.3},
{1.9, 3.2, 1.7}
};
/* on recupere l'ID */
int id = glGetUniformLocation(program, "vecteurs");
/* on defini notre program actif */
glUseProgram(program);
/* on envoie nos deux vecteurs */
glUniform3fv(id, 2, vecs);
|
Comme vous le voyez, côté GLSL vous pouvez remarquer que j'ai créé un tableau de vecteurs, tout comme je l'ai fait dans l'exemple en C. J'ai également placé le paramètre
count à la valeur
2 dans
glUniform*v(), pour indiquer que je souhaite envoyer
deux vecteurs.
Prenez garde : glUniform*v() attend une suite de données, donc un tableau ou un tableau de tableaux, mais en aucun cas un pointeur de pointeurs ! La solution suivante est envisageable et produira le même effet :
Code : C1 | float vec[2*3] = {0.8, 2.1, 1.3, 1.9, 3.2, 1.7};
|
Au final, le nombre de variables qui seront lues dans votre tableau sera égal au chiffre du nom de la fonction
glUniform*v() multiplié par
count (dans notre cas, 2*3).
Et si je veux envoyer un simple tableau, je fais comment ?
Invoquez
glUniform1*v(), elle ira prendre exactement
count données dans votre tableau et ira les loger dans votre shader dans un tableau de type
bool,
int ou
float. Eh oui, un vecteur à
une composante (
glUniform1*v() ) n'est rien d'autre qu'une simple variable
Et les matrices ?
À partir du moment où vous avez compris le fonctionnement de la version vectorielle de glUniform*(), les matrices vous paraîtront tout aussi simple ; en fait, le principe est identique, sauf qu'une matrice a plus de composantes qu'un vecteur
Afin d'envoyer une matrice à votre shader, chose qui risque d'arriver assez rarement dans la mesure où les matrices de projection, de texturage et de visualisation sont déjà à votre disposition en GLSL, la fonction
glUniform*() prend une autre... forme

:
Code : C1 | void glUniformMatrix*fv(GLint id, GLsizei count, GLboolean transpose, const float *val);
|
- id : ...
- count : ...
- val : les valeurs doivent être contenues dans un pointeur sur des flottants de type float ;
- transpose : positionné à GL_TRUE, les matrices envoyées seront transposées avant d'arriver dans le shader.
Comme vous le voyez, cette version laisse moins de libertés au programmeur, le forçant à utiliser des matrices codées sur des flottants de type
float. Toutefois, elle lui permet :
- de spécifier l'ordre de sa matrice, via le paramètre transpose ;
- d'indiquer la taille de sa matrice, via le nom de la fonction ( glUniformMatrix2fv() pour une matrice 2*2, glUniformMatrix3fv() pour une matrice 3*3, etc... ).
Pour plus d'informations sur la transposée d'une matrice, vous pouvez consulter
la FAQ de Developpez.com sur les matrices.
Voici un bref exemple pour illustrer une utilisation possible :
Code : Autre
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | /* la matrice que l'on veut envoyer en parametre */
float mat[3][3] =
{
{1.2, 0.0, 0.0},
{0.0, 2.5, 0.0},
{0.0, 0.0, 1.0}
};
/* on recupere l'ID */
int id = glGetUniformLocation(program, "matrix");
/* on defini notre program actif */
glUseProgram(program);
/* on envoie notre matrice */
glUniformMatrix3fv(id, 1, 0, mat);
|
Et voilà, vous savez tout sur les variables uniform des shaders

Servez-vous en pour envoyer des informations supplémentaires, de couleur par exemple, ou bien des données envoyées par l'utilisateur ; eh oui, aucun dialogue direct n'est possible entre l'utilisateur et le shader, servez-vous donc de votre application comme un tiers.
Un exemple complet est disponible en téléchargement à la fin de ce chapitre.