Programmation graphique Mastère photogrammétrie, positionnement, mesures de déformation Programmation graphique avec Open GL Yves EGELS
Open GL bibliothèque logicielle permettant la représentation graphique d’objets 3D (et éventuellement 2D). indépendante du matériel, du système d’exploitation, du langage de programmation. prend en compte les accélérations matérielles disponibles, et permet des optimisations logicielles élimine les partie cachées par la technique du ZBuffer pas de gestion de souris, son, video… comme dans DirectX (qui est lié à Windows): équivalent à Direct3D
Documentation Ce document est un aide mémoire, plutôt orienté Delphi, et ne décrit que le principe des fonctions principales. Il est destiné à faciliter l’accès à la documentation spécialisée/ Pour les API Windows (y compris l’implémentation Microsoft d’OpenGL), on se reportera par exemple aux aides en ligne correspondantes Sur OpenGL proprement dit, 4 sources principales http://www.opengl.org/documentation/ (la base) Deux ouvrages de référence, Bluebook (manuel de référence) et Redbook (manuel utilisateur, assez confus…) (les versions anciennes sont téléchargeables ci dessus) des manuels décrivant les implémentations de différents constructeurs (celui d’IBM est très bien fait) Beaucoup de sites Web (exemples de code, autoformation, librairies)
4 niveaux Initialisation (dépend du système d ’exploitation) : création de la fenêtre et description des fonctionnalités wgl….. : Windows, inclus dans l’API Win32 glX….. : Unix gl….. : routines de base (appel direct de fonctions du driver OpenGL) : gestion des matrices de transformation primitives géométriques simples couleurs et textures glu….. : utilitaires (appel standardisé de routines de base) gestion des vues primitives géométriques complexes (splines, quadriques…) gl_xxx_… : extensions propriétaires (syntaxe et dénomination normalisées, intégrées progressivement à la norme) optimisations spécifiques accès à des fonctionnalités complémentaires de certaines cartes en tester l’existence par glGetString(GL_EXTENSIONS) et gluGetString(GLU_EXTENSIONS)
Syntaxe générale Sous Windows, OpenGL se présente sous forme de DLL (OpenGl32.dll et Glu32.dll). Les prototypes d’accès sont disponibles dans gl.h et glu.h (C++) l’unité OpenGL de Delphi Delphi ne propose pas de composant standard, mais on en trouve beaucoup sur Internet. Les arguments des routines peuvent être de types divers. Pour les langages ne supportant pas le polymorphisme, le nom de la procedure est étendu par un code indiquant le nombre d’éléments de chaque tableau et par un code indiquant le type des paramètres : b,s,i,ub,us,ui pour des entiers 8,16,32 bits signés ou non, f et d pour des flottants simple ou double précision : par exemple glVertex2i attend des coordonnées de points 2D en entier, alors que glVertex3d attend des coordonnées de points 3D en flottants doubles,
Initialisation seule phase dépendante du système d’exploitation dans le cas de Windows : créer une fenêtre, et en récupérer le contexte par DC := GetDC(Handle) choisir éventuellement les caractéristiques par ChoosePixelFormat et SetPixelFormat (c’est ici que l’on précise par exemple que l’on veut de la stéréo, le nombre de plans etc) Initialiser OpenGL proprement dit Context := wglCreateContext( DC );// créer une passerelle OpenGL sur la fenêtre wglMakeCurrent( DC, Context); // la rendre active Ceci permet de dessiner éventuellement dans plusieurs fenêtre OpenGL en alternant l’activation, la passerelle de dessin n’étant pas fournie à chaque appel OpenGL faire ce que l’on a à faire : préparer les données (géométrie, visualisation, couleurs, textures,éclairage…) inclure les ordres de dessin dans le traitement de l’événement OnDraw après avoir effacé les buffers par glClear(GL_COLOR_BUFFER_BIT) et glClear(GL_DEPTH_BUFFER_BIT) à la fin, libérer tout ce qu’on a utilisé: wglMakeCurrent( DC, 0 ); // Désactiver la passerelle wglDeleteContext(Context ); // Détruire la passerelle ReleaseDC( Handle, DC ); //Libérer le contexte graphique de la fenêtre
Primitives géométriques simples se présentent sous forme d’une liste d’appels à la création des sommets, encadrée par glBegin et glEnd types simples : semis de points, segments, triangles, quadrilatères, polygones convexes glBegin(type_geometrique) glVertex3f(X1,Y1,Z1) glVertex3f(X2,Y2,Z2) … glEnd Objets composés : polyligne, ensemble de triangles (fan, strip), bande de quadrilatères Très simple, mais inefficace!
Primitives complexes Les primitives complexes ne sont pas gérées directement par OpenGL. Les utilitaires gluXXX permettent de fabriquer un certain nombre d’objets (on pourra se référer aux chapitres correspondants du RedBook): gluBeginPolygon : polygone non convexe gluDisk, gluPartialDisk, gluCylinder, gluSphere, gluQuadrics : surfaces du second degré gluBeginCurve gluBeginSurface : courbe et surfaces spline Rien n’est prévu pour afficher des MNT, qu’ils soient maillés ou triangulés. On peut toujours faire une boucle de glTriangles ou glQuads, mais c’est spécialement inefficace! Pour les MNT maillés, il est élémentaire de les transformer en bandes de quadrilatères, au moins par ligne (on peut ne faire qu’une bande en ajoutant des quadrilatères dégénérés aux extrémités de ligne) Pour les triangulations, c’est également possible, mais plus complexe, et nécessite de disposer de la topologie des triangles (triangles adjacents aux cotés). La solution optimale est très onéreuse à obtenir, on peut se contenter d’algorithmes heuristiques simples donnant une solution raisonnablement bonne.
Optimisation Mettre les coordonnées des points dans des tableaux On prévient OpenGL, on lui passe les caractéristiques du tableau, et on utilise l’indice du tableau au lieu du point : glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer (3, GL_INT, 6, Coord); // tableau Coord contenant 3 entiers par point, séparés par 6 octets glBegin(GL_TRIANGLES); glArrayElement (2); glArrayElement (3); glArrayElement (5); glEnd(); Mettre aussi les indices dans des tableaux glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, Indices); // dessine 1 triangle, avec les 3 sommets dont les numéros sont dans Indices, tableau d’entiers non signés Utiliser des points consécutifs dans les tableaux glVertexPointer (12, GL_INT, 6, Coord); glDrawArrays(GL_TRIANGLES, 3,6); // dessine 2 triangles, avec les 6 sommets contenus dans Coord, à partir du 3ème
Optimisation Créer des listes d’affichage Pour les objets qui ne sont pas modifiés, il est plus efficace de « compiler » une liste d’affichage, qui sera traitée une fois pour toutes et transférée au système graphique. Les listes d’affichage font partie de la norme; par contre, la fonctionnalité de compilation fait théoriquement partie des extensions, son existence doit être testée par glGetString(GL_EXTENSIONS). préparation : Liste := glGenLists(1); // création d’une liste . . . . glNewList(Liste, GL_COMPILE); // les ordres suivant iront dans la liste glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer (3, GL_INT, 6, Coord); glLockArraysEXT(0, 6); // Signale que les coord des points sont fixes glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, Indices); glUnlockArraysEXT(0, 6); glEndList; // fin de la liste glDeleteLists(Liste,1); // libération de la liste (à la fermeture) exécution dans la boucle de dessin : glCallList(Liste); // dessin du triangle précédent
Couleurs, normales La définition des couleurs est analogue à celle des coordonnées tous les éléments dessinés après glColor3ub(Rouge,Vert,Bleu); seront de cette couleur. On peut ajouter un 4ème canal Alpha (transparence) On peut fournir les couleurs dans des tableaux, et éventuellement les placer dans une liste d’affichage glEnableClientState(GL_COLOR_ARRAY); glColorPointer(3,GL_UNSIGNED_BYTE,stride,TabCouleur); Les couleurs de TabCouleur seront utilisées dans les appels suivants à glDrawArrays, glDrawElements ou glArrayElement Le rendu des objets surfaciques dépend du champ des normales à la surface Elles sont spécifiées par des appels à glNormal ou glEnableClientState(GL_NORMAL_ARRAY); glNormalPointer; par défaut, le rendu ne tient pas compte des normales
Textures Il est assez facile de draper une image sur un objet. Mais les implémentations peuvent varier, et il faut commencer par se renseigner! Avant OpenGL 2.0 (c’est à dire toujours…) les images doivent avoir des hauteurs et largeur puissance de 2, et la taille maxi dépend de l’implémentation. Il faudra donc rééchantillonner l’image. gluScaleImage est fait pour cela : glGetIntegerv(GL_MAX_TEXTURE_SIZE,@Maxsize); // Taille maxi autorisée . . . { Chargement de l’image dans un BitMap temporarire Btemp, choix de la taille finale,puissance de 2 <= à Maxsize} Bitmap := TBitmap32.Create; // définis dans GR32, plus efficace que les Bitmap de Windows gluScaleImage(GL_RGBA,BTemp.Width,BTemp.Height,GL_UNSIGNED_BYTE,BTemp.Bits, Bitmap.Width,Bitmap.Height,GL_UNSIGNED_BYTE,Bitmap.Bits); // rééchantillonnage Ensuite, on peut transformer cette image en texture : glGenTextures(1,@Texture); // Allocation d’une texture glBindTexture(GL_Texture_2D,Texture); // Texture 2D glEnable(GL_Texture_2D); // OpenGL s’en servira glTexEnvi(GL_Texture_Env,GL_Texture_Env_Mode,gl_Decal); // interaction entre texture et couleur de l’objet glTexParameteri(GL_Texture_2D,GL_Texture_mag_filter,gl_Linear); // Comment on rééchantillonne glTexParameteri(GL_Texture_2D,GL_Texture_min_filter,gl_Linear); glTexImage2D(GL_Texture_2D,0,4,bitmap.Width,bitmap.Height,0,GL_RGBA,GL_UNSIGNED_byte,bitmap.bits) // associe le bitmap à la texture On peut maintenant la draper sur l’objet, il suffit de donner à OpenGL les coordonnées image des points de l’objet (dans la séquence de dessin, en accord avec les coordonnées 3D des points correspondants) par une série de glTexCoord2f(X,Y); ou plus efficace glEnableClientState(GL_TEXTURE_COORD_ARRAY); glTexCoordPointer(2, GL_FLOAT, stride,CoorImage);
Transformations géométriques De l’objet à l’image, les coordonnées subissent un certain nombre de transformations géométriques (rotation, translation, perspective). Toutes sont modélisées par des matrices 4x4 opérant sur les coordonnées homogènes des points (x,y,z,w) – en fait w est toujours égal à 1, il est donc inutile de le fournir! Pour les points définis en 2D, z est mis à 0. OpenGL gère 3 piles de matrices, sélectionnées par glMatrixMode avec les arguments GL_ModelView, GL_Projection et GL_Texture. ModelView passe de l’objet au point de vue, Projection effectue la perspective, Texture opère sur les coordonnées des textures. Ces transformation sont directement appliquées par OpenGL lors de la visualisation La matrice active à un moment donné est la première de la pile. glPushMatrix glPopMatrix manipulent la pile. glLoadMatrix, glLoadIdentity, glMultMatrix, glScale, glTranslate, glRotate modifient la matrice active.
Projection Les appels suivant doivent être précédés de glMatrixMode(GL_PROJECTION), de façon à ce qu’ils agissent sur la matrice de projection (ce n’est pas automatique…) glFrustum définit une matrice de projection perspective, à partir du cadre image (gauche, droit, haut, bas), de la focale (plan avant) et du plan arrière. Cela défint donc un tronc de pyramide qui est le champ de vision. Le résultat de la projection est exprimé en coordonnées normalisées. gluPerpective a la même fonction, en donnant rapport hauteur/largeur, angle de champ, plans avant et arrière glOrtho définit une matrice de projection orthogonale La projection a lieu à partir de l’origine, suivant l’axe des Z. Pour spécifier un point de vue et une direction particuliers, on peut appeler gluLookAt en le faisant agir sur la matrice modèle par appel à glMatrixMode(GL_MODELVIEW); Le passage à l’écran se fait par appel à glViewport qui transforme les coordonnées normalisées en coordonnées écran. Pour naviguer avec la souris, il faut incorporer dans les gestionnaires d’événement de la souris (OnMouseDown,OnMouseMove) des appels à OpenGL pour modifier la vue (position, orientation, zoom…). OpenGL ne fournit aucun outil spécifique. Si le code nécessite la connaissance des matrices actuelles, elles sont toujours accessibles par des appel de style glGetDoublev(GL_MODELVIEW_MATRIX,@MatriceModele);
Accès aux buffers Un accès direct aux buffers est parfois utile : pour y dessiner directement en 2D (polygone de sélection par ex.), ou pour gérer la stéréoscopie. Si le format pixel contient PFD_SUPPORT_GDI (Ceci doit être vérifié à l’initialisation), les ordres de dessin s’adressant au Canvas de la fenêtre sont possibles. Il est bon de les précéder de glFlush pour s’assurer que OpenGL a terminé (son fonctionnement est asynchrone) et de SwapBuffers : OpenGL ne fonctionne bien qu’en double buffer, dessinant dans le BackBuffer quand le FrontBuffer est visible. Ce fonctionnement est commandé par PFD_DOUBLEBUFFER à l’initialisation Les fonction glReadPixels et glWritePixels permettent de lire/écrire directement les buffers image : utile par exemple pour une copie écran. Le Z-Buffer, servant à l’élimination des parties cachées, est traité de façon identique Pour la visualisation stéréoscopique, il faut préciser PFD_STEREO dans le format pixel à l’initialisation, après avoir vérifié la capacité de l’implémentation OpenGL par glGetBooleanv(GL_Stereo,@Test); On dispose alors de 4 buffers (gauche/droite, avant/arrière). La boucle de dessin peut alors ressembler à : glDrawBuffer(gl_Back_left); . . . // calcul de la matrice de projection gauche glCallList(Liste); glDrawBuffer(gl_Back_right); . . . // calcul de la matrice de projection droite SwapBuffers(Handle);