Aller au menu - Aller au contenu

Les Vertex Buffer Objects


Informations sur le tutoriel

Avatar
Auteur : Yno
Visualisations : 38 777
Licence : Creative Commons BY-SA


Plus d'informations Plus d'informations
Les Vertex Buffer Objects, abrégé VBOs, ou VBO au singulier, sont des zones mémoire où l'on peut stocker des données de sommet (d'où le mot Vertex). Cette mémoire a la particularité d'être rapidement accessible par votre carte graphique, puisqu'il s'agit en fait de sa propre mémoire.

En effet, comme je l'ai dis lors du précédent chapitre, avec les vertex arrays les données étaient situées du côté du client, alors qu'avec les VBOs on héberge les données du côté du serveur. Nous allons donc commencer par étudier les différentes façon de procéder à cet hébergement (innombrables ^^ ), puis nous verrons comment exploiter efficacement nos données hébergées :)

La lecture préalable du chapitre précédent traitant des vertex arrays est primordiale.


C'est parti :)
Chapitre précédent Sommaire

Les VBOs, des données côté serveur

Dans le dernier chapitre, nous avons vu que nos données étaient stockées côté client. Et bien ici ce sera l'inverse, c'est ce qui fait la puissance des VBOs :)

Résumons le principe des vertex arrays :

  1. activation;
  2. spécification des données;
  3. rendu;
  4. désactivation.

Lors de la seconde étape, OpenGL ne fait que retenir un pointeur (une adresse mémoire) et des informations sur la structuration des données (stride, type et size). À la 3eme étape, OpenGL envoie les informations au serveur pour le traitement (upload).

Un peu vaseux comme explication ? Ok :D :

Image utilisateur


Comme vous pouvez le voir, les données sont dupliquées en mémoire vidéo, dans le cas des vertex arrays, puis sont traitées pour enfin être affichées à l'écran. Ce processus a lieu à chaque rendu ( glDraw*() ), si vous avez beaucoup de géométrie à envoyer à votre carte graphique, je vous laisse imaginer le topo... Le transfert d'un million de vertices (par exemple) à chaque rendu mènerait à une saturation de la bande passante, et donc à une limitation de frame rate.
L'idée des VBOs est simple : on met les données directement du côté du serveur, et elles y restent :D Cela nous économisera le transfert des données à chaque frame, ce qui est un énorme gain de performance ;)

Voici ce qui se passe dans le cas des VBOs :

Image utilisateur


Les données ne sont ni dupliquées, ni transférées à chaque rendu :) Du coup, lors d'un appel à glDraw*(), OpenGL ira directement chercher nos données en mémoire vidéo. Que demande le peuple ?

Création d'un objet tampon

Un VBO est un objet, un objet OpenGL, donc un GLuint.
Celui-ci se manipule comme tous les objets OpenGL, sans exceptions apparente, avec des fonction préfixées Gen, Is, Delete et Bind.

Voici comment créer un identifiant pour un objet tampon :

Code : C
1
2
3
GLuint buf;

glGenBuffers(1, &buf);


Rien de bien sorcier :)

Lorsque nous voudrons appliquer des modifications à notre tampon (envoie de données, modifications diverses...) nous devrons, comme pour tout objet OpenGL, le binder. Voici le prototype de la fonction permettant le binding d'un objet tampon :

Code : C
1
void glBindBuffer(GLenum target, GLuint id);


C'est quoi ce paramètre target ? Il me fait peur :euh:


Il s'agit de placer ici une constante symbolique, nous nous en tiendrons à GL_ARRAY_BUFFER pour le moment.

Bien, commençons la création d'un code source complet, que nous compléterons au fur et à mesure :) :

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
42
43
44
45
46
47
#include <SDL/SDL.h>
#include <GL/glew.h>

/* dimensions de la fenetre */
#define W 300
#define H 200

