OpenGL Moderne

Tutoriel 4 : un cube coloré

Bienvenue dans le quatrième tutoriel ! Dans celui-ci, vous allez réaliser les choses suivantes :

  • afficher un cube au lieu d'un ennuyeux triangle ;
  • ajouter de jolies couleurs ;
  • apprendre ce qu'est le tampon de profondeur (Z-buffer).

Commentez Donner une note à l'article (5)

Article lu   fois.

Les deux auteur et traducteur

Site personnel

Traducteur : Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

Navigation

Tutoriel précédent : les matrices

 

Sommaire

 

Tutoriel suivant : un cube texturé

I. Introduction

Bienvenue dans le quatrième tutoriel ! Dans celui-ci, vous allez réaliser les choses suivantes :

  • afficher un cube au lieu d'un ennuyeux triangle ;
  • ajouter de jolies couleurs ;
  • apprendre ce qu'est le tampon de profondeur (Z-buffer).

II. Afficher un cube

Un cube possède six faces carrées. Comme OpenGL ne connaît que les triangles, on doit dessiner douze triangles : deux pour chaque face. On définit les sommets de la même façon que pour le triangle.

 
Sélectionnez
// Les sommets. Trois floats consécutifs donnent un sommet 3D ; trois sommets consécutifs donnent un triangle. 
// Un cube possède six faces avec deux triangles pour chaque, donc cela fait 6*2=12 triangles et 12*3=36 sommets 
static const GLfloat g_vertex_buffer_data[] = { 
    -1.0f,-1.0f,-1.0f, // triangle 1 : début
    -1.0f,-1.0f, 1.0f, 
    -1.0f, 1.0f, 1.0f, // triangle 1 : fin 
    1.0f, 1.0f,-1.0f, // triangle 2 : début 
    -1.0f,-1.0f,-1.0f, 
    -1.0f, 1.0f,-1.0f, // triangle 2 : fin 
    1.0f,-1.0f, 1.0f, 
    -1.0f,-1.0f,-1.0f, 
    1.0f,-1.0f,-1.0f, 
    1.0f, 1.0f,-1.0f, 
    1.0f,-1.0f,-1.0f, 
    -1.0f,-1.0f,-1.0f, 
    -1.0f,-1.0f,-1.0f, 
    -1.0f, 1.0f, 1.0f, 
    -1.0f, 1.0f,-1.0f, 
    1.0f,-1.0f, 1.0f, 
    -1.0f,-1.0f, 1.0f, 
    -1.0f,-1.0f,-1.0f, 
    -1.0f, 1.0f, 1.0f, 
    -1.0f,-1.0f, 1.0f, 
    1.0f,-1.0f, 1.0f, 
    1.0f, 1.0f, 1.0f, 
    1.0f,-1.0f,-1.0f, 
    1.0f, 1.0f,-1.0f, 
    1.0f,-1.0f,-1.0f, 
    1.0f, 1.0f, 1.0f, 
    1.0f,-1.0f, 1.0f, 
    1.0f, 1.0f, 1.0f, 
    1.0f, 1.0f,-1.0f, 
    -1.0f, 1.0f,-1.0f, 
    1.0f, 1.0f, 1.0f, 
    -1.0f, 1.0f,-1.0f, 
    -1.0f, 1.0f, 1.0f, 
    1.0f, 1.0f, 1.0f, 
    -1.0f, 1.0f, 1.0f, 
    1.0f,-1.0f, 1.0f 
};

Le tampon OpenGL est créé, lié, rempli et configuré avec les fonctions de base (glGenBuffers, glBindBuffer, glBufferData, glVertexAttribPointer) ; lisez le deuxième tutoriel pour un rappel rapide. L'appel pour l'affichage ne change pas non plus, vous avez simplement à donner le bon nombre de sommets à dessiner :

 
Sélectionnez
// Affiche le triangle ! 
glDrawArrays(GL_TRIANGLES, 0, 12*3); // 12*3 indices démarrant à 0 -> 12 triangles -> 6 carrés

Quelques remarques sur ce code :

  • pour l'instant, nos modèles 3D sont fixes : afin de les modifier, vous devez changer le code source, recompiler l'application et prier que tout aille bien. On apprendra comment charger dynamiquement les modèles dans le septième tutoriel ;
  • chaque sommet est écrit au moins trois fois (cherchez la ligne « -1.0f,-1.0f,-1.0f » dans le code ci-dessus). C'est une incroyable perte de mémoire. On apprendra comment gérer cela dans le neuvième tutoriel.

