Article     Discussion     Modifier     Historique     Forums     Salon IRC

Créer et visualiser une scène 3D avec OpenGL

Un article de Games Creators Network.


Sommaire

[modifier] Objectif

Après avoir créer une scène 2D relativement simple, on passe maintenant à la 3D, et en couleur ! :) L'objectif ici est d'afficher un cube, avec un éclairage permettant de se rendre compte du volume.

Figure 1 : L'objectif, dessiner un cube
Figure 1 : L'objectif, dessiner un cube

[modifier] Mettre en place la scène

Dans un premier temps, nous devons mettre en place la scène. Cela implique de définir les coordonnées du cube, placer la caméra, et la configurer.

[modifier] Le repère du monde

Par le terme "monde", je désigne l'univers virtuel où évoluent les objets, et que l'on observe à travers une caméra. Commençons donc par nous représenter le repère du monde :

Figure 2 : Le repère du monde
Figure 2 : Le repère du monde

Dans le tutoriel précédent, où nous avions fait uniquement de la 2D, nous avons travaillé sans trop nous préoccuper du répère du monde. En fait, nous avons travaillé dans une partie du monde, à savoir le plan z=0..

[modifier] Placer les objets

[modifier] Définir le cube

La première étape de la mise en scène des éléments consiste à placer le cube dans le monde virtuel. Pour nous simplifier les choses, nous allons le centrer sur l'origine, et lui donner une longueur de côté unitaire.

Figure 3 : Le cube placé dans le repère
Figure 3 : Le cube placé dans le repère

La définition du cube ne pose alors pas de problème : il suffit de définir les faces du cube une à une, une face étant un quadrilatère défini par ses 4 sommets :

glBegin(GL_QUADS);
 glVertex3i(1,1,1);
 glVertex3i(1,-1,1);
 glVertex3i(-1,-1,1);
 glVertex3i(-1,1,1);
 glVertex3i(1,1,-1);
 glVertex3i(1,-1,-1);
 glVertex3i(-1,-1,-1);
 glVertex3i(-1,1,-1);
 glVertex3i(1,1,1);
 glVertex3i(1,-1,1);
 glVertex3i(1,-1,-1);
 glVertex3i(1,1,-1);
 glVertex3i(-1,1,1);
 glVertex3i(-1,-1,1);
 glVertex3i(-1,-1,-1);
 glVertex3i(-1,1,-1);
 glVertex3i(-1,1,-1);
 glVertex3i(-1,1,1);
 glVertex3i(1,1,1);
 glVertex3i(1,1,-1);
 glVertex3i(-1,-1,-1);
 glVertex3i(-1,-1,1);
 glVertex3i(1,-1,1);
 glVertex3i(1,-1,-1);
glEnd();

[modifier] Attribuer une couleur à chaque face

Par défaut, la couleur utilisée est le blanc. En fait, la couleur à utiliser pour le dessin est une variable d'état, à laquelle on peut attribuer la valeur souhaitée grâce à glColor__(GL__ rouge, GL__ vert, GL__ bleu). Pour attribuer une couleur à chacune des faces du cube, il suffit donc de modifier la valeur de la couleur courante avant de définir chaque face. Le code suivant définit une face de couleur rouge :

 glColor3d(1,0,0); // face rouge
 glVertex3i(1,1,1);
 glVertex3i(1,-1,1);
 glVertex3i(-1,-1,1);
 glVertex3i(-1,1,1);

Il suffit d'appliquer le même principe pour les autres faces du cube, en choisissant pour chacune une couleur différente. On insère finalement ce code dans la fonction de dessin de la scène Display().

[modifier] Configurer la vue

Lorsque nous avons travaillé en 2D, nous avons utilisé une projection ortogonale. Ici, nous allons utiliser la projection en perspective, qui permet de visualiser la scène 3D dans notre fenêtre 2D, en donnant un résultat réaliste (les dimensions des objets changent lorsqu'ils se déplacent par rapport à l'utilisateur). La fonction gluPerspective(GLdouble angle, GLdouble ratio, GLdouble near, GLdouble far) permet de paramétrer la projection avec les paramètres suivants :

  • angle représente l'angle de vue. Il est compris entre 0° et 180°.
  • ratio est le quotient largeur/hauteur. Il permet de fixer le proportions des objets (voir tutoriel précédent). Si ratio vaut 1, alors les obets seront déformés en fonction des dimensions de la fenêtre, s'il vaut w/h (w et h dimensions de la fenêtre) alors les objets conserveront leurs proportions.
  • near et far détermine 2 des 6 plans de clipping de la scène. Le clipping d'une scène consiste à ne considérer que les objets qui sont à l'intérieur d'un certain volume. Grâce à near et far, on détermine la distance entre le point de vue et 2 plans perpendiculaires à la direction de la caméra. Les 4 autres plans de clipping forment une pyramide dont le sommet est le point de vue :


Figure 4 : Configuration de la vue en perspective
Figure 4 : Configuration de la vue en perspective

Nous allons donc pouvoir insérer dans notre fonction Reshape() la définition de la matrice de projection :


void Reshape(int w, int h)
{
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0,(float) w/h,1.,10.);
}

[modifier] Placer la caméra

