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.
Qu'est-ce qu'un attribut ?
Comme indiqué à l'introduction de ce chapitre, les
attributs de sommet sont des données supplémentaires, différentes pour chaque sommet, à l'instar de la position, de la coordonnée de texture, etc...
Ainsi,
les attributs de sommet ne sont accessibles que par les vertex shaders, là où les informations de sommets sont disponibles en lecture (et en lecture seulement).
Comment accéder aux attributs de sommet dans le vertex shader ?
Les données de position du sommet par exemple, sont disponible par défaut dans une variable du GLSL :
gl_Vertex. Pour les attributs de sommet, il va falloir créer cette variable, mais pas n'importe comment.
Le type « attribute »
À l'instar des variables uniform, que nous avons vue à l'instant, il va à nouveau falloir attribuer un type différents à nos attributs de sommet.
Tout comme les uniform, les attributs
doivent être des variables globales, mais préfixées cette fois-ci avec le mot clé
attribute :
Code : Autre1
| attribute vec3 donnee_supplementaire; |
Ici, j'ai décidé de rajouter une variable de type
vec3.
En gros, ce bout de code peut se traduire : «
je rajoute une donnée à mes sommets qui seront dessinés avec ce vertex shader ».
Supposons que nous voulions dessiner un triangle, nous allons donc spécifier trois sommets :
Chaque sommet possède son lot de données en tout genre : position, normale, couleur, etc...
Eh bien imaginez que vous vouliez en rajouter ; vous pouvez !
C'est ce qui se passe lorsque vous créez une variable avec le type
attribute
Côté API
Bien, voyons maintenant comment spécifier la valeur de ces données en plus que sont les attributs de sommet.
Globalement, le principe est le même que pour les variables uniform :
- on récupère l'ID (plus couramment appelé index dans le cas des attributs de sommet) de notre attribut ;
- on s'en sert pour localiser nos attributs et ainsi leur envoyer la valeur de notre choix.
Récupérer l'index
Bon, comme vous avez déjà pris l'habitude avec les uniforms, je vais aller un peu plus vite dans la pratique. Pour récupérer l'index d'un attribut de sommet dans un shader GLSL, invoquez
glGetAttribLocation() :
Code : C1 | GLint glGetAttribLocation(GLuint program, const char *name);
|
- program : désigne le program dans lequel nous voulons rechercher l'index.
- name : c'est le nom de l'attribut dont on souhaite obtenir l'index.
Une fois que nous avons notre index, il nous suffira de le spécifier à la fonction qui permet de définir les valeurs des attributs de sommet, et ces valeurs étant différentes pour chaque sommet, il nous faudra appeler cette fonction pour chaque sommet que nous définirons.
Spécifier la valeur d'un attribut
Prenons un exemple ; supposons un triangle, créé de la façon suivante avec OpenGL :
Code : C1
2
3
4
5 | glBegin(GL_TRIANGLES);
glVertex2f(0.9, -0.9);
glVertex2f(-0.9, -0.9);
glVertex2f(0.0, 0.9);
glEnd();
|
Avec ce code, nous demandons la création de trois sommets (pour former un triangle). Pour chaque sommet traité, le vertex shader actif sera invoqué et sa variable
gl_Vertex sera affectée aux valeurs que nous avons spécifiées à la fonction
glVertex*(). Il en va de même pour chaque variable d'entrée du vertex shader (couleur, normale, ...), y compris son attribut.
Ainsi, pour affecter un attribut d'un sommet, il suffit de spécifier sa valeur pour chaque création de sommet avec la fonction
glVertexAttrib*(), en spécifiant bien quel attribut nous souhaitons affecter via son index :
Code : C1
2
3
4
5 | glBegin(GL_TRIANGLES);
glVertexAttrib3f(index, 0.0, 0.0, 0.0); glVertex2f(0.9, -0.9);
glVertexAttrib3f(index, 0.0, 0.0, 0.0); glVertex2f(-0.9, -0.9);
glVertexAttrib3f(index, 0.0, 0.0, 0.0); glVertex2f(0.0, 0.9);
glEnd();
|
Et voici son prototype :
Code : C1 | void glVertexAttrib*(GLuint index, TYPE vals);
|
- index : il s'agit là de placer l'index que l'on a récupéré avec glGetAttribLocation().
- vals : ce sont les valeurs auquels on souhaite positionner notre attribut, le nombre de valeurs peut varier de 1 à 4 inclus.
La fonction
glVertexAttrib*() permet de spécifier la valeur de l'attribut de chaque sommet, comme vous venez de le voir.
Voici un exemple d'utilisation des attributs de sommet :
Code : Autre1
| attribute float numero_sommet; // representera le numero du sommet |
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | /* on recupere l'index de notre attribut de sommets */
int index = glGetAttribLocation(program, "numero_sommet");
...
/* on active notre program */
glUseProgram(program);
glBegin(GL_TRIANGLES);
glVertexAttrib1f(index, 0.0); glVertex2f(0.9, -0.9);
glVertexAttrib1f(index, 1.0); glVertex2f(-0.9, -0.9);
glVertexAttrib1f(index, 2.0); glVertex2f(0.0, 0.9);
glEnd();
/* on desactive les shaders */
glUseProgram(0);
|
Vous noterez que la forme de
glVertexAttrib*() utilisée doit correspondre au type de l'attribut dans le shader, sinon vous risquez de vous retrouver avec une valeur d'attribut erronée dans votre shader (en gros ça fera comme si vous faisiez un cast GLSL :
Attrib = TypeDeAttrib(TypeDeLaFonction) ).
Si vous connaissez les
vertex arrays, et que vous êtes un flemmard (quelqu'un de normal quoi), vous vous êtes probablement demandé : mais existe-t-il une alternative pour envoyer mes attributs de sommets par le biais de tableaux ? Eh bien la réponse est oui
Cette section est bien sûr facultative, elle ne vous aidera pas à mieux comprendre le fonctionnement des attributs de sommet, elle est plutôt réservée aux connaisseurs des vertex arrays.
Activation des tableaux d'attributs
À l'instar des vertex arrays simples, les tableaux d'attributs ont besoin d'une activation qui leur est propre.
Chaque index d'attribut doit être activé indépendamment, car chaque index représente un type de données différent.
Que quoi ? Hein ??
Oui, chaque index d'attribut représente un type différent ; la différence qu'il y a entre l'index 0 et l'index 1 est la même qu'entre la position d'un sommet et sa normale : ce ne sont pas les mêmes données, elles sont identifiées différemment.
Je vous conseille d'aller jeter un oeil à
ma petite définition de ce qu'est un sommet, dans mon tutoriel sur les vertex arrays.
Bien, revenons-en au sujet initial : l'activation. Étant donné que chaque type d'attribut est identifié différemment par OpenGL, il va falloir, comme pour chaque type de donnée d'un sommet, l'activer indépendamment des autres, et pour cela, il va nous falloir son
index.
Avec la fonction
glEnableClientState(), c'était facile, il nous suffisait de lui donner une constante au nom trivial et facile à retenir, et hop, elle activait le type de vertex array demandé.
La fonction
glEnableVertexAttribArray() quant à elle, demande l'index de l'attribut à activer.
Code : C1 | void glEnableVertexAttribArray(GLuint index);
|
- index : c'est l'index du type de l'attribut que l'on souhaite activer.
Avec les shaders GLSL, l'index d'un attribut de sommet se récupère comme nous l'avons vu plus haut : avec la fonction
glGetAttribLocation().
Après vous être muni de cet index, vous n'aurez qu'à le donner à
glEnableVertexAttribArray() pour que nous puissions exploiter les attributs de sommet demandés
Envoi d'un tableau
Allez, je ne vais pas passer par 36 chemins ; ici je considère que vous maîtrisez un minimum le concept des vertex arrays, je ne m'étalerai donc pas sur les détails.
Afin de spécifier un tableau d'attributs, utilisez la fonction
glVertexAttribPointer() :
Code : C1 | void glVertexAttribPointer(GLuint index, int size, GLenum type, GLboolean norm, GLsizei stride, const void *data);
|
- size, type, stride, data : référez-vous à mon tutoriel sur les vertex arrays, ces paramètres ont la même incidence que ceux des autres fonctions gl*Pointer().
- index : il s'agit d'indiquer ici le type de l'attribut, c'est-à-dire son index récupéré à l'aide de glGetAttribLocation().
- norm : positionné à GL_TRUE, les vecteurs contenus dans votre tableau de données seront normalisés. Je vous conseille personnellement de laisser ce paramètre à 0 (ou GL_FALSE), ainsi OpenGL ne touchera pas à vos données.
Comme vous le voyez, cette fonction demande à ce qu'on lui fournisse le type d'attribut que représentera le tableau qu'on lui envoie.
Comment ça se fait ? Pourquoi elle a besoin de savoir ça, alors qu'on a déjà donné l'index lorsqu'on a activé les tableaux ?
Car vous pouvez avoir activé plusieurs tableaux d'attributs à la fois (c'est possible sachez-le), il est donc important de spécifier explicitement quel est le type d'attribut que vous lui envoyez.
Le reste du fonctionnement est le même que pour les vertex arrays (les tabeaux d'attributs sont en fait des vertex arrays un peu différents sur certains points) : après avoir appelé cette fonction, le tableau sera envoyé à la carte graphique pour traitement lorsque vous appelerez une fonction de dessin comme
glDrawArrays() ou
glDrawElements().
Et les VBOs ?
Ah ! J'allais les oublier ceux-là.
Les tableaux t'attributs ayant un système de fonctionnement identique aux vertex arrays, vous pouvez également héberger vos tableaux dans un VBO, puis utiliser l'adresse 0 avec
glVertexAttribPointer() pour spécifier l'emplacement de votre VBO actif, et utiliser les données directement à partir de la mémoire de la carte graphique.
« VBO » ? Avons-nous été présentés ?
Vous ne connaissez pas les VBOs ? Eh bien alors il est grand temps que vous fassiez leur connaissance !
Les VBOs sont un sujet assez vaste, c'est pourquoi je vous ai concocté
un petit tutoriel à leur sujet. Pour faire court ; les VBOs sont une sorte d'amélioration du système des vertex arrays, ils servent à augmenter considérablement leur performance (pour peu qu'ils soient bien utilisés)
Désactivation des tableaux d'attributs
Je vous l'avais dit que les tableaux d'attributs étaient des vertex arrays

