• Facile

Ce cours est visible gratuitement en ligne.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Mis à jour le 13/03/2017

Les Vertex Array Objects

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Nous avons vu dans le chapitre précédent comment utiliser directement la mémoire de notre carte graphique en y stockant les données de nos modèles 3D. Aujourd'hui, nous allons continuer sur cette lancée en stockant autre chose dans cette mémoire, ce qui permettra de faire gagner encore plus de temps à OpenGL. ^^

Ne pensez pas que l'on maltraite notre carte graphique en la sollicitant ainsi, elle est justement faite pour ça et préfère même gérer ces informations elle-même plutôt que de devoir aller les chercher dans la RAM.

Ce chapitre sera très court car il se rapproche beaucoup du précédent, vous n'allez pas être dépaysés. :p

Encore un objet OpenGL ?

Les Vertex Array Object (ou VAO) sont des objets OpenGL relativement proches des Vertex Buffer Object (VBO) et font donc à peu près la même chose : stocker des informations sur la carte graphique.

Avec les VBO, nous facilitons la vie à OpenGL en lui évitant des transferts de données inutiles avec la RAM. Nous lui donnons toutes nos données une seule fois pendant le chargement et nous n'en parlons plus.
Avec les VAO, c'est un peu la même chose sauf que nous allons stocker des appels de fonctions.

Hein ? Ça veut dire qu'on va pouvoir stocker du code source dans la carte graphique ?

Alors oui et non. Nous n'allons pas lui envoyer directement du code, elle ne comprendrait pas et nous enverrait gentiment paitre. :-°

En fait, nous allons "enregistrer" certains appels de fonctions à l'intérieur de la carte graphique de façon à ce qu'OpenGL ne fasse pas des aller-retours répétitifs (à chaque frame donc 60 fois par seconde) avec l'application. Les appels concernés sont ceux qui s'occupent de l'envoi de données (vertices, ...) :

void Cube::afficher(glm::mat4 &projection, glm::mat4 &modelview)
{

    .....


    // Verrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, m_vboID);


        // Accès aux vertices dans la mémoire vidéo

        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
        glEnableVertexAttribArray(0);


        // Accès aux couleurs dans la mémoire vidéo

        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(m_tailleVerticesBytes));
        glEnableVertexAttribArray(1);


    // Déverrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, 0);


    .....
}

Malgré l'utilisation l'utilisation des VBO, OpenGL fait toujours quelques aller-retours entre la carte graphique et l'application pour savoir où chercher les données. Certes, il n'y a plus le transfert de millions de données mais il y existe quand même des appels répétitifs qui ne font que spécifier un emplacement dans la mémoire vidéo.

Image utilisateur

Pour éviter ça, OpenGL propose d'utiliser les Vertex Array Objects pour enregistrer un petit bout de code dans la carte graphique, ce qui lui permet d'aller chercher ses données dans la mémoire vidéo sans avoir à demander à l'application où elles se trouvent.

Image utilisateur
Image utilisateur

Grâce aux VAO, OpenGL sait exactement où aller chercher ses données sans avoir à le mander à personne. Tout se passe directement dans la carte graphique.
On ne sait pas vraiment ce qu'il se passe à l'intérieur des VAO et d'ailleurs on s'en moque on veut seulement que ça fonctionne, peu importe comment. :p
Ceux qui connaissent OpenGL 2.1 feront surement l'analogie avec les Display List, on peut dire que les VAO sont leurs petits frères.

Pour vous donner une petite info, l'utilisation combinée des VAO et des VBO nous permettra de passer à OpenGL 3.3. Dans la dernière partie de ce chapitre, nous modifierons notre contexte pour profiter de la dernière version d'OpenGL 3. :D
Les Mac-Users sont eux-aussi concernés car vous pourrez utiliser directement OpenGL 3 sur votre Mac, vous n'aurez plus besoin de passer par un autre OS.

Création et Utilisation

Création

Le header

Contrairement aux VBO, les VAO seront très simples à configurer et à utiliser. Nous n'aurons pas besoin d'allouer de mémoire vidéo ni de faire des transferts de données, il suffira juste de faire des opérations que l'on connait déjà.

Nous aurons tout d'abord besoin d'une variable de type GLuint pour représenter notre VAO au sein du code C++. Comme d'habitude, nous travaillerons avec la classe Cube (puis la classe Caisse) pour illustrer ce que nous verrons dans ce chapitre. Nous commençons donc pas déclarer un attribut de type GLuint dans la header, on l'appellera m_vaoID :