int main(int argc, char **argv)
{
    int loop = 1;       /* booleen du 'main loop' */
    SDL_Event ev;       /* structure d'evenement(s) SDL */
    GLuint buf;         /* identifiant de notre objet tampon */
    
    /* initialisation de la SDL en mode OpenGL */
    SDL_Init(SDL_INIT_VIDEO);
    SDL_SetVideoMode(W, H, 32, SDL_OPENGL);
    
    /* nom de la fenetre */
    SDL_WM_SetCaption("Vertex Buffer Objects GL", NULL);
    
    /* initialisation de glew */
    glewInit();
    
    /* creation d'un objet tampon et recuperation de son identifiant */
    glGenBuffers(1, &buf);
    
    /* boucle d'affichage principale */
    while(loop)
    {
        /* recuperation d'un evenement */
        SDL_WaitEvent(&ev);
        
        /* analyse */
        if(ev.type == SDL_QUIT)
            loop = 0;
        
        glClear(GL_COLOR_BUFFER_BIT);
        
        /* rendu ... */
        
        /* on flip les tampons */
        glFlush();
        SDL_GL_SwapBuffers();
    }
    
    return 0;
}


Notez la présence du fichier glew.h à la place de gl.h.
En effet, les Vertex Buffer Objects sont en réalité une extension d'OpenGL.

C'est quoi une extension d'OpenGL ?

Je vous conseille d'aller lire mon tutoriel sur la gestion des extensions d'OpenGL, il vous expliquera entre autres la présence de glew.h à la place de gl.h. Sa lecture est indispensable si vous souhaitez pouvoir utiliser les VBOs.
L'extension des VBOs s'appelle GL_ARB_vertex_buffer_object.


Allouer un espace de stockage



Comme nous le savons à présent, les VBOs sont des buffers (tampons) contenant des données qui se trouvent du côté de la mémoire du serveur. Comme pour toute gestion de mémoire, il faut commencer par l'allouer. Nous allons donc allouer de la mémoire côté serveur, pour y placer ensuite les données de nos sommets.
Voici la fonction permettant d'allouer de la mémoire pour un VBO :

Code : C
1
void glBufferData(GLenum target, GLsizei size, const GLvoid *data, GLenum mode);

  • target : dans notre cas, il s'agit de GL_ARRAY_BUFFER. Attention, un buffer bindé avec cette constante doit toujours être utilisé avec la même, donc à partir de maintenant, partout où vous verrez target, il faudra mettre le target de votre buffer. Un buffer ne peut avoir qu'un seul target.
  • size : c'est la taille de vos données, en bytes.
  • data : ceci désigne l'emplacement de vos données, pour l'instant nous ne metterons rien, la fontion glBufferData() est surtout déstinée à réserver un espace mémoire.
  • mode : paramètre un peu particulier. Il sert à définir le mode de traitement de vos données lors de leur mise à jour.


Comme je viens de le dire, le dernier paramètre est un peu bizarre... En fait il défini votre fréquence d'accès à vos données (voir dernière partie de ce chapitre sur la mise à jour d'un VBO). Voyons la liste des constantes acceptées pour mode :

  • GL_STREAM_DRAW : vous mettez vos données à jour à chaque affichage ( glDraw*() );
  • GL_DYNAMIC_DRAW : vous mettez fréquemment à jour vos données. (plus d'une fois par affichage);
  • GL_STATIC_DRAW : vous mettez peu souvent voir jamais vos données à jour.

Nous nous contenterons de GL_STREAM_DRAW pour l'instant, qui s'adapte très bien à toutes les situations ;)

Euh, où est-ce qu'on dit à cette fonction qu'on veut appliquer nos modifications sur notre objet buf et pas ailleurs ?


Comme pout tout objet OpenGL, il est nécessaire de le binder (glBindBuffer() dans notre cas) pour spécifier l'objet actif, et ainsi appliquer toutes les modifications futures sur cet objet et pas un autre. Afin de dire à OpenGL qu'aucun objet tampon n'est actif, appelez glBindBuffer() avec un identifiant de 0 :

Code : C
1
glBindBuffer(GL_ARRAY_BUFFER, 0);


Cela fonctionne pareil pour tous les objets OpenGL, exemple pour les textures : glBindTexture(GL_TEXTURE_2D, 0)


Voyons à présent à quoi ressemble notre code :

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <SDL/SDL.h>
#include <GL/glew.h>

/* dimensions de la fenetre */
#define W 300
#define H 200

