VI. Les Vertex Buffer Objects▲
Pour plus d'informations sur les Vertex Buffer Object (VBO), consultez ces articles :
Vous pouvez utiliser les VBO pour accélérer le rendu de scènes complexes.
Ceux-ci étant apparus avec la version 1.5, ils sont supportés par de nombreuses cartes graphiques (même sur portable ou ordinateur de bureautique).
Commençons tout de même par vérifier que la carte graphique supporte les VBO, en plaçant ce code avant l'appel à InitScene :
' Teste si les VBO sont supportés, soit par la version, soit l'extension ARB
If
val
(
LongToString
(
glGetString
(
GL_VERSION))) <
1
.5
And
InStr
(
LongToString
(
glGetString
(
GL_EXTENSIONS)), "GL_ARB_vertex_buffer_object"
) =
0
Then
MsgBox
"VBO non supportés"
, vbOKOnly
Exit
Function
End
If
Bien entendu, Exit Function est à remplacer par Exit Sub si le test est codé dans la procédure Main d'une application VB6.
Les VBO sont supportés à partir de la version 1.5, ou dans l'extension GL_ARB_vertex_buffer_object.
Si on n'est pas dans ce cas, on affiche un message et on quitte la fonction.
Importez maintenant le module ModOpenGL_1_5 qui contient les fonctions de la version 1.5.
N'oubliez pas de « mapper » ensuite les fonctions en exécutant la procédure RemapVBToGL de ce module.
' Initialise les fonctions
ModOpenGL_1_5.RemapVBToGL
Nous voici prêts à programmer les VBO !
VI-A. Préparation des tableaux de données▲
Pour utiliser les VBO, il faut mettre les données du cube en tableaux.
Pour remplir facilement des tableaux, ajoutez la fonction BuildArray vue dans le premier tutoriel. :
' Fonction pour remplir un tableau
Public
Function
BuildArray
(
parray, pValues As
Variant
)
Dim
lcpt As
Long
Dim
lIndice As
Long
lIndice =
LBound
(
parray)
For
lcpt =
LBound
(
pValues) To
UBound
(
pValues)
parray
(
lIndice) =
pValues
(
lcpt)
lIndice =
lIndice +
1
Next
End
Function
Les tableaux de données sont initialisés à la fin de la procédure InitScene :
' Déclaration des tableaux
Dim
CubeArray
(
1
To
144
) As
Single
Dim
IndiceArray
(
1
To
36
) As
Long
CubeArray est un tableau contenant les coordonnées des vertex (les sommets) et leurs couleurs.
Il y a pour chaque sommet trois coordonnées et trois composantes de couleurs.
On définit quatre vertex par face, car chaque sommet est utilisé par plusieurs faces avec une couleur différente.
Donc le tableau CubeArray a une taille de (3 coordonnées + 3 couleurs) * 4 sommets * 6 faces = 144 vertex.
IndiceArray est un tableau d'indices qui va désigner chaque vertex du tableau CubeArray qu'il faut utiliser pour dessiner le cube.
Nous allons dessiner ce cube à base de triangles : le triangle étant l'élément de base géré par les cartes graphiques, il est le plus rapide à afficher.
Chaque face du cube est composée de deux triangles de trois sommets.
Donc le tableau IndiceArray a une taille de 6 faces * 2 triangles * 3 sommets = 36 indices.
Voici l'initialisation de ces deux tableaux :
' Initialisation des tableaux
BuildArray CubeArray, Array
(
_
-
1
, -
1
, 1
, 1
, 1
, 0
, 1
, -
1
, 1
, 1
, 1
, 0
, 1
, 1
, 1
, 1
, 1
, 0
, -
1
, 1
, 1
, 1
, 1
, 0
, _
1
, 1
, -
1
, 0
, 1
, 0
, -
1
, 1
, -
1
, 0
, 1
, 0
, -
1
, 1
, 1
, 0
, 1
, 0
, 1
, 1
, 1
, 0
, 1
, 0
, _
-
1
, -
1
, -
1
, 1
, 0
.5
, 0
, 1
, -
1
, -
1
, 1
, 0
.5
, 0
, 1
, -
1
, 1
, 1
, 0
.5
, 0
, -
1
, -
1
, 1
, 1
, 0
.5
, 0
, _
1
, -
1
, -
1
, 1
, 0
, 0
, -
1
, -
1
, -
1
, 1
, 0
, 0
, -
1
, 1
, -
1
, 1
, 0
, 0
, 1
, 1
, -
1
, 1
, 0
, 0
, _
-
1
, 1
, 1
, 1
, 0
, 1
, -
1
, 1
, -
1
, 1
, 0
, 1
, -
1
, -
1
, -
1
, 1
, 0
, 1
, -
1
, -
1
, 1
, 1
, 0
, 1
, _
1
, 1
, -
1
, 0
, 0
, 1
, 1
, 1
, 1
, 0
, 0
, 1
, 1
, -
1
, 1
, 0
, 0
, 1
, 1
, -
1
, -
1
, 0
, 0
, 1
)
BuildArray IndiceArray, Array
(
0
, 1
, 2
, 0
, 2
, 3
, _
4
, 5
, 6
, 4
, 6
, 7
, _
8
, 9
, 10
, 8
, 10
, 11
, _
12
, 13
, 14
, 12
, 14
, 15
, _
16
, 17
, 18
, 16
, 18
, 19
, _
20
, 21
, 22
, 20
, 22
, 23
)
Petite explication de ces tableaux, avec en exemple les premières lignes qui correspondent à la face avant :
En haut de l'image le tableau CubeArray contient les coordonnées des sommets (en rouge) et leur couleur (en bleu).
Notez qu'on utilise un tableau entrelacé : c'est-à-dire qu'on alterne coordonnées et couleur.
La face avant est positionnée en z = 1, donc la troisième coordonnée est toujours 1.
La face est jaune, donc la couleur est composée de rouge et de vert => (1, 1, 0).
On définit ainsi quatre sommets, dont la couleur est jaune.
En bas à droite le tableau IndiceArray contient les indices des sommets qui composent les triangles.
Attention, le premier sommet ne commence pas à 1 mais à 0.
Le premier triangle est composé des points d'indice 0, 1 et 2 ; c'est le triangle en bas à droite.
Vous remarquez la flèche verte qui montre que les points sont définis dans le sens inverse au sens horaire.
La face avant du triangle est donc celle que l'on voit.
Il est important de faire attention à l'orientation des faces, notamment pour le culling (masquage des faces arrière par exemple pour accélérer le rendu).
Le deuxième triangle est composé des points d'indice 0, 2 et 3.
Nous utilisons donc plusieurs fois les mêmes vertex.
VI-B. Création des buffers▲
Chacun des tableaux va être envoyé dans un buffer.
Les identifiants de ces buffers sont stockés dans un tableau, que nous déclarons en en-tête de module :
' Buffers pour le cube
Dim
CubeBuffers
(
1
To
2
) As
Long
Retournons ensuite à la fin de la procédure InitScene.
À la suite du code d'initialisation des tableaux, générez deux buffers :
' Crée les buffers pour le cube
glGenBuffers 2
, CubeBuffers
(
1
)
On demande la création de deux buffers, et on donne le premier buffer en paramètre :
- CubeBuffers(1) contient l'identifiant du premier buffer ;
- CubeBuffers(2) contient l'identifiant du deuxième buffer.
Ces buffers sont vides, il faut ensuite les remplir avec la fonction glBufferData.
Avant d'utiliser la fonction glBufferData, nous devons « activer » le buffer souhaité avec la fonction glBindBuffer.
Cela nous donne pour le premier buffer qui contient les données de vertex et de couleurs :
' Données de vertex / couleur
glBindBuffer GL_ARRAY_BUFFER, CubeBuffers
(
1
)
glBufferData GL_ARRAY_BUFFER, UBound
(
CubeArray) *
Len
(
CubeArray
(
1
)), VarPtr
(
CubeArray
(
1
)), GL_STATIC_DRAW
glBindBuffer GL_ARRAY_BUFFER, 0
&
GL_ARRAY_BUFFER désigne un tableau de données : vertex, couleurs, normales, coordonnées de textures…
Le deuxième paramètre de la fonction glBufferData est la taille des données, en bytes.
Cette taille est calculée en multipliant le nombre de données dans le tableau par la taille d'un élément du tableau.
Le troisième paramètre de la fonction glBufferData est un pointeur vers le premier élément du tableau.
Ce pointeur est obtenu grâce à la fonction VarPtr.
Le dernier paramètre de la fonction glBufferData est le type d'utilisation des données du buffer.
GL_STATIC_DRAW désigne une utilisation du buffer en lecture seule.
Nous verrons ensuite une utilisation dynamique d'un buffer.
On procède de la même manière pour remplir le buffer d'indices, mais avec un type de tableau GL_ELEMENT_ARRAY_BUFFER (car c'est un tableau d'indices) :
' Données d'indices
glBindBuffer GL_ELEMENT_ARRAY_BUFFER, CubeBuffers
(
2
)
glBufferData GL_ELEMENT_ARRAY_BUFFER, UBound
(
IndiceArray) *
Len
(
IndiceArray
(
1
)), VarPtr
(
IndiceArray
(
1
)), GL_STATIC_DRAW
glBindBuffer GL_ELEMENT_ARRAY_BUFFER, 0
&
Une fois les buffers remplis, les tableaux IndiceArray et CubeArray ne sont plus utilisés.
Il ne faut pas oublier de supprimer les buffers lorsqu'ils ne sont plus utiles.
' Supprime les buffers
glDeleteBuffers 2
, CubeBuffers
(
1
)
Pour notre application il n'est pas utile de supprimer les buffers.
Lorsqu'on ferme la fenêtre et que la fonction glutMainLoop nous rend la main, les buffers sont déjà supprimés.
Pour s'en assurer il suffit de tester après glutMainLoop si les identifiants dans le tableau CubeBuffers sont des buffers valides.
' Teste les buffers
MsgBox
"Buffer 1 : "
&
glIsBuffer
(
CubeBuffers
(
1
))
MsgBox
"Buffer 2 : "
&
glIsBuffer
(
CubeBuffers
(
2
))
Buffer 1
: 0
Buffer 2
: 0
VI-C. Utilisation des buffers▲
L'utilisation des buffers est très similaire à l'utilisation des tableaux vue dans le premier tutoriel.
La différence est que les données ne sont plus stockées dans un tableau, mais dans un buffer.
Lorsqu'on appelle les fonctions, on ne donne donc plus en paramètre un tableau, mais une position dans le buffer actif.
Avant d'écrire le nouveau code de dessin du cube, supprimez les anciennes lignes situées dans la procédure Render (de glBegin à glEnd inclus).
Nous allons écrire à la place le code avec VBO.
Il faut d'abord activer les tableaux utilisés, pour notre cas on active les tableaux de vertex et de couleurs :
' Début de dessin du cube
' Activation des tableaux
glEnableClientState GL_VERTEX_ARRAY
glEnableClientState GL_COLOR_ARRAY
Ensuite on positionne les pointeurs pour indiquer où sont les données de vertex et de couleurs.
Ces données étant dans le premier buffer, on l'active au préalable :
' Position des données à utiliser
glBindBuffer GL_ARRAY_BUFFER, CubeBuffers
(
1
)
glVertexPointer 3
, GL_FLOAT, (
3
+
3
) *
4
, ByVal
0
&
glColorPointer 3
, GL_FLOAT, (
3
+
3
) *
4
, ByVal
12
&
Les fonctions glVertexPointer et glColorPointer méritent une explication.
1er paramètre = 3 : c'est le nombre d'informations par sommet : trois coordonnées ou trois composantes de couleur.
2e paramètre = GL_FLOAT : c'est le type des données contenues dans le buffer (GL_FLOAT = Single).
3e paramètre = (3 + 3) * 4 : c'est la taille de données entre deux sommets.
Il y a 3 coordonnées + 3 composantes de couleur, que l'on multiplie par la taille d'un élément du tableau.
Ce 3e paramètre, nommé stride est très important dans ce cas où le tableau est entrelacé.
Si on avait utilisé un tableau par type de données, ce paramètre serait égal à zéro.
4e paramètre = 0& ou 12& : c'est le décalage des informations, en bytes.
Les vertex commencent au début du tableau donc avec un décalage de 0, alors que les coordonnées commencent après les trois premiers éléments, donc un décalage de 3*4=12 (car chaque élément occupe quatre bytes).
Notez l'utilisation de ByVal pour le dernier paramètre afin de bien passer la valeur en paramètre.
À ce stade on a défini un buffer de données avec GL_ARRAY_BUFFER.
Ce buffer serait suffisant si on n'utilisait pas de buffer d'indices : on utiliserait alors la fonction glDrawArrays pour dessiner.
Mais ici nous utilisons un buffer d'indices, c'est le deuxième buffer que nous activons :
' Utilisation des indices
glBindBuffer GL_ELEMENT_ARRAY_BUFFER, CubeBuffers
(
2
)
Et enfin on peut exécuter la commande de dessin :
' Dessine le cube
glDrawElements GL_TRIANGLES, 36
, GL_UNSIGNED_INT, ByVal
0
&
Cette fonction demande l'affichage :
- de triangles (GL_TRIANGLES) ;
- au nombre de 12 (composés de 36 indices) ;
- à partir de données d'indices de type Long (GL_UNSIGNED_INT) ;
- et sans décalage dans le buffer d'indices (ByVal 0&).
Enfin, il faut désactiver l'utilisation des tableaux et buffers :
' Désactivation des tableaux
glDisableClientState GL_VERTEX_ARRAY
glDisableClientState GL_COLOR_ARRAY
' Désactive les buffers
glBindBuffer GL_ARRAY_BUFFER, 0
&
glBindBuffer GL_ELEMENT_ARRAY_BUFFER, 0
&
Voici le cube dessiné avec les VBO :
VI-D. Modification des buffers▲
Il est également possible de modifier dynamiquement le contenu des buffers.
Pour cela on récupèrera la position des données en mémoire avec la fonction glMapBuffer.
Avant toute chose, il faut définir le type de buffer afin qu'il soit modifiable.
GL_STATIC_DRAW est utilisé pour un buffer statique, donc jamais modifié.
GL_STREAM_DRAW est utilisé pour un buffer que l'on modifie une fois par affichage.
GL_DYNAMIC_DRAW est utilisé pour un buffer dynamique que l'on modifie souvent, plusieurs fois par affichage.
Remplacez GL_STATIC_DRAW par GL_STREAM_DRAW pour le tableau CubeArray :
glBufferData GL_ARRAY_BUFFER, UBound
(
CubeArray) *
Len
(
CubeArray
(
1
)), VarPtr
(
CubeArray
(
1
)), GL_STREAM_DRAW
Juste avant l'utilisation du buffer d'indices, on va boucler sur les données pour modifier dynamiquement leur couleur.
Il nous faut donc un pointeur (lPtr), un compteur (lCpt) et un tableau de composantes de couleur (lColor) :
#If VBA7 Then
Dim
lPtr As
LongPtr
Dim
lCpt As
LongPtr
#Else
Dim
lPtr As
Long
Dim
lCpt As
Long
#End If
Dim
lColor
(
1
To
3
) As
Single
Les variables lPtr et lCpt sont des pointeurs et doivent donc être déclarés en fonction de l’environnement d’exécution (LongPtr ou Long).
On lit ensuite la position des données du buffer :
lPtr =
glMapBuffer
(
GL_ARRAY_BUFFER, GL_READ_WRITE)
Puis on boucle sur les données, on les lit et on les modifie :
' Boucle sur les données
For
lCpt =
1
To
24
' Lecture des données de couleur
RtlMoveMemory lColor
(
1
), ByVal
lPtr +
(
lCpt -
1
) *
(
3
+
3
) *
4
+
3
*
4
, 3
*
4
' Atténuation de la couleur
lColor
(
1
) =
lColor
(
1
) -
0
.005
lColor
(
2
) =
lColor
(
2
) -
0
.005
lColor
(
3
) =
lColor
(
3
) -
0
.005
' Écriture des nouvelles données de couleur
RtlMoveMemory ByVal
lPtr +
(
lCpt -
1
) *
(
3
+
3
) *
4
+
3
*
4
, lColor
(
1
), 3
*
4
Next
On boucle de 1 à 24, car on a 24 sommets.
Le tableau lColor reçoit les composantes de couleurs.
Pour lire le buffer, on utilise la fonction RtlMoveMemory (déclarée dans le module ModOpenGLTools).
Cette fonction permet de copier des zones de la mémoire.
Notez l'utilisation du ByVal lorsqu'on utilise un pointeur.
Enfin on libère le buffer avec glUnmapBuffer :
' Libère le buffer
glUnmapBuffer GL_ARRAY_BUFFER
L'exécution de ce projet affiche un cube dont les couleurs sont progressivement atténuées :
Il est bien sûr possible de modifier également les coordonnées des sommets.
Il suffit de modifier les données de vertex (décalage 0 au lieu de 3*4) à la place des données de couleurs.
On obtient ainsi des objets dont la géométrie peut varier, tout en conservant une grande rapidité d'exécution.