Chapitre VIII : La 3D et l`image de synthèse
Transcription
Chapitre VIII : La 3D et l`image de synthèse
3D et image de synthèse Chapitre VIII : La 3D et l'image de synthèse L'image de synthèse est née du besoin de communication avec l'ordinateur. Débutant aux Etats-Unis au début des années 50, ce domaine - l'infographie - était réservé à la recherche (notamment universitaire). Cette année là, un système composé d'un tube cathodique et d'un crayon optique fut construit pour le contrôle aérien de l'armée de l'air, en 1961 on ajoutait une croix à l'écran pour définir la position du crayon optique (projet SKETCHPAD). Pendant cette période, on a pratiqué de l'image 2D (2 dimensions) puis de la 3D beaucoup plus coûteuse en temps de calcul et en matériel. C'est par exemple en 1975 à Utah que fut créé une des images les plus célèbre de ce domaine: la fameuse théière. Cet objet est devenu depuis, un objet classique de test pour les applications 3D. La théière (figure 1) ayant servi de modèle repose aujourd'hui au Boston Computer Muséum à côté d'un ordinateur reproduisant son image... Figure 1 : Teapot for ever Jusqu'aux années 1980 ce domaine restait peu abordable en raison du coût du matériel mais aussi du fait de l'absence d'applications graphiques simples à utiliser. Mais l'apparition des ordinateurs personnels tels le Xerox Star, l'IBM-PC (1981) et le Macintosh (1984), a démocratisé l'utilisation du graphisme pour la création, l'étude et la production. VIII.1 – Les objets en 3D Dans une scène d'image de synthèse, le plus important reste les objets eux-mêmes. Néanmoins, ce concept reste à définir. 1 3D et image de synthèse En dessin, les objets consistent en des traits ou des surfaces remplies. En image de synthèse ceci constitue l'objectif final. Il nous faut une définition d'objet plus élémentaire afin de les traiter mathématiquement. En général on stocke les objets comme une collection de sommets et une liste de faces. Cette dernière indique les sommets qui sont reliés entre eux. VIII.1.a : Les sommets Les sommets ou vertex peuvent être définis en formalisme C par une structure dans laquelle sont stockées les 3 coordonnées cartésiennes : struct sommet { float x, y, z; } De façon générale, ces sommets, bien qu'aucune relation d'ordre n'existe dans R3, doivent être numérotés afin d'être identifiés de façon unique dans la suite de la définition des objets. VIII.1.b : Les faces La définition d'une face est aussi arbitraire : s'agit-il d'un triangle, un quadrilatère ou un polygone à n côtés? La plupart du temps on préférera les triangles pour une raison simple: trois points sont toujours coplanaires (dans un même plan) alors que 4, 5,...,n points peuvent ne pas être situés dans un même plan. Ce besoin d'avoir des points dans un même plan est fondamental. En effet, la synthèse d'image mêle habilement travail en 3D et 2D, on aura par exemple besoin de points dans un même plan (donc représentables en 2D) pour remplir d'une couleur une face. Mais prendre comme définition le triangle possède un inconvénient : il faudra plus de faces à 3 points pour définir un objet que de faces à 4 points... Quel est le meilleurs choix ? Tout dépend de l'espace mémoire disponible, de la vitesse recherchée ou de la facilité mais de nombreux formats de fichier conservent le triangle comme élément de base. En formalisme C, on aurait donc la définition suivante : struct face { int a,b,c ; } // numéro des sommets dans la liste Nous verrons par la suite que l'ordre de déclaration de ces sommets a une importance fondamentale puisqu'il détermine l'orientation de la face, c'est à dire le définition d'un "recto" et d'un "verso". Cette précision peut permettre de gérer le rendu 3D de ces faces. Pour ce faire, on peut également avoir besoin de connaître en chaque point de la face la normale qui est le vecteur basé sur ce point et perpendiculaire à la face (forcément plane puisque nous sommes en présence de triangles). Une normale peut être représentée en C de la façon suivante : struct normale { 2 3D et image de synthèse float x, y, z; } Les structures tridimensionnelles sont donc représentées (c'est à dire visualisées) par des ensembles de faces, qui au final sont les approximations de surfaces délimitant des volumes. Une représentation "intermédiaire" peut être mise en œuvre par le biais uniquement d'arrêtes. VIII.1.b : Les arêtes Les arêtes sont simplement déterminées par la données de deux points. Ici, le sens importe peu et on cherche juste à visualiser l"armature" de l'objet, sans rendu d'opacité quelconque. Seule les éventuels effets de perspective interviennent. Ce mode est appelé fil de fer ou wireframe. La figure 2 présente une visualisation wireframe et "réaliste" d'une sphère échantillonnée. Figure 2 : Sphère à 360 faces pour 182 sommets en fil de fer et avec des surfaces remplies et ombrées Au final, un objet peut être décrit par l'ensembles des déclarations suivantes : int NbPoints; struct sommet *Vertex; int NbFace; struct face *list_face; struct normale *n_face; // le nombre de sommet // la liste des sommet // nombre de face // la liste des faces // liste des normales au faces Remarque : On peut également inclure les normales à la structure face afin de ne pas les gérer séparément. VIII.2 – Le repérage VII.2.a : La scène Nous allons ici nous intéresser à ce qui caractérise une image de synthèse. Le concept le plus global est la scène : il s'agit de l'espace qui contient tous les objets (cercles, sphères, cubes...), les sources de lumière et les caméras, exactement comme dans un film. Cette scène - souvent 3 3D et image de synthèse appelée World - est munie d'un repère permettant d'indiquer la position des objets. Ce repère est classiquement un repère cartésien, composé des vecteurs X,Y et Z perpendiculaires deux à deux (on obtient ainsi une base orthogonale ). Les objets eux-mêmes peuvent posséder des repères. Il parait donc nécessaire de distinguer des repères de différente nature : • • • • le repère global : il s'agit d'un repère fixe, le repère général de la scène. le repère objet ( ou local ) : il s'agit d'un repère spécifique à l'objet dans lequel sont souvent exprimées les coordonnées de ses sommets le repère caméra : il s'apparente à un repère objet mais pour la caméra le repère écran Le dernier repère est relativement spécial car il est le seul "physique"; en effet le repère écran représente simplement le repère défini par votre moniteur. Ce repère écran correspond en fait à la surface de votre image finale et possède que 2 coordonnées car il est en deux dimensions. Sur un PC l'écran est définie par l'origine (0,0) en haut à gauche, l'axe X part de la gauche vers la droite et l'axe Y est inversé, il part du haut vers le bas (Figure 3) : Figure 3 : Le repère écran Les passages d'un repère à l'autre se font par des matrices de transformation que nous allons présenter. VIII.2.b : La caméra et la projection Il faut penser une scène 3D comme la scène d'un film : les objets sont des objets ou des acteurs (selon leur rôle), les points (sources) de lumière correspondent aux spots, et la caméra représente la vision du spectateur. La caméra, ou cône de vision, détermine donc la direction dans laquelle on regarde et ce que l'on voit. Mais ce n'est pas tout, la caméra peut être "penchée", en contre plongée, ou dans une position arbitraire : elle indique donc l'angle avec lequel on observe les objets. Comment "voir" les objets d'un point de vue informatique ? La caméra indique d'où voir la scène (origine de la caméra ou point de vue) et vers où regarder. Il faut ensuite projeter les objets sur la caméra, comme un projeteur de diapositive projette sur un écran ou un appareil photo imprime l'image (projection) sur la pellicule. Ceci est résumé sur la figure 4 : 4 3D et image de synthèse Figure 4 : Mise en place d'une caméra La mise en place du repère lié à la caméra se fait de la façon suivante : on définit : • • • • une origine O=(Ox,Oy,Oz) un vecteur direction OD un vecteur Vup (direction verticale de la caméra) un repère : un ensemble de trois vecteurs UXcam, UYcam et UZcam par analogie à un repère classique Le point d'origine est défini par l'utilisateur selon l'endroit où il veut placer la caméra dans la scène. Cette position est un point (x, y, z) défini dans le repère absolu. Le vecteur direction indique vers quel endroit porte le regard dans la scène (LookAt). Le vecteur Vup ( up = haut) permet de connaître la position verticale du repère caméra. Le repère caméra est obtenu en deux étapes : 1ère étape : Le vecteur UZcam est défini par la normalisation du vecteur OD Le vecteur UYcam = Vup Le vecteur UXcam =UYcam ⊗ UZcam (produit vectoriel ) 2ème étape : Les vecteurs UZcam et UYcam n'étant pas forcément perpendiculaires par la définition de Vup et le vecteur direction, il est nécessaire de faire un dernier calcul pour obtenir un UYcam satisfaisant : UYcam =UZcam ⊗ UXcam Projection : Pour projeter les objets sur l'écran de la caméra, il suffit de diviser les coordonnées de l'objet dans le repère caméra par leur Z : X = Xcam / Zcam Y = Ycam / Zcam Remarque : Ceci donne en théorie les coordonnées dans le repère écran, il faut néanmoins tenir compte d'un dernier aspect : bien souvent le Ycam et le Yécran sont inversés (l'écran sur un PC définit Y de haut en bas ). Pour obtenir Y dans le sens voulu, il faudra faire le calcul suivant : Yécran = - Y + Yres/2 5 3D et image de synthèse Xécran = X + Xres/2 Note : On doit aussi centrer l'origine au milieu de l'écran. La dernière difficulté consiste donc à transformer les coordonnées de l'objet du repère global vers le repère caméra. Ceci s'effectue grâce à une matrice de transformation VIII.2.c : La matrice de transformation Le premier préalable pour mettre en œuvre des matrices de transformation 3D est de passer en coordonnées homogènes. En effet, si l'on utilise les coordonnées cartésiennes pour représenter les points de R3 et des matrices 3x3 pour définir les transformations usuelles de R3 dans R3, on s'aperçoit que la translation ne peut pas, à l'encontre de l'homothétie et de la rotation être représentée par une matrice 3x3. Cette absence d'unité pour la représentation matricielle des transformation géométriques classiques est d'autant plus gênante que l'on trouve maintenant des machines spécialisées dans le produit matriciel. Pour remédier à cet inconvénient, il faut introduire la notion de coordonnées homogènes qui permettra de résoudre ce problème. Les coordonnées homogènes d'un point de R3 sont constituées par un quadruplet (x,y,z,t). Si t≠0, M de coordonnées (x,y,z,t) est le point de R3 de coordonnées (x/t,y/t,z/t). Si t=0, le point M est un point "à l'infini" et le triplet (x,y,z) s'interprète comme un vecteur. Les quadruplets (x,y,z,t) et (x/t,y/t,z/t,1) représentent le m^me point de R3 si t≠0. Aussi le choix a été, ici, de prendre t=1 pour les points à distance finie de R3. Revenons donc au passage du repère global à un autre repère. Cette opération est décomposée en deux étapes : une translation au nouveau point d'origine puis une rotation autour des axes. La matrice de translation T est : 1 0 0 Ox 0 1 0 Oy 0 0 1 Oz 0 0 0 1 La matrice de rotation R est : UXcam(x) UYcam(x) UZcam(x) 0 UXcam(y) UYcam(y) UZcam(y) 0 UXcam(z) UYcam(z) UZcam(z) 0 0 0 0 1 6 3D et image de synthèse La matrice totale de la caméra est : M = T.R (attention la translation est avant la rotation !!!!!) Note : tous les vecteurs utilisés doivent être unitaires : UXcam, UYcam... Les coordonnées caméra sont alors obtenues en multipliant les coordonnées globales par la matrice caméra. VIII.2.d : Résumé Voici maintenant l'algorithme permettant, à partir d'une caméra et d'une définition d'objet, d'obtenir les coordonnées écran des objets. On possède tout ce qu'il nous faut pour la caméra : point d'origine O, direction et Vup. De même on connaît les coordonnées des sommets des objets dans le repère absolu. Les étapes à faire sont dans l'ordre : • • • • • Obtenir le repère caméra UXcam, UYcam, UZcam Calculer la matrice caméra Pour chaque sommet obtenir les coordonnées dans le repère caméra en multipliant les coordonnées absolues par la matrice caméra Pour chaque groupe de coordonnées obtenues, on divise Xcam et Ycam par Zcam pour obtenir les X et Y Il suffit ensuite de relier les sommets devant l'être Note : Pour l'instant cet algorithme ne gère pas le clipping (le fait qu'un point peut être en dehors du champ de la caméra, hors de l'écran) : si un objet est hors de l'écran, le programme génère une erreur. VIII.3 – Le rendu 3D Le processus de synthèse d'image aboutit à la visualisation de l'image sur un support (écran en général). Pour affronter cette étape de visualisation, il faut nécessairement répondre aux deux questions suivantes : que voit-on ? et quel aspect cela a-t-il ?. L'histoire, bien que récente, de la synthèse d'image se caractérise par une recherche constante vers un plus grand réalisme des images obtenues. On est ainsi passé des images noir et blanc "fil de fer" aux images pouvant utiliser plusieurs millions de couleurs, avec – entre autres – l'élimination des parties cachées, la création d'ombres portées, de textures, d'effets de transparences, de réflexion de réfraction. Les méthodes et les modèles utilisés à cette fin sont très nombreux. Nous n'en présenterons que les principaux, par "réalisme" – et complexité – croissante. VIII.3.a : L'élimination des parties cachées Dans la quête du réalisme en synthèse d'image, ce problème fut le premier a être abordé. Il reçut des solutions dès les années 1960 mais des algorithmes étaient encore publiés récemment. 7 3D et image de synthèse Nous pouvons appréhender la notion de profondeur dans la réalité parce que les objets sont en général opaques et se cachent en partie. En particulier, il serait pratiquement impossible de déterminer les positions relatives des objets si notre univers était totalement transparent. De ce fait, en synthèse d'image, la projection perspective d'un objet en "fil de fer" n'est pas satisfaisante, car elle ne rend pas compte de l'éloignement à l'œil des divers éléments de la scène. La nécessité d'une élimination des parties cachées se fait donc sentir, ne serait-ce que pour pouvoir saisir sans ambiguïté les objets visualisés. La figure 5 illustre une telle ambiguïté. Figure 5 : De l'utilité de l'élimination des parties cachées Les algorithmes d'élimination des parties cachées sont difficiles à classifier. Aussi s'agit-il d'indiquer quels sont les critères qui, appliqués à ces divers algorithmes, peuvent permettre de les différencier et de les comparer. On peut néanmoins considérer qu'éliminer les parties cachés revient approximativement à effectuer un tri géométrique dans l'espace à 3 dimensions entre les divers objets de la scène. L'algorithme le plus célèbre est l'algorithme du peintre. Il est également appelé méthode du tri préalable. Il s'applique à une scène constituée d'objets polyédriques. Son principe est le suivant : • On classe les polygones en fonction de leur distance par rapport à l'observateur (caméra). On trie en fait selon les profondeurs des sommets les plus éloignés de chaque face. Si des facettes se chevauchent, l'algorithme découpe une face par le plan contenant l'autre face (figure 6). • On projette les faces classées sur le plan de l'écran, en commençant par celles qui sont le plus éloignées de l'observateur. Si une face en cache une autre, elles sera ainsi visualisée après, et sa projection recouvrira la projection de l'autre. Figure 6 : Illustration du principe de l'algorithme du peintre 8 3D et image de synthèse L'avantage de cet algorithme est la simplicité de son principe, qui permet une implantation facile. Par contre, la détermination de la visibilité d'une face ne se fait qu'au moment de l'affichage, ce qui impose d'afficher tous les polygones, même ceux non visibles, d'où une perte de temps (figure 7). Figure 7 : Fonctionnement de l'algorithme du peintre sur une scène à 4 objets Il existe d'autres algorithmes, plus adaptés à des problèmes précis, tel l'algorithme des lignes de crête pour la représentation de surface de la forme z=f(x,y). On se réfèrera à la littérature pour les découvrir. VIII.3.b : L'éclairage La production d'images réalistes oblige à donner une impression de volume, de relief aux objets affichés. Ainsi voudrait-on représenter une sphère rouge en coloriant uniformément tous ses pixels, on aurait l'impression non d'une sphère mais d'un disque. Aussi faut-il répartir les intensités de couleur sur la surface, chaque point pouvant avoir une intensité différente. Cette intensité est fonction de la lumière présente dans la scène. Ce paragraphe présente donc - de façon succincte – la manière de synthétiser la lumière et simuler ses effets en informatique graphique. On distingue en général plusieurs types de sources de lumière : • La lumière ambiante, qui éclaire la scène de manière uniforme. • Les sources de lumières directionnelles, qui sont supposées être à l'infini et qui éclairent la scène avec des rayons parallèles à une direction donnée. • Les sources ponctuelles, qui sont supposées placées en un point précis de l'espace objet. Elles peuvent rayonner uniformément dans toutes les directions (cas le plus simple) ou, au contraire, suivre une certaine loi. 9 3D et image de synthèse Réflexion et transmission de la lumière La réflexion peut être spéculaire, avec production de reflets lorsque la surface est brillante (Figure 8a). Dans les autres cas, la réflexion est diffuse (Figure 8b) Figure 8 : Réflexion et transmission La transmission peut également être spéculaire (Figure 8c) dans le cas d'objets transparents ou diffuse (figure 8d) dans le cas d'objets translucides. La réflexion spéculaire suit la loi de Descartes : r=i où r est l'angle entre le vecteur directeur de la source lumineuse L et le vecteur normal N et i celui entre le vecteur normal N et le vecteur réfléchi R. La transmission spéculaire suit la loi de Snell : sin( i) n2 = sin( r) n1 où n1 (resp. n2) est l'indice de réfraction du premier (resp. second) milieu traversé. Les réflexion et transmissions diffuses se font dans toutes les directions suivant une distribution probabiliste qui dépend de la surface. Il existe plusieurs modèles d'éclairage, nous allons commencer par le plus simple : Le Flat Le Flat Le flat est aussi appelé ombrage constant. Le "flat shading" obéit à la loi de Lambert qui spécifie que l'éclairement d'une face est fonction du rayon incident de la lumière par rapport à la normale de la face. En résumé plus la lumière est directe plus la face est éclairée et inversement plus la lumière est tangente à la face moins elle l'éclaire. Ici la normale utilisée sera la normale à la face (et non au sommet comme dans le Gouraud ou le lissage de type Phong que nous verrons plus en avant dans ce cours) 10 3D et image de synthèse La formule utilisée est la suivante : Id = Kd·cos(i) ·Lobj Avec : Id : intensité de diffusion i : angle d'incidence Kd : coefficient de diffusion Lobj : lumière de l'objet (le plus souvent, une lumière blanche) On ajoute à ce terme une composante ambiente : I = Ia·Ka+ Kd·cos(i) ·Lobj ·Cobj Avec : Ia : intensité ambiante Ka : coefficient ambiant (dépend des propriétés de l'objet) Cobj : couleur de l'objet La lumière ambiante est la lumière qui persiste dans une pièce même s'il n'y a aucune lumière. Pour obtenir cos(i), il suffit de normaliser L et N, dans ce cas cos(i)=N·L La méthode à suivre lors du rendu sera donc la suivante : • • • • • Pour chaque point, on calcule le vecteur Lumière L On normalise L, N est à priori normalisé au chargement du fichier On calcule I ( compris entre 0 et 1) pour chaque composante : R, G, B On obtient les valeurs R, G, B en multipliant Ir, Ig, Ib par 255. Attention il faut être sûr que les RGB obtenus ne dépassent par 255 (saturation). Rendu en scanline (remplissage des polygones) La figure 9 illustre le résultat d'une sphère représentée en flat shading. Les coefficients K (ambiant, diffus, spéculaire...) ainsi que les intensités sont toujours limitées entre 0 et 1. Au final on les multiplie par 255 pour obtenir l'index de la couleur. Il se peut que la valeur finale dépasse 255 mais on la limite à 255, on dit alors qu'on "brûle" la couleur; l'image apparaîtra blanc clair saturé. 11 3D et image de synthèse Figure 9 : Illustration du flat shading On voit apparaître sur cette image les défauts du flat shading : • • l'aspect n'est pas lissé l'objet parait mat et sans reflet Le modèle flat est très limité : les faces sont unies et les frontières entre chaque surface ont des couleurs très différente alors qu'un objet passe lentement d'une couleur à une autre. Il existe deux modèles plus perfectionnés pour l'éclairage des facettes : le modèle de Gouraud et le modèle de Phong qui ont tous deux la particularité d'utiliser l'interpolation. L'équation de la lumière Soit I l'intensité en un point (x, y, z) de la face. On devra calculer un I pour chaque composante (IR, IV, IB). On a : I = Ia + Id + Is Ia = Ka ; Id = Kd· (N·L) ·Cobj·Lobj ; Is = Ks· Lobj · (cos(α))n Is est l'intensité due au spéculaire et Ks le coefficient spéculaire. Le spéculaire est une réflexion de lumière due à l'objet, placez par exemple un bout de plastique coloré sous une lampe et vous verrez un point lumineux blanc sur la surface, c'est le spéculaire. La position et l'intensité du spéculaire varie selon la position de l'observateur. α est l'angle entre les vecteurs R (vecteur réflexion de la lumière ) et V (vecteur vision ou œil). Pour obtenir ce cosinus on se sert de la définition du produit scalaire : R⋅V = R ⋅ V ⋅cos(α) R est donné par la formule : L + 2·(N.L)⊗N Enfin n est appelé le coefficient de brillance : plus n est grand et plus l'effet spéculaire sera petit et brillant sur l'objet. 12 3D et image de synthèse Le modèle de Gouraud On s'aperçoit très vite que si on calcule les intensités I pour chaque composante en chaque point, les calculs prennent un certain temps et nécessitent notamment la connaissance de N, R, V et L. De plus il existera des discontinuités entre les facettes, ce qui rend tout objet très polygonal. En 67, une méthode d'ombrage interpolé est inventée par Wylie, Romney, Evans et Erdahl pour les triangles et en 1971 Gouraud étend la méthode à tout type de polygone. La méthode Gouraud est aussi appelée : interpolation des intensités. Le principe est simple : on calcule l'intensité I à tous les sommets de nos objets, lors du scanline d'une face on va évaluer l'intensité du point courant comme une moyenne (selon la position) pondérée des intensités aux sommets. Il y a néanmoins une petite subtilité : afin de lisser les arêtes, on calcule les normales aux sommets comme la moyenne des normales aux faces que partage le sommet. Il sera donc nécessaire de calculer pour chaque sommet la normale associée avant le rendu final. Lors du calcul de I au sommet on prendra en compte la normale au sommet. Le modèle de Gouraud présente néanmoins quelques failles : • • • • Si la partie spéculaire de la lumière est entièrement contenue dans la facette alors elle sera omise (seuls les sommets servent lors de l'interpolation) De même si la partie spéculaire ne touche pas un des sommets elle sera invisible ( non prise en compte par l'interpolation !) L'interpolation est faite sur les coordonnées écran, ce qui peut provoquer des erreurs lors de séquence animées Les objets non lisses paraissent bizarrement lisses ( essayez avec un cube !!!) Le modèle de Phong La méthode Phong diffère du Gouraud sur un seul point : Plutôt que d'interpoler les intensités aux sommets, on va interpoler les normales et calculer en chaque point l'intensité avec cette nouvelle normale. Evidemment le calcul est alors beaucoup plus long... Le Phong n'est pas "parfait". Même en faisant de multiples essais en modifiant les paramètres Kd, Ka, Ks et n, malgré tous les changements l'objet ressemblera à du plastique... On pourra éviter cela en utilisant le mapping de texture, ou des textures procédurales. La figure 10 reprend l'exemple de la sphère précédente et réalise un rendu plus réaliste, en utilisant le modèle de Phong 13 3D et image de synthèse Figure 10 : Rendu par le modèle de Phong VIII.3.c : Le Z-buffer ou tampon de profondeur Développé au départ par Catmull en 1975, le Z-buffer est maintenant aussi connu que le modèle de Phong ou l'interpolation voire le scanline. Le principe du Z-buffer est très simple. Imaginons une image de 640 x 480, lorsque l'on fait un rendu en scanline, on accède aux différents polygones de façon aléatoire, autrement dit il se peut que l'on dessine une surface devant une autre alors qu'elle devrait être cachée. Ceci est dû à l'ordre dans lequel on réalise le "rendu" des différents éléments de la scène. Le problème est donc de savoir quel polygone est devant quel polygone. Une première technique consiste à trier les objets par Z (dans le repère écran ) croissant de leur centre de gravité. Mais cela ne résout que partiellement le problème : on peut très bien avoir un très grand plan horizontal dont le centre de gravité est loin coupé par un plan vertical proche, on ne verra alors que le plan vertical proche... (voir figure 11). Figure 11 : Les problèmes du positionnement relatif de plusieurs polyèdres Le Z-buffer a un principe différent : 14 3D et image de synthèse • • • • on crée un buffer de l'écran qui va stocker les Z (dans le repère écran) des points à l'écran (float z_buf [640][480]) chaque élément du buffer est initialisé à l'infini ( 106 par exemple ) Pour chaque polygone : o lors du calcul d'un point, on interpole sa valeur Z écran (à partir des valeurs Z connues aux sommets) o Si le Z obtenu est inférieur au z_buf[x][y] § alors : z_buf[x][y]= Z <= mise à jour du Z le plus proche trouvé § et écrit le pixel à l'écran o Passer aux points suivants (scanline) Passer aux polygones suivants Pour chaque calcul de point, la valeur Z est interpolée et est comparée à la valeur précédente dans le Z-buffer. Si le point est plus proche que celui actuellement affiché alors on remplace la valeur du Z-buffer et on affiche le pixel. Puis on passe au point suivant (puis au polygone suivant). Dans de nombreuses stations de travail (notamment les machines Silicon Graphics), le ZBuffer existe dans la partie câblée de la carte d'affichage écran. Il n'est pas indispensable que cette fonction soit câblée car elle peut aussi être réalisée de façon logicielle. On notera que depuis 1997, la fonction Z-Buffer est câblée dans toutes les cartes accélératrices 3D dignes de ce nom (3DFx, ATI Rage, etc…) VIII.3.d : Le lancer de rayon Le lancer de rayon ou raytracing en Anglais, est une méthode pour créer des images de synthèse plus proches de la réalité. La philosophie du lancer de rayon est la suivante : le point sur une surface vu par l'observateur est la résultante de tous les rayons de lumière arrivant en ce point. Seuls inconvénients du raytracing : cette méthode est très gourmande en temps de calcul et ne résout pas pour autant le problème de l'illumination globale d'une scène. Le raytracing vise à reproduire le parcours réel d'un rayon de lumière, considérons un point sur un objet : la couleur que nous percevons est due à l'illumination de ce point par tous les rayons provenant des sources lumineuses. Les rayons émanant de sources lumineuses sont appelés rayons de lumière et contribue à l' "illumination directe" d'un point. Mais les rayons directs de lumière ne sont pas les seuls qui contribuent à la couleur, d'autres rayons provenant des autres surfaces d'une scène atteignent le point que nous considérons. Ces rayons indirects sont la réflexion (propriété de miroir des objets) et la transmission aussi appelée réfraction. Ces combinaisons sont résumées par la figure 12. Le trait bleu représente le rayon de l'œil (eye ray) autrement dit la vision de l'observateur (ou de la caméra). Ce dernier fixe un point situé sur un plan incliné. Plusieurs rayons contribuent à la couleur obtenue : • • en rouge, le rayon réfléchi (qui rencontre un nouvel objet). La couleur du point sur la sphère sera donc plus ou moins visible sur le plan incliné (comme un miroir). en vert, le rayon transmis ou réfracté. Ce rayon rencontre une autre sphère, la couleur du point sur la sphère sera donc plus ou moins visible sur le plan incliné (on obtient le 15 3D et image de synthèse • même effet qu'en plaçant une paille dans un verre plein d'eau : l'image de la paille parait déviée) en jaune, tous les rayons de lumière. Figure 12 : Eclairage direct et indirect On observe donc le mécanisme suivant : A chaque intersection avec une surface, un rayon incident (qui arrive sur la surface) se décompose de la façon suivante : • • un rayon réfléchi un rayon transmis La récursivité Dans la réalité, le procédé est répété. En effet tout rayon réfléchi ou transmis possède le comportement d'un rayon "classique". Ainsi si le rayon réfléchi ou transmis rencontre un objet il devient un rayon incident pour cet objet et génère de nouveaux rayons réfléchis et transmis (qui eux - même deviendront des rayons incidents ). Cette situation est résumée par la figure 13 où l'on s'est arrêté à un certain nombre d'itérations. Figure 13 : Réflexion et transmission des rayons de lumière 16 3D et image de synthèse Les rayons rouges partant d'un point sont les rayons réfléchi, les verts sont les rayons transmis. Le raytracing est donc une fonction récursive qui lance des rayons à chaque intersection trouvée. En certains points on pourrait générer des rayons indéfiniment : par exemple lorsqu'un rayon entre dans une sphère il va s'"autoréfléchir" contre les parois indéfiniment. On voit donc qu'il sera nécessaire de limiter la profondeur des rayons (nombre d'itérations du phénomène pour un rayon primaire). Mise en œuvre Un rendu en raytracing consiste à évaluer l'intersection d'un rayon ( une ligne droite ) avec les objets de la scène. Nous considérons ici le plus simple des cas : on ne prend pas en compte les rayons réfléchis et transmis (autrement appelés rayons secondaires), nous ne gardons que les rayons primaires (autrement dit ceux qui partent de l'œil). L'algorithme pour tracer un seul rayon est le suivant : 1. Pour un point (x,y) de l'image voulue générer le rayon : point d'origine et direction 2. Faire l'intersection du rayon avec la scène (parcourir tous les objets et rechercher les intersections, on garde l'intersection la plus proche de l'origine du rayon) 3. Récupérer la couleur de l'objet intercepté 4. Le point (x,y) reçoit la valeur de couleur récupérée (c'est la couleur "vue" par l'œil ) Le problème des faces cachées doit bien évidemment être pris en compte. : il suffit de chercher parmi les intersections possibles celle la plus proche du point d'origine du rayon (Figure 14) Figure 14 : Ray tracing et faces cachées Cette gestion des intersections est assez coûteuse et plus complexe a imposer. Elle n'a pas pour objet de figurer dans ce cours. On se contentera donc de garder le principe et de conserver en mémoire le fait qu'à l'heure actuelle, le lancer de rayons reste la méthode la plus utilisée pour créer les images de synthèse les plus réalistes (Figure 15). 17 3D et image de synthèse Figure 15 : Lancer de rayons. VIII.4 – OpenGL : une bibliothèque 3D de haut niveau VIII.4.a : Introduction OpenGL est une interface logicielle qui se propose de standardiser, dans le monde des applications graphiques, les différentes approches matérielles. Cette interface qui compte environ 120 commandes couvre la création, l'animation et le rendu d'objets 3D avec comme caractéristique principale d'être complètement indépendante du système de fenêtrage. Cette indépendance signifie que le noyau de cette interface ne contient aucune commande pour gérer des actions du genre OpenDisplay, SelectInput, NexEvent. L'ensemble de l'interface s'inscrit dans la philosophie client-serveur où une application dite cliente envoie des requêtes/commandes vers un serveur d'affichage local ou distant. L'indépendance de ces requêtes vis-à-vis du matériel permet d'intégrer clients et serveurs de marques différentes. OpenGL est, sans aucun doute, l'interface de programmation industrielle prédominante pour le développement d'application graphique en 2 et 3D. Elle peut être considérée comme le successeur de la bibliothèque Silicon Graphics IRIS GL, qui a rendu si populaire les stations de travail SGI en tant que plates-formes de prédilection des scientifiques, ingénieurs et pour 18 3D et image de synthèse effets spéciaux. SGI a amené à OpenGL une grande part de son expérience, en faisant une interface de programmation du futur, facile à utiliser, intuitive, portable et ouverte aux réseaux. Par opposition à l'ancienne bibliothèque IRIS GL de SGI, OpenGL est, par conception, indépendante de la machine et du système d'exploitation. Elle reconnaît les réseaux ce qui permet de séparer notre application OpenGL en un serveur et un client qui est chargé d'afficher le graphique. Il existe un protocole de transfert des commandes OpenGL sur le réseau entre serveur et client. Grâce à son indépendance vis à vis du système d'exploitation, serveur et client n'ont pas à fonctionner sur le même type de plate-forme. Assez souvent, le serveur est un super-ordinateur qui exécute une simulation complexe, et le client une simple station de travail dédiée à la visualisation graphique. OpenGL permet aux développeurs d'écrire des applications qui peuvent facilement être réparties sur plusieurs plates-formes. VIII.4.b : L'interface avec le système En dessous d'OpenGL est située une bibliothèque de visualisation graphique très performante et optimisée. Beaucoup de cartes graphiques accélérées et spécialisée en 3D comprennent les primitives OpenGL au niveau matériel. Jusqu'à très récemment, ces cartes graphiques évoluées étaient très chère et disponibles uniquement pour les stations SGI ou UNIX. Les choses changent rapidement et grâce aux licences généreuses et au kit de développement de pilotes de Silicon Graphics, nous allons voir de plus en plus de matériel OpenGL pour les utilisateurs de Macintosh et de PC. Les utilisateurs de Linux peuvent en profiter car 3Dfx Interactive fabrique une série de carte graphiques 3D et fournit un support pour Linux au moyen de la bibliothèque Glide library. Pour obtenir son indépendance matérielle, la spécification d'OpenGL a été écartée de toute dépendance à un système de fenêtrage. L'interface qui en résulte est portable, cohérente et efficace en termes de bibliothèque de tracés 2 et 3D. Il appartient au gestionnaire de fenêtres du système d'ouvrir et de dessiner les fenêtres. La bibliothèque OpenGL communique avec le système au travers de bibliothèques additionnelles auxiliaires. Par exemple, la bibliothèque auxiliaire GLX décrit les interactions entre OpenGL et le système X windows. Heureusement, nous avons GLUT ! La Boite à Outils Utilitaire OpenGL (GLUT) (OpenGL Utility Toolkit) est une interface de programmation entre le C ANSI (par exemple) et OpenGL. Elle a été écrite par Mark J. Kilgard et comble le grand vide laissé par la spécification OpenGL. Grâce aux développeurs de GLUT, nous pouvons maintenant utiliser une interface commune avec les gestionnaires de fenêtres, et ce indépendamment de la plate-forme utilisée. Les applications OpenGL qui utilisent GLUT sont facilement portables sans modification du code source. GLUT simplifie assurément la production de code OpenGL, et elle en complémente la bibliothèque. GLUT est relativement petite et facile à apprendre. L'Interface de Programmation d'Application (API) de GLUT est un automate à états tout comme OpenGL. GLUT a donc un nombre d'états variables vivant pendant l'exécution d'une application. L'état initial de la machine GLUT a été choisi pour s'adapter raisonnablement à la plupart des applications. Le programme peut modifier les valeurs des variables d'état à sa guise. Chaque fois qu'une fonction de GLUT est appelée, son action est modifiée selon les valeurs des variables d'état. Les fonctions GLUT sont simples et prennent peu de paramètres. 19 3D et image de synthèse VIII.4.c : L'initialisation Tout programme OpenGL qui utilise GLUT doit commencer par initialiser la machine d'état GLUT. Les fonctions d'initialisation GLUT sont préfixées par glutInit-. La routine principale d'initialisation est glutInit: glutInit(int **argcp, char **argv); glutInit extrait de la ligne de commande les options adaptées à la bibliothèque GLUT, par exemple : sous l'environnement du système X Windows, toute option adaptée pour X windows et associée à la fenêtre GLUT (geometry, display). glutInitWindowPosition(int x, int **y); glutInitWindowSize(int width, int **height); x,y = position écran en pixels dans la fenêtre window (coin supérieur gauche) width,height en pixels de la fenêtre. glutInitDisplayMode(unsigned int mode); mode est le mode d'affichage, un OU logique bit à bit du masque des modes d'affichage GLUT. Les principales valeurs possibles du masque sont: GLUT_RGBA Sélectionne une fenêtre en mode RGBA (choix par défaut). GLUT_SINGLE Sélectionne une fenêtre simple tampon (choix par défaut). GLUT_DOUBLE Sélectionne une fenêtre double tampon . GLUT_DEPTH Sélectionne une fenêtre avec un tampon de profondeur. int glutCreateWindow(char *nom); nom est le tire de la fenêtre. Cette fonction crée une fenêtre selon les valeurs des initialisations ci-dessus. Elle renvoi un entier unique qui identifie la fenêtre. Un programme GLUT commence donc, par exemple, comme ceci : #include <GL/glut.h> void main(int argcp, char **argv) { /* Initialisation de GLUT */ glutInit(&argcp, argv); /* Taille et emplacement de la fenêtre */ glutInitWindowSize(400, 400); 20 3D et image de synthèse glutInitWindowPosition(50, 50); /* Choix du type et d'affichage et d'un simple buffer */ glutInitDisplayMode(GLUT_RGBA | GLUT_SINGLE); glutCreateWindow(" Test" ) ; .....encore du code }; VIII.4.d : Le traitement des évènements Comme mentionné auparavant, GLUT est une machine d'état, c'est aussi un processeur d'évènements. Il existe une "horloge" ou une boucle continue qui traite, un par un, tous les évènements déclarés à GLUT pendant la phase d'initialisation. Les évènements sont : un clic de souris, une fenêtre fermée ou dimensionnée, un curseur déplacé, une frappe sur une touche et, plus curieux, l'évènement "idle", c'est à dire que rien ne se passe ! Chacun de ces évènements doit être enregistré dans une des variables d'état de GLUT pour que la boucle du processeur de GLUT puisse les interroger périodiquement et déterminer si l'utilisateur les a activés. Par exemple, nous pourrions enregistrer : "cliquez un bouton de la souris", comme évènement à surveiller par GLUT. Les évènements sont enregistrés au moyen de routines de rappel (callback en anglais). Toutes suivent la syntaxe glut[unEvenement]Func. Dans le cas du clic de souris ce sera glutMouseFunc. L'enregistrement d'un callback dit au processeur GLUT quelle fonction, définie par l'utilisateur, doit être appellée quand l'évènement se produit. Ainsi, si j'écris ma propre fonction : MyMouse, qui définit ce qu'il faut faire si le bouton gauche de la souris est cliqué, je dois enregistrer mon callback après glutInit() dans main() en utilisant l'instruction "glutMouseFunc(MyMouse);". Pour finir, et après avoir enregistré tous les évènements de notre application, il faut appeler le processeur d'évènement GLUT, soit la fonction glutMainLoop(). Cette fonction ne se termine jamais, autrement dit, notre programme entre dans une boucle infinie. Elle appellera , tant que nécessaire, toutes les callback que nous avons précédemment enregistrées. La fonction main(), dans une application OpenGL, doit donc se terminer par une instruction glutMainLoop(). Pour tester tout ceci, essayez de compiler l'exemple ci-dessous, qui doit ouvrir une fenêtre et enregistrer le callback Clic Gauche, ainsi que celui de la touche ESC pour quitter l'application. Bon voilà pour cette première partie. Nous verrons au fur et à mesure les différents callback existants, la création de menus sans oublier les instructions OpenGL ! #include <sdtio.h> #include <stdlib.h> #include <GL/glut.h> void affichage(void) 21 3D et image de synthèse { glClear ( GL_COLOR_BUFFER_BIT) ; /*Oups ! une instruction OpenGL*/ } void clavier ( unsigned char key, int x, int y ) { switch ( key ) { case 27: /* ESC */ case 'q': case 'Q': exit (0); break ; } } void souris(int button, int state, int x, int y) { switch(button) { case GLUT_LEFT_BUTTON: if (state == GLUT_DOWN) printf("Clic gauche OK\n") ; break; } } void main(int argcp, char **argv) { /* Initialisation de GLUT */ glutInit(&argcp, argv); /* Taille et emplacement de la fenetre */ glutInitWindowSize(400, 400); glutInitWindowPosition(50, 50); /* Choix du type et d'affichage et d'un simple buffer */ glutInitDisplayMode(GLUT_RGBA | GLUT_SINGLE); /* Creation de la fenetre */ glutCreateWindow("Test") ; /* Association des callback */ glutDisplayFunc(affichage); glutMouseFunc(souris); glutKeyboardFunc(clavier); /* On entre dans la boucle d'evenements */ glutMainLoop() ; } VIII.4.e : Les primitives OpenGL n'a que quelques primitives géométriques : points, lignes, polygones. Tous sont décrits par des vecteurs. Un vecteur est caractérisé par 2 ou 3 points réels, les coordonnées cartésiennes du vecteur, (x,y) en 2D et (x,y,z) en 3D. Il existe bien sur également le système homogène de coordonnées dans lequel chaque point est décrit par 4 nombres réels (x,y,z,w). Les points Puisque avec OpenGL, tous les objets géométriques sont décrit en termes de vecteurs ordonnés, il y a une famille de fonctions pour déclarer un vecteur. Sa syntaxe est : void glVertex{234}{sifd}[v](TYPE coords); 22 3D et image de synthèse La notation devra vous devenir familière. Les accolades indiquent une partie du nom de la fonction. Cette dernière peut prendre 2,3 ou 4 paramètres soit de type entier, réels ou doubles. Ces paramètres peuvent être fournis optionnellement sous forme de vecteurs, auquel cas, le nom de la fonction comportera un v. Voici quelques exemples : void glVertex2s(1, 3); void glVertex2i(23L, 43L); void glVertex3f(1.0F, 1.0F, 5.87220972F); float vector[3]; void glVertex3fv(vector); A titre de simplification, toutes ces fonctions sont appellées glVertex*. OpenGL interprète une séquence quelconque de vecteurs selon son contexte. Le contexte est déclaré par la paire de fonctions glBegin(GLenum mode) et glEnd(). Toute instruction glVertex* exécutée entre les deux est interprétée en fonction de la valeur de mode, par exemple: glBegin(GL_POINTS); glVertex2f(0.0, glVertex2f(1.0, glVertex2f(0.0, glVertex2f(1.0, glVertex2f(0.5, glEnd(); 0.0); 0.0); 1.0); 1.0); 0.5); dessine 5 points en 2D avec les coordonnées spécifiées. GL_POINTS est une des constantes définies dans le fichier d'entête <GL/gl.h>. Il existe de nombreux autres modes disponibles mais nous les décrirons au fur et à mesure des besoins. Chaque point est tracé avec la couleur mémorisée par la variable d'état OpenGL associée au tampon des couleurs. Pour changer de couleur, on utilise la famille de fonctions glColor*; il y a beaucoup à dire sur la sélection et la manipulation des couleurs. Pour l'instant, nous utiliserons 3 nombres réels variants de 0.0 à 1.0. C'est le codage RVB (Rouge-Vert-Bleu): glColor3f(1.0, 1.0, 1.0); /* White */ glColor3f(1.0, 0.0, 0.0); /* Red */ glColor3f(1.0, 1.0, 0.0); /* Yellow */ etc... La taille d'un point peut-être spécifié en pixels en utilisant glPointSize: void glPointSize(GLfloat size) Par défaut la taille d'un point est de 1.0 et elle est toujours supérieure à zéro. Vous remarquerez que la taille d'un point est donnée par un nombre réel; les portions de point et de ligne sont autorisées. OpenGL interprète les portions de point en fonction du contexte de rendu. Si l'anti-crénelage est actif alors OpenGL modifie les pixels voisins de la ligne pour lui donner l'apparence de la largeur spécifiée. L'anti-crénelage est une technique visant à éliminer les gros carrés que l'on voit lorsque l'on dessine des lignes à basse résolution. Si l'anticrénelage n'est pas actif, glPointSize arrondira la taille à l'entier le plus proche. 23 3D et image de synthèse La largeur des lignes est spécifiée par la fonction glLineWidth qui doit être appelée avant le bloc glBegin() - glEnd() qui dessine la (les) ligne(s). Voici la syntaxe complète de la fonction : void glLineWidth(GLfloat width) Il se peut que l'implémentation d'OpenGL limite la largeur des lignes sans anti-crénelage à la valeur maximale de la largeur avec anti-crénelage (arrondi à l'entier le plus proche). Gardez également en mémoire que la largeur d'une ligne n'est pas mesurée perpendiculairement à celle-ci, mais dans la direction des Y si la valeur absolue de la pente est inférieure à 1, dans la direction des X dans le cas contraire. Juste pour information et parce que nous venons d'aborder le sujet, l'anti-crénelage s'active par l'instruction : glEnable (GL_LINE_SMOOTH); et se désactive par l'instruction : glDisable (GL_LINE_SMOOTH); Nous allons compléter notre exemple afin d'obtenir quelques points à l'écran (voir à la fin). Remarquez la fonction d'initialisation repere(). Elle utilise une instruction de la bibliothèque d'Utilitaires OpenGL (GLU) : gluOrtho2D(). Cette fonction établit un système de coordonnées 2D orthogonal. Les paramètres sont "x minimum, x maximum, y minimum, y maximum". Les polygones Nous avons déjà mentionné que glBegin(GLenum mode) accepte divers modes et que les séquences de vecteurs v0, v1,v2, v3,v4,... vn-1 déclarés à posteriori sont interprétés en conséquence. Les valeurs possibles du mode et les actions associées sont : GL_POINTS Trace un point pour chacun des n vecteurs. GL_LINES Trace une série de lignes non connectées. Les segments sont tracés entre v0 et v1, v2 et v3,...etc. Si n est impair vn-1 est ignoré. GL_POLYGON Trace un polygone avec v0, v1,..,vn-1 comme vecteurs. n doit être au moins égal à 3 ou rien n'est dessiné. De plus, le polygone ne peut se croiser lui et doit être convexe à cause de limitations (dues aux algorithmes). GL_TRIANGLES Trace une série de triangles avec les vecteurs v0, v1 et v2, puis v3, v4 et v5 etc. Si n n'est pas un multiple de 3, les points restants sont ignorés. GL_LINE_STRIP Trace une ligne de v0 à v1, puis de v1 à v2 et ainsi de suite. Finalement de vn-2 à vn-1 pour un total de n-1 segments. Il n'y a pas de restrictions sur les vecteurs décrivant les tronçons de lignes, les lignes peuvent arbitrairement se croiser. GL_LINE_LOOP Identique à GL_LINE_STRIP excepté qu'un segment final est dessiné de vn-1 à v0, pour fermer la boucle. GL_QUADS Dessine une série de quadrilatères avec les vecteurs v0, v1, v2, v3 et v4, v5, v6, v7 et ainsi de suite. GL_QUAD_STRIP Dessine une série de quadrilatères avec les vecteurs v0, v1, v3, v2 puis v2, v3, v5, v4 et ainsi de suite. 24 3D et image de synthèse GL_TRIANGLE_STRIP Dessine une série de triangles avec les vecteurs v0, v1, v2, puis v2, v1, v3, puis v2, v3, v4, etc. L'ordre permet de s'assurer que les triangles ont l'orientation correcte et l'a bande peut être utilisée pour former une partie de la surface GL_TRIANGLE_FAN Similaire à GL_TRIANGLE_STRIP excepté que les triangles sont v0, v1, v2, puis v0, v2, v3, puis v0, v3, v4, et ainsi de suite. . Tous les triangles ont v0 en commun. #include <stdio.h> #include <stdlib.h> #include <GL/glut.h> #define NB_POINTS 20 /* 20 points au plus */ int points[NB_POINTS][2] ; /* pour stocker les points */ int n=0 ; /* le nombre effectif de points */ void initPoints( void ) { int i ; for (i=0 ; i<NB_POINTS ; i++) points[i][0]= points[i][1]= 15*i ; n= NB_POINTS ; } void affichage ( void ) { int i ; glClear ( GL_COLOR_BUFFER_BIT) ; /* Efface la fenetre */ glPointSize(5.0) ; glBegin(GL_POINTS); for (i=0;i<n;i++) glVertex2i(points[i][0],points[i][1]) ; /*definition points*/ glEnd(); glFlush() ; /* lance l#affichage pour un simple buffer */ } des void repere ( ) { gluOrtho2D(0, 400, 0, 400) ; /* definit un repere orthogonal*/ }/* les valeurs donnees sont x minimum, x maximum, y minimum, y maximum */ void clavier ( unsigned char key, int x, int y ) { switch ( key ) { case 27: /* ESC */ case 'q': case 'Q': exit (0); break ; } } void souris(int button, int state, int x, int y) { switch(button) { case GLUT_LEFT_BUTTON: if (state == GLUT_DOWN) printf("Clic gauche OK\n") ; break; } } void main(int argcp, char **argv) 25 3D et image de synthèse { initPoints(); /* remplissage du tableau de points */ glutInit(&argcp, argv); /*Initialisation de GLUT*/ glutInitWindowSize(400, 400); /*Taille et emplacement de la fenetre*/ glutInitWindowPosition(50, 50); glutInitDisplayMode(GLUT_RGBA | GLUT_SINGLE); /*Choix mode d#affichage*/ glutCreateWindow("Test") ; /*Creation de la fenetre*/ glutDisplayFunc(affichage); /*Association des callback*/ glutMouseFunc(souris); glutKeyboardFunc(clavier); glutReshapeFunc(repere) ; glutMainLoop() ; /*On entre dans la boucle d#evenements*/ } VIII.4.f : Les transformations géométriques La translation void glTranslated (int x, int y, int z) void glTranslatef (float x, float y, float z) ... translation de vecteur (x, y, z) ... La rotation void glRotated (int angle, int x, int y, int z) void glRotatef (float angle, float x, float y, float z) ... rotation d'angle angle (en degré) autour du vecteur (x, y, z) ... par exemple : glRotated (180, 1, 0, 0) ; crée la matrice de rotation d'angle 180° autour de l'axe (Ox.). void glScaled (int x, int y, int z) - void glScalef (float x, float y, float z) ... mise à l'echelle selon les facteurs x, y, z sur les axes (Ox), (Oy), (Oz) ... VIII.4.g : Projection en perspective de la scène void gluPerspective( Gldouble fovy, Gldouble aspect, Gldouble near, Gldouble far) Cette fonction crée la matrice de projection. Elle remplace la projection orthogonale de notre exemple de tracé en deux dimensions 26 3D et image de synthèse VIII.4.h : Gestion de l'état des variables OpenGL void glEnable (Glenum cap) - void glDisable (Glenum cap) glEnable active une fonctionnalité, glDisable la désactive. Il existe pas moins de 40 valeurs possibles pour le paramètre cap. Certaines sont : GL_DEPTH_TEST (qui contrôle le rendu 3D), GL_LIGHTING (qui contrôle l'éclairage), GL_LIGHTi (qui contrôle la lampe i) ... VIII.4.i : L'exemple de la théière #include <GL/glut.h> #include <stdlib.h> void init(void) { glEnable(GL_DEPTH_TEST); /* on active le z-buffer */ glEnable(GL_LIGHT0); /* on active l'eclairage par defaut */ glEnable(GL_LIGHTING); } void display(void) { glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /* on efface aussi le z-buffer */ glTranslatef(0.,0.,-5.); /* on amene l'objet dans le volume de vision */ glutSolidTeapot(1.0); glutSwapBuffers(); } void reshape (int w, int h) { glViewport(0, 0, w, h); /*l'image prend toute la fenetre */ glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(45., (float) w/h, 1., 10.); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } void keyboard(unsigned char key, int x, int y) { switch (key) { case 27: exit(0); break; } 27 3D et image de synthèse } int main(int argc, char** argv) { glutInit(&argc, argv); /* ouverture de la fenetre avec tampon de profondeur (Z-Buffer) => GLUT_DEPTH */ glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutInitWindowSize(500, 500); glutCreateWindow(argv[0]); init(); glutDisplayFunc(display); glutReshapeFunc(reshape); glutKeyboardFunc(keyboard); glutMainLoop(); return 0; } 28
Documents pareils
Chapitre II : Infographie et bibliothèques graphiques
(généralement tridimensionnel) à l'espace de l'écran. Non seulement ce dernier est à deux dimensions, mais les constructeurs de matériel graphique se sont ingéniés à définir des espaces d'adresses ...
Plus en détail