Sommaire Index Rechercher Liens A Propos
[LinuxFocus Image]
[Barre de Navigation]
  Nouveautés   Archives

OpenGl : Rendu de scène 3D

par Miguel Angel Sepulveda


Après une longue discussion sur d'autre aspect d'OpenGL, voici finalement venu le temps de parler de graphique 3D. Je ne vous mentirais pas en disant que c'est un sujet facile, car ce n'en est pas un. Tous les bon programmeurs d'applications 3D sous OpenGL, et en particulier d'animations, doivent avoir des connaissances en algèbre linéaire, géométrie analytique, physique (mécanique) et bien sure un peu d'analyse numérique.

Je vais essayer de rendre le reste de cette série aussi accessible que possible à tous. Malheureusement, on ne peut se passer de connaissances sur les matrices, sur la représentation mathématique des plans et surfaces dans l'espace, sur les vecteurs et l'approximation polynomiale de courbes, pour n'en citer que quelques-uns.

Durant ces dernières semaines, je me suis longtemps interrogé sur la manière de présenter cela à l'audience la plus large. Les livres classiques suivent une approche étape par étape, plus ou moins ce que j'ai fais dans les premiers articles. J'ai décidé ne de plus suivre cette méthode car elle nous prendrait beaucoup trop de temps (des mois!) pour amener le lecteur au point ou il lui serait possible d'écrire son propre code. Aussi, vais-je m'essayer à une autre méthode que j'appellerai le "traitement de choc". Ce mois-ci, je vais inclure dans cet article une démo de l'une de mes simulations 3D, et ensuite essayer de vous expliquer pas à pas ce qu'il y a dans le code. Eventuellement nous explorerons en détail les sujets abordé traditionnellement dans les livres sur OpenGL mais je crois que donner au lecteur un exemple complet, amènera le lecteur à expérimenter et essayer certains choses par lui-même malgré le fait que je n'expliquerai pas ici tous les détails. J'espère que cette méthode marchera et sera plus directe.

Fini le B.A.BA! Durant les six derniers mois, j'ai travaillé à l'université de Pittsburgh sur une boite à outils logicielle Orientée Objets pour faciliter le développement de simulations sur les gels et polymères. Le projet est maintenant bien avancé, la physique qui sous-tend tout cela est très intéressantes même pour les informaticiens car un gel peut-être vu comme un réseaux de neurones de polymères et la plupart des techniques développées pour les réseaux neuronaux s'appliquent à la construction de gel. J'ai pioché quelques objets de cette bibliothèque et je les ai mis dans un exemple ../../common/May1998/example2.tar.gz Il peut être compilé sous Linux, la plupart des UNIX ainsi que sous Windows95/NT si GLUT est installé. La démo montre un seul polymère (une chaîne de plusieurs monomères reliés entre eux) en mouvement lorsqu'il se trouve plongé dans une solution à une température donné. La simulation est fascinante, cela ressemble à un serpent que l'on taquinerai! Cette "vie" est due aux collisions avec les molécules de solvant. Vous ne verrez pas le solvant car son action est intégrée aux équations de mouvement du polymère.

Le modèle utilisé pour simuler le polymère est assez simple; example2 mémorise les coordonnées (x, y ,z) de chaque noeud (monomère) tout au long de la chaîne du polymère. A chaque image de l'animation, nous dessinons une sphère aux coordonnées d'un monomère que nous relions ensuite par des cylindres. Le polymère à donc deux primitives 3D principales: une sphère et un cylindre. Comme avec toutes les molécules, la distance entre monomères varie au cours du temps, et nous sommes donc obligé d'utiliser plusieurs cylindres de longueurs différentes.

Question 1: Vous disposez de deux objets 3D, une sphère et un cylindre vertical de hauteur 1. Les deux objets sont centrés à l'origine du repère de coordonnées. Tout ce que vous connaissez à propos du polymère c'est la séquence des coordonnées (x, y, z) des noeuds. Comment allez vous appliquer les différentes transformations que sont: le changement d'échelle, la rotation, la transformation sur les copies de nos primitives pour construire le polymère?

Pour des raisons qui me sont inconnues, les informaticiens ont décidé de ne pas respecter l'ordre communément admis pour les coordonnées cartésiennes: ici, X est horizontal, y vertical et Z viens vers l'observateur. Rappelez-vous de cela car si vous venez des mathématiques ou des sciences cela vous perdra un peu au début.

Il y a une jolie zone d'information au sommet de la fenêtre qui vous indique la date courante, la température actuelle du polymère, sa température moyenne ainsi que la température de la solution, le coefficient de frottement du solvant et l'angle de rotation de la caméra extérieure. Afin de donner une vue la plus large du polymère, la caméra (votre point de vue) tourne lentement autour du centre de gravité du polymère.