int main(int argc, char **argv)
{
    int loop = 1;       /* booleen du 'main loop' */
    SDL_Event ev;       /* structure d'evenement(s) SDL */
    GLuint buf;         /* identifiant de notre objet tampon */
    
    #define N_VERTS 3
    #define P_SIZE 2
    #define C_SIZE 3
    
    float pos[N_VERTS*P_SIZE] =
    {
        -0.8, -0.8,
        0.8, -0.8,
        0.0, 0.8
    };
    
    float colors[N_VERTS*C_SIZE] =
    {
        1.0, 0.0, 0.0,
        0.0, 1.0, 0.0,
        0.0, 0.0, 1.0
    };
    
    
    /* initialisation de la SDL en mode OpenGL */
    SDL_Init(SDL_INIT_VIDEO);
    SDL_SetVideoMode(W, H, 32, SDL_OPENGL);
    
    /* nom de la fenetre */
    SDL_WM_SetCaption("Vertex Buffer Objects GL", NULL);
    
    /* initialisation de glew */
    glewInit();
    
    /* creation d'un objet tampon et recuperation de son identifiant */
    glGenBuffers(1, &buf);
    
    /* on bind le buffer afin que glBufferData s'applique sur buf */
    glBindBuffer(GL_ARRAY_BUFFER, buf);
    
    /* on alloue de l'espace */
    glBufferData(GL_ARRAY_BUFFER,                   /* target */
                  (N_VERTS*P_SIZE*sizeof *pos) +    /* taille des positions */
                  (N_VERTS*C_SIZE*sizeof *colors),  /* taille des couleurs */
                 NULL,                              /* ... */
                 GL_STREAM_DRAW);                   /* mode */
    
    /* boucle d'affichage principale */
    while(loop)
    {
        /* recuperation d'un evenement */
        SDL_WaitEvent(&ev);
        
        /* analyse */
        if(ev.type == SDL_QUIT)
            loop = 0;
        
        glClear(GL_COLOR_BUFFER_BIT);
        
        /* rendu ... */
        
        /* on flip les tampons */
        glFlush();
        SDL_GL_SwapBuffers();
    }
    
    /* suppression de l'objet tampon */
    glDeleteBuffers(1, &buf);
    
    return 0;
}


La suppression d'un objet tampon se fait en toute simplicité :

Code : C
1
glDeleteBuffers(1, &buf);


Et on ne libère pas la mémoire ? (free())


OpenGL s'en charge lors de l'appel à glDeleteBuffers() ;)

Héberger des données

Bon, c'est bien joli tout ça, je sais créer un VBO, lui allouer de la mémoire sur le serveur, le détruire, mais je ne sais toujours pas quoi faire de mes tableaux de sommets ?

Nous y voilà ^^

Pour ceux qui maîtriseraient déjà la création et la manipulation des textures, vous devriez savoir que glTexImage*() c'est pour allouer un espace, glTexSubImage*() pour remplir/mettre à jour cet espace.
Eh bien avec les VBOs c'est pareil :)

Code : C
1
glBufferSubData(GLenum target, GLint first, GLsizei size, const GLvoid *data);

target, size et data sont équivalents à ceux de la fonction glBufferData(). Ici, nous avons droit à un nouveau paramètre : first.
  • first: indique l'emplacement mémoire (en bytes) dans le VBO où commencer le stockage des données.


Au début, cette valeur sera logiquement à zéro (début de la mémoire de notre VBO, OpenGL y additionnera automatiquement la véritable adresse de notre buffer). Mais supposons que nous voulions ajouter des données par la suite, pour éviter d'écraser les précédentes, il faudra spécifier explicitement via first où commencer la copie des données dans la mémoire du VBO.

J'ai rien compris... ?

Je vous propose de reprendre mes super schémas :p
Voici la mémoire du VBO, sa taille est égale à celle spécifiée dans glBufferData() :

Image utilisateur


Bien, nous allons commencer simplement.
Tout d'abord, nous allons y stocker nos positions de sommet :


Code : C
1
2
3
4
glBufferSubData(GL_ARRAY_BUFFER,
                0,                            /* emplacement des donnees dans le VBO (first) */
                (N_VERTS*P_SIZE*sizeof *pos), /* taille des donnees (size) */
                pos);                         /* adresse des donnees */

Image utilisateur


Voilà qui est fait :)

En fait, c'est ici que le transfert des données (upload) de la mémoire centrale vers la mémoire vidéo a lieu.

Bien, puisque nous avons stocké avec succès nos positions de sommets, faisons de même avec les couleurs :


