gKit2 light
glDraw( ) et la famille

cf tuto6GL.cpp

glDraw( ) est une famille de fonctions, et selon la description du maillage (sommets partagés, ou pas) il faut utiliser :

la classe Mesh peut stocker les 2 types de maillages : il suffit de vérifier qu'un index buffer existe (cf Mesh::index_count() > 0 ou Mesh::index_buffer_size() > 0) pour savoir qu'il faut utiliser glDrawElements( ) plutot que glDrawArrays( ). cf configurer un format de sommet, vertex array object pour des exemples.

remarque : quel est l'intêret d'utiliser des sommets partagés ?

la description de l'objet est plus compacte... un indice est un entier stocké sur 4, 2 ou 1 octets, un sommet est a priori 3 floats qui occuppent 4 octets chacuns, soit 12 octets au total. si l'on ajoute les normales (3 floats) et les coordonnées de textures (2 floats), un sommet occuppe 3x4 + 3x4 + 2x4 = 32 octets.

un simple cube composé de 8 sommets (soit 8x32 = 256 octets) et de 12 triangles indexés (12x3x4 = 144 octets) occuppe 400 octets. sans utiliser l'indexation, il faut décrire les 3 sommets des 12 triangles : 12x3x32 = 1152 octets... et autre amélioration, les vertex shaders ne transforment que 8 sommets dans un cas, contre 36 dans l'autre...

glDrawArrays et Mesh

s'il n'existe pas d'index buffer dans l'objet, il suffit de créer un vertex buffer et de configurer un vao :

Mesh mesh = { ... };
// configuration
GLuint vao= 0;
GLuint vertex_buffer= 0;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glGenBuffers(1, &vertex_buffer);
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
glBufferData(GL_ARRAY_BUFFER, mesh.vertex_buffer_size(), mesh.vertex_buffer(), GL_STATIC_DRAW);
// configure l'attribut 0, cf layout(location= 0) in vec3 position, dans le vertex shader
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttrib(0);
// nettoyage
glBindVertexArray(0); // !! pourquoi en premier ? !!
glBindBuffer(GL_ARRAY_BUFFER, 0);
// draw
glBindVertexArray(vao);
glUseProgram( ... );
glUniform( ... );
glDrawArrays(GL_TRIANGLES, /* first */ 0, /* count */ mesh.vertex_count());
representation d'un objet / maillage.
Definition: mesh.h:112
const float * vertex_buffer() const
renvoie l'adresse de la position du premier sommet. permet de construire les vertex buffers openGL....
Definition: mesh.h:296
std::size_t vertex_buffer_size() const
renvoie la longueur (en octets) du vertex buffer.
Definition: mesh.h:298
int vertex_count() const
renvoie le nombre de sommets.
Definition: mesh.h:291

glDrawElements et Mesh

la démarche est la même, il faut créer l'index buffer, en plus du vertex buffer et bien sur configurer le vao :

Mesh mesh = { ... };
// configuration
GLuint vao= 0;
GLuint vertex_buffer= 0;
GLuint index_buffer= 0;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glGenBuffers(1, &vertex_buffer);
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
glBufferData(GL_ARRAY_BUFFER, mesh.vertex_buffer_size(), mesh.vertex_buffer(), GL_STATIC_DRAW);
// configure l'attribut 0, cf layout(location= 0) in vec3 position, dans le vertex shader
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttrib(0);
if(mesh.index_count() > 0)
{
glGenBuffers(1, &index_buffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, mesh.index_buffer_size(), mesh.index_buffer(), GL_STATIC_DRAW);
// !! la configuration du vao est implicite au moment du BindBuffer(GL_ELEMENT_ARRAY_BUFFER) !!
}
// nettoyage
glBindVertexArray(0); // !! pourquoi en premier ? !!
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
// draw
glBindVertexArray(vao);
glUseProgram( ... );
glUniform( ... );
// choisir le draw
if(mesh.index_count() == 0)
// pas d'index buffer
glDrawArrays(GL_TRIANGLES, /* first */ 0, /* count */ mesh.vertex_count());
else
// avec un index buffer
glDrawElements(GL_TRIANGLES, /* count */ mesh.index_count(), /* index type */ GL_UNSIGNED_INT, /* offset */ 0);
std::size_t index_buffer_size() const
renvoie la taille (en octets) de l'index buffer.
Definition: mesh.h:318
const void * index_buffer() const
renvoie l'adresse du premier indice du premier triangle. par convention c'est un uint,...
Definition: mesh.h:316
int index_count() const
renvoie le nombre d'indices de sommets.
Definition: mesh.h:293

