gKit2 light
|
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...
s'il n'existe pas d'index buffer dans l'objet, il suffit de créer un vertex buffer et de configurer un vao :
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 :
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 ?
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
et dans l'application :
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 :
et voici comment tuto6GL_buffer.cpp configure le vao, pour l'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...
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( ):
et il aussi possible de dessiner les 2 objets avec un seul draw :
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( ) :
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 :
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( ) :
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
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.