Code : C
1
2
3
4
glBufferSubData(GL_ARRAY_BUFFER,
                (N_VERTS*P_SIZE*sizeof *pos),   /* emplacement (first) */
                (N_VERTS*C_SIZE*sizeof *colors),/* taille (size) */
                colors);                        /* donnees */

Image utilisateur


Comme vous le voyez (j'espère :p ) nous avons dû spécifier explicitement à partir d'où OpenGL avait le droit de stocker nos données dans la mémoire du VBO. Puisque nous avions déjà stocké nos positions de sommet, si on avait laissé first à zéro, il y aurait eu un écrasement de données (écrasement de pos par colors).

Pourquoi OpenGL ne gère-t-il pas tout seul le stockage de nos données ?


Parce qu'OpenGL est une API bas niveau, il faut donc que vous puissiez faire un maximum de choses, imaginez que vous vouliez mettre à jour vos données, comment feriez vous ? OpenGL vous laisse l'opportunité de vous gourer, mais aussi de faire ce que vous voulez ;)

Exploiter des données hébergées

Faisons le point :)
  • Nous savons créer un VBO.
  • Nous savons lui allouer de l'espace mémoire sur le serveur.
  • Nous savons remplir cet espace convenablement.
  • Nous savons détruire un VBO (cela implique la destruction des données du côté serveur).

C'est bien me direz-vous, mais comment on va afficher ça ?


Spécifier une adresse mémoire située du côté serveur



En fait c'est rien de bien compliqué, vous allez voir. Pour tout vous dire, l'explication tient en deux phrases :-°

  1. bindez votre buffer ( glBindBuffer() );
  2. appelez gl*Pointer() avec pour adresse... 0.

Ça mérite bien quelques explications tout de même. En effet, lors de l'appel à glBindBuffer() avec un identifiant valide, OpenGL passe en mode VBO et tous les appels à gl*Pointer() pointent alors sur les données de notre VBO gentiment bindé.

Vous allez voir, avec un exemple ça ira tout de suite mieux :

Code : C
1
2
3
4
5
6
7
#define BUFFER_OFFSET(a) ((char*)NULL + (a))

/* on passe en mode VBO */
glBindBuffer(GL_ARRAY_BUFFER, buf);

glVertexPointer(P_SIZE, GL_FLOAT, 0, BUFFER_OFFSET(0));
glColorPointer(C_SIZE, GL_FLOAT, 0, BUFFER_OFFSET(N_VERTS*P_SIZE*sizeof *pos));


BUFFER_OFFSET ? Avons-nous été présentés ?

Non effectivement. Il s'agit d'une macro fournie par les spécifications qui vous permet de donner un offset et non une adresse.
En fait, à la place de donner notre tableau à gl*Pointer(), nous donnons sa position de stockage dans le VBO...

Son first !

Tout à fait ;)

Voici notre code à présent complété :

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
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
#include <SDL/SDL.h>
#include <GL/glew.h>

/* dimensions de la fenetre */
#define W 300
#define H 200

#define BUFFER_OFFSET(a) ((char*)NULL + (a))

