I. L'article original▲
Cet article est une adaptation en langue française de 4. OpenGL 4 Vertex Array Objects (VAO), de Donald Urquhart.
II. Introduction▲
Avec la suppression des états OpenGL et du mode immédiat, on ne peut plus effectuer d'appel à glBegin(), soit plus de glBegin(GL_TRIANGLES) ou autres. De même, cela signifie que les appels à glVertex() ou glColor() ne sont plus possibles. Ainsi, on aura besoin d'une nouvelle manière de transférer des données à la carte graphique pour le rendu de la géométrie. On pourrait utiliser les VBO, présentés dans un tutoriel sur les terrains, ou bien les VAO, une fonctionnalité d'OpenGL 3 et plus récents. Il s'agit des vertex array objects, un seul pouvant stocker plusieurs VBO. Grâce à cette fonctionnalité, on peut stocker les données des sommets et des couleurs dans des VBO différents, mais dans le même VAO. On peut appliquer le même principe pour tous les types de données généralement transmis comme VBO, dont les données des normales ou n'importe quelle donnée requise au niveau des sommets.
Un VAO est une manière de stocker des informations sur les objets dans la carte graphique, au lieu de lui envoyer des sommets au fur et à mesure des besoins. C'est la manière de fonctionner de Direct3D, ce dernier n'ayant jamais disposé du mode immédiat d'OpenGL (abandonné avec la version 3). Ceci signifie que l'application ne doit pas passer son temps à transférer des données depuis et vers la carte graphique, d'où un grand gain de performances.
Le mieux est que cela n'est pas si compliqué à mettre en place. Au lieu d'effectuer une série d'appels à glVertex(), on stocke tous les sommets dans un tableau que l'on place dans un VBO, puis dans un VAO. OpenGL s'occupe de tout en coulisses et on dispose alors de tous les avantages associés.
III. Code▲
On ne considérera pas le lecteur aguerri de l'art des VBO, on repart donc des bases en expliquant les diverses notions au fur et à mesure. Il ne reste plus tellement de travail pour afficher un carré à l'écran.
III-A. opengl_3.h▲
Dans opengl_3.h, on crée deux variables et une méthode. Cette dernière ne prendra pas de paramètre et ne retournera rien, mais créera le VAO et le VBO nécessaires pour dessiner un carré. Dans cette optique, on la nomme createSquare().
void
createSquare(void
); // Méthode pour créer les VAO du carré
Les deux variables seront des tableaux d'entiers non signés de longueur unitaire, on a ainsi un VAO et un VBO. On appelle ces variables privées vaoID et vboID.
unsigned
int
vaoID[1
]; // VAO
unsigned
int
vboID[1
]; // VBO
III-B. opengl_3.cpp▲
Jusque-là, rien d'effrayant. Dans opengl_3.cpp, on commence à l'utiliser. Dans la méthode setupScene(), on ajoute un appel à createSquare() puis on s'occupe de cette dernière. On garde à l'esprit qu'on crée le carré, le VAO et le VBO avant de les utiliser, sinon on risque d'avoir des problèmes de mémoire non allouée.
void
OpenGLContext::
setupScene(void
) {
...
createSquare(); // Crée un carré
}
Ensuite, la méthode createSquare(). On remplit ce squelette :
/**
createSquare est utilisée pour créer le VAO qui contiendra le carré. On stocke en dur ses sommets.
*/
void
OpenGLContext::
createSquare(void
) {
}
Avant de créer le VAO et le VBO, on a besoin de données décrivant les sommets de la forme. On dessine un carré formé par deux triangles, on a donc besoin de six sommets (trois par triangle). Ensuite, puisque chaque sommet contient trois valeurs (les coordonnées x, y et z) et qu'on a six sommets, on aura un total de dix-huit valeurs pour décrire le carré. Il s'agira de valeurs float stockées dans un tableau vertices. On utilise un tableau dynamique, qu'il faudra supprimer à la fin de la méthode pour éviter les fuites de mémoire.
void OpenGLContext::createSquare(void) {
float* vertices = new float[18]; // Sommets du carré
delete [] vertices; // Supprimer les sommets
}
On remplit alors quelques valeurs pour ces sommets. On met tous les sommets à 0 sur l'axe 0 et on donne au carré une longueur d'une unité. On centre le carré à l'origine, ce qui donne un sommet en haut à gauche de coordonnées (- 0,5 ; 0,5 ; 0.0) ; en bas à droite (0,5 ; - 0,5 ; 0.0).
void OpenGLContext::createSquare(void) {
float* vertices = new float[18]; // Sommets du carré
vertices[0] = -0.5; vertices[1] = -0.5; vertices[2] = 0.0; // Coin en bas à gauche
vertices[3] = -0.5; vertices[4] = 0.5; vertices[5] = 0.0; // Coin en haut à gauche
vertices[6] = 0.5; vertices[7] = 0.5; vertices[8] = 0.0; // Coin en haut à droite
vertices[9] = 0.5; vertices[10] = -0.5; vertices[11] = 0.0; // Coin en bas à droite
vertices[12] = -0.5; vertices[13] = -0.5; vertices[14] = 0.0; // Coin en bas à gauche
vertices[15] = 0.5; vertices[16] = 0.5; vertices[17] = 0.0; // Coin en haut à droite
delete [] vertices; // Supprimer les sommets
}
Les coordonnées des sommets remplies, on peut avancer et créer un VBO avec un appel à glGenVertexArrays(). Ensuite, on utilise un appel à glBindVertexArray() pour activer le VAO. De là, on peut appeler glGenBuffers() pour créer le VBO que l'on lie avec glBindBuffer().
- Générer le vertex array object ;
- Lier le vertex array object ;
- Générer le vertex buffer object ;
- Lier le vertex buffer object ;
Dès lors, on remplit le VBO avec les données des sommets. À la création du VBO, on peut en définir le type ; dans le cas présent, on utilise GL_STATIC_DRAW pour indiquer à OpenGL qu'on n'envisage pas de changer les données, d'aucune manière, on veut juste les afficher. L'API définit également des types pour les données qui changent, notamment précisés sur le wiki OpenGL.
Ensuite, on appelle glBufferData() pour transférer les données du tableau précédemment créé dans le VBO, avant de dire à OpenGL que ce VBO servira de stockage de sommets. Finalement, on nettoie en désactivant tant le tableau d'attributs des sommets que le VAO.
void
OpenGLContext::
createSquare(void
) {
...
glGenVertexArrays(1
, &
vaoID[0
]); // Créer le VAO
glBindVertexArray(vaoID[0
]); // Lier le VAO pour l'utiliser
glGenBuffers(1
, vboID); // Générer le VBO
glBindBuffer(GL_ARRAY_BUFFER, vboID[0
]); // Lier le VBO
glBufferData(GL_ARRAY_BUFFER, 18
*
sizeof
(GLfloat), vertices, GL_STATIC_DRAW); // Définir la taille, les données et le type du VBO
glVertexAttribPointer((GLuint)0
, 3
, GL_FLOAT, GL_FALSE, 0
, 0
); // Définir le pointeur d'attributs des sommets
glEnableVertexAttribArray(0
); // Désactiver le VAO
glBindVertexArray(0
); // Désactiver le VBO
delete
[] vertices; // Supprimer les sommets
}
On a maintenant un VAO avec un VBO contenant les sommets d'un carré. Jusqu'ici, cela a été relativement rapide et facile, on affiche donc ces données à l'écran. On saute ainsi à la méthode renderScene() et, après la définition des variables glUniformMatrix4vf pour le shader du tutoriel précédent, on ajoute trois lignes pour le rendu. Tout d'abord, on active le VAO ; ensuite, on le dessine ; finalement, on le désactive. Cela donne :
glBindVertexArray(vaoID[0
]); // Lier le VAO
glDrawArrays(GL_TRIANGLES, 0
, 6
); // Dessiner le carré
glBindVertexArray(0
); // Délier le VAO
IV. Résultat▲
On a maintenant créé un carré sans mode immédiat et en stockant toutes les données sur la carte graphique, ce qui assurera des performances optimales. On pourrait optimiser ce résultat en utilisant GL_TRIANGLE_STRIP au lieu de GL_TRIANGLE et n'utiliser que quatre sommets (NDT : on aurait aussi pu utiliser des tableaux d'index). Dans le prochain tutoriel, on ajoutera une couleur au carré.
Les fichiers de projet Visual Studio 2010 de ce tutoriel sont disponibles au téléchargement.
Cliquez pour lire la vidéo
V. Remerciements▲
Merci à Alexandre Laurent pour son aide à la traduction et à Claude Leloup pour sa relecture orthographique !