les paramètres type et offset permettent d'itérer sur les indices stockés dans l'index buffer associé au vao.

remarque : read_mesh( ) ne construit pas, pour l'instant, d'index buffer, donc un objet chargé par read_mesh( ) peut etre affiché directement par glDrawArrays().

en consultant la doc d'openGL, d'autres fonctions existent, à quoi servent-elles ?

draw instanced, dessiner plusieurs copies

dans pas mal de cas, il est nécessaire d'afficher plusieurs fois le même objet ou des variantes du même objet. par exemple, des unités dans un jeu de stratégie, des arbres pour afficher une forêt, des touffes d'herbes pour une prairie, des cubes, etc.

malheureusement, la solution directe, faire un draw par copie n'est vraiment pas une solution efficace. cf tuto_time.cpp et (les explications mesure du temps cpu et gpu) pour une démonstration.

glDrawArraysInstanced( ) / glDrawElementsInstanced() fournissent une solution (partielle) à ce problème. les fonctions prennent un paramètre supplémentaire (par rappport à glDrawArrays() / glDrawElements()), le nombre de copies à dessiner.

parfait, reste un petit problème à régler... toutes les copies sont identiques et sont dessinées au même endroit, ce qui ne sert à rien. il faut pouvoir, au minimum, les placer à des endroits différents dans la scène / le monde.

il y a 2 deux moyens : on peut stocker les propriétés de chaque copie dans un buffer et modifier le vertex array object en fonction, et/ou utiliser l'indice de la copie en cours de traitement (cf int gl_InstanceID) dans le vertex shader pour calculer des propriétés uniques à chaque instance.

exemple : dessiner n copies alignées avec gl_InstanceID : il suffit de modifier le vertex shader pour calculer une position qui dépend de gl_InstanceID

// vertex shader
in vec3 position;
uniform mat4 mvpMatrix;
void main( )
{
// int gl_InstanceID varie de 0 a N, le nombre d'instances dessinees
vec3 p= position + vec3(gl_InstanceID * 10, 0, 0);
gl_Position= mvpMatrix * vec3(p, 1);
}
vecteur generique, utilitaire.
Definition: vec.h:146

et dans l'application :

glBindVertexArray( ... );
glUseProgram( ... );
glUniform( ... );
glDrawArraysInstanced(GL_TRIANGLES, /* first */ 0, /* count */ 36, /* instance count */ 10); // dessine N= 10 copies

permet de dessiner 10 cubes, par exemple. exemple complet dans tuto6GL.cpp et instanceID.glsl. tuto_time.cpp utilise aussi cette solution pour dessiner des objets disposés sur une grille.

l'autre solution nécessite de créer un buffer pour stocker les paramètres de chaque instance et de configurer le format de sommet avec glVertexAttribDivisor(index, 1). le vertex shader doit également déclarer et utiliser l'attribut supplémentaire cf instance_buffer.glsl :

in vec3 position; // attribut du sommet
in vec3 instance_position; // l'attribut d'instance se declare "normalement",
// c'est l'application qui doit correctement configurer le vao...
void main( )
{
vec3 p= position + instance_position;
gl_Position= mvpMatrix * vec4(p, 1);
vertex_normal= mat3(normalMatrix) * normal;
}
vecteur generique 4d, ou 3d homogene, utilitaire.
Definition: vec.h:168

et voici comment tuto6GL_buffer.cpp configure le vao, pour l'attribut d'instance :

std::vector<vec3> positions;
// remplir les positions
{ ... }
// creer le buffer
glGenBuffers(1, &m_instance_buffer);
glBindBuffer(GL_ARRAY_BUFFER, m_instance_buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vec3) * positions.size(), &positions.front().x, GL_STATIC_DRAW);
// configure l'attribut, vec3 instance_position
GLint index= glGetAttribLocation(program, "instance_position");
// configure le vao pour l'attribut d'instance
glVertexAttribPointer(index, 3, GL_FLOAT, GL_FALSE, /* stride */ 0, /* offset */ 0);
glEnableVertexAttribArray(index);
glVertexAttribDivisor(index, 1); // !! c'est la seule difference entre un attribut de sommet et un attribut d'instance !!