int main(int argc, char **argv)
{
    int loop = 1;       /* booleen du 'main loop' */
    SDL_Event ev;       /* structure d'evenement(s) SDL */
    GLuint buf;         /* identifiant de notre objet tampon */
    
    #define N_VERTS 3
    #define P_SIZE 2
    #define C_SIZE 3
    
    float pos[N_VERTS*P_SIZE] =
    {
        -0.8, -0.8,
        0.8, -0.8,
        0.0, 0.8
    };
    
    float colors[N_VERTS*C_SIZE] =
    {
        1.0, 0.0, 0.0,
        0.0, 1.0, 0.0,
        0.0, 0.0, 1.0
    };
    
    
    /* initialisation de la SDL en mode OpenGL */
    SDL_Init(SDL_INIT_VIDEO);
    SDL_SetVideoMode(W, H, 32, SDL_OPENGL);
    
    /* nom de la fenetre */
    SDL_WM_SetCaption("Vertex Buffer Objects GL", NULL);
    
    /* initialisation de glew */
    glewInit();
    
    /* creation d'un objet tampon et recuperation de son identifiant */
    glGenBuffers(1, &buf);
    
    
    /** creation de notre VBO **/
    
    /* on bind le buffer */
    glBindBuffer(GL_ARRAY_BUFFER, buf);
    
    /* on alloue un espace */
    glBufferData(GL_ARRAY_BUFFER,                   /* target */
                    (N_VERTS*P_SIZE*sizeof *pos) +  /* taille des positions */
                    (N_VERTS*C_SIZE*sizeof *colors),/* taille des couleurs */
                    NULL,                           /* ... */
                    GL_STREAM_DRAW);                /* mode */
    
    /* on specifie les donnees */
    glBufferSubData(GL_ARRAY_BUFFER,
                    0,                            /* emplacement des donnees dans le VBO */
                    (N_VERTS*P_SIZE*sizeof *pos), /* taille des donnees */
                    pos);                         /* adresse des donnees */
    
    glBufferSubData(GL_ARRAY_BUFFER,
                    (N_VERTS*P_SIZE*sizeof *pos),   /* emplacement */
                    (N_VERTS*C_SIZE*sizeof *colors),/* taille */
                    colors);                        /* donnees */
    
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    
    /** fin **/
    
    /* boucle d'affichage principale */
    while(loop)
    {
        /* recuperation d'un evenement */
        SDL_WaitEvent(&ev);
        
        /* analyse */
        if(ev.type == SDL_QUIT)
            loop = 0;
        
        glClear(GL_COLOR_BUFFER_BIT);
        
        /* on passe en mode VBO */
        glBindBuffer(GL_ARRAY_BUFFER, buf);
        
        glVertexPointer(P_SIZE, GL_FLOAT, 0, BUFFER_OFFSET(0));
        glColorPointer(C_SIZE, GL_FLOAT, 0, BUFFER_OFFSET(N_VERTS*P_SIZE*sizeof *pos));
        
        /* activation des tableaux de sommets */
        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_COLOR_ARRAY);
        
        glDrawArrays(GL_TRIANGLES, 0, 3);
        
        /* desactivation des tableaux de sommet */
        glDisableClientState(GL_COLOR_ARRAY);
        glDisableClientState(GL_VERTEX_ARRAY);
        
        /* on flip les tampons */
        glFlush();
        SDL_GL_SwapBuffers();
    }
    
    glDeleteBuffers(1, &buf);
    
    return 0;
}


Hé oui, j'en ai profité pour rajouter un petit glDrawArrays(). En effet, le rendu est à présent possible puisque nous avons spécifié nos données à gl*Pointer() ;)


Utiliser plusieurs VBOs en même temps



Sachez que c'est tout à fait possible, et même conseillé dans certains cas.
Imaginez que vous avez fait un programme qui permet à l'utilisateur d'activer et de désactiver la couleur à volonté. Lorsqu'elle est désactivée, l'utilisation des informations de coloriage est inutile et s'avèrerait être une perte de performance plus qu'autre chose.
Nous allons donc séparer nos couleurs de nos positions de sommet. Je pense que pour l'occasion, un bout de code vaut mieux qu'un long discours, si vous avez bien compris l'utilisation d'un VBO, ce code ne devrait pas vous poser de problèmes :

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
42
43
44
45
46
47
48
GLuint buf_pos, buf_col;
    
    ...
    
    
    /* creation de nos VBO */
    glGenBuffers(1, &buf_pos);
    glGenBuffers(1, &buf_col);
    
    
    /* on bind le buffer des positions de sommet */
    glBindBuffer(GL_ARRAY_BUFFER, buf_pos);
    
    /* on alloue un espace */
    glBufferData(GL_ARRAY_BUFFER, (N_VERTS*P_SIZE*sizeof *pos), NULL, GL_STREAM_DRAW);
    /* on specifie les donnees */
    glBufferSubData(GL_ARRAY_BUFFER, 0, (N_VERTS*P_SIZE*sizeof *pos), pos);
    
    
    /* on bind le buffer des positions de sommet */
    glBindBuffer(GL_ARRAY_BUFFER, buf_col);
    
    /* on alloue un espace */
    glBufferData(GL_ARRAY_BUFFER, (N_VERTS*C_SIZE*sizeof *colors), NULL, GL_STREAM_DRAW);
    /* on specifie les donnees */
    glBufferSubData(GL_ARRAY_BUFFER, 0, (N_VERTS*C_SIZE*sizeof *colors), colors);
    
    
    /* plus aucun buffer n'est a utiliser */
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    
    ...
    
    
    /* specification du buffer des positions de sommet */
    glBindBuffer(GL_ARRAY_BUFFER, buf_pos);
    glVertexPointer(P_SIZE, GL_FLOAT, 0, BUFFER_OFFSET(0));
    
    /* specification du buffer des couleurs de sommet */
    glBindBuffer(GL_ARRAY_BUFFER, buf_col);
    glColorPointer(C_SIZE, GL_FLOAT, 0, BUFFER_OFFSET(0));
    
    ...
    
    
    /* suppression de nos VBOs */
    glDeleteBuffers(1, &buf_col);
    glDeleteBuffers(1, &buf_pos);