#ifndef DEF_CUBE
#define DEF_CUBE


// Includes 

....


// Classe Cube

class Cube
{
    public:

    Cube(float taille, std::string const vertexShader, std::string const fragmentShader);
    ~Cube();

    void charger();
    void afficher(glm::mat4 &projection, glm::mat4 &modelview);


    protected:

    Shader m_shader;
    float m_vertices[108];
    float m_couleurs[108];

    GLuint m_vboID;
    int m_tailleVerticesBytes;
    int m_tailleCouleursBytes;
    GLuint m_vaoID;
};

#endif

Bien entendu, on pense à l'initialiser dans le constructeur :

Cube::Cube(float taille, std::string const vertexShader, std::string const fragmentShader) : m_shader(vertexShader, fragmentShader), m_vboID(0),
                                                                                             m_tailleVerticesBytes(108 * sizeof(float)),
                                                                                             m_tailleCouleursBytes(108 * sizeof(float)), m_vaoID(0)
{
    // Initialisation

    ....
}
Chargement

Dans l'introduction, nous avons vu que les VAO permettent d'enregistrer certains appels de fonction dans la mémoire de la carte graphique. Ces appels concernent surtout l'affichage avec les tabl qui sont concernés Les appels qui sont concernés sont ceux relatifs aux tableaux Vertex Attrib. Ils vont donc disparaitre de la méthode afficher() pour se retrouver dans la configuration des VAO. Nous verrons cela en détail dans un instant car avant, nous devons créer un objet OpenGL valide et le verrouiller pour le configurer.

Cela se fera dans la méthode charger() exactement comme le VBO. Nous placerons notre code après sa configuration.

On commence d'ailleurs avec la création du VAO grâce l'habituelle fonction de génération d'identifiant glGenXXX(). Elle s'appelle ici glGenVertexArrays() :

void glGenVertexArrays(GLsizei number, GLuint *arrays);
  • number : Le nombre d'ID à initialiser, nous lui donnerons la valeur 1

  • arrays : Un tableau de GLuint ou l'adresse d'une seule variable du même type. Nous lui donnerons l'adresse du VAO

Nous devons l'appeler juste après la configuration du VBO avec les paramètres donnés ci-dessus :

void Cube::charger()
{
    // Création du VBO

    ....



    // Génération de l'ID du VAO

    glGenVertexArrays(1, &m_vaoID);
}

Comme toujours avec les objets OpenGL, nous devons les verrouiller lorsque nous voulons les configurer ou les utiliser. Tout ceux que nous vus jusqu'à maintenant (texture, VBO, etc.) nécessitaient cette opération, les VAO n’échappent pas à cette règle. Nous utiliserons pour cela la fonction glBindVertexArray() :

void glBindVertexArray(GLuint array);
  • array : Le VAO à verrouiller

Remarquez que cette fonction ne prend pas de paramètre target contrairement aux textures et aux VBO. En temps normal, il y en a toujours un mais dans certains cas il arrive qu'il n'y en ait pas. :)

On verrouille donc notre VAO en appelant la fonction glBindVertexArray(), on lui donnera en paramètre l'attribut m_vaoID. On en profitera au passage pour le déverrouiller immédiatement en utilisant la même fonction mais avec le paramètre 0 :

void Cube::charger()
{
    // Gestion du VBO

    ....


    // Génération de l'identifiant du VAO

    glGenVertexArrays(1, &m_vaoID);


    // Verrouillage du VAO

    glBindVertexArray(m_vaoID);


        // Vide pour le moment

        ....


    // Déverrouillage du VAO

    glBindVertexArray(0);
}

Notre VAO est maintenant prêt à être configuré.

Nous savons que celui-ci devra contenir le code relatif aux tableaux Vertex Attrib, il devra enregistrer tous les appels de fonctions qui y sont associés comme glVertexAttribPointer(), glEnableVertexAttribPointer(), glBindBuffer(), etc.

Pour ce faire, il suffit simplement de couper le code d'envoi de données se trouvant dans la méthode afficher() et de le placer au moment de configurer le VAO (pendant le verrouillage).

Ça veut dire qu'on n'envoie plus les vertices et tout dans la méthode afficher() ?

Oui tout à fait. ^^

