OpenGL: principe et astuces
Ce document est issu du groupe de travail iMAGIS du 10/07/98.
Il est destiné à donner à la fois des pistes au débutant en 3D
et des idées et astuces au programmeur plus confirmé.
Il n'est cependant pas complet: le principe consiste plutôt
à donner la description des fonctions essentielles,
après quoi il faut tirer les fils dans le man pour savoir
tout le reste.
D'autre part, ce document trahit mes propres limites:
je connais davantage le `vieux gl' qu'OpenGL, et je ne connais
pas tout loin s'en faut, donc il peut y avoir des erreurs.
Les lecteurs sont inviter à en faire part, et à signaler
les parties qui demandent éclaircissement ou extension.
Fabrice Neyret, le 30/07/98
Voir aussi:
utilisation des
NVidia GeForce sous OpenGL, et références et
astuces.
Voir aussi: le tutoriel OpenGL d'Antoine Bouthors.
Sommaire
Organisation d'ensemble: les 3 moteurs
OpenGL est une librairie graphique 3D.
Cela signifie qu'on lui donne des ordres de tracé
de primitives graphiques (facettes, etc) directement en 3D,
une position de caméra, des lumières, des textures à plaquer
sur les surfaces, etc,
et qu'à partir de là OpenGL se charge de faire les changements
de repère, la projection en perspective à l'écran, le clipping,
l'élimination des parties cachées,
d'interpoler les couleurs, et de rasteriser (tracer
ligne à ligne) les faces pour en faire
des pixels.
OpenGL s'appuie sur le hardware disponible selon la carte graphique.
Toutes les opérations de base sont a priori accessibles
sur toute machine, simplement elles iront plus ou moins vite
selon qu'elles sont implémentées en hard ou pas
(pour les fonctions évoluées qui ne font pas partie de la norme OpenGL
mais figurent parmi les extensions, il se peut par contre
qu'elles ne soient disponibles que sur certaines machines).
La machine OpenGL (graphic engine) se décompose en plusieurs moteurs:
le geometric engine s'occupe de la partie purement 3D:
triangulation des polygones, changements de repère,
projection en perspective à l'écran, clipping (élagage
de ce qui sort de l'écran).
le raster engine prend en charge la partie 2D:
il rasterise les triangles de manière à produire des pixels
(que l'on nomme fragments tant qu'ils ne sont pas
véritablement inscrits à l'écran),
interpole au passage les couleurs et les coordonnées texture,
puis élimine les parties cachées par Z-buffer:
en fonction de la profondeur z du fragment en cours de tracé et
du z actuellement stocké dans le pixel visé, le pixel est ou non
modifié (i.e. sa couleur et son z).
Ceci constitue le schéma de base, on verra par la suite qu'OpenGL
offre de nombreux mécanismes supplémentaires et moyens de contrôle.
le raster manager, à qui le pixel est confié pour être
tracé à l'écran après d'éventuels traitements supplémentaires
(dithering, etc).
Ouvrir une fenêtre
A la différence du `vieux GL', OpenGL ne s'occupe pas
de l'interface graphique 2D, donc en particulier ni de l'ouverture
des fenêtres, ni de la gestion des évènements (e.g. clicks de souris).
Ces tâches dépendant de l'OS et de la machine sont laissées à
d'autres librairies.
Comme il faut bien ouvrir une fenêtre pour dessiner,
j'en dis néanmoins quelques mots ici:
Plusieurs librairies sont disponibles pour faciliter
la gestion de l'interface sans descendre dans les bas-fonds
de X-window. GLUT en est une simple, fournie avec OpenGL (et elle-même
très portable).
On inclut alors dans le main() les lignes suivantes:
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
glutInitWindowSize(largeur,hauteur);
glutCreateWindow(titre);
... initialisations ...
glutMainLoop();
Parmi les initialisations, on positionne les flags utiles,
et on indique éventuellement les fonctions chargées de gérer
les évènements avec glutMouseFunc(), glutMotionFunc(),
glutKeyboardFunc(), glutCreateMenu().
Se ramener au man de GLUT pour les détails.
Contenu d'un pixel
En mode true colors,
la couleur se code avec 3 octets représentant les composantes rouge,
verte et bleue. Mais les pixels peuvent contenir bien plus d'information
au delà de la simple couleur: on utilise généralement aussi une
profondeur z (utilisée pour le Z-buffer) et une opacité
alpha (d'où la notation RGBA).
De plus, ces valeurs sont doublées en mode double buffer,
lequel permet de faire des animations fluides en laissant visible
l'image précédente pendant que l'on trace la nouvelle
dans le back-buffer.
Comme ces informations diverses réfèrent aux mêmes pixels,
on parle de `plans', superposés comme des calques.
On dispose également d'un plan de pop-up qui permet de tracer
des menus ou des pointeurs sans altérer le contenu de l'image,
de plans de stencil dont nous parlerons plus loin
qui permettent de masquer certaines parties de l'image,
voire d'une image de fond pour les machines qui ont assez de mémoire.
Le nombre de bits alloué aux divers plans est paramètrable,
la quantité disponible dépendant de la mémoire de la carte graphique.
La commande shell privilégiée Xscreen
permet de modifier la configuration.
Par défaut, sur les machines à peu de mémoire, passer en double buffer
impose de prendre sur les autres plans, notamment alpha et z.
Sur O2, il est recommandé de changer cette valeur par défaut inutilement
économe pour le mode complet 32+32; pensez a vérifier si ça a été
fait sur votre station.
Description d'une primitive géométrique
Toute primitive surfacique 3D est décomposée en triangles par OpenGL.
Le triangle, la ligne et le point
sont donc les seules primitives géométriques traitées par le hardware,
ce qui ramène toutes les interpolations au cas linéaire
(facile à traiter de façon incrémentale en hard, d'où
cette limitation aux triangles, mais qui peut produire un
aspect légèrement `anguleux').
On spécifie un triangle de la façon suivante:
glBegin(GL_TRIANGLES);
glVertex3f(x1,y1,z1);
glVertex3f(x2,y2,z2);
glVertex3f(x3,y3,z3);
glEnd();
OpenGL permet de spécifier des primitives plus complexes, qui
seront décomposées: quadrilatères avec GL_QUADS, polygones avec GL_POLYGON.
On peut également ne tracer que les sommets (GL_POINTS), ou que les contours
(GL_LINE_LOOP).
NB: on gagne à indiquer à OpenGL qu'il peut éliminer les
`faces arrières' (i.e. que l'on voit de dos), avec
glEnable(GL_CULL_FACE), sauf si bien sûr on doit vraiment
les voir (transparence, facettes à deux faces type feuilles d'arbre).
Comme pour tous les attributs, l'indication reste valable jusqu'à nouvel ordre.
Noter que la syntaxe de glVertex(), comme celle de tous les
attributs (couleur, normale, etc), est polyforme:
on peut fournir 2 à 4 composantes (la 4ième correspond à la coordonnée
homogène pour des positions, à l'opacité alpha pour des couleurs,
et on peut se passer de la 3ième coordonnée pour les positions
si l'on fait des tracés 2D, i.e. dans un plan parallèle à l'écran);
on peut utiliser des floats, des doubles, des shorts, des ints
pour les coordonnées (suffixe f,d,s ou i);
on peut citer explicitement les coordonnées, ou passer
par un vecteur (sur-suffixe v).
Primitives non géométriques
A côté des primitives géométriques, OpenGL permet
de manipuler des blocks de pixels, notamment à l'aide
de la fonction glDrawPixels(). On peut également
récupérer en mémoire de tels blocks avec glReadPixels(),
ou les recopier d'une zone de l'écran à une autre
avec glCopyPixels().
Les divers repères
Plusieurs repères
interviennent dans la description d'une scène;
on ne donne pas toutes les coordonnées dans un unique repère monde.
Ceci permet de changer facilement de point de vue sans modifier
la description des objets, ou de changer la position des lumières,
ou encore de modifier l'orientation des objets.
Ceci permet en outre de réutiliser facilement des descriptions
de parties d'objets par simple changement de repère.
Pour positionner un objet, on passe par la matrice MODELVIEW:
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(tx,ty,tz);
glRotatef(a,axex,axey,axez);
on peut alors effectuer le tracé dans un repère local,
et les coordonnées subiront ensuite la pile de transformations
indiquée (en remontant l'ordre des déclarations).
Cette pile est formelle, car en fait on construit une matrice 4x4 unique
au fur et à mesure, laquelle s'applique ensuite aux coordonnées.
Il existe cependant bien une pile:
à tout moment on peut faire glPushMatrix()
ou glPopMatrix() pour effectuer une transformation temporaire.
Ainsi si l'on s'est placé dans le repère local à un
bonhomme, et que l'on sait dessiner un bras dans un repère local,
on pourra procéder comme suit:
tracé_du_tronc();
glPushMatrix();
glRotatef(40.,0,0,1); /* le gauche */
tracé_du_bras();
glPopMatrix();
glPushMatrix();
glRotatef(-40.,0,0,1); /* le droit */
tracé_du_bras();
glPopMatrix();
Pour décrire la caméra, on passe par la matrice GL_PROJECTION.
On y décrit tout d'abord le type de projection:
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-1.,1.,-1.,1.,near,far); /*si proj orthographique */
gluPerspective(45.,1.,near,far); /*si proj perspective */
Pour la projection orthographique on indique le domaine [xmin,xmax]x[ymin,ymax]
des coordonnées visibles, pour la projection perspective on donne l'angle
d'ouverture et le ratio largeur/hauteur; dans les deux cas on précise le
domaine de validité de la profondeur z (on verra plus tard que ça a
une importance sur la précision).
Puis on peut orienter la caméra soit en visant un point avec
gluLookAt(position,visée,haut),
soit en procédant par rotations et translations.
Voir le manuel de ces fonctions ou les exemples donnés plus loin
pour les détails de mise en oeuvre.
Pour déplacer les lumières, on utilise la matrice MODELVIEW
comme pour un objet. Le point indiqué à glLightf()
est transformé au moment où l'on appelle cette fonction.
On peut alors réinitialiser MODELVIEW et s'en servir
pour positionner les objets.
On verra bientôt que l'on dispose également d'une matrice
GL_TEXTURE pour les textures,
qui permet notamment de calculer des textures projetées.
Selon le même principe que précédemment, on
stocke alors comme valeurs `locales' dans les coordonnées texture
(u,v,s,t) les coordonnées du point 3D qui se projette là,
et la matrice décrit la transformation (par exemple projection
orthographique ou perspective).
Attributs d'une face
Il existe plusieurs façons de spécifier l'apparence d'une facette:
couleur de la face: on utilise glColor3f(r,g,b).
On peut également préciser l'opacité en ajoutant un paramètre alpha.
(A noter que coder la transparence par une seule valeur et non
par un triplet Ar,Ag,Ab est assez pauvre: un objet rouge derrière un
transparent vert apparaît bien atténué, mais toujours rouge !
On verra en traitant du mélange des pixels des moyens de faire mieux,
qui sont cependant peu utilisés).
On gagne à préciser au système qu'il est inutile d'interpoler,
avec glShadeModel(GL_FLAT).
couleur aux sommets, à interpoler sur la face:
on redéfinit simplement cette couleur avec glColor()
pour chaque sommet, juste avant le glVertex().
Mieux vaut repréciser auparavant avec glShadeModel(GL_SMOOTH)
que l'on souhaite interpoler les valeurs.
modèle d'illumination (Phong) en fonction de
l'orientation par rapport aux lumières. Un tel modèle
nécessite la définition d'une matière composée d'une
couleur ambiante (celle qui apparaît dans l'ombre),
une couleur diffuse (celle qui apparaît du côté éclairé, souvent la même),
une couleur spéculaire (celle des reflets, blanche pour du plastique),
et un coefficient de rugosité (qui contrôle l'épaisseur de la tache spéculaire).
static GLfloat ambient[] = { 0.1, 0.1, 0.1, 1.0 };
static GLfloat diffuse[] = { 0.8, 0.4, 0.4, 1.0 };
static GLfloat specular[] = { 0.8, 0.8, 0.8, 1.0 };
static GLfloat shininess = 50.;
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambient);
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse);
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular);
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess);
glEnable(GL_LIGHTING);
Comme le suggère le flag GL_FRONT_AND_BACK, on peut
définir une matière différente sur les deux faces.
(A noter que l'on peut aussi définir une couleur d'émissivité
avec le flag GL_EMISSION).
On peut alors mettre en place jusqu'à 8 lumières,
dont on peut spécifier de nombreux attributs par
glLightfv(GL_LIGHT0, attribut, vecteur):
tout d'abord la position (GL_POSITION) et la couleur (dont on donne
séparement les composantes ambiante, diffuse et spéculaire,
ce qui permet de faire des choses peu physiques,
comme des sources qui ne génèrent pas de reflets, ou que des reflets,
ou qui ne contrôlent que la lumière ambiante),
mais aussi l'atténuation en fonction de la distance, et de l'angle
(pour un spot).
Une lumière est activée par glEnable(GL_LIGHT0).
L'illumination tient compte de la direction de la lumière et de l'observateur
par rapport à l'orientation de la facette, il faut donc
préciser cette dernière via glNormal() à chaque sommet,
avant le glVertex().
On peut aussi laisser OpenGL estimer tout seul les normales
par glEnable(GL_AUTO_NORMAL), mais c'est coûteux et moins précis.
On peut également lui demander de normaliser les normales
par glEnable(GL_NORMALIZE), ce qui est notamment utile
quand on spécifie une déformation de l'objet (ne serait-ce qu'un scaling),
mais il faut bien comprendre le fait que cela entraîne un calcul supplémentaire
par sommet, lequel calcul inclut l'évaluation d'une racine carrée,
sachant que chaque sommet est redéfini pour toutes les faces auxquelles
il appartient (en général 6).
Texture plaquée sur la face.
On décrira plus loin les diverses modalités qui contrôlent les textures.
De toutes façons on sépare la description du motif
de la spécification de son plaquage, lequel est donné en fixant les
valeurs des coordonnées texture (u,v)
à l'aide de glTexCoord2f(u,v)
à chaque sommet, avant le glVertex().
On fait ainsi la correspondance entre la position sur la `tapisserie'
et la position dans le monde 3D (ce qui suggère tous les problèmes
qui peuvent apparaître dans les coins, pour les surfaces non développables,
etc). On verra plus tard que l'on peut donner jusqu'à 4 coordonnées
de textures, ce qui s'utilise dans diverses circonstances (textures projetées,
textures d'environnement, textures solides...).
Exemples de programmes OpenGL+GLUT
exemple simple appli OpenGL animé
(92 lignes).
squelette simple appli interactive OpenGL+GLUT
(172 lignes).
squelette complet appli interactive OpenGL+GLUT
(300 lignes).
Mélange des attributs d'une face
On souhaite pouvoir combiner ces divers modes, notamment en tenant compte
de la transparence que l'on a spécifiée au niveau de la face,
des sommets, ou des pixels de texture.
OpenGL offre de nombreuses modalités de combinaison de ces attributs.
Il faut bien voir que l'on est ici à moitié dans les territoires
du geometric engine et du raster engine:
le premier choisi les valeurs qu'il faut stocker aux sommets
et fait les transformations nécessaires (ainsi l'illumination n'est
évaluée qu'aux sommets, et constitue alors simplement une couleur
à interpoler sur la face comme dans le cas le plus simple;
de même la machine peut calculer à ce stade les (u,v) aux sommets
pour simuler le reflet de l'environnement),
le second interpole les attributs retenus et en fait le mélange
comme spécifié pour produire les fragments.
Couleur et matière
On peut utiliser la couleur de la face ou la couleur
interpolée à partie des sommets pour moduler l'illumination du matériau.
Pour cela, on indique lequel des paramètres du modèle d'illumination
est contrôlé par la couleur de la surface à l'aide de
glColorMaterial(GL_FRONT_AND_BACK,param).
NB: le paramètre spécial GL_AMBIENT_AND_DIFFUSE
permet de faire varier ces deux composantes en même temps.
Couleur et texture
On passe par la fonction
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,mode).
On considère que la texture écrase toute autre couleur
si le mode vaut GL_REPLACE,
qu'elle correspond à un vernis coloré
qui altère la couleur de la surface (GL_BLEND),
qu'elle indique la lumière et l'ombre qui atteint la face en chaque point
(GL_MODULATE),
qu'elle contient une couleur qui s'additionne à celle de la face
(GL_ADD),
ou encore qu'elle contient une couleur dont l'opacité alpha contrôle
ce qu'il apparaît de la couleur de la face (GL_DECAL).
Il existe d'autres attributs de contrôle (notamment une couleur constante),
et de nombreuses variantes selon le nombre de plans fournis dans la texture,
il faut donc se référer au manuel pour plus de détails.
On voit ainsi que l'on peut se servir des textures
pour creuser une surface (via la transparence),
ou pour donner l'apparence d'une illumination détaillée.
En fait avec un peu de travail on peut même s'en servir
pour obtenir des ombres (textures en Z) et simuler du relief (bump)
comme on le verra plus loin.
Limitations
A ce stade, on tombe sur deux limitations conceptuelles d'OpenGL:
même s'il y a de nombreuses combinaisons possibles entre
couleur de la surface et de la texture, on ne peut
utiliser qu'une texture à la fois.
C'est une contrainte très dure, qui force à exécuter plusieurs passes
pour construire des images complexes (on parlera plus loin du multipass).
le Z-buffer s'accommode très mal d'une transparence:
même si plusieurs couches transparentes ont contribué à la couleur
d'un pixel, on ne peut mémoriser que le z d'une seule d'entre elles.
Il faut alors prendre garde à ne tracer ces couches
que de l'arrière vers l'avant où de l'avant vers l'arrière (et
indiquer ce choix à la machine, voir plus bas),
ce qui peut nécessiter de procéder
à un tri des faces, lequel peut être coûteux.
Faute de quoi le raster engine
n'a aucun moyen de savoir comment s'intercale
la nouvelle face transparente parmi les anciennes,
et il fera un choix arbitraire, généralement en considérant
le z comme si la face était opaque:
si le z du fragment est plus grand que celui en place
dans le pixel on ne fait rien,
sinon on trace le pixel en combinant les couleurs (en tenant compte
de la transparence, via une opération de blend dont on parlera plus
loin), et on remplace l'ancien z par le nouveau.
C'est souvent très fâcheux, notamment quand on utilise un texture
d'opacité pour creuser une surface: du point de vue du raster engine,
tous les fragments du triangle existent, et ont une couleur et un z
auxquels le traitement standard du Z-buffer s'applique,
même s'il se trouve que certains fragments sont totalement transparents
(et se retrouvent néanmoins masquant).
Un palliatif commode est fourni par OpenGL avec la fonction
glAlphaFunc(GL_GREATER,0.), qui permet de décréter un seuil
sur l'opacité en dessous duquel le tracé du fragment courant est abandonné.
Si l'on utilise que des transparences en 0 ou 1, on obtient
alors toujours un résultat correct sans précaution particulière.
D'autre part, le codage des valeurs sur un certain nombre de bits
engendre plusieurs limites pratiques:
Selon la mémoire disponible, les z sont stockés sur 16 ou 24 bits.
Ceci engendre des artefacts dès que 2 facettes sont proches,
où des pixels qui ne devraient pas apparaissent au devant,
ou clignotent lors de l'animation.
Pour profiter au mieux de la résolution en z, il faut impérativement
tenir compte de la dynamique de cette valeur dans la scène, ce qui se fait
en positionnant au mieux les plans de clipping avant et arrière
qui définissent l'intervalle des z,
que l'on précise dans glOrtho() ou gluPerspective().
NB: le codage des z n'est pas linéaire,
ce qui permet de bénéficier de plus de précision
en z pour les objets proches.
A noter que lorsqu'on modifie des flags (e.g. attribut rendu, mode de texture),
on est jamais certain que c'est exactement le même z qui sera tracé,
ce qui peut parfois entraîner des problèmes
(e.g. lors du tracé d'une ligne sur un plan).
La fonction glPolygonOffset() est un hack qui permet
de résoudre certains d'entre eux, comme le tracé de lignes sur une surface.
les valeurs (couleur, transparence)
qui sortent de l'intervalle [0,1] sont `clampées'
au moment d'être stockées (i.e. les valeurs sortant de l'intervalle sont
ramenées à la borne la plus proche),
mais pas forcément pendant les traitements.
- Exemples de cas défavorables:
si un des sommets est à l'ombre (N.L<0) et les deux autres
à la lumière, le dégradé commencera néanmoins dès le sommet, car
la valeur négative a été ramenée à 0. Si l'on veut amplifier la modulation d'une
texture sombre par l'utilisation de couleurs aux sommets supérieures à 1,
ça ne marche pas non plus car la valeur est clampée à 1.
- Exemples de cas favorables:
on peut utiliser des sources de lumières dont l'intensité est quelconque,
voire négative; le clamping n'intervient qu'au niveau de l'intensité
totale stockée à chaque sommet. L'accumulation buffer (cf plus bas)
dispose d'une résolution plus importante pour stocker les valeurs
(car utiliser de faibles pondérations entraîne une perte de précision).
Mélange des pixels
Il s'agit ici de préciser comment la couleur d'un fragment
(une fois que l'on sait qu'il doit être tracé)
se mélange
à celle du pixel déjà en place,
notamment pour tenir compte
de la transparence; on est donc purement dans le
territoire du raster engine.
Quand on ajoute une couche colorée transparente C2=RGBA2 devant
une couche C1=RGBA1 (l'image déjà présente à l'écran),
la couleur résultante est C2*A2+C1*(1-A2),
et si on l'ajoute derrière, C2*(1-A1)+C1
(à noter que dans le premier cas, on a pas besoin de stocker véritablement
les opacités dans le buffer d'image, alors qu'il le faut
dans le second cas).
OpenGL offre en fait un contrôle plus souple du mélange:
on spécifie un coefficient multiplicateur pour C1 et C2
avec glBlendFunc(),
et une fonction de mixage glBlendEquation().
Dans le cas classique on aura alors
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBlendEquationEXT(GL_FUNC_ADD_EXT);
Ces paramètres correspondent à un tracé des faces transparentes
triées de l'arrière vers l'avant (on dessine en estompant
ce qui est déjà présent),
mais l'on peut tout aussi bien additionner 2 images (e.g. image aux rayons X),
les multiplier (pour simuler des filtres transparents),
les soustraire, passer par des opérateurs min et max, etc.
Les coefficients peuvent désigner l'opacité de la source ou de la destination,
ou 1-opacité, mais aussi leur couleur, ou 1-couleur, voire une opacité
ou une couleur constante donnée par glBlendColor() (typiquement
pour faire du fondu-enchaîné entre images).
A noter que l'on peut ainsi calculer la somme des carrés de deux images,
en utilisant glBlendFunc(GL_SRC_COLOR,GL_DST_COLOR),
et l'on verra avec les lookup tables que l'on peut ensuite
en prendre la racine carrée dans la foulée.
L'accumulation buffer
Il s'agit de l'autre technique pour mélanger les images,
qui consiste à additionner une série d'images entières par
glAccum(GL_ACCUM,1./n),
avec éventuellement des pondérations, sans tenir compte du z
ou de l'alpha mais purement des couleurs.
On utilise notamment cette fonctionnalité (lourde, car faisant intervenir
tous les pixels de l'image) pour traiter
la profondeur de champ, le flou de bougé ou l'antialiasing,
en bougeant légèrement la position de la caméra ou des objets
entre chaque image.
Les stencils
Le stencil plane est un plan supplémentaire qui permet
d'associer un flag à chaque pixel.
OpenGL fournit des fonctions pour positionner ces flags,
généralement en même temps qu'un premier tracé, et pour les tester
au cours des tracés suivants.
Cette fonctionnalité est activée par glEnable(GL_STENCIL_TEST).
Ceci sert notamment à masquer une partie de l'image:
pour un simulateur de vol, on souhaite ne tracer le cockpit qu'une fois,
et ensuite le paysage ne doit être tracé que dans les fenêtres.
En dessinant le cockpit on demande alors à OpenGL de mettre à 1
le stencil plane là où l'on aura dessiné, par
glStencilFunc(GL_ALWAYS,1,1); glStencilOp(,,GL_REPLACE),
puis on indique qu'il ne faut désormais plus tracer que
dans les zones où le stencil est à 0, par
glStencilFunc(GL_EQUAL,0,1); glStencilOp(GL_KEEP,GL_KEEP,GL_KEEP).
Ces deux fonctions permettent en fait des opérations plus générales:
le plan de stencil comporte plusieurs bits (souvent 8),
lesquels peuvent être considérés soit comme des flags indépendants,
soit comme formant un nombre entier.
L'opérateur glStencilFunc(cmp,ref,mask)
peut alors tester si la valeur du stencil est inférieure,
égale, ou supérieure à un seuil avant d'autoriser le tracé,
et on peut préciser un masque pour isoler une partie des bits.
La fonction glStencilOp() permet d'indiquer comment affecter
le stencil selon que le fragment courant a été écarté par le test,
qu'il l'a franchi mais a été écarté par le Z-buffer,
ou qu'il a pu être tracé à l'écran.
Les opérations disponibles consistent à mettre à zéro (GL_ZERO)
ou à une valeur (GL_REPLACE) de
référence (donnée via glStencilFunc()), incrémenter ou décrémenter
(GL_INCR, GL_DECR)
la valeur de stencil considérée comme un nombre entier,
ou inverser (GL_INVERT) les bits (autorisés par le masque indiqué
par glStencilMask()).
Ceci permet de réaliser de nombreux effets:
on peut ainsi repérer et inscrire dans le stencil une zone d'ombre
(par exemple en effectuant la projection d'un objet sur le plan du sol),
et l'on tracera ensuite d'une couleur différente la zone extérieure
et la zone intérieure; on peut réaliser un miroir plan
en ne traçant la scène renversée que dans la limite du miroir;
on peut aussi créer une zone circulaire autour de la souris
où la scène apparaît en filaire plutôt qu'en faces cachées (ou l'inverse);
on peut compter le nombre de primitives se projetant en chaque pixel
(en incrémentant le stencil pour chaque fragment généré, qu'il passe
ou non le test du Z-buffer), etc.
On pourrait également utiliser les stencils pour tracer
des maillages en face cachée (le tracé de lignes sur des polygones
ayant le même z posant de gros problèmes d'imprécision):
pour chaque face, on tracerait d'abord le contour en positionnant
à 1 le stencil (si le fragment franchit le test du Z-buffer).
on tracerait ensuite le polygone en n'autorisant que les zones à stencil nul,
puis on effacerait les 1 du stencil pour ne pas
affecter les tracés ultérieurs, en redessinant le contour.
Cette méthode élimine proprement les conflits de z,
par contre elle est inutilement coûteuse puisque glPolygonOffset()
permet de traiter directement ce problème spécifique.
D'autres masques
Il existe deux autres façons de cacher une partie du tracé:
pour se restreindre à une zone rectangulaire de l'écran
(e.g. une sous-fenêtre, ou pour laisser une bordure),
on utilise glScissor().
Il faut penser à activer le test, avec glEnable(GL_SCISSOR_TEST).
Mais il faut prendre conscience que ce test intervient au niveau du
raster engine, et donc que tous les fragments sont générés
(i.e. le clipping continue à considérer la fenêtre en vraie grandeur): si
la zone autorisée est toute petite, c'est du gaspillage.
pour éliminer une partie de la scène 3D,
on peut jouer sur les plans de clipping avant et arrière
dont on a déjà parlé.
De même, OpenGL effectue un clipping sur les quatre plans
qui constituent la pyramide de vision (en dehors de laquelle les primitives
se projettent hors de l'écran).
On peut en outre préciser plusieurs plans de clipping
supplémentaires arbitraires avec
glClipPlane(i,params[]); glEnable(GL_CLIP_PLANEi).
Ceci peut permettre d'isoler un objet, mais aussi
de tracer différemment deux zones de l'espace:
on fait par exemple un premier tracé
en faces cachées à droite du plan, puis on retourne le plan de clipping
et on fait le reste du tracé en filaire (lequel plan peut même couper
un objet en deux).
[Attention je connais mal l'aspect qui suit en OpenGL,
j'ai donc pu dire des bêtises.]
Quand on réalise un logiciel de type modeleur plutôt que
de chercher à produire un rendu réaliste,
on peut se permettre d'utiliser une palette de couleurs
plutôt que le champ continu du true colors.
Les 3*8bits peuvent alors être utilisés comme autant de flags,
ce qui revient à considérer que l'image est constituée de calques superposés.
On peut par exemple utiliser 3 calques de 8 bits, qui autorisent
chacun 256 couleurs.
Pour un modeleur où les objets apparaissent en filaire, on
peut aussi considérer 24 calques monochromes.
On peut alors librement tracer ou effacer sélectivement un de ces
calques sans toucher aux autres, ce qui permet de ne rafraîchir
que l'objet de la scène que l'utilisateur est en train de modifier,
ce qui représente un gain considérable en nombre de primitives à tracer.
On entre dans ce mode avec glEnable(???) (par opposition
à ???), et on donne les couleurs avec glIndex()
à la place de glColor().
On joue sur le masquage des bits avec
glIndexMask() (on pourrait aussi le faire
en mode true colors avec glColorMask()).
Attention, de nombreuses opérations
décrites dans ce document
ne fonctionnent plus dans ce mode (transparence, mélanges, ...).
Du fragment au pixel: ordre des tests
Comme on l'a vu, un fragment d'un triangle subit de nombreux
tests avant d'affecter le pixel correspondant.
Pour éviter les ambiguïtés, il est important de se
rappeler que ceux-ci interviennent dans l'ordre suivant:
scissor test: le fragment n'est gardé que s'il tombe dans la boîte.
alpha test: le fragment n'est gardé que si son opacité est
au dessus du seuil.
stencil test: le fragment n'est gardé que si la valeur du stencil
du pixel franchit le test. S'il échoue, on peut laisser une
trace spécifique dans le stencil plane.
z test: le fragment n'est gardé que s'il est accepté par le Z-buffer,
c'est à dire (en général) qu'il est devant le contenu précédant du pixel
(i.e. son z est inférieur). S'il échoue, on peut laisser une
trace spécifique dans le stencil plane.
alors seulement le fragment peut être tracé (et éventuellement
laisser une trace spécifique dans le stencil plane).
A ce stade interviennent les opérations de mélange faisant intervenir
la transparence, dont on a parlé plus haut.
Comme tous les autres, le test du Z-buffer doit être activé
par glEnable(DEPTH_TEST) pour être mis en oeuvre,
et il est paramétrable:
glDepthFunc() permet de choisir l'opération à tester pour
retenir un fragment lors du test du z.
Multipass rendering
Les restrictions d'OpenGL, qui découlent notamment de celles du hardware,
introduisent de nombreuses limites,
par exemple sur le nombre de lumières, mais surtout sur le nombre de textures:
on ne peut en utiliser qu'une à la fois,
alors que les différents modes de mélange permettent
de moduler pratiquement chaque attribut à l'aide des textures !
De même on ne peut pas savoir directement si l'on se situe dans
une zone d'ombre, la réflection dans les miroirs n'est pas
prise en compte, etc.
Une image complexe sera donc rendu en plusieurs passes successives,
chacune modifiant ou complétant le résultat des précédentes.
Ainsi les opérations de mélange permettent
de tracer une première couche de surface,
puis d'autres qui affectent la teinte (vernis) ou la luminosité
(ombres fines), puis ajoutent des reflets, etc.
Dans le même esprit, on peut générer du pseudo-relief (bump mapping)
en soustrayant deux textures identiques représentant le relief
légèrement décalées dans le sens de la lumière (de manière à réaliser
un opérateur de type Sobel). On ajoute la couleur au relief
en multipliant le résultat par une texture de couleur,
le tout prend donc trois passes.
Les stencils permettent de séparer des régions qui devront recevoir
des traitements différents (par exemple dans et hors des ombres).
Le multipass permet donc de regagner de la souplesse
pour dépasser les limites de ce que le hardware peut gérer en même temps.
Cependant multiplier les passes coûte cher, même si
certaines ne concernent que quelques faces. Il faut donc veiller à
éviter tout traitement inutile (on verra bientôt comment).
Optimisations
Toute fonctionnalité utilisée coûte cher !
D'une part par le traitement et les tests qu'elle impose,
mais aussi par le simple fait qu'il
faut alors gérer des données supplémentaires,
lesquelles doivent être générées, stockées, transmises et interpolées.
Il faut se méfier de la valeur par défaut des flags,
et désactiver par glDisable() tout ce dont on n'a pas besoin,
car OpenGL ne fait aucun test de cohérence:
si l'on utilise une texture qui remplace totalement la couleur
de la surface, cette couleur est néanmoins interpolée à partir
des valeurs aux sommets, et le coûteux modèle d'illumination
sera lui aussi inutilement
évalué aux sommets (autant de fois qu'il y a de lumières)
si le mode LIGHTING est actif.
De même, l'atténuation due au brouillard est calculée
dès que le mode FOG est actif (peut-être l'est-il par
défaut sur certaines machines), quand bien même
sa densité serait nulle ! Dans le même esprit,
la couleur aux sommets est interpolée par défaut, même si l'on
a fourni qu'une seule couleur pour toute la facette.
Typiquement, lors du multipass, il faut repérer à quels moments
on a réellement besoin de tester le z ou le alpha,
et quelles sont les passes qui nécessitent vraiment
le calcul de l'illumination (dans la négative, on peut aussi
se passer de normaliser les normales).
Inversement, certaines fonctions permettent de réduire les calculs:
Ainsi, l'évaluation de l'illumination coûte moins cher lorsque
les lumières sont à l'infini (4ième coordonnée nulle).
Dans un autre registre, on peut pratiquement diviser
par deux le nombre de faces à tracer en éliminant celles qui
tournent le dos à la caméra avec glEnable(GL_CULL_FACE).
Il faut utiliser autant que possible les instructions OpenGL
plutôt que de faire les opérations à la main (e.g. calcul matriciel),
passer par les primitives les plus simples et spécifiques
(triangles plutôt que polygones),
et les grouper dans un seul glBegin()..glEnd()
(séries de faces, de lignes, de points).
En outre, OpenGL introduit des primitives adaptées aux maillages,
comme les GL_TRIANGLE_STRIP
où chaque triangle partage deux sommets avec
le précédent, ce qui évite de répéter inutilement l'évaluation
des attributs au sommet (ce qui se produit quand on doit mentionner
plusieurs fois le même sommet, et qui peut être coûteux quand il s'agit
de l'illumination ou des normales).
Quand on doit utiliser plusieurs fois le même objet (instances multiples,
mouvement rigide, multipass), OpenGL prévoit l'utilisation
de display lists, qui constituent une version précompilée
d'une description 3D: la génération automatique éventuelle
des normales et des (u,v) est effectuée ainsi que les changements
de repères (rotations, translations, scaling entre membres de l'objet),
et toutes les données sont converties au format interne une bonne fois.
On peut ensuite afficher la liste autant de fois que nécessaire
sans recalculs inutiles.
On construit une liste en encadrant une série d'ordres OpenGL classiques
par glNewList(i,GL_COMPILE) et glEndList(),
puis on utilise glCallList(i) pour l'afficher.
Il faut faire attention à la saturation du pipeline graphique:
les instructions graphiques sont exécutées de manière asynchrone,
mais le processeur doit attendre si on a rempli la pile
plus vite qu'elle est traitée.
Il faut donc en profiter pour intercaler les calculs et l'affichage.
Ainsi, effacer l'écran est une opération lourde.
On gagne donc à effectuer immédiatement après
tous les calculs nécessaires pour préparer
le tracé, pendant que l'effacement se réalise et avec un peu de chance
se termine, le pipeline étant alors près à recevoir les
instructions de tracé au moment où on les donne.
Si l'on avait effectué les calculs puis le glClear() et
l'affichage, il y aurait eu un temps où le moteur graphique
n'avait rien à faire pendant que le processeur calculait,
puis un temps où le processeur était en attente pendant
que le moteur graphique avait à venir à bout d'un effacement
et d'un tracé.
Quand on souhaite optimiser une application graphique, il
faut commencer par déterminer où se situe le goulot
d'étranglement: dans les calculs, dans le geometric engine,
ou dans le raster engine.
- si désactiver l'affichage (par exemple en changeant les
glVertex() en glColor()) ne réduit pas le temps,
le goulot est dans la partie calcul;
- si activer ou désactiver l'illumination locale
change la vitesse, le goulot se situe probablement dans la partie 3D;
- si supprimer les textures, le test des z
ou le mélange accélère l'affichage, le goulot se situe dans la partie 2D.
Les textures
On a vu plus haut comment régler l'apparence des textures et
leur combinaison à la couleur d'une surface.
L'autre moitié du problème consiste à spécifier la façon
dont une texture se plaque sur la surface, ce
dont on va traiter ici.
Mais avant tout, il faut charger l'image en mémoire.
Charger une image
La lecture depuis le disque d'une image au format RGB se fait
avec iopen(), puis en lisant chaque ligne avec getrow().
Le format n'étant pas le même en mémoire, il faut réorganiser
les informations de manière à obtenir une table de triplets RGB:
image = iopen(filename,"r");
l = image->xsize;
h = image->ysize;
ptr = table = (unsigned char*) calloc(l*h,3);
for(y = 0; y < h; y++) {
    getrow(image, rbuf, y, 0);
    getrow(image, gbuf, y, 1);
    getrow(image, bbuf, y, 2);
    for(x = 0; x < l; x++) {
      *(ptr++) = rbuf[x];
      *(ptr++) = gbuf[x];
      *(ptr++) = bbuf[x];
     }
   }
iclose(image);
On déclare que cette image doit être utilisée comme texture courante par
glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,l,h,0,GL_RGBA,GL_UNSIGNED_BYTE,table).
Transférer ainsi une image vers la mémoire texture coûte cher,
il serait assez prohibitif d'avoir à procéder ainsi à chaque fois
qu'on change de texture pour un autre objet de la scène.
OpenGL permet de créer une banque de textures, à concurrence
de la mémoire disponible, que l'on peut constituer une fois pour toute.
On encadre la définition de la texture (y compris les paramètres
dont nous parlerons plus loin)
d'une déclaration permettant de donner un numéro à une texture,
par glBindTexture(GL_TEXTURE_2D,i) et
glBindTexture(GL_TEXTURE_2D,0).
A l'utilisation, il suffit alors de mentionner
à nouveau glBindTexture(GL_TEXTURE_2D,i) pour
rendre cette texture courante, sans qu'un nouveau transfert soit nécessaire.
Il ne faut bien sûr pas oublier d'activer le rendu des textures,
par glEnable(GL_TEXTURE_2D).
NB: si l'on souhaite remettre à jour une texture qui évolue au cours du temps,
on gagne énormément si l'on peut se contenter de transférer la
petite partie qui a changé, avec
glTexSubImage2D(GL_TEXTURE_2D,0,x,y,l2,h2,GL_RGBA,GL_UNSIGNED_BYTE,table2).
Paramètres de la texture
OpenGL permet de régler de nombreux paramètres
régissant la façon dont la texture se répète,
les traitements à effectuer quand on la voit de près ou de loin, etc.
Se référer au manuel pour plus de détails.
A titre d'exemple,
on fait répéter le motif ou on ne le fait apparaître qu'une seule fois
en utilisant le flag GL_REPEAT ou GL_CLAMP
pour chacune des deux coordonnées textures u et v
repérées par les flags GL_TEXTURE_WRAP_S et
GL_TEXTURE_WRAP_T
dans la commande glTexParameteri(GL_TEXTURE_2D,coord,flag).
D'autre part, on peut choisir d'interpoler ou pas la texture
(flag GL_LINEAR ou GL_NEAREST) lorsqu'on
est loin ou proche (cas TEXTURE_MIN_FILTER ou
TEXTURE_MAG_FILTER) via la fonction
glTexParameteri(GL_TEXTURE_2D,cas,flag).
D'autres paramètres permettent de régler finement ce qu'il se passe
au bord du motif, de gérer les niveaux de détail par MIP-mapping
pour traiter le cas des textures éloignées en évitant l'aliasing,
ou encore de traiter des textures plus grandes que la mémoire
décomposées en plusieurs tiles.
Plaquage de la texture
Pour appliquer une texture sur une surface, la méthode
consiste à indiquer où les sommets définissant la surface
tombent dans l'espace (u,v) de la texture.
Par interpolation des coordonnées texture sur les facettes,
on obtient alors la projection sur toute la surface
(on devine cependant tous les problèmes de type tapisserie
qu'occasionne cette technique. On peut juste se consoler en
se disant que tout le monde subit le même problème ! ).
Les coordonnées texture d'un sommet s'indiquent
par glTexCoord2f(u,v) juste avant le glVertex().
Il existe cependant des méthodes pour générer automatiquement
les (u,v) dans certains cas spécifiques:
quand la surface est obtenue automatiquement par subdivision
d'un patch de Bézier ou de NURBS, quand la texture
est directement corrélée à l'altitude ou à la direction de vue
pour certains effets de visualisation,
ou quand on se sert de la texture pour simuler
les reflets de l'environnement dont on parlera bientôt.
Textures non 2D
Pour OpenGL, une coordonnée texture est simplement
un attribut supplémentaire des sommets, au même titre que
la couleur, et pour lui interpoler 1 ou 4 valeurs revient au même.
Cette valeur sert ensuite d'index dans une table,
et là encore définir une table à une seule ou à 4 dimensions
n'est pas fondamentalement différent de ce qu'on fait pour 2.
De même les opérations de répétition ou de filtrage se
généralisent sans problème.
Ainsi donc, OpenGL prévoit jusqu'à 4 coordonnées
(u,v,s,t) de texture;
libre aux utilisateurs d'y trouver un usage.
Il y a plusieurs applications intéressantes:
pour les textures 1D, qui sont d'autant plus souples qu'elles
ne supposent aucun paramètrage de la surface,
on peut par exemple considérer u comme un
potentiel à la surface, ou encore comme une altitude,
et représenter en couleur les diverses taches de potentiel,
ou les isovaleurs.
Les textures 3D permettent d'implémenter en Z-buffer les
textures solides comme le bois et le marbre, matériau
dans lequel on taille les objets (les (u,v,s) sont alors
une transformation affine des coordonnées géométriques).
On peut également se servir des textures 3D pour visualiser des volumes
style données scanner,
en taillant des tranches plus ou moins opaques dans ce
volume de texture.
Ces modes s'activent simplement en précisant 1D,
3D ou 4D à la place de 2D dans les
fonctions que l'on a vu plus haut.
Textures projetées
Comme on l'a vu plus haut, OpenGL fournit
une matrice 4x4 permettant
au geometric engine de transformer les coordonnées de texture
(u,v,s,t) comme on le fait
avec les changements de repère pour les coordonnées géométriques.
Ceci permet par exemple de texturer une surface
en simulant une projection parallèle ou conique depuis une diapositive:
on duplique dans les (u,v,s,t) des sommets les coordonnées
géométriques,
et on positionne la matrice de sorte à réaliser une projection
sur le plan de la diapo.
Textures d'environnement
On peut simuler des objets réfléchissants avec les textures!
Le principe consiste à préparer un image de l'environnement
entourant un objet, en supposant que cette image se situe sur la surface
intérieure d'une sphère infiniment grande centrée sur l'objet.
Dans un tel cas, deux rayons réfléchis parallèles voient
le même point de cette sphère (i.e. celui pointé par
la direction du rayon), ce qui revient à dire que seule
la direction de réflection importe et non la position.
Pour faire de telles textures de reflets,
OpenGL va donc stocker en chaque sommet de l'objet le (u,v) que l'on
y voit sur la sphère, simplement en calculant la direction de réflection
qui est le symétrique de la direction de l'observateur par rapport
à la normale au sommet, ces deux informations étant connues au moment
où est effectué le glVertex().
Ce plaquage particulier est obtenu par:
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
Textures d'ombre
On peut obtenir des ombres portées grâce au textures!
Le principe consiste à construire une texture de z,
représentant la distance de ce que voit une lumière dans
la direction du point où se projette chaque pixel de texture.
Une telle image s'obtient en effectuant un rendu préalable
par Z-buffer avec le point de vue positionné au lieu de la lumière.
Pour le rendu final,
on duplique dans les (u,v,s,t) des sommets les coordonnées
géométriques, et on positionne la matrice de texture pour
effectuer une projection perspective sur la lumière, de façon à
retomber sur l'image préalable.
En appliquant cette transformation, le geometric engine
transforme ainsi les (u,v,s,t) en les coordonnées
`écran' vues depuis la lumière dont seul la distance
z' nous intéresse.
Au moment du rendu, le raster engine compare le z'
du fragment à celui stocké dans la texture de z obtenue
grâce au premier rendu,
et considère qu'on est dans l'ombre si le z vu par la lumière
est plus petit que le z' du fragment.
Le picking
Quand on réalise une application interactive, on peut souhaiter
savoir ce qui se trouve sous la souris (objet ou face, voire
coordonnée), par exemple pour permettre à l'utilisateur de
le désigner (c'est le picking).
D'une manière plus générale, on peut souhaiter avoir un retour
quant à ce qui en définitive a vraiment été tracé à l'écran
(ne serait-ce que pour mieux élaguer les parties non visibles
de la scène).
De ce point de vue, savoir ce qu'il se passe sous la souris
est un cas particulier: il suffit de restreindre la fenêtre
à quelques pixels (voire un seul) autour de la souris
et se poser la question de ce qui s'affiche dans cette fenêtre.
La librairie utilitaire GLU permet de positionner
cette fenêtre d'intérêt sans modifier les autres paramètres:
il suffit d'ajouter juste avant l'affichage
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
gluPickMatrix(xmouse,viewport[3]-ymouse,1.,1., viewport);
Comme le geometric engine réalise tous les changements de repère,
la projection à l'écran et le clipping,
à la fin de son traitement on peut savoir tout ce qui peut apparaître
à l'écran.
Le picking consiste donc à interrompre le traitement du rendu à la fin
de la phase 3D, juste avant que le raster engine ne génère les fragments
(donc rien ne s'affiche, on a fait un tracé virtuel).
On demande alors au geometric engine de fournir une liste
décrivant ce qu'il y a à tracer, dans le repère de l'écran.
Si un face donnée se trouve dans la liste, c'est qu'elle
devrait être visible à l'écran (sauf si elle est cachée
par un autre objet), et si l'on avait restreint la fenêtre à quelques
pixels sous la souris, on en déduit que cette face est juste sous la souris.
OpenGL prévoit deux modalités pour le picking selon le type d'information
que l'on souhaite récupérer:
le mode GL_SELECT, qui permet de savoir
quel objet ou quelle face est visible, et le mode GL_FEEDBACK,
qui permet d'obtenir toutes les informations disponibles sur la scène,
coordonnées géométriques dans le repère écran,
couleur, coordonnées textures (u,v), etc.
On peut par exemple en tirer les coordonnées du point cliqué à la souris à la
surface d'un objet.
On active ce mode par glRenderMode(mode)
(le mode de tracé normal avec affichage correspondant au mode
GL_RENDER).
Dans les deux cas il faut fournir un buffer où seront stockées
les informations retournées par le geometric engine,
avec selon le mode
glSelectBuffer(size,buff) ou
glFeedbackBuffer(size,GL_3D_COLOR_TEXTURE,buff).
On accède à ce buffer après être sorti du mode picking par
nbinfo = glRenderMode(GL_RENDER).
Dans le mode GL_SELECT, on regroupe les primitives
qui constituent une unité logique en insérant des déclarations
glPushName(i) entre leurs tracés, ce qui revient
à donner un identifiant à ces unité.
Le geometric engine inscrit dans le buffer l'identifiant
de toute unité dont au moins une primitive
se projette dans la zone de l'écran, c'est ainsi que
l'on sait quel objet apparaît.
Dans le mode GL_FEEDBACK,
chaque primitive apparaissant potentiellement à l'écran
va générer une entrée dans le buffer,
consistant en un identifiant du type de primitive,
GL_POLYGON_TOKEN pour un polygone,
suivit du nombre de sommets, puis pour chaque sommet, de
tous les attributs demandé,
soit pour GL_3D_COLOR_TEXTURE
les coordonnées x,y,z dans le repère écran,
la couleur apparente RGBA, et les coordonnées de texture u,v,s,t
(dans le cas général, il y a 4 coordonnées texture).
Comme la phase de clipping redécoupe les facettes qui sortent de l'écran,
dans le cas d'une fenêtre de 1 pixel de large positionnée sous la souris
on obtient ainsi les coordonnées, l'illumination, etc, sur le lieu
de la surface situé sous la souris.
NB: on peut remonter aux coordonnées dans le repère monde via
la fonction gluUnproject(). (Mais cette fonction est coûteuse
si l'on veut retrouver plusieurs points, car elle inverse la matrice
de transformation à chaque appel. Mieux vaut alors faire soit
même l'inversion de matrice pour appliquer la même transformation réciproque
à tous les points.)
A noter que l'on obtient dans ce mode un descriptif de ce
qui est visible, mais pas la désignation des objets
comme dans le mode GL_SELECT.
On peut retrouver cette fonctionnalité en
insérant des appels à glPassThrough(f) entre les
primitives constituant une unité, dans le même esprit que
glPushName(i).
On récupère alors dans le buffer
l'identifiant de primitive GL_PASS_THROUGH_TOKEN suivit
de la valeur f passée en argument,
ce qui permet de reconnaître l'unité à laquelle appartiennent
les entrées du buffer.
Lexique
graphics engine, moteur graphique: ensemble logiciel
et matériel permettant de transformer
les ordres graphiques 3D en images à l'écran.
geometric engine: portion du graphics engine assurant
les transformations géométriques (changements de repère, projection, etc...).
culling: élagage (e.g. des faces tournant le dos à l'écran).
clipping: élagage des faces qui sortent de l'écran, et découpe des faces partiellement à l'écran.
strip / fan: bande, ribambelle / éventail:
méthodes de description de maillage
qui évitent les redondances (redescription des points qui ont déjà servi
pour un autre triangle) en considérant que les triangles s'enchaînent
les uns aux autres.
raster engine: portion du graphics engine assurant la transformation
en pixels (découpe des primitives en lignes puis en fragments).
raster / rasteriser: ligne de balayage / tracer ligne par ligne.
pixel: point de l'écran.
fragment: point (e.g. sur une surface) candidat à apparaître dans le pixel.
true color: mode permettant l'affichage `libre' des teintes,
sans passer par une table de couleurs, en spécifiant leur décomposition
dans les trois composantes rouge, verte et bleue.
alpha: opacité (i.e. 1-transparence).
Remarque: cette transparence n'est
pas colorée, elle ne fait qu'estomper plus ou moins l'arrière-plan.
clamper: couper les valeurs sortant de l'intervalle [0,1].
blend, blending: mélange (de couleur, entre deux tracés
successifs au même endroit).
Z-buffer: nom de la structure de donnée, et par extension de
l'algorithme, réalisant l'élimination des parties cachées en stockant
la valeur de z correspondant à chaque pixel.
double buffer: ensemble de deux `écrans' (le front buffer
et le back buffer) permettant l'animation sans clignottement: on
affiche le premier buffer pendant qu'on dessine dans le second,
puis on permute les buffers (swap).
Phong, illumination locale: formule décrivant la façon dont une surface
renvoit la lumière dans les diverses directions, contribuant à caractériser
l'aspect du matériau. Phong est un modèle particulier, isolant les effets de
la lumière ambiante, diffuse ou spéculaire (reflets de la source),
et prévoyant une couleur spécifique à chacun d'eux.
textures: variation des paramètres d'aspect le long de la surface.
Dans le cadre restreint d'OpenGL, cela consiste en la donnée d'une image
(que l'on désigne usuellement par `texture')
et d'une fonction de mapping
indiquant comment cette image est plaquée sur la surface.
Ceci est obtenu en indiquant pour chaque sommet de la surface
ces coordonnées (u,v) dans l'image (i.e. dans l'espace de la texture).
picking: action de désigner un objet à la souris.
stencil: calque, ou masque, permettant (à l'origine) de n'effectuer
des opérations que sur certaines zones de l'écran.
masquer: cacher.
dithering: mode d'affichage enrichissant la dynamique des
couleurs affichables
en utilisant un `pattern' (motif rectangulaire)
de plusieurs points de couleurs différentes.
interpoler: faire passer continument d'une valeur à une autre
(e.g. la couleur entre deux (voire trois) sommets, ou la position entre
deux instants).
enable / disable: activer / désactiver (une option).
URL références et astuces
-
Les man pages d'OpenGL par ordre alphabétique
http://www-evasion.imag.fr/Membres/Fabrice.Neyret/doc/man.html
-
About,Tutoriels, FAQ (site officiel d'OpenGL)
http://www.opengl.org/developers/
-
Tutoriel de NeHe sur GameDevelopper
http://nehe.gamedev.net/
-
Tutoriel interactif (matrices, materiaux)
http://www.xmission.com/~nate/tutors.html
-
Docs OpenGL, GLUT, etc
http://www.opengl.org/developers/documentation/specs.html
-
Les extensions d'OpenGL
http://oss.sgi.com/projects/ogl-sample/registry/
-
Le cours Siggraph 'advanced opengl'
http://www.sgi.com/software/opengl/advanced98/notes/
-
Le pipeline graphique, les librairies EXT
http://www.sgi.co.il/support/OGLT/OGLT.html
-
Trucs et astuces (bump, caustics, fresnel, ombres, volumes...)
http://toolbox.sgi.com/TasteOfDT/src/exampleCode/WitchesBrew/
-
Trucs et astuces (postscript, halo, ombres, reflets, particules...)
http://reality.sgi.com/opengl/tips/
- Nvidia papers
- dont l'indispensable
Avoiding 19 Common OpenGL Pitfalls
-
Brown university documentation
- The OpenGL graphics system diagram