Un peu différents toutefois, la fonction pour désactiver les tableaux d'attributs est la suivante :
Code : C1 | void glDisableVertexAttribArray(GLuint index);
|
- index : encore une fois, il est nécessaire d'indiquer quel type d'attribut on souhaite désactiver.
Exemple d'utilisation
Un petit exemple ne fera pas de mal je pense. Vous vous rappelez de l'exemple qui présentait une utilisation possible des attributs de sommet, en spécifiant les attributs un à un ? Je vous en propose ici l'adaptation qui utilise les tableaux d'attributs :
Code : Autre1
| attribute float numero_sommet; |
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 | /* notre index d'attribut */
int index;
/* tableau des positions */
float pos[6] =
{
0.9, -0.9,
-0.9, -0.9,
0.0, 0.9
};
/* tableau t'attributs */
float attribs[3] =
{
0.0, 1.0, 2.0
};
...
/* on recupere l'index de notre attribut de sommets */
index = glGetAttribLocation(program, "numero_sommet");
...
/* on active les tableaux */
glEnableClientState(GL_VERTEX_ARRAY);
glEnableVertexAttribArray(index);
/* on specifie nos donnees */
glVertexPointer(2, GL_FLOAT, 0, pos);
glVertexAttribPointer(index, 1, GL_FLOAT, 0, 0, attribs);
glUseProgram(program);
/* dessin */
glDrawArrays(GL_TRIANGLES, 0, 3);
glUseProgram(0);
/* on desactive les tableaux */
glDisableVertexAttribArray(index);
glDisableClientState(GL_VERTEX_ARRAY);
|