Le but est de sauvegarder ces appels dans la carte graphique, on les place donc dans le VAO. Nous devons donc les supprimer de la méthode afficher() pour les placer chez lui. Ne vous inquiétez pas cependant, nous ajouterons un petit bout de code dans cette méthode pour qu'OpenGL sache où chercher les données.

Au niveau de code, on coupe simplement le code suivant :

void Cube::afficher(glm::mat4 &projection, glm::mat4 &modelview)
{

    .....


    // Verrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, m_vboID);


        // Accès aux vertices dans la mémoire vidéo

        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
        glEnableVertexAttribArray(0);


        // Accès aux couleurs dans la mémoire vidéo

        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(m_tailleVerticesBytes));
        glEnableVertexAttribArray(1);


    // Déverrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, 0);


    .....
}

Que l'on colle ici (entre les lignes surlignées) :

void Cube::charger()
{
    // Gestion du VBO

    ....


    // Génération de l'identifiant du VAO

    glGenVertexArrays(1, &m_vaoID);


    // Verrouillage du VAO

    glBindVertexArray(m_vaoID);


        // Verrouillage du VBO

        glBindBuffer(GL_ARRAY_BUFFER, m_vboID);


            // Accès aux vertices dans la mémoire vidéo

            glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
            glEnableVertexAttribArray(0);


            // Accès aux couleurs dans la mémoire vidéo

            glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(m_tailleVerticesBytes));
            glEnableVertexAttribArray(1);
 

        // Déverrouillage du VBO

        glBindBuffer(GL_ARRAY_BUFFER, 0);


    // Déverrouillage du VAO

    glBindVertexArray(0);
}

De cette façon, le processus d'envoi de données est enregistré sur la carte graphique et OpenGL n'a pas besoin d'interroger l'application C++ pour savoir ce qu'il doit faire.

Petit détail, je vous conseille d'indenter le code qui se trouve dans le VAO tout comme les shaders et les VBO. :)

Éviter les fuites de mémoire

Vous vous souvenez de ce que nous avons vu dans le chapitre précédent sur les fuites de mémoire des VBO ? Que si on en chargeait un deux fois alors le premier chargement était perdu et les ressources considérées comme étant "toujours utilisées".

Le problème est le même ici, si on charge un VAO plusieurs fois alors on gâche de la mémoire. Pour éviter cela, nous allons utiliser la même astuce que celle que nous avons utilisée pour les textures et les VBO, à savoir : appeler une fonction du type glIsXXX() pour savoir si une initialisation a déjà eu lieu, puis en appeler une autre du type glDeleteXXX() si c'est le cas.

La première de ces fonctions s'appelle dans notre cas :

GLboolean glIsVertexArray(GLuint array);

Elle prend en paramètre l'identifiant à vérifier et renvoie comme d'habitude GL_TRUE ou GL_FALSE pour confirmer ou non une précédente initialisation.

La fonction de destruction de VAO quant à elle s'appelle glDeleteVertexArrays() :

void glDeleteVertexArrays(GLsizei n​umber, const GLuint *arrays​);
  • number : Le nombre d'ID à initialiser, nous lui donnerons la valeur 1

  • arrays : Un tableau de GLuint ou l'adresse d'une seule variable du même type. Nous lui donnerons l'adresse de l'attribut m_vaoID

Nous devons les appeler dans un bloc if juste avant la génération d'ID :

void Cube::afficher(glm::mat4 &projection, glm::mat4 &modelview)
{
    // Gestion du VBO

    ....


    // Destruction d'un éventuel ancien VAO

    if(glIsVertexArray(m_vaoID) == GL_TRUE)
        glDeleteVertexArrays(1, &m_vaoID);


    // Génération de l'identifiant du VAO

    glGenVertexArrays(1, &m_vaoID);


    // Verrouillage du VAO

    glBindVertexArray(m_vaoID);


        // Verrouillage du VBO

        glBindBuffer(GL_ARRAY_BUFFER, m_vboID);


            // Accès aux vertices dans la mémoire vidéo

            glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
            glEnableVertexAttribArray(0);


            // Accès aux couleurs dans la mémoire vidéo

            glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(m_tailleVerticesBytes));
            glEnableVertexAttribArray(1);
 

        // Déverrouillage du VBO

        glBindBuffer(GL_ARRAY_BUFFER, 0);


    // Déverrouillage du VAO

    glBindVertexArray(0);
}

De cette façon, on évite tout éventuel chargement perdu dans la mémoire.

Le destructeur