Vous avez maintenant tous les morceaux pour dessiner le cube en blanc. Faites fonctionner les shaders ! Allez, ou au moins, essayez :).

III. Ajouter des couleurs

Une couleur est, conceptuellement, exactement identique à une position : ce ne sont que des données. Avec les mots d'OpenGL, ce sont des « attributs ». En fait, on les a déjà utilisés avec glEnableVertexAttribArray() et glVertexAttribPointer(). On ajoute un autre attribut à chaque sommet. Le code va ressembler à celui pour les positions.

Premièrement, la déclaration des couleurs : un triplet RGB par sommet. Ici, j'en ai généré quelques-uns aléatoirement, donc le résultat ne sera pas beau, mais vous pouvez faire mieux, par exemple en copiant la position des sommets pour définir sa couleur.

 
Sélectionnez
// Une couleur pour chaque sommet. Elles ont été générées aléatoirement. 
static const GLfloat g_color_buffer_data[] = { 
    0.583f,  0.771f,  0.014f, 
    0.609f,  0.115f,  0.436f, 
    0.327f,  0.483f,  0.844f, 
    0.822f,  0.569f,  0.201f, 
    0.435f,  0.602f,  0.223f, 
    0.310f,  0.747f,  0.185f, 
    0.597f,  0.770f,  0.761f, 
    0.559f,  0.436f,  0.730f, 
    0.359f,  0.583f,  0.152f, 
    0.483f,  0.596f,  0.789f, 
    0.559f,  0.861f,  0.639f, 
    0.195f,  0.548f,  0.859f, 
    0.014f,  0.184f,  0.576f, 
    0.771f,  0.328f,  0.970f, 
    0.406f,  0.615f,  0.116f, 
    0.676f,  0.977f,  0.133f, 
    0.971f,  0.572f,  0.833f, 
    0.140f,  0.616f,  0.489f, 
    0.997f,  0.513f,  0.064f, 
    0.945f,  0.719f,  0.592f, 
    0.543f,  0.021f,  0.978f, 
    0.279f,  0.317f,  0.505f, 
    0.167f,  0.620f,  0.077f, 
    0.347f,  0.857f,  0.137f, 
    0.055f,  0.953f,  0.042f, 
    0.714f,  0.505f,  0.345f, 
    0.783f,  0.290f,  0.734f, 
    0.722f,  0.645f,  0.174f, 
    0.302f,  0.455f,  0.848f, 
    0.225f,  0.587f,  0.040f, 
    0.517f,  0.713f,  0.338f, 
    0.053f,  0.959f,  0.120f, 
    0.393f,  0.621f,  0.362f, 
    0.673f,  0.211f,  0.457f, 
    0.820f,  0.883f,  0.371f, 
    0.982f,  0.099f,  0.879f 
};

Le tampon est créé, lié et rempli de la même façon que le précédent :

 
Sélectionnez
GLuint colorbuffer; 
glGenBuffers(1, &colorbuffer); 
glBindBuffer(GL_ARRAY_BUFFER, colorbuffer); 
glBufferData(GL_ARRAY_BUFFER, sizeof(g_color_buffer_data), g_color_buffer_data, GL_STATIC_DRAW);

La configuration est identique, elle aussi :

 
Sélectionnez
// second tampon d'attributs : couleurs 
glEnableVertexAttribArray(1); 
glBindBuffer(GL_ARRAY_BUFFER, colorbuffer); 
glVertexAttribPointer( 
    1,                                // attribut. Aucune raison particulière pour 1, mais cela doit correspondre au « layout » du shader 
    3,                                // taille 
    GL_FLOAT,                         // type 
    GL_FALSE,                         // normalisé ? 
    0,                                // nombre d'octets séparant deux sommets dans le tampon 
    (void*)0                          // décalage du tableau de tampons 
);

Maintenant, dans le vertex shader, on accède au nouveau tampon :

 
Sélectionnez
// Notez que le "1" ici correspond au "1" dans glVertexAttribPointer 
layout(location = 1) in vec3 vertexColor;

