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

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