Tout comme le VBO, nous devons détruire le VAO dans le destructeur (qui lui-même est appelé au moment de détruire un objet). Nous utiliserons une fois de plus la fonction glDeleteVertexArrays(), je pense que vous n'avez pas besoin de plus d'explication. :p

Cube::~Cube()
{
    // Destruction du VBO

    glDeleteBuffers(1, &m_vboID);


    // Destruction du VAO

    glDeleteVertexArrays(1, &m_vaoID);
}

Utilisation

L'utilisation des VAO est encore plus simple que leur création puisqu'il suffit juste de les verrouiller exactement comme si on utilisait une texture. C'est-à-dire que nous devons le verrouiller avant et le déverrouiller après la fonction glDrawArrays(). Bien évidemment, il faut supprimer toute référence aux tableaux Vertex Attrib maintenant qu'ils sont utilisés autre part :

void Cube::afficher(glm::mat4 &projection, glm::mat4 &modelview)
{
    // Activation du shader

    glUseProgram(m_shader.getProgramID());


        // Verrouillage du VAO

        glBindVertexArray(m_vaoID);


            // Envoi des matrices

            glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));
            glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));


            // Rendu

            glDrawArrays(GL_TRIANGLES, 0, 36);


        // Déverrouillage du VAO

        glBindVertexArray(0);


    // Désactivation du shader

    glUseProgram(0);
}

Grâce à ce verrouillage, OpenGL saura automatiquement où se trouveront les données dont il a besoin.

Petit détail : vous n'êtes pas obligés d'insérer l'envoi des matrices pendant le verrouillage du VAO, ça fonctionnera très bien si vous le faites avant. Personnellement, j'aime bien tout mettre à l'intérieur (textures comprises). :)

Au fait, on ne déverrouille pas les tableaux Vertex Attrib ? On les a activés pendant la configuration du VAO mais on ne les désactive à aucun moment.

C'est une question que je me suis aussi posée la première fois. Apparemment, les appels aux fonctions glDisableVertexAttribPointer() sont inutiles avec les VAO, ils se désactivent en même temps que le déverrouillage du VAO. Pas besoin de se triturer la tête avec ça.

Un autre exemple

Le header

Nous avons vu, dans la partie précédentes, comment implémenter un VAO dans la classe Cube, nous allons maintenant faire la même chose dans sa classe fille (Caisse). Ça va nous donner l'occasion de voir un autre exemple pour mieux intégrer les notions que nous avons abordées.

Avant de commencer, nous savons maintenant que nous avons besoin d'une variable de type GLuint pour gérer le VAO. Cependant, vu que la classe Caisse hérite de celui de la classe Cube alors il est inutile d'en utiliser une nouvelle. De plus, contrairement aux VBO, nous n'avons pas besoin d'attribut supplémentaire pour définir la taille des tableaux de données.

Il n'y a donc aucune modification à faire dans le header. :)

La méthode charger()

Au niveau de la méthode charger(), on commence par générer un nouvel ID pour le VAO grâce à la fonction glGenVertexArrays(). On le verrouille ensuite à l'aide de glBindVertexarray() :

void Caisse::charger()
{
    // Gestion du VBO

    ....


    // Destruction d'un éventuel ancien VAO

    if(glIsVertexArray(m_vaoID) == GL_TRUE)
        glDeleteVertexArrays(1, &m_vaoID);


    // Verrouillage du VAO

    glBindVertexArray(m_vaoID);


        // Vide pour le moment

        ....


    // Déverrouillage du VAO

    glBindVertexArray(0);
}

Il ne faut pas oublier de rajouter les fonctions qui permettent d'éviter les fuites de mémoire en détruisant un éventuel ancien VAO :

void Caisse::charger()
{
    // Gestion du VBO

    ....


    // Destruction d'un éventuel ancien VAO

    if(glIsVertexArray(m_vaoID) == GL_TRUE)
        glDeleteVertexArrays(1, &m_vaoID);


    // Génération de l'ID du VAO

    glGenVertexArrays(1, &m_vaoID);


    // Verrouillage du VAO

    glBindVertexArray(m_vaoID);


        // Vide pour le moment

        ....


    // Déverrouillage du VAO

    glBindVertexArray(0);
}

Comme nous l'avons vu depuis le début du chapitre, le VAO doit contenir le code relatif aux tableaux Vertex Attrib. On coupe donc le code concerné dans la méthode afficher() pour le transférer ici. Ce qui donne la méthode charger() suivante :