Par défaut, la caméra est placée à l'origine (0,0,0), et est orientée vers le point (0,0,-1) de manière à ce que la direction de l'axe y représente le haut de la scène.

Figure 5 : Position par défaut de la caméra
Figure 5 : Position par défaut de la caméra

Pour placer et orienter la caméra comme on le désire, OpenGL met à notre disposition la fonction gluLookAt(GLdouble eyex, GLdouble eyey, GLdouble eyez, GLdouble centerx, GLdouble centery, GLdouble centerz, GLdouble upx, GLdouble upy, GLdouble upz) :

  • (eyex,eyey,eyez) sont les coordonnées du point de vue (position de la caméra) ;
  • (centerx,centery,centerz) sont les coordonnées du point de visée ;
  • (upx,upy,upz) définit la direction et le sens du vecteur "haut".


Nous choisissons ici de positionner la caméra en (4,3,3), de la diriger vers l'origine, en prenant l'axe (0,y) comme "haut".

Le code complet de la fonction display() s'écrit donc :

void Display()
{
 glClearColor(0,0,0,0);
 glClear(GL_COLOR_BUFFER_BIT);
 glMatrixMode(GL_MODELVIEW);
 glLoadIdentity();
 gluLookAt(4,3,3,0,0,0,0,1,0);
 glBegin(GL_QUADS);
 // définition de toutes les faces du cube
 glEnd();
 glFlush();
}

[modifier] Résultat intermédiaire

Affichons le résultat de notre programme à ce stade :

Figure 6 : Résultat intermédiaire
Figure 6 : Résultat intermédiaire

Commentons ce résultat : le cube est bien placé à l'origine, la caméra bien placée et correctement orientée. Toutefois, on remarque clairement un problème au niveau des faces du cube. En effet, certains faces qui devraient être cachées par d'autres (puisque derrière) apparaissent quand même, alors que des faces au premier plan n'apparaissent pas.

Que s'est-il passé ? En fait, OpenGL a dessiné les faces une à une, dans l'ordre de leur définition. Si on prend par exemple le cas de la face rouge qui devrait apparaître au premier plan, celle-ci a été dessinée en premier, puis les faces suivantes se sont dessinées "par-dessus". OpenGL ne s'est donc pas du tout préoccupé du phénomène de faces cachées, et a superposé directement toutes les faces. Nous allons voir maintenant comment résoudre ce problème.

[modifier] Mise en place d'un Z-Buffer

[modifier] Qu'est ce qu'un Z-Buffer ?

Un Z-buffer, appelé également Depth Buffer, ou tampon de profondeur, est un tampon de données qui contient la distance de chaque pixel à l'objectif.

Avant d'être exécuté, les valeurs du Z-Buffer sont initialisées à "l'infini" (comme si tous les objets étaient infiniment loin de la caméra). L'algorithme de création du Z-Buffer parcourt alors tous les objets de la scène et calcule pour chaque point des objets la distance qui le sépare de l'objectif. Si la valeur du Z-Buffer en ce point était infinie, alors l'algorithme la remplace par la nouvelle distance. Si une valeur existait déjà en ce point, alors l'algorithme garde la plus faible des deux. Ceci permet alors d'éliminer les points de la scène qui sont cachés par d'autres.

[modifier] Mise en place du Z-Buffer

Il faut donc apporter quelques modifications à notre programme.

D'abord, revenons à l'initialisation du mode d'affichage, dans la procédure main(). L'appel à la fonction glutInitDisplayMode devient :

glutInitDisplayMode(GLUT_RGBA | GLUT_SINGLE | GLUT_DEPTH);

Seconde opération : activer le test du Z-Buffer. Ici, on va se créer une fonction d'initialisation Init(), qui contiendra les initialisations nécessaires au bon fonctionnement du programme :

void Init()
{
 glEnable(GL_DEPTH_TEST);
}

Init() doit être appelée dans la procédure main(), juste après la création de la fenêtre par glutCreateWindow().

Dernière étape, initialiser le Z-Buffer avant l'affichage de la scène (fontion Display) : afin d'améliorer le temps d'éxécution, on l'initialise en même temps que le Frame Buffer :

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

[modifier] Conclusion

Nous avons donc atteint notre objectif, à savoir la visualisation (correcte) d'une scène 3D. En utilisant les fonctions de définitions de primitives vu précédemment, nous avons su créer facilement des objets simples. Il a fallu par la suite apprendre à placer correctement les éléments de la scène pour avoir un résultat convenable.

Ce document a été publié sur la version 3 du G.C.N. par Le Gritche.

  • Auteur Original : Le Gritche
  • Date de publication : 10 janvier 2002

 

Rechercher
Installer l'extension de recherche Plus d'informations

 

Comprendre
Tu me dis, j'oublie. Tu m'enseignes, je me souviens. Tu m'impliques, j'apprends. - Benjamin Franklin

 

Partager
La connaissance est la seule chose qui s'accroit lorsqu'on la partage. - Sacha Boudjema

 

Créer
L'imagination est plus importante que la connaissance. - Albert Einstein

 

 

Le wiki en images Le wiki en images Image du mois: «Snowball: un prototype de jeu développé avec NeL.