Dans ce cas, on ne fait rien de fantaisiste dans le vertex shader. On envoie simplement la couleur au fragment shader :

 
Sélectionnez
// Données de sortie ; sera interpolée pour chaque fragment 
out vec3 fragmentColor; 
 
void main(){ 
 
    [...] 
 
    // La couleur de chaque sommet sera interpolée
    // pour produire la couleur de chaque fragment
    fragmentColor = vertexColor; 
}

Dans le fragment shader, vous déclarez encore une fois fragmentColor :

 
Sélectionnez
// Valeurs interpolées à partir du vertex shader
in vec3 fragmentColor;

… et la copie dans la couleur finale de sortie :

 
Sélectionnez
// Données de sortie
out vec3 color; 
 
void main(){ 
    // Output color = color spécifié dans le vertex shader
    // interpolé entre les trois sommets alentours
    color = fragmentColor; 
}

Et voici ce que l'on obtient :

Un cube sans tampon de profondeur

Argh. Horrible. Pour comprendre ce qui se passe, voici un schéma de ce qui se produit lorsque vous dessinez un triangle « au loin » et un triangle « proche » :

Triange proche dessiné après le triangle au loin

Cela semble ok. Maintenant le triangle « au loin » en deuxième :

Triange au loin dessiné après le triangle proche

Il recouvre le triangle « proche » même s'il est supposé être derrière ! C'est ce qui se passe avec notre cube : certaines faces sont supposées être cachées, mais comme elles sont dessinées après, elles sont visibles. Le tampon de profondeur (Z-buffer) va venir à la rescousse !

Si vous ne voyez pas le problème, changez la position de votre caméra à (4,3,-3)

Si « la couleur est comme une position, c'est un attribut », pourquoi doit-on déclarer out vec3 fragmentColor ;et in vec3 fragmentColor ; pour la couleur et pas pour la position ? Car la position est un peu spéciale : c'est la seule qui est obligatoire (sinon OpenGL ne saurait pas où dessiner le triangle !). Donc, dans le vertex shader, gl_Position est une variable intégrée du langage.

IV. Le tampon de profondeur

La solution à ce problème est de conserver la profondeur ('Z') de chaque fragment dans un tampon et pour toutes les fois où vous voulez écrire un fragment, vous vérifiez d'abord si vous le pouvez (si le nouveau fragment est plus proche que l'ancien).

Vous pouvez le faire vous-même, mais c'est beaucoup plus simple de demander au matériel de le faire :

 
Sélectionnez
// Active le test de profondeur
glEnable(GL_DEPTH_TEST); 
// Accepte le fragment s'il est plus proche de la caméra que le précédent accepté
glDepthFunc(GL_LESS);

Vous devez aussi nettoyer le tampon de profondeur à chaque image, au lieu de ne le faire que pour la couleur :

 
Sélectionnez
// Nettoie l'écran
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

Et cela suffit pour régler tous vos problèmes.

Un cube coloré normal

V. Exercices

  • Dessinez le cube ET le triangle à deux emplacements différents. Vous aurez besoin de générer deux matrices MVP, de faire deux appels pour l'affichage dans la boucle principale, mais seulement un shader est nécessaire.
  • Générez les valeurs des couleurs vous-même. Quelques idées : aléatoirement, de manière à ce que les couleurs changent à chaque exécution ; dépendantes de la position des sommets ; un mélange des deux ; quelques idées créatives :). Au cas où vous ne connaîtriez pas la syntaxe C++, la voici :

     
    Sélectionnez
    static GLfloat g_color_buffer_data[12*3*3]; 
    for (int v = 0; v < 12*3 ; v++){ 
        g_color_buffer_data[3*v+0] = votre couleur rouge ici ; 
        g_color_buffer_data[3*v+1] = votre couleur verte ici ; 
        g_color_buffer_data[3*v+2] = votre couleur bleu ici ; 
    }
  • Une fois que vous avez fait cela, modifiez les couleurs à chaque affichage. Vous devez appeler glBufferData à chaque image. Assurez-vous que le tampon approprié est lié (glBindBuffer).

VI. Remerciements

Cet article est une traduction autorisée dont le texte original peut être trouvé sur opengl-tutorial.org.

Navigation

Tutoriel précédent : les matrices

 

Sommaire

 

Tutoriel suivant : un cube texturé

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2014 opengl-tutorial.org. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.