void Caisse::charger()
{
    // Gestion du VBO

    ....


    // Destruction d'un éventuel ancien VAO

    if(glIsVertexArray(m_vaoID) == GL_TRUE)
        glDeleteVertexArrays(1, &m_vaoID);


    // Génération de l'ID du VAO

    glGenVertexArrays(1, &m_vaoID);


    // Verrouillage du VAO

    glBindVertexArray(m_vaoID);


        // Verrouillage du VBO

        glBindBuffer(GL_ARRAY_BUFFER, m_vboID);


            // Accès aux vertices dans la mémoire vidéo

            glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
            glEnableVertexAttribArray(0);


            // Accès aux coordonnées de texture dans la mémoire vidéo

            glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(m_tailleVerticesBytes));
            glEnableVertexAttribArray(2);


        // Déverrouillage du VBO

        glBindBuffer(GL_ARRAY_BUFFER, 0);


    // Déverrouillage du VAO

    glBindVertexArray(0);
}

Configuration terminée. ;)

La méthode afficher()

Pour afficher notre caisse au final, il suffit simplement de remplacer ce que l'on a supprimé dans la méthode afficher() par le verrouillage du VAO. La seule différence avec la classe Cube c'est qu'ici on utilise ici une texture :

// Verrouillage du VAO

glBindVertexArray(m_vaoID);


    // Envoi des matrices

    glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));
    glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));


    // Verrouillage de la texture

    glBindTexture(GL_TEXTURE_2D, m_texture.getID());


    // Rendu

    glDrawArrays(GL_TRIANGLES, 0, 36);


    // Déverrouillage de la texture

    glBindTexture(GL_TEXTURE_2D, 0);


// Déverrouillage du VAO

glBindVertexArray(0);

Le code final de la méthode afficher() est maintenant :

void Caisse::afficher(glm::mat4 &projection, glm::mat4 &modelview)
{
    // Activation du shader

    glUseProgram(m_shader.getProgramID());


        // Verrouillage du VAO

        glBindVertexArray(m_vaoID);


            // Envoi des matrices

            glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));
            glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));


            // Verrouillage de la texture

            glBindTexture(GL_TEXTURE_2D, m_texture.getID());


            // Rendu

            glDrawArrays(GL_TRIANGLES, 0, 36);


            // Déverrouillage de la texture

            glBindTexture(GL_TEXTURE_2D, 0);


        // Déverrouillage du VAO

        glBindVertexArray(0);


    // Désactivation du shader

    glUseProgram(0);
}

Vous pouvez maintenant compiler votre projet.

Image utilisateur

Ce chapitre est maintenant terminé. :-°

Enfin juste en théorie, car nous allons voir maintenant comment passer à la version 3.3 d'OpenGL. Comme je vous l'ai dit tout à l'heure, l'utilisation combinée des VBO et des VAO nous permettent de profiter pleinement d'OpenGL 3. Les mac-users sont également concernés car vous pourrez maintenant coder vos applications directement sur votre Mac.

OpenGL 3.3 et Mac OS X

Passer à OpenGL 3.3

Mine de rien, nous avons fait pas mal de chemin depuis le début de ce tutoriel. Nous avons vu les principes de base d'OpenGL et nous venons de voir deux fonctionnalités avancées (ou plutôt extensions) exploitant directement notre carte graphique. Et croyez-moi ce n'est pas fini. ^^

Grâce aux deux derniers chapitres, nous connaissons tout ce qui est nécessaire pour mettre à jour notre version d'OpenGL. Vous n'êtes pas sans savoir que depuis le début du tutoriel, nous utilisons la version 3.1. Cependant, je vous avais dit que nous passerions à OpenGL 3.3 dans le futur, et c'est justement ce que nous allons faire.

J'ai préféré utiliser la 3.1 jusqu'à maintenant car avec cette version, nous n'étions pas obligé d'utiliser les VBO et les VAO pour chaque rendu. Imaginez si je vous avais montré ça dès le début, vous auriez fuis en courant. :p
Mais maintenant que nous connaissons tout ça, nous pouvons enfin passer à la 3.3.

Pour faire cela, la SDL va beaucoup nous aider puisqu'elle fera tout pour nous. Il suffira de lui demander la version qui nous intéresse. Vous vous souvenez du moment où nous initialisions la SDL avec des paramètres OpenGL ? Nous utilisions le code suivant dans la méthode initialiserFenetre() :