Hébergement et utilisation d'un tableau d'indices

Et oui, à nouveau ces fameux indices, sachez qu'ils sont tout à fait adaptés avec l'utilisation des VBOs :)
Tout comme nos sommets, nos indices seront également hébergés sur notre serveur.


Héberger un tableau d'indices



Cela se passe exactement comme pour héberger un tableau de données quelconques :) À quelques exceptions près.
Vous vous souvenez de target, ce paramètre à priori inutile ? Eh bien dans le cas des index buffers (ou tableau d'indices, ou encore tampon d'indices) il va falloir placer ce paramètre à GL_ELEMENT_ARRAY_BUFFER.

Nous appellerons donc ces buffers des IBOs (Index Buffer Objects), car ils hébergent des indices (index), et non des sommets (vertex).


Exemple d'utilisation



Nous allons sauter les étapes de stockage des tableaux de sommets, vous les connaissez suffisamment bien :

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
GLuint buf_index;
    
    ...
    
    
    glGenBuffers(1, &buf_index);
    
    /* construction du IBO */
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buf_index);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, N_VERTS * sizeof *index, NULL, GL_STREAM_DRAW);
    glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, N_VERTS * sizeof *index, index);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    
    ...
    
    
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buf_index);
    glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, BUFFER_OFFSET(0));
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);


À l'instar d'un appel à glBindBuffer() avec GL_ARRAY_BUFFER, lorsqu'on met GL_ELEMENT_ARRAY_BUFFER c'est la fonction glDrawElements() qui est affectée et qui pointe alors vers le tampon (IBO) actif.
Je pense que le reste se passe aisément de commentaires, surtout si vous avez bien compris le concept des VBOs ;)

Le stockage des indices dans un tampon côté serveur n'est pas obligatoire, mais cela a de fortes chances d'augmenter les performances de votre application ;)

Mettre à jour un tampon

Il nous reste un dernier point à éclairer, et nous aurons fait le tour des VBOs et de leur utilisation en général :)

Supposons que vous vouliez faire une animation (un personnage, ou n'importe quel maillage animé), il va bien falloir déplacer vos sommets. Mais ce n'est pas tant le déplacement en lui-même qui nous intéresse, mais plutôt la façon de mettre à jour nos vertex buffers, qui sont perdus au fin-fond de la mémoire de notre carte graphique.


Lire et écrire dans un VBO



Bien, raisonnons logiquement :
Si nous voulons accéder à nos données, il va falloir trouver leur adresse. Simple, il suffit de la demander à OpenGL :) La fonction glMapBuffer() est là pour ça :

Code : C
1
void* glMapBuffer(GLenum target, GLenum mode);

  • target: bon, maintenant celui-là vous le connaissez ;) si c'est pour mettre à jour un vertex buffer, vous mettrez GL_ARRAY_BUFFER, si c'est pour mettre à jour un index buffer, vous mettrez GL_ELEMENT_ARRAY_BUFFER.
  • mode: ça, c'est déjà un paramètre un peu plus subtile, il vous permet de définir le mode d'accès à vos données. On peut comparer ce mode aux modes d'ouvertures d'un fichier en C.

Constantes symboliques acceptables pour mode :

  • GL_READ_ONLY : les données seront accessibles en lecture seule.
  • GL_WRITE_ONLY : les données seront accessibles en écriture seule.
  • GL_READ_WRITE : les données seront accessibles en lecture et en écriture.


glMapBuffer() renvoie un pointeur sur votre VBO.

