glView
Le but de ce projet est de réaliser une interface interactive de visualisation d'images médicales en trois dimensions. Pour nous, ces images sont issues de fichiers d'objets de divers formats (obj,asc,s3d,...), pouvant donner plusieurs objets à afficher simultanément. On doit pouvoir observer ces objets depuis n'importe quel point de vue. Ils peuvent être imbriqués les uns dans les autres: c'est pourquoi, on doit permettre d'en rendre certains transparents ou de ne pas les afficher du tout, pour pouvoir les visualiser facilement et trés clairement. On doit également pouvoir effectuer des actions dessus, comme des transformations géométriques ou le changement des couleurs. La possibilité doit être laissée de sauvegarder les modifications ainsi faites.
Ce logiciel doit fonctionner sous différents systèmes d'exploitation: Windows95/NT, Linux, UNIX/Solaris ;OpenGL représente une bonne solution en terme de portabilité et permet le support des cartes accélératrices 3D ( 3DFX ou autres ...) pour gagner en rapidité.
Le logiciel est écrit en C et fut développé avec Visual C++ 4.0 pour la version Windows ainsi que gcc/xemacs/gdb pour la version UNIX.
OpenGL est une API professionnelle de graphisme 2D/3D développée initialement par Silicon Graphics ( controlée de nos jours par un consortium industriel : ARB-OpenGL Architectural Review Board). Elle réalise une interface entre le matériel graphique installé sur la machine et l’application elle-même. Cela permet de faire abstraction du hardware, vu du coté du programmeur. Cette API est disponible sous divers systèmes d’exploitation tels que DOS, Windows, MacOS, AmigaOS, Linux/UNIX ou OS/2.
Le kit de développement OpenGL est constitué de deux ensembles : GL qui fournit les appels aux 120 fonctions OpenGL et Glu qui est une surcouche OpenGL fournissant des fonctions plus évoluées pour la gestion des caméras ou la création de surfaces Nurbs.
Les schémas ci-dessous représente la manière dont l’interface hardware/application est réalisée au sein de l’API.
On peut remarquer que tous les appels à OpenGL passent par un véritable PipeLine d’exécution, partant de vertices, jusqu’au frame buffer de la carte vidéo. Cette architecture est répercutée au niveau de l’utilisation de la librairie, notamment avec la gestion des matrices organisées autour d’une pile. OpenGL est une machine à état et chaque appel à une primitive de l’API contribue à modifier l’état courant. La quasi totalité des primitives agit soit sur des variables d’état de la machine, soit sur les matrices courantes. La pile sert donc à empiler ces états pour pouvoir les restituer plus tard ( glPushMatrix, glPushAttrib…).
Les objets 3D manipulés sont constitués d’un ensemble de vertices (sommets) que l’on définit entre deux appels à glBegin et glEnd ( placent la machine dans un état prêt pour recevoir cet ensemble de vertex ).
Un des objets OpenGL les plus importants et la displaylist qui permet d’encapsuler une séquence d’instruction OpenGL en vue d’être compilée en mémoire pour pouvoir être exécutée par la machine de façon optimale. Ainsi, dans notre programme, nous avons utilisé ces displaylists pour optimiser les performances d’affichage et stocker les vertices et autres (normales, coordonnées de mapping ) de chaque objet. Une fois que la liste est créée, elle est figée et ne peut plus être modifiée, elle est à l’intèrieur de la machine et il n’y a plus d’accés possible mise à part la demande d’exécution glCallList(indice). Lors de l’appel de la liste, la matrice courante est appliquée.
En permanence, nous avons accés à deux matrices GL_MODELVIEW_MATRIX qui représente la position du modèle dans le monde, GL_PROJECTION_MATRIX qui est la matrice qui définit la zone de projection du monde en tenant compte des plans de clipping et des paramètres de la caméra. Chaque appel à glRotate, glMultMatrix, glScale ou glTranslate effectue la multiplication entre la matrice courante et la matrice de la transformation générée par l’appel à la fonction. La matrice courante peut être changée par glMatrixMode ( permet de sélectionner MODELVIEW ou PROJECTION ) ou encore glLoadMatrix. La pile sert alors à sauvegarder l’état de la matrice pour la restituer ultérieurement.
Tous ces appels sont "assemblés" ( module primitive assembly ) et fournis à l’évaluateur qui va effectuer le rendu effectif en tenant compte de tous les paramètres de la machine.
GLUT ( GL Utility Toolkit ) fournit une interface simple, portable et indépendante du système d’exploitation utilisé pour la réalisation d’interface et d’intéraction avec le matériel du type souris ou clavier. GLUT s’occupe de gérer les évènements qui peuvent survenir ainsi que la gestion d’une fenêtre d’affichage ( rafraichissement, etc…)
D’autres Toolkit sont également disponibles tels que aux, GLx (pour sytèmes Xwindows) ou Open Inventor qui est beaucoup plus évolué.
3.1) Organisation des modules :
Le logiciel a une structure assez simple, organisée autour de différents modules dont les imbriquations sont représentées ci-dessous :
3.2) Boucle principale, boite glView :
La boucle principale du programme nous est fournie par GLUT qui a un fonctionnement évènementiel. Le principe est d’enregistrer au début de l’exécution, les différentes fonctions destinées à répondre à tel ou tel évènement ( clic souris, retaillage de la fenêtre, etc...) puis de lancer la boucle principale réalisée par la fonction glutMainLoop. Ainsi, GLUT s’occupe de créer et afficher la fenêtre de visualisation, de récupérer les évènements de la souris, de tester l’appuie des touches, d’exécuter des fonctions en tâche de fond, et surtout, de commander le raffraichissement de la fenêtre de visualisation.
La fonction la plus importante est donc celle qui redessine la scène lorsque cela devient nécéssaire, draw(). L’organisation de cette fonction est dictée par la structure interne de l’API OpenGL. Le principe est de partir du centre du monde et de se positionner successivement sur tous les objets pour les dessiner. La rotation est générée par la boule virtuelle ( track ball ) qui maintient une matrice de rotation pure pour chaque objet. Il suffit alors d’appliquer la boule sur chaque objet par un glMultMatrix(), le centre de rotation sera alors la position courante dans la scène ( valeur de la matrice GL_MODELVIEW ). Lorsqu’on se place sur un objet, la pile OpenGL permet de sauvegarder la matrice courante avec glPushMatrix() avant de se translater sur l’objet en question pour revenir très rapidement à l’ancienne position avec glPopMatrix(). En bref, le principe est de mettre la matrice GL_MODELVIEW à l’identité pour se placer au centre du monde, et de dessiner chaque objet avec le schéma suivant :
L’affichage d’un objet tient compte des différents critères tels que son niveau de transparence ou sa couleur. Tous ces paramètres peuvent être gérés à l’aide du clavier ou de l’interface du programme qui agissent sur les variables d’état d’OpenGL .
La principale difficulté a été de réaliser les fonctions de lecture des fichiers images utiles pour les textures. Notre logiciel permet de charger les images bitmaps au format rgb de Silicon Graphics. L’utilisation de la librairie ImageMagick permettrait de lire un plus grand nombre de formats ( gifs, jpeg, png, tiff ,etc...).
Partant du fait que les objets n’ont pas des coordonnées de mapping définies, nous nous sommes servis d’OpenGL pour les générer automatiquement. Ceci nous permet de réaliser l’environment mapping et le texture mapping normal. La méthode de mapping est le mipmapping qui consiste à stocker plusieurs versions de la texture à différentes résolutions et de swapper entre les textures suivant l’éloignement de l’objet dans la scène : utiliser une texture de 320x200 pour un objet ne faisant que 2 pixels de haut est inutile. Dans notre logiciel, nous n’utilisons qu’une seule image pour faire plus simple.
La qualité du mapping est réglable ( lissage, correction de perspective, etc ...). A noter que pour la version 3DFX, la résolution des textures est limitée à 256x256 par le hardware.
Chaque objet 3D est représenté par son nom, sa couleur, son barycentre et la liste OpenGL ( en fait son indice, la liste elle même étant interne à OpenGL ) de ses vertex et des normales aux faces et sommets. Ces informations sont rassemblées dans une structure C réalisant le type Object.
Une scène est en fait composée d’une liste d’objets non-hiérarchisés. Elle posséde elle aussi un nom, un barycentre, une taille max… mais l’indice est remplacé par un tableau d’Objects. Nous avons également besoin de connaître l’objet courant et de savoir si la scène existe ou pas.
Ces structures sont remplies lors de la lecture du/des fichier(s). Le fait d’utiliser un tableau d’objet nous permet de rajouter simplement des objets à la scène.
Qui dit 3D, dit calcul vectoriel et matriciel, le module Matrix définit les types de bases tels que les vecteurs et les matrices ainsi que les fonctions de manipulation courante. En effet les procédures de calcul matriciel existent dans OpenGL mais pas le calcul vectoriel.
Les vecteurs que l’on manipule sont des triplets de double :
Les matrices sont des matrices de transformation homogène (4X4).
typedef GLdouble m[16] ;
GLUT nous permet d’attacher un petit popup menu sur le bouton droit de la souris, pour une meilleure manipulation des options du logiciel, le bouton gauche étant réservé pour la manipulation des objets (rotation, translation … ). Bien sûr, toutes les fonctions sont doublées au clavier. A noter que dans la version 3DFX, les menus ne s’affichent pas ; cela résulte d’un support partiel de la part de GLUT.
Pour les versions UNIX, en plus de l’interface GLUT, nous avons ajouté une interface plus classique utilisant la librairie Xforms et la libpthread pour une meilleure convivialité du programme. Elle fournit donc toutes les boites de dialogue nécessaires à la gestion des options du programme et de la sélection des objets.
4) Fonctionnalités et problèmes :
Tous les modèles de rendus classiques sont disponibles, Gouraud, Flat Shading, Wireframe et texture Mapping … La scène est éclairée par trois lampes, deux soleils et un spot. Le modèle de lumière utilisé est le modèle de Phong qui prends en compte les composantes ambiante, diffuse et spéculaire de la lumière.
Des fonctions ont été rajoutées pour améliorer la qualité du rendu, bien sûr, cela se fait au détriment de la vitesse : parmi elles on peut noter l’antialiasing ( élimination du crénelage sur les contrastes élevés), fog, la profondeur de champ ou le filtrage des textures. Tous ces effets sont paramétrables.
Des fonctions de transformation sont bien évidemment disponibles, telles que la rotation, la translation ou la mise à l’échelle ( scaling ). A noter que la rotation est effectuée sur le principe de la boule virtuelle ou track-ball pour faire tourner l’objet de façon intuitive. C’est comme si l’objet était au centre d’une boule et qu’avec la souris on fasse tourner cette boule.
Mais la fonctionnalité la plus importante, outre le fait de pouvoir charger plusieurs objets pour les visualiser simultanément, est la sélection de ceux-ci. Il est en effet possible de sélectionner un objet que l’on pourra rendre transparent ou mettre en valeur en rendant tous les autres transparents. Il est aussi permis de changer la couleur de la sélection grâce à l’interface UNIX.
Dans une version future, il serait très utile de pouvoir sauvegarder le point de vue courant pour pouvoir y revenir facilement.
La principale difficulté a été d’apprendre le fonctionnement de l’API OpenGL, surtout au niveau de la gestion des matrices et des positions dans le monde au travers de la pile. Notamment, la track ball nous a donné du " fil à retordre " et nous a permis de comprendre le fonctionnement de la pile.
Le support 3DFX n’a posé aucun problème dans la mesure où il suffit juste de faire le linkage avec les librairies OpenGL3DFX et d’utiliser les dll’s OpenGL pour 3DFX. Le problème vient du fait que la 3DFX ne fonctionne qu’en mode plein écran et interdit donc toute interface Windows quelle qu’elle soit. De plus, il semble que le support 3DFX de GLUT ne soit pas complet puisque les menus sur le bouton droit et le pointeur de la souris ne s’affichent pas à l’écran, c’est pourquoi toutes les fonctions sont doublées au clavier. Pour résoudre ce problème, nous avons créé notre propre pointeur de souris.
En mode 3DFX, le pointeur ne suit pas exactement la souris (retard). D’autre part, il semble que malgré l’affichage en plein écran de la 3DFX, GLUT considère que l’affichage se fait en mode fenêtre et gère la souris uniquement dans la zone de l’écran correspondant à cette fenêtre, ce qui a pour effet de revenir à l’écran Windows, suivant l’endroit où l’on clique dans l’écran.
L’interface UNIX est incomplète et présente des options qui ne marchent pas bien ou pas du tout.
Principalement, le groupe était divisé en deux sous-groupes : un spécialisé dans le squelette OpenGL de l’application et l’autre sur les structures de données ( scènes, objets, matrices ) ainsi que les chargements de fichiers. Par la suite, on a remanié les groupes pour former un sous-groupe sous Windows et un autre sous UNIX pour assurer la compatibilité et la mise en œuvre de l’interface XForms.
Les trois semaines de travail ont été découpées comme suit :
Ce projet nous a permis de nous familiariser avec une API 3D du commerce, qui est de plus en plus utilisée dans le domaine grand public, avec les jeux vidéos par, notamment, sa réputation n’étant plus à faire dans le domaine du graphisme professionnel. OpenGL représente un concurrent sérieux pour Direct3D de Microsoft, malgré la fusion annoncée des deux API’s.
Le fait d’avoir utilisé et compris les fondements de cette API, nous permettra d’être rapidement opérationnels sur un futur projet OpenGL, ce qui peut présenter un argument de choix pour un employeur éventuel.
Cette réalisation nous a permis de cerner les limitations mais aussi la puissance d’OpenGL et du support des cartes 3DFX, ainsi que des difficultés à organiser un groupe de travail sur le développement d’applications multi-plateformes.
Nous pensons que le délai de trois semaines était un peu court pour réaliser une application vraiment conviviale et utilisable à 100%, compte tenu du temps d’apprentissage de l’API . Si nous avions eu plus de temps, nous aurions pu nous pencher plus en detail sur une " vraie " interface graphique sous Windows et une interface plus complète sous UNIX. Pouvoir sélectionner plusieurs objets en même temps aurait été appréciable. Une sortie pour un raytracer du type POV était en prévision mais fut abandonnée faute de temps.
Dans l’ensemble, et pour finir, on peut noter une bonne entente du groupe ainsi qu’une bonne motivation de chacun.