bool SceneOpenGL::initialiserFenetre()
{
    // Initialisation de la SDL
	
    if(SDL_Init(SDL_INIT_VIDEO) < 0)
    {
        std::cout << "Erreur lors de l'initialisation de la SDL : " << SDL_GetError() << std::endl;
        SDL_Quit();
		
        return false;
    }
	
	
    // Version d'OpenGL
	
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
	
	
    // Double Buffer
	
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
	
	
    // Création de la fenêtre
	
    m_fenetre = SDL_CreateWindow(m_titreFenetre.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, m_largeurFenetre, m_hauteurFenetre, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL);
    m_contexteOpenGL = SDL_GL_CreateContext(m_fenetre);

    return true;
}

Les lignes intéressantes dans ce code sont celles-ci :

// Version d'OpenGL
	
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);

Elles permettent de demander à la SDL la version 3.1 d'OpenGL. ;)

Pour passer à la 3.3, il suffit de changer la valeur du paramètre SDL_GL_CONTEXT_MINOR_VERSION pour lui affecter la valeur 3 :

// Version d'OpenGL
	
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);

Mais ce n'est pas tout. En effet, à partir d'OpenGL 3.2 nous pouvons demander deux profils OpenGL : le profil de compatibilité (aussi appelé Legacy ou Compatibility) et le profil Core.

La premier permet de conserver une compatibilité avec OpenGL 2.1. Il a été créé à l'origine pour ceux qui utilisent de gros moteurs 3D pour qu'ils n'aient pas à tout recoder.

Le second, lui, purge toutes les fonctionnalités inutiles pour ne garder que celles qui sont intéressantes. Il bénéficie en plus de pas mal de nouveautés spécifiques à OpenGL 3 dont un nouveau pipeline plus souple et plus performant. Nous travaillerons toujours avec ce profil.

Pour le dire à la SDL, il suffit juste d'appeler la fonction SDL_GL_SetAttribut() avec en premier paramètre la valeur SDL_GL_CONTEXT_PROFILE_MASK. Le second quant à lui pourra prendre deux valeurs :

  • SDL_GL_CONTEXT_PROFILE_COMPATIBILITY : Pour le profil de Compatibilité

  • SDL_GL_CONTEXT_PROFILE_CORE : Pour le profil Core

Vous l'aurez compris, nous utiliserons la seconde valeur. ^^

Au final, l'appel à la fonction SDL_GL_SetAttribute() ressemble à ceci :

// Utilisation du profil Core

SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);

Ce qui donne en somme les paramètres suivants :

// Version d'OpenGL
	
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);

Maintenant, nous sommes totalement à jour avec OpenGL 3. :D

OpenGL 3 sous Mac OS X

Si vous n'êtes pas utilisateurs de Mac OS X, vous n'êtes pas obligés de lire cette partie mais je vous conseille quand même de le faire (au moins à partir du titre "Version d'OpenGL") car si vous voulez créer des jeux multiplateformes, il vous suffira juste d'inclure quelques lignes de code pour les rendre compatibles avec l'OS d'Apple.

Si vous êtes utilisateurs de Mac OS X vous avez dû vous sentir frustrés depuis le début du tutoriel vu qu'il était impossible d'utiliser OpenGL 3. :(

Le problème vient du fait qu'Apple n'utilise que deux versions pour OpenGL : la 2.1 et la 3.2, donc pas de 3.1. De plus, pour profiter de la dernière version vous devez au moins être sous OS X Lion (10.7) vu qu'Apple ne soutient plus ses anciens OS.

Mais bon, si vous êtes au moins sous Lion et que vous avez suivi le tutoriel jusqu'ici, vous êtes donc capables d'utiliser OpenGL 3 directement sur votre Mac. Vous n'aurez pas la dernière version mais ce n'est pas grave, la plus importante étant la 3.2. ;)

Je vais vous expliquer comment faire en utilisant Xcode qui est un IDE beaucoup plus adapté pour vous. Pour commencer, je vais vous demander de télécharger les deux DMG suivants qui contiennent respectivement les Frameworks de SDL2 et de SDL2_image ainsi que la librairie GLM :

Copiez les Frameworks se trouvant dans ces deux DMG dans le répertoire /Library/Frameworks/ (ou /Bibliothèque/Frameworks/).