En supposant que vous ayez gardé une copie de vos données dans votre mémoire centrale (ce qui est le cas dans nos exemples ci-dessus, en effet, les tableaux pos et colors existent toujours), vous pouvez procéder à une mise à jour de vos données côté serveur de cette façon :

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <string.h>

...


float *pos_vbo = NULL;

glBindBuffer(GL_ARRAY_BUFFER, buf_pos);
pos_vbo = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
if(pos_vbo == NULL)
{
    fprintf(stderr, "impossible d'acceder aux donnees du vbo!\n");
    exit(EXIT_FAILURE);
}

/* transfert */
memcpy(pos_vbo, pos, N_VERTS * P_SIZE * sizeof *pos);

glUnmapBuffer(GL_ARRAY_BUFFER);
pos_vbo = NULL;


Nouveauté du jour, la fonction glUnmapBuffer(). Elle a pour effet de rendre invalide le pointeur renvoyé par glMapBuffer(), je place donc logiquement mon pointeur à NULL après l'appel à glUnmapBuffer().
Autre détail important, il se peut (mais c'est rare, ou alors l'erreur provient de vous) que la récupération de l'adresse de votre VBO via glMapBuffer() échoue, dans ce cas glMapBuffer() renvoie NULL. C'est important de le vérifier, car écrire dans NULL provoque une erreur de segmentation (segmentation fault) assurée ;)

Q.C.M.

Qu'est-ce qu'un objet tampon ?
Que fait la fonction glBufferData() ?
Quel comportement indésirable ce code va-t-il provoquer ? :

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
#define N_VERTS 3

#define P_SIZE 2
#define C_SIZE 3

GLuint buf;

float pos[N_VERTS*P_SIZE] =
{
    ...
};

float colors[N_VERTS*C_SIZE] =
{
    ...
};

...

glGenBuffers(1, &buf);
glBindBuffer(GL_ARRAY_BUFFER, buf);
glBufferData(GL_ARRAY_BUFFER,
             (N_VERTS*P_SIZE*sizeof(*pos))+
             (N_VERTS*C_SIZE*sizeof(*colors)),
             NULL,
             GL_STREAM_DRAW);

glBufferSubData(GL_ARRAY_DATA, 0, (N_VERTS*P_SIZE*sizeof(*pos)), pos);
glBufferSubData(GL_ARRAY_DATA, 0, (N_VERTS*P_SIZE*sizeof(*colors)), colors);
glBindBuffer(GL_ARRAY_BUFFER, 0);
Ce code est-il correct ? Sinon, quels sont ses défauts ?

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
float pos[TAILLE_POS] = { ... };

...

float *ptr = NULL;

glBindBuffer(GL_ARRAY_BUFFER, buf);
ptr = glMapBuffer(GL_ARRAY_BUFFER, GL_READ_ONLY);

memcpy(ptr, pos, TAILLE_POS);

glUnmapBuffer(GL_ARRAY_BUFFER);
ptr = NULL;

Statistiques de réponses au QCM


Les VBOs représentent à l'heure actuelle, et pour encore longtemps, la meilleure méthode de rendu et la plus utilisée. Les VBOs combinent performances et flexibilité à merveille. Le fait de stocker nos données dans la mémoire de la carte graphique nous permet de libérer la nôtre (client) et d'optimiser le transfert des données pour procéder au rendu, en fait, plus aucun transfert n'a lieu, les données restent chez le serveur.
Les VBOs sont les plus utilisés dans les rendus 3D récents, et possèdent leur équivalent chez DirectX.
Je vous conseille par dessus tout les VBOs (et IBOs) pour effectuer vos rendus, vous verrez que lorsqu'on les maîtrises il nous devient impossible de s'en passer ;)

Et voilà, c'est la fin de ce tutoriel.

J'espère que vous aurez su en tirer le meilleur profit :)

Merci à G-Truc Création pour son excellent tutoriel sur les VBOs (pdf en français),
par le biais duquel j'ai moi-même appris à m'en servir
;)
Chapitre précédent Sommaire

Informations sur le tutoriel

Retour en haut Retour en haut

Créé : Le 10/05/2007 à 23:04:43
Modifié : Le 08/11/2008 à 15:22:54
Avancement : 100%

L'orthographe, la grammaire et la présentation de ce tutoriel ont été vérifiées par les zCorrecteurs.
30 commentaires