remarque : pourquoi divisor pour nommer une propriete d'instance ? euh, c'est pareil dans directx ? l'idée est de décrire à quel moment l'attribut change de valeur, pour chaque sommet (divisor == 0), ou pour chaque instance (divisor == 1), toutes les 2 instances (divisor == 2), etc.

remarque : oui, ça permet d'avoir des propriétés d'instances qui sont définies pour un groupe d'instances, mais ce n'est pas très flexible...

draw base vertex, organisation des données

si les données de plusieurs objets sont rangées dans le même vertex buffer, ca permet de ne configurer et de n'utiliser qu'un seul vao, ce qui peut simplifier le code d'affichage de plusieurs objets et permet aussi au driver de faire moins de travail.

si les sommets des objets A et B se trouvent dans le même buffer :

AAAAAA + BBB

on peut les afficher en utilisant le paramètre first de glDrawArrays( ):

GLuint vao= { ... };
glBindVertexArray(vao);
glUseProgram( ... );
glUniform( ... );
glDrawArrays(GL_TRIANGLES, /* first */ 0, /* count */ 6); // dessine A
glDrawArrays(GL_TRIANGLES, /* first */ 6, /* count */ 3); // dessine B

et il aussi possible de dessiner les 2 objets avec un seul draw :

GLuint vao= { ... };
glBindVertexArray(vao);
glUseProgram( ... );
glUniform( ... );
glDrawArrays(GL_TRIANGLES, /* first */ 0, /* count */ 9); // dessine A et B

remarque : si les objets ne sont pas placés les uns après les autres dans le vertex buffer, il faudra utiliser un draw par intervalle d'objets, ou utiliser glMultiDrawArrays()...

si les sommets des objets A, B, et C se trouvent dans le même buffer :

AAAAAA + BBB + CCC

et que l'on veut afficher A et C, il faudra utiliser 2 DrawArrays() ou préparer les paramètres de MultiDrawArrays( ) :

std::vector<GLint> first;
std::vector<GLSizei> count;
// draw A
first.push_back(0);
count.push_back(6);
// draw C
first.push_back(9);
count.push_back(3);
glMultiDrawArrays(GL_TRIANGLES, /* first */ &first.front(), /* count */ &count.front(), /* draw count */ first.size());

et avec glDrawElements( ) ?

La solution précédente est simple à utiliser tant que l'affichage des objets peut se faire avec glDrawArrays(), si les objets utilisent un index buffer / des sommets partagés, glDrawElements( ) ne permet pas directement de faire la même chose... pourquoi ?

si les sommets des objets A et B se trouvent dans le même buffer :

AAAA + BBB

et que leur indexation est : 012023 pour le quad A et 012 pour le triangle B, et qu'ils sont dans le même index buffer :

012023 + 012

que va dessiner :

// dessiner un triangle indexé à partir du 6ième indice du buffer
glDrawElements(GL_TRIANGLES, /* count */ 3, /* index type */ GL_UNSIGNED_INT, /* offset */ sizeof(unsigned int) * 6);

indication : quel est l'indice du sommet 0 de l'objet B rangé dans le vertex buffer ? habituellement c'est 0 ? non ??

réponse : ça dépend du nombre de sommets du premier objet rangé dans le buffer...

et c'est très exactement cette valeur qui sert de paramètre base_vertex à glDrawElementBaseVertex( ) :

// dessiner un triangle indexé à partir du 6ième indice du buffer
glDrawElementsBaseVertex(GL_TRIANGLES, /* count */ 3, /* index type */ GL_UNSIGNED_INT, /* offset */ sizeof(unsigned int) * 6, /* base vertex */ 4);
// dont les sommets se trouvent a partir du 4ieme sommet dans le vertex buffer...

bonus : il y a une autre solution, qui utilise toujours glDrawElements() mais qui demande un peu plus de préparation... que se passe-t-il si l'index buffer contient :

012023 + 456

et les autres ?

les glDrawInstancedBaseInstance( ) permettent de faire la même chose avec les buffers d'attributs d'instances.

l'autre draw interressant et plus souple que draw instanced s'appelle glMultiDraw( ) et surtout glMultiDrawIndirect( ), mais il n'est utilisable qu'à partir d'openGL 4.3.

cf openGL 4.3 : multi draw indirect