Copiez le dossier glm (en minuscules) se trouvant dans l'archive OpenG Mathematics.zip dans le répertoire /usr/local/include. Si vous ne voyez pas ce dossier, regardez dans le menu du Finder et cliquez sur "Aller" puis sur "Aller au dossier" et renseignez le chemin que je vous ai donné.

Créer un projet SDL2 sous Mac OS X

Le projet

La création de projet SDL2 avec Xcode est quelque chose d'assez folklo vu les changements fréquents de cet IDE. En effet, en temps normal vous devriez utiliser un template spécial qui crée votre projet SDL2 automatiquement. Cependant, Apple change tellement le modèle de ses templates qu'il est devenu ingérable de fournir les fichiers nécessaires pour tout le monde.

A la place, nous allons utiliser une petite astuce qui, au moins, fonctionnera pour tout le monde. :)

Pour commencer, vous devez créer un projet "Cocoa Application" :

Image utilisateur

Une fois le projet créé, vous devez supprimer les fichiers suivants :

  • AppDelegate.h

  • AppDelegate.m

  • main.m

Image utilisateur

Ensuite, ajoutez toutes les sources à votre projet (dossiers Shaders et Textures inclus). Au moment de cet ajout, faites attention à bien spécifier les options suivantes :

Image utilisateur

Une fois les fichiers ajoutés, vous devriez avoir :

Image utilisateur

Une fois que vous avez importé les sources, vous devez importer les Frameworks qui vous permettront de compiler vos applications :

  • SDL2.framework : Vous l'avez normalement installé dans le dossier /Library/Frameworks

  • SDL2_image.framework : Même chose

  • OpenGL.framework : Lui, il se trouve dans le répertoire /System/Library/Frameworks

Là par contre, il faut décocher la case "Copy items into destination group's folder" (Laissez le paramètre Folders au deuxième choix). Ceci est valable uniquement pour les Frameworks, pour le reste il faut qu'elle soit cochée.

Image utilisateur
Image utilisateur

Enfin dernier point pour l'importation, vérifiez la présence des dossiers Textures et Shaders dans la liste "Copy Bundle Resources" dans l'onglet Build Phases :

Image utilisateur

S'ils n'y sont pas, faites un glisser-coller depuis le menu de navigation à gauche.

La librairie GLM

Pour vous permettre d'utiliser la librairie GLM dans vos projets, il va falloir ajouter un paramètre dans le fichier de votre projet.

Commencez par cliquer sur le nom de votre projet sous le menu Targets :

Image utilisateur

Puis cliquez sur le menu "Build Settings" et sur le bouton "Basic" :

Image utilisateur

Repérez ensuite la ligne contenant le paramètre "Header Search Paths" et renseignez le chemin où vous avez copié le dossier glm tout à l'heure (/usr/local/include) :

Image utilisateur
Version d'OpenGL

N'oubliez pas que votre version d'OpenGL est la 3.2, vous devez ajouter un bloc de type #ifdef dans votre code pour gérer le cas des Mac. Si la define __APPLE__ est définie, alors l'application doit passer en mode OpenGL 3.2, sinon elle reste en 3.3 :

#ifdef __APPLE__

    // Version d'OpenGL
	
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);

#else

    // Version d'OpenGL
	
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);

#endif
Les ressources

A ce stade vous devriez être capables de compiler votre application. Cependant, vous aurez encore un problème pour trouver les ressources. En effet, Mac OS X ne se place pas dans le bon dossier lorsque vous lancez une application, ce qui fait qu'il est impossible pour lui de trouver les ressources que nous lui demandons.

Pour corriger ce problème, nous allons ajouter un petit bout de code spécifique à Mac OS X dans le même bloc #ifdef que précédemment.

Ce code est un peu spécial parce qu'il s'agit à l'origine d'un code Objective-C qui a été transposé en C. L'objective-C est le langage de base utilisé par les appareils Apple. Je ne vous expliquerai pas ce code car il faudrait faire un cours sur le Cocoa mais sachez simplement qu'il permet à Mac OS X de se placer dans le bon dossier des ressources.

Mais avant de copier ce code, vous devez inclure l'en-tête CoreFoundation.h toujours dans un bloc #ifdef à l'intérieur de la classe SceneOpenGL :

#ifdef __APPLE__
    #include <CoreFoundation/CoreFoundation.h>
#endif

Maintenant, copiez le code suivant ...

// Récupération du Bundle