En fait la longueur de polymère que j'ai choisi pour cette démo est si courte que faire tourner la caméra n'est pas vraiment nécessaire car le polymère tourne de lui même. SI vous le voulez, éditez le fichier example2.cxx et modifiez la définition de POLYMERLENGTH entre 2 et 100. La caméra tourne car j'ai voulu que le lecteur prenne conscience d'un problème: Le changement de système de coordonnées. Les coordonnées des noeuds utilisées par les équations de mouvements sont définis en coordonnées absolues ( se sont les coordonnées du "monde") et sont indépendante du point de vue de l'observateur. Ces coordonnées sont projetées sur les système de coordonnées 2D de l'écran. A chaque fois que le point de vue change, les formules nécessaire à cette projection changent.

Question 2: Comment résoudriez vous ce problème? Transformer les équations de mouvement de coordonnées du monde en coordonnées 2D de l'observateur et hors de questions, cela entraînerai trop de calculs, serait compliqué à coder et cela ferai beaucoup d'erreurs.

La réponse à la question 2 est simple, il n'y a qu'une solution pour calculer le mouvement, calculer la représentation 3D et réaliser la transformation vers le dispositif de rendu (NDT: "viewport" en anglais) à chaque image. OpenGL! OpenGL est efficace pour ce genre de choses elles sont même parfois réalisées par les cartes graphiques (ceux qui en possèdent verront la différence). Mais avant de voir comment OpenGL répond à ce problème, regardons combien il y a de transformation entre les coordonnées "réelles" du monde 3D et les coordonnées 2D finales sur l'écran.

En premier viennent les transformations sur le modèle (Modelview Transformation sur le schéma), elles ont pour but de transformer les coordonnées absolues du modèle du monde en coordonnées "caméra". Ce sont donc des coordonnées tridimensionnelles relatives à l'observateur. On les appelle Modelview car ces transformations bien que différentes se ressemblent fortement : le modelage et la visu; la dernière est analogue au positionnement d'une caméra, la première correspondant au placement des objets dans la scène à photographier.

En continuant le long du tube des différentes transformations, les coordonnées "caméra" sont transmises aux Projections. Le rôle de ces transformations pourront paraître un peu ésotérique au premier abord. Après avoir placé la caméra et les objets dans le champs de vision, OpenGL veut savoir quelle proportion de ce champs de vision doit être pris en compte. Par exemple, la caméra peut-être dirigée vers une montagne lointaine, le champs de vision est alors très large. Les Ordinateurs ne pouvant prendre en compte un nombre finis de choses nous devons définir quelle proportion du champs de vision doit être coupée (clip en anglais, le plan de coupe s'appelant clipping plane par exemple. NDT). cette transformation s'occupe également d'enlever les faces cachées à l'observateurs. Les coordonnées ainsi obtenues sont les coordonnées de clipping, mémorisez dés à présent, qu'il ne suffit pas que votre objet soit devant la caméra pour être vu, il faut également qu'il soit à l'intérieur du volume visible. Les différentes perspectives possibles (conique, orthogonale par exemple) sont définies à ce niveau.

Pour le moment nous n'entrerons pas dans la description de la division de perspective ni dans la différence entre les coordonnées de clipping et les les coordonnées normalisées pour un dispositif donné. Ce n'est pas encore nécessaire.

La dernière étape importante dans les transformations, est projection sur le dispositif de rendu (pour nous l'écran de l'ordinateur - le viewport en anglais). c'est ici que les coordonnées 3D sont projetée sur l'espace 2D de l'écran.

Les coordonnées de transformations sont représentées par des matrices (tableaux bidimensionnels de coefficients). Pour chacune des transformations ci-dessus, il y a une matrice associée. Elles peuvent être spécifiées à n'importe quel moment dans le programme avant de déclencher le rendu. OpenGL garde sur une pile, les matrices de transformation devant s'appliquer à chaque points de la scène. C'est une technique efficace et puissante que nous étudirons plus avant dans de prochains articles. Pour le moment plongeons dans le code source et voyons comment certaines de ces transformations sont définies. Dans le programme example2.cxx il ya la désormais célèbre fonction reshape :

   
void mainReshape(int w, int h){  
  
  // VIEWPORT TRANSFORMATION
   glViewport(0, 0, w, h);  

  // PROJECTION TRANSFORMATION
  glMatrixMode(GL_PROJECTION);   
  glLoadIdentity();     
  glFrustum(wnLEFT, wnRIGHT, wnBOT, wnTOP, wnNEAR, wnFAR);   

  // MODELVIEW TRANSFORMATION
  glMatrixMode(GL_MODELVIEW);   
  
  ....

la directive glViewport(x,y,width,height) spécifie le type de projection sur l'écran. : x, y sont les coordonnées du coin inférieur gauche de la fenêtre de projection et width, heigth sont les dimensions de la zone de rendu. Tous ces nombres sont donnés en pixels.

Ensuite glMatricMode(), utilisé pour sélectionner la matrice courante, est appelée avec le paramètre GL_PROJECTION af in de pouvoir spécifier le contenu de la matrice de projection. Avant d'entrer une quelconque des matrices de transformation, il est conseillé de charger la matrice identité (qui ne réalise aucune transformation), ce qui est fait par glLoadIdentity() qui remet à zéro la matrice courante. Ensuite vient la déclaration de la perspective utilisée; l'ordre glFrustrum(left, rigth, bottom, top, near, far) declare le plan de coupe (clipping plane) à la position left, rigth, bottom, top,near and far. Ces nombres sont donnés en coordonnée de vue et leur grandeurs détermine la forme (donc la perspective) du volume de la scène qui sera projeté sur l'écran. Peut-être cela vous paraît-il un peu compliqué, j'ai également mis un certain temps pour m'y faire. Le mieux pour appréhender cela est de faire quelques essais avec différents nombres, rappelez vous seulement que quelque soit les nombres choisis les coordonnées modèle-vue doivent rester à l'intérieur du volume de vue sinon rien n'apparaîtra à l'écran. Il y a d'autre moyen de spécifier les transformations de projections, nous y viendrons plus tard.

Enfin, nous changeons la matrice courante de modèle-vue, de nouveau par glMatrixMode() en utilisant le paramètre GL_MODELVIEW. La fonction MainReshape continue ensuite avec d'autre lignes qui n'ont rien à voir avec les transformations et se termine. Ce qui compte ici c'est qu'après un retaillage de la fenêtre principale, cette fonction à spécifié la matrice de projection sur l'écran, la matrice de transformation et à fait de la matrice modèle-vue la matrice courante.

La fonction mainDisplay() termine la spécification de la matrice modèle-vue et finalement défini le polymère par la fonction scene() :

 
void mainDisplay(){  
  glutSetWindow(winIdMain); 


  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  
      // Clear the color and depth buffers    
      // This is like cleaning up the blackboard    
        
  // CONTINUE MODELVIEW TRANSFORMATION: Set and orient the camera    
  glLoadIdentity();                // Load unity on current matrix
  glTranslatef(0.0, 0.0, -4.0);    // Move the camera 4 steps back    
      // we leave the camera pointing in the -z direction. Actually    
      // this operates by moving the following scene 4 steps in the -z    
      // direction. Only the elements of the scene that fall inside the    
      // viewing volume (see projection transformation later) will appear     
      //on screen.    
      
  // Render polymer  
  glScalef(0.5, 0.5, 0.5); 
  glRotatef(Angle, 0, 1, 0); 
  scene();  
 
  glutSwapBuffers(); 
}; 
 
J'espère ne pas avoir par trop perdu le lecteur en utilisant deux sous-fenêtres. Je n'expliquerais pas ici ce qui concerne l'utilisation de ces sous-fenêtre car cela a déjà été fait dans un précédent article ( Gestion de Fenêtres). Si vous avez quelques doutes n'hésitez pas à aller le consulter et rafraîchir votre mémoire.

Cette fonction est assez directe et simple. Tout d'abord glClear() efface le tampon de rendu et d'information de profondeur.. Le tampon contenant ces informations est important car la coordonnée de profondeur de chaque point doit être testé pour savoir s'il est visible ou non et de la si telle ou telle surface doit être supprimée ou gardée. Ensuite nous chargeons la matrice identité sur la matrice courante modèle-vue et appelons 3 transformations :

  • glTranslatef(xt, yt, zt) , qui déplace le système de coordonnées courant de (xt, yt, zt). Dans notre cas cela a pour effet de nous éloigner de 4 unité du modèle. Si nous ne faisions pas cela, la caméra resterait à l'origine (en plein milieu du modèle) et nous ne verrions pas grand chose...
  • glScalef(xs, ys, zs) , comme son nom l'indique, à pour but de réaliser un changement d'échelle de facteurs xs, ys et zs sur les axes x, y and z respectivement. Ce changement d'échelle est nécessaire pour faire tenir le polymère à l'intérieur du volume de clipping.
  • glRotatef(angle, vx, vy, vz) , tourne le système de coordonnées autour du vecteur normalisé (vx, vy, vz) de "angle" degrés. C'est ce que nous utilisons pour donner l'illusion d'une caméra tournant autour du modèle alors qu'en fait nous faisons tourner le modèle. Il y a de nombreuses manière de bouger la caméra mais pour l'insatnt celle-ci est la plus simple.

Un petit avertissement : L'ordre des transformations est important. Vous devez comprendre ce qui arrive à la matrice modèle-vue à chaque fois que vous demandez une transformation de coordonnées. Chaque transformation Ti est représentée mathématiquement par une matrice Mi. la superposition de séquence de transformation Tn Tn-1... T1 (par exemple : translation + homothétie + rotation ) est représenté par une seule matrice M = Mn Mn-1 .... M1. L'ordre est crucial car lorsque une transformation composite M est appliqué sur un vecteur v les transformations sont en fait appliquées dans l'ordre inverse :

M v = Mn Mn-1 .... M1 v

D'abord M1,ensuite M2, etc.. et finalement Mn. Dans notre exemple, J'ai déclaré les transformations dans l'ordre translation -> homothétie -> rotation, donc chaque point de notre modèle sera tourné -> agrandi -> déplacé avant d'être projeté sur l'écran. Gardez cela présent à l'esprit lorsque vous écrivez du code ou bien vous aurez droit à des résultats surprenant.

La fonction scene() se contente de lancer le rendu 3D du polymère. Pour comprendre comment le modèle 3D est construit, il faut dans le fichier GD_opengl.cxx regarder la fonction draw(GdPolymer &p). Il y a une boucle principale qui parcoure la liste des monomères récupère leur coordonnées (x, y, z), dessine une sphère à cet endroit, dessine et relie par un cylindre les monomères adjacents. Vous vous rappelez de la question 1, voilà une réponse possible, si vous en connaissez une plus rapide, n'hésitez pas à nous le dire.

Il y a encore une chose que le lecteur doit savoir pour comprendre intégralement la routine de rendu du polymère. A quoi servent ces glPushMatrix() et glPopMatrix() ?

Il n'y a que deux primitives géométriques dans le modèle du polymère. Une sphère de rayon 0.40 centrée à l'origine et un cylindre de hauteur 1 et de rayon 0.40 . Le polymère est dessiné en utilisant ces deux primitives plus une série de transformations afin de les placer au bon endroit. A chaque appel de glCallList(MONOMER) ou glCallList(CYLINDER), une sphère ou un cylindre est dessinée à l'origine. Pour bouger la sphère aux coordonnées (x, y, z) nous utilisons une translation (cf. glTranslatef(x,y,z)); pour dessiner et placer le cylindre qui relie deux sphères, c'est un peu plus complexe car nous devons tout d'abord lui donner la bonne longueur (i.e la distance entre les deux sphères), puis l'orienter dans la bonne direction(i.e une rotation) - dans mon algorithme j'utilise un changement d'échelle -> une rotation.

Mais quelque soit la méthode que vous utilisiez pour dessiner votre modèle 3D vous aurez besoin de telles transformations. Lorsque la fonction scene() est appelée la matrice courante de la machine à état d'OpenGL est la matrice MODELVIEW, comme nous l'avons précédemment dit, cette matrice représente la projection des coordonnées du monde dans celle de clipping. Il y a là un sérieux problème, comme la matrice MODELVIEW est encore la matrice courante, toutes les transformations futures appliquées pour dessiner le modèle seront composé avec cette matrice et donc détruira les valeurs de la transformation modèle-vue. De même, nous avons parfois envie de n'appliquer des transformations qu'à une partie du modèle mais pas aux autres (par exemple : changer l'échelle du cylindre, mais pas de la sphère). OpenGL résout ce problème en utilisant une pile interne pour les matrices. Il y a deux opérations de base sur cette pile : push (i.e l'empilement) implémenté par glPushMatrix() et pop (i.e le dépilement) implémenté par glPopMatrix(). Regardez nue fois encore le code de scene() et notez qu'avant de déclarer chaque sphère du monomère nous appelons l'opération d'empilement ce qui met la matrice MODELVIEW sur la pile et en fin de déclaration l'opération de dépilement ce qui restaure la matrice MODELVIEW. De même pour les cylindres afin d'isoler les opérations de mise à l'échelle et de rotation de l'opération de translation qui elle affecte à la fois la sphère et le cylindre.

Il y aurait encore beaucoup à dire sur les transformations 3D et la pile de matrices. Dans cet article, nous n'avons que soulevé un coin du voile sur ces sujets. Pour le moment nous laisserons le sujet dans l'état en laissant le soin au lecteur intéressé explorer le code source et essayer ses propres modèles. Le code de example2 utilise un certain nombre de fonctionnalités dont nous n'avons pas encore parlé : les matériaux et l'éclairage, que nous gardons pour un article futur. Dans le prochain article nous creuserons un peu plus les transformations 3D et la pile de matrice, nous montrerons également comment utiliser OpenGL pour animer un robot. d'ici là, amusez vous avec OpenGL.


Traduit par Mathieu veron

Pour plus d'informations:
© 1998 Miguel Angel Sepulveda
Ce site Web est maintenu par Miguel A Sepulveda.