CFURLRef URLBundle = CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle());
char *cheminResources = new char[PATH_MAX];


// Changement du 'Working Directory'

if(CFURLGetFileSystemRepresentation(URLBundle, 1, (UInt8*)cheminResources, PATH_MAX))
    chdir(cheminResources);


// Libération de la mémoire

delete[] cheminResources;
CFRelease(URLBundle);

... juste après la spécification de la version d'OpenGL :

#ifdef __APPLE__

    // Version d'OpenGL
	
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);


    // Récupération du Bundle

    CFURLRef URLBundle = CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle());
    char *cheminResources = new char[PATH_MAX];


    // Changement du 'Working Directory'

    if(CFURLGetFileSystemRepresentation(URLBundle, 1, (UInt8*)cheminResources, PATH_MAX))
        chdir(cheminResources);


    // Libération de la mémoire

    delete[] cheminResources;
    CFRelease(URLBundle);

#else

    // Version d'OpenGL
	
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);

#endif

Grâce à ce bout de code, vous n'aurez plus de problème pour trouver vos ressources. :D

Les headers

Pour terminer, faites attention aux en-têtes de la SDL et d'OpenGL qui diffèrent des autres OS :

// Include d'OpenGL

#include <OpenGL/gl3.h>


// Include de la SDL

#include <SDL2/SDL.h>


// Include de la SDL_image

#include <SDL2_image/SDL_image.h>

Ainsi, l'inclusion d'OpenGL ressemblera maintenant à ceci :

 

// Include Windows

#ifdef WIN32
#include <GL/glew.h>


// Include Mac

#elif __APPLE__
#define GL3_PROTOTYPES 1
#include <OpenGL/gl3.h>


// Include UNIX/Linux

#else
#define GL3_PROTOTYPES 1
#include <GL3/gl3.h>

#endif

Celui de la classe SceneOpenGL va légèrement changer par rapport aux autres puisqu'il faudra inclure l'en-tête CoreFoundation.h en plus :

 

// Include Windows

#ifdef WIN32
#include <GL/glew.h>


// Include Mac

#elif __APPLE__
#define GL3_PROTOTYPES 1
#include <OpenGL/gl3.h>
#include <CoreFoundation/CoreFoundation.h>


// Include UNIX/Linux

#else
#define GL3_PROTOTYPES 1
#include <GL3/gl3.h>

#endif

Et enfin, celui de la classe Texture sera :

 

// Include Windows

#ifdef WIN32
#include <GL/glew.h>
#include <SDL2/SDL_image.h>


// Include Mac

#elif __APPLE__
#define GL3_PROTOTYPES 1
#include <OpenGL/gl3.h>
#include <SDL2_image/SDL_image.h>


// Include UNIX/Linux

#else
#define GL3_PROTOTYPES 1
#include <GL3/gl3.h>
#include <SDL2/SDL_image.h>

#endif

Voilà pour les explications. ^^

Ce qu'il faut retenir

Si on résume la création de projet SDL 2 sous Mac OS X :

  • Création d'un projet Cocoa Application

  • Suppression de l'AppDelegate et du main.m

  • Ajout des sources C++

  • Ajout du code concernant les ressources dans le fichier SceneOpenGL.cpp

  • Modification des en-têtes

Vous êtes maintenant capables de coder avec OpenGL 3.2 sur votre Mac. ;)

Image utilisateur

Télécharger (Windows, UNIX/Linux et Mac) : Code Source C++ des Vertex Array Objects

Nous avons vu ce que sont les VAO et comment ils fonctionnement. Ils sont très proches des VBO et s'utilisent même conjointement avec eux.

Grâce à ces deux notions, OpenGL possède tout ce qu'il faut à portée de main vu que tout se trouve dans la carte graphique. Les seules choses qu'il a besoin d'aller chercher dans la RAM, ce sont les textures. Cependant nous pouvons régler ce problème en utilisant les Texture Buffer Objects (TBO) qui permettent de stocker des textures directement dans la carte graphique. Mais bon, nous ne verrons pas ça pour le moment vous allez me faire une overdose sinon. :p

Si je peux vous donner une bonne nouvelle c'est que vous avez acquis assez d'expérience avec OpenGL pour pouvoir apprendre les shaders. Nous commencerons à les attaquer en profondeur dès le prochain chapitre. Vous serez alors capables de créer votre propre classe Shader. ^^

Exemple de certificat de réussite
Exemple de certificat de réussite