Vous êtes sur la page 1sur 114

OpenGL (Open Graphics Library) est une blibliothque graphique trs complte qui permet aux programmeurs de dvelopper

des applications 2D, 3D assez facilement. Vous avez dj d l'utiliser ou en entendre parler, car de nombreux jeux, comme Quake III, proposent l'OpenGL comme mode d'affichage.

Quake III Arena Bon d'accord, je dois vous l'avouer aprs avoir lu ce tuto vous ne saurez pas encore faire des jeux comme a (notamment cause du moteur physique pour grer les collisions, dplacements et projectiles) mais il est beau de rver. OpenGL s'utilise principalement en C++, c'est pourquoi il est conseill de connatre ce langage. Mais rassurez-vous ! Mme sans base vous arriverez parfaitement comprendre tous les exemples du cours, cependant les applications que vous serez amens dvelopper par vous-mmes seront peuttre moins pousses. La lecture dututo C/C++ est fortement recommande. Il est possible d'utiliser OpenGL en Python, Delphi, Java, et les commandes y sont similaires. Ici nous utiliserons le C/C++ pour profiter du cours dj prsent sur le site.

Une multitude de fonctionnalits


OpenGL dispose de nombreuses fonctions que vous pourrez facilement utiliser, notamment la gestion de :

la camra ; la rotation 3D des objets ; le remplissage des faces ; les textures ; la lumire ; et bien plus encore...

Dans ce tuto je compte vous montrer un maximum de techniques telles que la gnration d'un terrain 3D, un moteur de particules, le bump-mapping, le cell-shading, etc. Comment alors ne pas saliver devant toutes ces possibilits qui s'ouvrent vous ? D'autant que la plupart sont trs accessibles d'un point de vue difficult de programmation. D'un point de vue pdagogique, nous ne verrons la 3D qu' partir du 4e chapitre, car des connaissances de base sont ncessaires afin de se lancer correctement.

Fentrage et vnements
OpenGL ne fournit que des fonctions 3D qui doivent tre excutes dans un contexte graphique dj cr. Il nous faut donc choisir un systme pour crer les fentres et grer les vnements pour donner une interactivit aux applications. OpenGL tant implment sur de nombreuses platesformes, j'ai choisi de vous faire utiliser la SDL (d'autant que son installation/utilisation vous est enseigne dans le cours de M@teo). La SDL nous permettra ainsi : de crer une fentre ; de charger trs facilement des textures grce SDL_image ; d'utiliser le clavier et la souris ; d'animer nos scnes.

Ainsi nous combinerons la facilit d'utilisation de la SDL avec la puissance d'OpenGL tout en gardant le cot multi plate-formes.

Et les moteurs 3D ?
Je reviendrai en dtail plus tard dans ce tutoriel sur ce qui diffre OpenGL des moteurs 3D et sur comment crer son moteur 3D bas sur OpenGL. Quoi qu'il en soit ce que vous apprendrez ici est souvent applicable ailleurs et vous fournit des explications gnrales sur la programmation 3D. Se plonger dans un moteur tel Irrlicht ou Ogre sera d'autant plus facile que vous saurez ce qu'ils utilisent derrire.

Que dois-je installer ?

Vous tes enfin dcids vous lancer ?

Parfait !

Les fichiers ncessaires pour dvelopper


Si vous avez tlcharg Code::Block ou DevC++pour suivre le tuto de C/C++ ne changez rien, vous avez dj les headers et les .a ncessaires. Vous devez donc avoir :

OS Windows Unix**

include

lib libopengl32.a* libglu32.a

GL/gl.h GL/glu.h libopengl.a libglu.a

* Si vous utilisez Visual Studio, vous devez dj avoir l'quivalent en .lib. ** Nous verrons lors de la cration de notre premier programme quoi rajouter exactement sous Linux.

Les fichiers ncessaires pour excuter


Sous Windows sauf cas rare trs exceptionnel vous avez dj les .dll ncessaires soit : opengl32.dll et glu32.dll. Vous n'aurez pas les fournir avec votre excutable, car ils sont prsents par dfaut sous Windows. Vous devrez continuer fournir les .dll de la SDL. Sous Linux il vous faut les .so associs au .a.

Vrifier que l'acclration 3D est active


Avoir les bons fichiers ne signifie pas forcment que les applications 3D tourneront parfaitement. Si l'acclration matrielle n'est pas active, les performances en seront trs fortement diminues. Vrifiez donc que vous avez bien install les derniers drivers de votre carte graphique. Sous Windows si vous avez l'habitude de jouer des jeux vidos sans problmes c'est que c'est bon ! Sous Linux utilisez le fameux programme glxgears pour tester. Les instructions tant trop dpendantes de votre distribution et de votre carte, reportez-vous aux nombreuses ressources disponibles sur Internet.

Premire application OpenGL avec SDL


Je vous l'ai dit plus haut nous allons utiliser OpenGL dans un contexte SDL. La premire chose faire est donc de crer un projet SDL de base (comme expliqu dans le tuto de M@teo). Nous

viendrons y remplacer tout le code et complter les options de compilation pour rajouter OpenGL lors de la phase d'dition des liens. Si vous utilisez un IDE qui fournit des templates de projet (comme Code::Blocks), ne choisissez pas un projet OpenGL mais bien SDL. Nous le complterons pour avoir un projet SDL/OpenGL. Je fournirai d'ailleurs en fin de chapitre le projet final utilis et vous pourrez vous en servir comme point de dpart pour vos applications OpenGL cres en suivant ce tutoriel.

Code de dpart
Souvenez-vous du code minimal pour ouvrir une fentre SDL : Code : C++ - Slectionner

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

#include <SDL/SDL.h> int main(int argc, char *argv[]) { SDL_Init(SDL_INIT_VIDEO);

SDL_Surface* ecran = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE) SDL_Flip(ecran); bool continuer = true; SDL_Event event; while (continuer) { SDL_WaitEvent(&event); switch(event.type) { case SDL_QUIT: continuer = false; } } SDL_Quit(); return 0; }

Je ne respecte volontairement pas les rgles du C car je compile en C++ et vous inviterai en faire autant dans le futur pour suivre ce tutoriel. En effet les concepts que nous verrons plus tard seront beaucoup plus facilement implmentables en C++. Si vous ne connaissez que le C pour l'instant, rassurez-vous, nous introduirons le concept objet de manire simple et intuitive.

Initialiser SDL en mode OpenGL


La premire chose changer est au niveau de l'initialisation de mode vido. Nous allons

utiliser SDL_OPENGL au lieu de SDL_HWSURFACE : Code : C++ - Slectionner

SDL_SetVideoMode(640, 480, 32, SDL_OPENGL);

Que devient le SDL_DOUBLEBUF utilis avec la SDL normalement pour activer le double-buffering (cf. Travailler avec le double buffer) ? L'quivalent avec OpenGL reviendrait appeler SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); mais il s'avre qu'il est dj activ par dfaut pour OpenGL. Rien de spcial faire donc.

Le premier dessin
Ensuite nous allons faire nos premiers appels OpenGL pour dessiner un simple triangle. Nous rajoutons donc les headers ncessaires : Code : C++ - Slectionner

1 2

#include <GL/gl.h> #include <GL/glu.h>

Avec Visual Studio (uniquement), il faut rajouter #include <windows.h> avant d'inclure les headers OpenGL. Voici le code du triangle qui vous sera expliqu dans le prochaine chapitre : Code : C++ - Slectionner

1 2 3 4 5 6 7

glClear(GL_COLOR_BUFFER_BIT); glBegin(GL_TRIANGLES); glColor3ub(255,0,0); glColor3ub(0,255,0); glColor3ub(0,0,255); glEnd(); glVertex2d(-0.75,-0.75); glVertex2d(0,0.75); glVertex2d(0.75,-0.75);

SDL_Flip(ecran); disparat au profit de l'appel suivant : Code : C++ - Slectionner

1 2

glFlush(); SDL_GL_SwapBuffers();

La premire commande, glFlush, vient s'assurer que toutes les commandes OpenGL ont t excutes, et SDL_GL_SwapBuffers est l'quivalent de l'ancien SDL_Flip.

Le code complet
Code : C++ - Slectionner

1 2 3 4 5

#include <SDL/SDL.h> #include <GL/gl.h> #include <GL/glu.h> int main(int argc, char *argv[])

6 { 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 }

SDL_Init(SDL_INIT_VIDEO); SDL_WM_SetCaption("Mon premier programme OpenGL !",NULL); SDL_SetVideoMode(640, 480, 32, SDL_OPENGL); bool continuer = true; SDL_Event event; while (continuer) { SDL_WaitEvent(&event); switch(event.type) { case SDL_QUIT: continuer = false; } glClear(GL_COLOR_BUFFER_BIT); glBegin(GL_TRIANGLES); glColor3ub(255,0,0); glColor3ub(0,255,0); glColor3ub(0,0,255); glEnd(); glFlush(); SDL_GL_SwapBuffers(); } SDL_Quit(); return 0; glVertex2d(-0.75,-0.75); glVertex2d(0,0.75); glVertex2d(0.75,-0.75);

Compilation
Pour compiler il nous faut rajouter les fichiers cits plus haut. Dans nos options de projet nous rajoutons donc :

IDE / Compilateur Code::Blocks Windows DevC++ Windows Visual Studio Windows Code::Blocks Linux gcc / g++ Linux

lib opengl32 glu32 -lopengl32 -lglu32 opengl32.lib glu32.lib GL GLU -lGL -lGLU

Rsultat

Tlchargez le projet Code::Blocks final, l'excutable Windows et le Makefile Unix (116 Ko)
Pour les utilisateurs de Code::Blocks, vous pouvez ouvrir le projet fourni et faire Project > Save project as user-template pour crer un projet de dpart pour toutes vos applications SDL/OpenGL. Ainsi plus tard vous n'aurez qu' faire File > New Project... > (Onglet) User templates > Votre template pour crer un projet oprationnel immdiatement.

Couleur et interpolation

Dans le screenshot final du chapitre prcdent (le triangle color) vous avez pu admirer la puret et la beaut des couleurs en OpenGL. Pour dfinir la couleur d'un vertex on utilise : Code : C++ - Slectionner

glColor3ub(rouge, vert, bleu);

Les valeurs de rouge, vert et bleu sont des entiers entre 0 et 255. Nous aurions pu aussi bien utiliser glColor3f (avec donc des composantes de couleurs entre 0 et 1) mais la plage de valeur [0..255] est plus facile comprendre car fortement utilise dans les logiciels de dessins. partir d'une couleur prcise, pour connatre ses composantes RVB (ou RGB en anglais) il suffit d'utiliser la palette de couleurs de Windows (par exemple en utilisant Paint).

Un appel glColor affecte la couleur courante, celle qui sera utilise pour les vertices qui seront dfinis aprs. On peut ainsi changer de couleur chaque vertex ou juste de temps en temps. Et pour les points qui ne sont pas des sommets, comment la couleur est-elle dfinie ? Les couleurs des points qui constituent une face sont calcules par interpolation des couleurs des sommets. Citation : Dictionnaire Interpolation : valuation de la valeur d'une fonction entre deux points de valeurs connues.

Cela donne donc lieu de jolis dgrads entre les sommets.

Exercice

Modifier le code du chapitre prcdent pour dessiner un rectangle avec un dgrad bleu/rouge.

Corrig
Code : C++ - Slectionner

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41

#include <SDL/SDL.h> #include <GL/gl.h> #include <GL/glu.h> int main(int argc, char *argv[]) { SDL_Init(SDL_INIT_VIDEO); SDL_WM_SetCaption("Un joli \"carr\u00E9\"",NULL); SDL_SetVideoMode(640, 480, 32, SDL_OPENGL); bool continuer = true; SDL_Event event; while (continuer) { SDL_WaitEvent(&event); switch(event.type) { case SDL_QUIT: continuer = false; } glClear(GL_COLOR_BUFFER_BIT); glBegin(GL_QUADS); glColor3ub(0,0,255); glVertex2d(-0.75,-0.75); glVertex2d(-0.75,0.75); glColor3ub(255,0,0); glVertex2d(0.75,0.75); glVertex2d(0.75,-0.75); glEnd(); glFlush(); SDL_GL_SwapBuffers(); } SDL_Quit(); return 0; }

Un dgrad trs facilement avec OpenGL

Tlchargez le projet Code::Blocks, l'excutable Windows et le Makefile Unix (116 Ko)


Euh oui mais ton carr n'est pas vraiment carr l !?

En effet par dfaut les coordonnes vont de -1 1 la fois sur X et sur Y quel que soit le ratio (rapport largeur/hauteur) de la fentre. Nous verrons plus tard comment tenir compte du ratio de la fentre et changer les coordonnes min/max. Vous pouvez ds prsent vous amuser dessiner des formes en 2D dans cet intervalle de coordonnes. Jouez avec les couleurs, les formes, les dgrads.

Vertices et polygones

En OpenGL tout est base de polygones, le polygone de base le plus utilis tant le triangle. Mme un model 3D se reprsente avec des polygones qui en composent les faces.

Terrain en mode filaire*

* Nous verrons dans la partie II de ce tuto comment gnrer un terrain en OpenGL. Pour dessiner un polygone il faut : dfinir tous les sommets qui le composent ; indiquer OpenGL comment il doit utiliser ses sommets.

Sommet se dit vertex en anglais, vertices au pluriel. Pour rester proches du langage OpenGl et du monde de la 3D, nous garderons l'appellation anglaise. a fait plus pro, isn't it ?

Regardons la 1re ligne du code barbare du chapitre prcedent : Code : C++ - Slectionner

glBegin(GL_TRIANGLES);

Avec GL_TRIANGLES on indique OpenGL qu'il doit faire des triangles avec les vertices qui seront dclars. Voici la liste des modes que nous pouvons utiliser :

mode GL_POINTS GL_LINES

dtail chaque vertex sera reprsent comme un point les vertices seront relis 2 2 pour faire des lignes. Si on dfinit 4 vertices,

mode

dtail le 1er sera reli avec le 2nd, le 3e avec le 4e, mais en aucun cas le 2ene sera reli au 3e les vertices sont connects par des lignes du 1er au dernier. Si on dfinit 3 vertices, le 1er sera reli avec le 2nd et le 2nd avec le 3e identique GL_LINE_STRIP, mais le dernier vertex est aussi reli au 1er (on ralise une boucle : loop) les triplets de vertices sont utiliss pour former des triangles pleins. Ce mode sera le plus utilis lorsque nous reprsenterons des objets complexes base de faces triangulaires

GL_LINE_STRIP

GL_LINE_LOOP

GL_TRIANGLES

Les triangles se touchent, c'est--dire qu'il faut 3 vertices pour faire un GL_TRIANGLE_STRIP premier triangle, et un 4e vertex suffit pour en dfinir un autre car les 2 derniers vertices seront aussi utiliss les triangles se touchent et ont en commun le 1er vertex dfini. Si on dfinit 4 vertices, le 1er le 2e et le 3e forment un triangle, le 1er le 3e et le 4eforment un deuxime triangle. les quadruplets de vertices forment des quadrilatres pleins tout comme prcdemment, les quadrilatres sont connects, il suffit de 2 nouveaux vertices pour en dfinir un nouveau, les 2 derniers vertices du quadrilatre prcdents tant utiliss tous les vertices forment un polygone convexe (comme un pentagone ou tout autre polygone ayant plus de 4 sommets)

GL_TRIANGLE_FAN

GL_QUADS

GL_QUAD_STRIP

GL_POLYGON

Une fois le mode de dessin dfini, il faut dclarer les vertices avec glVertex : Code : C++ - Slectionner

1 2 3

glVertex2d(-0.75,-0.75); glVertex2d(0,0.75); glVertex2d(0.75,-0.75);

Nous l'avons plus haut, une mme fonction OpenGL, ici glVertex, peut tre appele avec un nombre variable d'arguments. Ici nous en utilisons 2 (d'o le 2d) donc nous dfinissions le X et le Y du sommet en question et le Z est automatiquement mis 0. Il va sans dire que l'ordre de dfinition des vertices est important. Il faut suivre le contour du

polygone si on ne veut pas se retrouver avec des aberrations. Pour finir il faut fermer le bloc ouvert par glBegin avec glEnd : Code : C++ - Slectionner

glEnd();

Oublier de fermer un bloc ouvert avec glBegin par glEnd rendra le bloc invalide et rien ne sera dessin.

Quelques options

Si vous tes amens travailler avec des points ou des lignes, vous aurez srement envie d'en changer la taille... OpenGL a pens vous : Code : C++ - Slectionner

1 2

glPointSize( taille ); glLineWidth( largeur );

taille et largeur sont des rels qui valent 1.0 par dfaut.

Comparatif

Pour comprendre l'influence du mode sur le rendu, voici une animation du rendu de 6 vertices avec des modes diffrents :

Certains modes n'ont pas t utiliss car ils n'avaient que peu d'intrt dans cet exemple. Dans le mode GL_QUADS, comme nous avons 6 vertices, les 4 premiers forment un rectangle, mais les 2 autres sont inutiliss.

Fonctions et types

Dans le chapitre prcdent nous avons rencontr nos premires fonctions OpenGL. Par exemple nous avions : Code : C++ - Slectionner

1 2

glBegin(GL_TRIANGLES); glColor3ub(255,0,0);

glVertex2d(-0.75,-0.75);

Pour pouvoir les diffrencier des autres fonctions, les fonctions OpenGL ont une syntaxe particulire dans leur nom :

glNom[NbType] ( [ Paramtres ] );
J'imagine qu'une petite explication de la formule barbare du dessus n'est pas de refus.

gl ou glu : prfixes communs toutes les fonctions OpenGL ; Nom : il s'agit du nom de la fonction comme Begin ou encore Vertex ; Nb : pour les fonctions nombre de paramtres variables dfinit le nombre de paramtres
qui vont suivre (ex : pour Begin aucun, pour Vertex 2) ; Type : pour les fonctions type variable dfinit le type des paramtres utiliss. Nous utiliserons notamment : o i pour integer (entier) ; o f pour float (rel) ; o d pour double (rel plus prcis) ; o ub pour unsigned byte (entier entre 0 et 255). Paramtres : pour finir si la fonction a besoin de paramtres il faut les rentrer.

Avec ce formalisme vous comprenez maintenant que glVertex2i est une fonction qui a besoin de deux paramtres de type entier. Dans nos codes nous serons amens rencontrer des constantes OpenGL. On les reconnat facilement car leur nom est intgralement en majuscules. Ex : GL_TRUE, GL_FALSE qui sont les quivalents de true, et false (vrai et faux). Si vous vous rappelez bien, nous sommes dj passs devant une constante sans nous en rendre compte : Code : C++ - Slectionner

glClear(GL_COLOR_BUFFER_BIT);

Nous disions ici OpenGL d'effacer le tampon d'affichage en passant en paramtre de glClear une constante qui lui permet de savoir quel tampon effacer. tout moment, n'hsitez pas consulter la documentation pour avoir plus de dtails sur l'utilisation d'une fonction. La documention est disponible sur le site d'OpenGL :

Documentation OpenGL*

* par exemple pour avoir le dtail sur glVertex nous cliquons sur gl puis cherchons Vertex.

Un brin de folie mathmatique


Les transformations... un bien grand mot. En prenant notre bon vieux bouquin de Maths de 4e ou 3e nous trouvons comme transformations lmentaires : la translation ; la rotation ; la symtrie* ; le changement d'chelle.

* la symtrie centrale quivaut une rotation de 180 et certaines symtries axiales sont dfinissables via des changements d'chelle (nous y reviendrons lorsque nous verrons comment raliser un miroir en OpenGL) Je ne vais pas m'taler sur l'explication des actions de ces transformations sur les angles, les longueurs... c'tait le boulot de votre prof de collge ! Pour ma part je vais vous parler des matrices, car c'est sous cette forme que sont utilises les transformations en OpenGL (et dans la gomtrie spatiale en gnral). Les matrices vous connaissez ? Oui euh... non, pas la matrice ok... c'est pas gagn... Une matrice se reprsente sous la forme d'un tableau de nombres... mais attention son sens et son utilisation vont au-del de sa reprsentation. Ce n'est pas qu'un tableau . D'une manire gnrale une matrice reprsente une transformation. La matrice dfinit la manire d'voluer d'un systme. Ici ce qui nous intresse c'est l'utilisation gomtrique des matrices pour prdire numriquement le rsultat d'une transformation. Je prendrai l'exemple de la rotation 2D car il est facilement comprhensible et reprsentable. Le principe est le mme pour la 3D, avec une dimension en plus bien videmment.

La rotation d'angle thta sur le plan (voir dessin ci-dessus), s'crit matriciellement :

En prenant le cas particulier de la rotation d'angle thta = 90, le matrice devient :

Maintenant prenons un vecteur de base, disons le vecteur V de coordonnes V=(1,1).

Appliquons-lui la transformation dcrite par la matrice. Pour ce faire calculons simplement le produit* matrice x vecteur : V' = M x V * Un produit matrice x vecteur est assez simple, le dessin ci-dessous l'explique en couleur.

On obtient comme vous le voyez le vecteur V'=(-1,1).

Et en effet en vrifiant graphiquement, le vecteur rouge est bien obtenu par rotation de 90 du vecteur noir comme nous l'avions prvu par le calcul.

Ici nous avons vu comment une matrice pouvait tre utilise pour raliser une rotation. En ralit les matrices utilises sont des matrices 4x4 qui peuvent reprsenter la fois une rotation, une translation et une mise l'chelle . Je ne dtaillerai pas ici les mathmatiques des matrices 3D, mon but tant juste pour l'instant de vous faire comprendre qu'une matrice peut tre (et sera) utilise pour faire des transformations gomtriques.

Les matrices OpenGL


La plupart du temps, avec OpenGL, nous ne manipulerons pas directement ces matrices mais vous devez savoir qu'il existe trois matrices que nous serons amens utiliservia des appels de fonctions simples :

GL_PROJECTION GL_MODELVIEW

dans laquelle nous dfinissons le mode de projection (orthogonale, perspective) pour positionner les objets dans la scne (camra, vertices, lumires et autres effets). C'est celle que nous manipulerons le plus. pour les textures. Nous verrons lors du chapitre sur les textures comment le fait de dfinir une translation grce cette matrice nous permettra de faire des textures animes.

GL_TEXTURE

Transformations cumulatives
Comme je vous l'ai dit tout l'heure en OpenGL on modifie rarement la matrice directement (en affectant des valeurs). On vient plutt modifier la matrice existante grce un appel de fonction. Par exemple si la transformation actuellement stocke dans la matrice est rotation de 90 et qu'on vient demander faire une rotation de -10, la matrice contiendra en fait la transformation cumule c'est--dire rotation de 80. Pour viter de cumuler des transformations et repartir zro en quelque sorte il faut rinitialiser la matrice (toute analogie avec un film est purement fortuite...).

Rinitialiser une matrice


Avant de faire des modifications sur la matrice de transformation, il faut tre sr de son tat de dpart. Pour cela on la rinitialise avec la matrice d'identit.* * La transforme d'un point par la matrice d'identit est lui-mme, en gros elle ne transforme rien. Pour ce faire on utilise la fonction :

glLoadIdentity();
Ainsi nous rajouterons ce morceau de code avant de faire un quelconque dessin : Code : C++ - Slectionner

1 2

glMatrixMode( GL_MODELVIEW ); glLoadIdentity( );

Pile de matrices

Vous l'avez compris maintenant, appliquer une transformation en OpenGL revient multiplier la matrice actuelle par la matrice de notre nouvelle transformation. Avant d'effectuer une transformation, il faut savoir si nous allons l'appliquer uniquement un objet, ou tous ceux qui seront dfinis par la suite. En effet si on applique une rotation un triangle, et qu'on dessine un carr juste aprs il subira aussi cette transformation. Deux fonctions permettent d'viter a : glPushMatrix( ) : sauvegarde la matrice actuelle ; glPopMatrix( ) : restitue la matrice sauvegarde.

Aprs un glPushMatrix, on continue travailler avec l'tat actuel de la matrice, on a juste rajout la possibilit de revenir en arrire. On peut toujours, tout moment, recommencer zro et rinitialiser la matrice de transformation la matrice identit : glLoadIdentity(); Quand une matrice est sauvegarde, elle est mise en tte de la pile (de sauvegarde) des matrices. Un appel glPopMatrix prend la matrice en tte, l'enlve de la pile et l'utilise comme matrice de transformation actuelle. La profondeur de cette pile est de 32 matrices pour GL_MODELVIEW, on peut donc faire 32 appels conscutifs glPushMatrix. Maintenant que nous savons comment prserver notre matrice, voyons tout de mme ce qui nous intresse ici... la modifier pour appliquer des transformations.

Les transformations
Transformation = changement de repre.

Transformation = changement de repre.

Transformation = changement de repre.

transformation = changement de repre


Ah oui au fait, saviez-vous que : ?

Je me permets d'insister (lourdement), car c'est ce qu'il faut assimiler pour devenir le roi des transformations. Dans la vraie vie pour appliquer une transformation un objet nous le placerions d'abord dans le monde puis nous le ferions tourner par exemple. Ici il faut rflchir en terme de repre : par des modifications successives de la matrice GL_MODELVIEW nous plaons, tournons, dimensionnons le repre dans lequel sera ensuite dessin notre objet. Ainsi nous appliquons d'abord les transformations que nous voulons, puis au dernier moment nous dessinons notre objet. J'illustrerai donc les transformations sur la modification apporte au repre. Et pour marquer les

esprits sur le fait que les transformations sont accumules tant que la matrice n'est pas rinitialise, j'effectuerai les transformations les unes la suite des autres. Partons donc du repre de base, ici reprsent uniquement dans le plan (X,Y) par souci de simplicit.

Repre de base

Translation
La translation permet de dplacer le repre actuel selon un vecteur V = (x,y,z) ou x, y, et z sont rels. Mme si on ne veut dplacer que selon une composante, il faut dfinir les autres (en les mettant 0).

glTranslatef ( x , y, z );
Exemple ici : Code : C++ - Slectionner

glTranslated(-2,-1,0);

Donne :

Repre aprs translation

Rotation

La rotation fait tourner le repre actuel d'un angle thta (exprim en degr) selon l'axe dfini par le vecteur V = (x,y,z) ou x, y, et z sont des rels :

glRotated ( thta, x , y, z );
Gnralement on ne fait tourner qu'autour d'un des axes principaux (X Y ou Z) la fois. Les rotations sur le plan (X,Y) se font autour de l'axe Z, donc pour faire tourner notre repre de 45 il faut faire : Code : C++ - Slectionner

glRotated(45,0,0,1);

Repre aprs rotation Je vous l'ai dit tout l'heure, chaque transformation modifie la matrice. Si on ne revient pas en arrire (remise zro, ou restitution d'une matrice sauvegarde), les transformations se combinent. C'est pour a qu'ici le repre a tourn partir de la position qui lui avait t donne aprs la translation.

Changement d'chelle
Ce n'est pas vraiment une homothtie car on peut changer d'chelle diffremment selon les axes. Elle permet de transformer les axes du repre afin de grossir, diminuer, tirer les objets qui y seront dessins ( scale en anglais). Ainsi si l'on appelle

glScalef ( i, j, k );
le nouveau repre sera tel que x' = i * x, y' = j * y, z' = k * z. Si l'on souhaite ne pas modifier un axe en particulier (par exemple Z quand on fait de la 2D) il faut mettre 1 et non 0. Gnralement on applique le mme facteur tous les axes pour ne pas dformer, mais rien ne nous empche de transformer diffremment les axes : Code : C++ - Slectionner

glScalef(2,0.5,1);

Repre aprs changement d'chelle

Importance de l'ordre des transformations


Il est important que vous rflchissiez l'ordre dans lequel vous appliquez vos transformations. Par exemple faire une translation suivie d'une rotation n'a pas forcment le mme rsultat que de faire la rotation puis la translation. Code : C++ - Slectionner

1 2 3 4 5 6 7 8 9

glPushMatrix(); glTranslated(-2,0,0); glRotated(45,0,0,1); dessin1(); //triangle rouge glPopMatrix(); glRotated(45,0,0,1); glTranslated(-2,0,0); dessin2(); //triangle bleu

Ordre des transformations Remarquez au passage l'utilisation qui a t faite de glPushMatrix( ) et glPopMatrix( ), qui nous ont permis, aprs avoir dessin le triangle rouge, de revenir au repre de base afin d'entamer les transformations pour le triangle bleu. Pour tre sr que vous ayez bien saisi les finesses des transformations, rien de tel qu'un exercice pratique et rigolo !

Exercice : une grue


Je vous propose de vous familiariser avec les transformations par l'intermdiaire d'un petit exercice simple : la construction d'une grue 2D contrle par le clavier.

La grue est assez simple et est compose : d'une base ; d'un grand bras ; d'un petit bras ; d'un fil ; d'une caisse.

Je vous conseille de contrler la grue au clavier et ainsi de pouvoir modifier : l'angle entre le bras et la base ; l'angle entre le petit bras et le grand bras ; la longueur du fil (pour faire monter et descendre la caisse).

Gestion du clavier
Je vous laisse libres du choix des touches. Pour ma part j'ai utilis les flches directionnelles : haut/bas pour le fil, gauche/droite pour les bras (shift enfonc pour le grand bras). Une rception des vnements avec SDL_WaitEvent suffit car on veut ne faire bouger la grue que lors de l'appui sur une touche. Pensez toutefois activer la rptition des touches au pralable* : Code : C++ - Slectionner

SDL_EnableKeyRepeat(10,10);

* les valeurs proposes par SDL (SDL_DEFAULT_REPEAT_DELAY et SDL_DEFAULT_REPEAT_INTERVAL) sont trop lentes. Avec 10, 10 vous aurez un mouvement plus fluide. Pour avoir quelque chose d'un chouilla raliste, je vous conseille de limiter la plage de valeurs que peuvent prendre vos variables (angles et longueur). J'utilise : angle grand bras/base entre 10 et 90 ; angle petit bras/grand bras entre -90 et 90 ; longueur entre 10 et 100.

Dessin de la grue

Nous restons encore en 2D mais pour faciliter les choses il serait bien de pouvoir avoir des coordonnes de l'ordre des pixels. Pour ce faire nous allons modifier la matrice de projection pour faire de la projection 2D dont nous spcifierons cette fois les dimensions (alors qu'elles taient par dfaut entre -1 et 1 au pralable). Code : C++ - Slectionner

1 2 3 4 5 6

SDL_WM_SetCaption("Exercice : une grue", NULL); SDL_SetVideoMode(LARGEUR_ECRAN, HAUTEUR_ECRAN, 32, SDL_OPENGL); glMatrixMode( GL_PROJECTION ); glLoadIdentity( ); gluOrtho2D(0,LARGEUR_ECRAN,0,HAUTEUR_ECRAN);

De cette manire nous aurons l'espace de coordonnes suivant :

Enfin dernier conseil avant de vous lcher dans la nature, il peut tre utile tout moment de savoir o est le repre actuel et comment il est orient. Pour ce faire voici une petite fonction que vous pouvez appeler n'importe quand dans votre dessin pour dboguer et mieux visualiser vos transformations. Code : C++ - Slectionner

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

/*

*/

Dessine le repre actuel pour faciliter la comprhension des transformations. Utiliser echelle pour avoir un repre bien orient et position mais avec une chelle diffrente.

void dessinerRepere(unsigned int echelle = 1) { glPushMatrix(); glScalef(echelle,echelle,echelle); glBegin(GL_LINES); glColor3ub(0,0,255); glVertex2i(0,0); glVertex2i(1,0); glColor3ub(0,255,0); glVertex2i(0,0);

17 18 19 20 }

glVertex2i(0,1); glEnd(); glPopMatrix();

Par exemple, aprs avoir dessin ma base si j'appelle Code : C++ - Slectionner

j'obtiens :

dessinerRepere(50);

Je vois donc ici que je suis prt faire la premire rotation et dessiner le grand bras. En ce qui concerne le fil, il doit toujours tre la verticale. Il faut donc que vous trouviez, une fois au bout du petit bras, un moyen d'annuler les rotations pour remettre le repre l'endroit .

Bon courage !
Correction
Je vous donne ma version de la grue. Ce n'est qu'un guide rien ne vous oblige faire exactement pareil. Code : C++ - Slectionner

1 2 3 4 5 6 7 8 9 10 11

#include #include #include #include

<SDL/SDL.h> <GL/gl.h> <GL/glu.h> <cstdlib>

#define LARGEUR_BASE 50 #define HAUTEUR_BASE 20 #define LARGEUR_BRAS_1 150 #define HAUTEUR_BRAS_1 15

12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69

#define LARGEUR_BRAS_2 50 #define HAUTEUR_BRAS_2 10 #define TAILLE_CAISSE 10

#define LARGEUR_ECRAN (LARGEUR_BASE + LARGEUR_BRAS_1 + HAUTEUR_BRAS_2 #define HAUTEUR_ECRAN (HAUTEUR_BASE + LARGEUR_BRAS_1 + HAUTEUR_BRAS_2 int angle1 = 45; int angle2 = -20; int longueur = 50; void Dessiner(); int main(int argc, char *argv[]) { SDL_Event event; SDL_Init(SDL_INIT_VIDEO); atexit(SDL_Quit); SDL_WM_SetCaption("Exercice : une grue", NULL); SDL_SetVideoMode(LARGEUR_ECRAN, HAUTEUR_ECRAN, 32, SDL_OPENGL); glMatrixMode( GL_PROJECTION ); glLoadIdentity( ); gluOrtho2D(0,LARGEUR_ECRAN,0,HAUTEUR_ECRAN); SDL_EnableKeyRepeat(10,10); Dessiner();

while(SDL_WaitEvent(&amp;event)) { switch(event.type) { case SDL_QUIT: exit(0); break; case SDL_KEYDOWN: switch (event.key.keysym.sym) { case SDLK_UP: longueur --; if (longueur < 10) longueur = 10; break; case SDLK_DOWN: longueur ++; if (longueur > 100) longueur = 100; break; case SDLK_LEFT: if ((event.key.keysym.mod &amp; KMOD_LSHIFT) == KMOD_ { angle1++; if (angle1 > 90) angle1 = 90;

70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127

} else { angle2++; if (angle2 > 90) angle2 = 90; } break; case SDLK_RIGHT: if ((event.key.keysym.mod &amp; KMOD_LSHIFT) == KMOD_ { angle1--; if (angle1 < 10) angle1 = 10; } else { angle2--; if (angle2 < -90) angle2 = -90; } break; } break; } Dessiner(); }

return 0; }

/* */

Dessine un rectangle avec comme point de rfrence le milieu du ct gauche

void dessineRectangle(double largeur,double hauteur) { glBegin(GL_QUADS); glVertex2d(0,-hauteur/2); glVertex2d(0,hauteur/2); glVertex2d(largeur,hauteur/2); glVertex2d(largeur,-hauteur/2); glEnd(); }

/*

*/

Dessine le repre actuel pour faciliter la comprhension des transformations. Utiliser "echelle" pour avoir un repre bien orient et positionn mais avec une chelle diffrente.

void dessinerRepere(unsigned int echelle = 1) { glPushMatrix(); glScalef(echelle,echelle,echelle); glBegin(GL_LINES);

128 glColor3ub(0,0,255); 129 glVertex2i(0,0); 130 glVertex2i(1,0); 131 glColor3ub(0,255,0); 132 glVertex2i(0,0); 133 glVertex2i(0,1); 134 glEnd(); 135 glPopMatrix(); 136 } 137 138 void Dessiner() 139 { 140 glClear( GL_COLOR_BUFFER_BIT ); 141 142 glMatrixMode( GL_MODELVIEW ); 143 glLoadIdentity( ); 144 145 /* Je dplace mon rpre initial (actuellement 146 en bas gauche de l'cran) */ 147 glTranslated(LARGEUR_BASE/2,HAUTEUR_BASE,0); 148 149 // La base 150 glColor3ub(254,128,1); 151 dessineRectangle(LARGEUR_BASE,HAUTEUR_BASE); 152 153 //Je me place en haut au milieu de la base 154 glTranslated(LARGEUR_BASE/2,HAUTEUR_BASE/2,0); 155 156 // Le grand bras 157 glRotated(angle1,0,0,1); 158 glColor3ub(248,230,7); 159 dessineRectangle(LARGEUR_BRAS_1,HAUTEUR_BRAS_1); 160 161 // Je me place au bout du grand bras 162 glTranslated(LARGEUR_BRAS_1,0,0); 163 164 // Puis m'occupe du petit bras 165 glRotated(angle2,0,0,1); 166 glColor3ub(186,234,21); 167 dessineRectangle(LARGEUR_BRAS_2,HAUTEUR_BRAS_2); 168 169 // Je me place au bout du petit bras 170 glTranslated(LARGEUR_BRAS_2,0,0); 171 /* J'annule les rotations pour avoir mon repre align 172 avec le repre d'origine */ 173 glRotated(-angle1-angle2,0,0,1); 174 175 // Je dessine le fil 176 glColor3ub(255,255,255); 177 glBegin(GL_LINES); 178 glVertex2i(0,0); 179 glVertex2i(0,-longueur); 180 glEnd(); 181 182 /* Je descends en bas du fil (avec un petit dcalage 183 sur X pour anticiper le dessin de la caisse */ 184 glTranslated(-TAILLE_CAISSE/2,-longueur,0); 185

186 187 188 189 190 191 192 }

// Et je dessine enfin la caisse

glColor3ub(175,175,85); dessineRectangle(TAILLE_CAISSE,TAILLE_CAISSE); glFlush(); SDL_GL_SwapBuffers();

Tlchargez le projet Code::Blocks, l'excutable Windows et le Makefile Unix (118 Ko)


Voil, si vous avez fait l'exercice propos vous tes maintenant les rois des transformations OpenGL. Nous allons enfin pouvoir nous attaquer ce que nous attendons tant : la 3D. Direction le prochain chapitre pour voir comment paramtrer et utiliser la camra.

Du rel l'cran
Quand vous dcrivez votre scne 3D en OpenGL ( coup de glVertex), vous dcrivez le monde tel qu'il est dans l'absolu, dans son propre repre. Pour passer du monde rel l'cran, il faut donner quelques indications OpenGL : dfinir quelle zone de la fentre servira pour le rendu* ; dfinir le mode de projection (perspective par exemple) ; placer la camra dans le monde rel.

* Cette phase est automatiquement ralise la cration de la fentre. Nous verrons dans la partie II comment la changer pour faire du split-screen (plusieurs crans de rendu sur la mme fentre). Grce ces informations, OpenGL pourra dterminer les transformations faire subir aux objets du monde rel 3 dimensions pour les dessiner sur l'cran 2 dimensions.

Dans le monde rel on travaille au niveau des vertices, et sur l'cran au niveau des pixels. Sur le dessin du haut vous avez peut-tre remarqu que je n'ai pas fait apparatre de Z cran. C'tait juste pour ne pas vous embrouiller car l'cran est bien 2 dimensions. Mais le Z cran sert tout de mme donner une information de profondeur des pixels.

Le repre X,Y,Z de l'cran n'est pas direct : si on applique la rgle de la main droite. L'axe Z devrait aller de l'cran vers nous. Cependant la convention a t prise en synthse d'image que l'axe Z s'enfonce dans l'cran.

Quelle est l'unit de distance en OpenGL ? Rponse : celle que vous voulez ! En effet quand nous y pensons qu'est-ce qui fait que quelque chose est grand ou petit en ralit ? Sans rfrentiel il nous est difficile de dterminer visuellement la taille d'un objet. Nous savons qu'il est petit car il y a un autre objet ct dont nous connaissons la taille. Nous savons comparer. Comment savoir que quelque chose est loin ? Parce que quand nous bougeons, sa taille ne varie pas beaucoup et parce que nous mettons du temps nous en approcher. Ainsi en OpenGL se dire que 1 est 1 mtre comme 1 millimtre ou comme 1 kilomtre revient au mme du moment que les proportions sont gardes et que les vitesses des mouvements sont adaptes. En effet si on fait face un cube de 1 de largeur et qu'appuyer une fois sur la touche avancer nous fait bouger de 10000 on se dira : soit je vais trs vite, soit l'objet tait (car on vient de le perdre) trs trs petit en fait. Cependant il est parfois utile de se rapporter des units connues. Si 1 unit OpenGL est 1 mtre, alors il sera facile d'utiliser un moteur physique qui utilise des vraies units.

La perspective
Le passage le plus important dans la 3D est la projection. Pour passer d'un monde dcrit en 3D une fentre avec des pixels 2D il nous faut perdre une dimension et donc projeter. La mthode de projection que nous utiliserons en 3D est la perspective. La perspective est dfinie par la pyramide ci-dessous :

pyramide de clipping Cette pyramide s'appelle la pyramide de clipping. C'est--dire que tout objet ne se trouvant pas l'intrieur de la zone bleue ne sera pas dessin. De plus sa forme permet de dfinir comment projeter les objets sur l'cran en faisant un parallle avec la pyramide relle qui a pour sommet l'oeil de l'utilisateur et coupe son cran :

pyramide relle (ici vue du dessus) Le ratio (vu dans le schma plus haut) est un rapport entre la largeur et la hauteur. Pour une tlvision ce ratio vaut 4/3, des fois 16/9. Pour les tailles informatiques standards (1024x768, 800x600, 640x480) il est aussi de 4/3. Mais votre fentre n'est pas oblige d'utiliser tout l'cran et peut donc avoir ses propres proportions et il existe aussi de plus en plus d'crans avec des ratios spciaux. Il est donc important de ne pas le prendre comme acquis dans vos applications et toujours le dfinir en fonction de la taille de la fentre que vous crez (je ferai une annexe SDL sur comment dtecter les modes disponibles pour le plein cran). Les paramtres near et far dterminent les distances minimales et maximales des objets. En dehors de cet intervalle les objets ne seront pas affichs. Cela posera parfois problme si la valeur de far est trop petite, l'utilisateur risque de voir disparatre des objets quand il s'loigne, ou pire voir la scne apparatre subitement. Nous verrons dans la partie II comment utiliser le brouillard pour

parer ce problme. Pour l'instant une valeur grande (vis--vis de la taille de votre scne) suffira amplement. Le seul paramtre qui peut vous chapper ici est l'angle. Il s'agit de l'angle de vision entre les plans haut et bas de la pyramide (souvent dnot fovy pour field of view sur l'axe y). Gnralement on utilise une valeur aux alentours de 70. Lors de la sortie d' Half-Life 2 il y a eu toute une polmique sur l'angle de vue mal choisi (90) qui rendait certaines personnes mal l'aise. Pour voir l'influence de cet angle, je vous ai fait 3 rendus d'une mme scne avec des angles diffrents :

On constate que si l'on donne un angle petit, a donne un effet de zoom. Et je ne vous cacherai pas que c'est exactement ce qui est utilis dans les jeux pour faire un zoom (jumelles ou sniper).

Application en OpenGL
Pour dfinir la perspective comme mode de projection il suffit d'appeler la fonction

gluPerspective( fovy, ratio, near, far );


Par exemple pour une fentre de 640x480 : Code : C++ - Slectionner

gluPerspective(70,(double)640/480,1,1000);

Cet appel modifie la matrice courante, mme s'il ne s'agit pas de la matrice de projection. Il faut donc bien veiller slectionner la matrice GL_PROJECTION, l'initialiser et ensuite appeler gluPerspective. Voici donc notre nouveau code d'initialisation de notre application OpenGL : Code : C++ - Slectionner

1 2 3 4 5 6

SDL_Init(SDL_INIT_VIDEO); SDL_WM_SetCaption("SDL GL Application", NULL); SDL_SetVideoMode(640, 480, 32, SDL_OPENGL); glMatrixMode( GL_PROJECTION ); glLoadIdentity( ); gluPerspective(70,(double)640/480,1,1000);

Comme la dfinition de la perspective ne concerne que la matrice GL_PROJECTION nous pouvons nous contenter de l'initialiser une seule fois et non pas chaque image. Bien sr si le ratio de la fentre venait changer dynamiquement il faudrait redfinir la perspective au risque de voir tous les objets dforms.

Placer la camra
Maintenant que nous savons comment projeter, il serait bien de pouvoir placer le point de vue n'importe o dans la scne. Pour cela nous utilisons une sorte de camravirtuelle avec l'appel de :

gluLookAt( camX, camY, camZ, cibleX, cibleY, cibleZ, vertX, vertY, vertZ );
camX, camY et camZ dfinissent la position de la camra ; cibleX, cibleY et cibleZ dfinissent la position du point que fixe la camra (le point correspondant se trouvera au centre de la fentre d'affichage) ; vertX, vertY et vertZ dfinissent le vecteur vertical.

Voici 3 images prises avec des paramtres diffrents de la camra pour que vous compreniez l'utilit de chaque paramtre :

gluLookAt(1.5, 1.5, 5, 0, 0, 0, 0, 1, 0); La camra est en (1.5,1.5,5) et regarde en (0,0,0).

gluLookAt(1.5, 1.5, 5, 1.5, 1.5, 0, 0, 1, 0); La camra est en (1.5,1.5,5) et regarde en (1.5,1.5,0) c'est--dire droit devant elle (contrairement l'image prcdente).

gluLookAt(1.5, 1.5, 5, 0, 0, 0, 1, 1, 0); Mme position et regard que prcdemment mais son vecteur vertical est (1,1,0), or la scne a t pense avec une verticale de (0,1,0), l'image est donc penche sur le ct.

L'importance de la verticale

Vous l'avez vu, l'appel gluLookAt vous permet de dfinir la verticale que vous voulez pour votre scne. Le tout est d'tre cohrent et de concevoir votre scne en consquence. Il est nanmoins beaucoup plus pratique de choisir comme verticale un vecteur du repre de base X (1,0,0), Y (0,1,0) ou Z (0,0,1). Entre Y et Z lequel est le meilleur choix ? Bonne question. L'un ou l'autre sont des choix valables : on peut imaginer le passage la 3e dimension comme l'ajout de la profondeur tout comme l'ajout de la hauteur. Dans le monde de la synthse d'image il n'est pas rare de voir l'un ou l'autre. l'avenir, j'essayerai de respecter le choix verticale = Z car il a t fait dans de nombreux jeux et OpenGL semble l'avoir favoris (nous le verrons lors du chapitre sur lesquadriques...).

diteur de FarCry, Z est la verticale

Quand l'appeler ?
Placer la camra revient dplacer tout le monde pour qu'il soit centr sur la camra et orient selon l'axe du regard. Cela influe donc logiquement sur la matriceGL_MODELVIEW et vous ne serez pas tonns donc que l'appel de la camra soit juste aprs la rinitialisation de GL_MODELVIEW. Code : C++ - Slectionner

1 void Dessiner() 2 { 3 glClear( GL_COLOR_BUFFER_BIT ); 4 5 glMatrixMode( GL_MODELVIEW ); 6 glLoadIdentity( ); 7 8 gluLookAt(3,3,3,0,0,0,0,0,1); //exemple 9 10 /* Dessin 3D ici */ 11 12 glFlush(); 13 SDL_GL_SwapBuffers(); 14 }

Voil vous savez maintenant comment il est possible de passer d'un monde 3D un cran 2D et savez placer la camra. Pour rcapituler, notre nouveau squelette de programme pour dessiner de la 3D : Code : C++ - Slectionner

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49

#include #include #include #include

<SDL/SDL.h> <GL/gl.h> <GL/glu.h> <cstdlib>

void Dessiner(); int main(int argc, char *argv[]) { SDL_Event event; SDL_Init(SDL_INIT_VIDEO); atexit(SDL_Quit); SDL_WM_SetCaption("SDL GL Application", NULL); SDL_SetVideoMode(640, 480, 32, SDL_OPENGL); glMatrixMode( GL_PROJECTION ); glLoadIdentity(); gluPerspective(70,(double)640/480,1,1000); Dessiner(); for (;;) { SDL_WaitEvent(&amp;event); switch(event.type) { case SDL_QUIT: exit(0); break; } Dessiner(); } return 0; } void Dessiner() { glClear( GL_COLOR_BUFFER_BIT ); glMatrixMode( GL_MODELVIEW ); glLoadIdentity( ); gluLookAt(3,3,3,0,0,0,0,0,1);

/* Dessin 3D */

50 51 52 53 }

glFlush(); SDL_GL_SwapBuffers();

Maintenant il ne reste plus qu' remplir la partie dessin. Et pour a direction le chapitre suivant avec notre premier dessin 3D : un cube !

Un cube
L'exemple du cube est assez simple et nous continuerons avec lors de la cration de notre premire scne texture. Voyons tout d'abord comment est constitu un cube :

Coordonnes des sommets du cube Un cube est compos de 8 sommets et 6 faces, chaque face faisant intervenir 4 sommets. Nous n'allons pas nous contenter de dessiner chacun des 8 sommets, nous n'aurions pas de faces pleines. Il nous faut donc dcrire les faces une par une, en indiquant les sommets qu'elles font intervenir.

Dcrire des sommets en 3D


Ici nous devons dfinir 3 coordonnes pour chaque sommet : X, Y et Z. Nous ne pouvons donc plus utiliser le basique glVertex2d que nous utilisions auparavant. Il va falloir donc utiliser la version avec 3 arguments soitglVertex3d. Exemple : Code : C++ - Slectionner

glVertex3d(1,1,1);

Le 3d de glVertex3d ne veut pas dire ouais je fais de la 3D ! . Rappelez-vous le chapitre Notions de base, 3 spcifie le nombre d'arguments et d le fait que les arguments donns soient des double (rels).

Dcrire le cube
Les faces tant des carrs, nous allons utiliser le mode GL_QUADS pour dcrire les vertices. Pour diffrencier les faces nous leur attribuerons une couleur diffrente ; nous ferons la premire (celle avec les flches) en rouge. En ce qui concerne la camra, j'ai choisi de la placer en (3,4,2) pour regarder le cube centr en (0,0,0), car cela donnera un bon angle de vue (c'est la position utilise pour le schma plus haut). Ce qui donne donc : Code : C++ - Slectionner

1 void Dessiner() 2 { 3 glClear( GL_COLOR_BUFFER_BIT ); 4 5 glMatrixMode( GL_MODELVIEW ); 6 glLoadIdentity( ); 7 8 gluLookAt(3,4,2,0,0,0,0,0,1); 9 10 glBegin(GL_QUADS); 11 12 glColor3ub(255,0,0); //face rouge 13 glVertex3d(1,1,1); 14 glVertex3d(1,1,-1); 15 glVertex3d(-1,1,-1); 16 glVertex3d(-1,1,1); 17 glEnd(); 18 19 glFlush(); 20 SDL_GL_SwapBuffers(); 21 }
Facile ! Il suffit de choisir un point de dpart et de suivre le contour de la face pour dcrire les sommets un par un. Je vous conseille, pour les dcrire, de vous imaginer faisant face la face (hum... ) et de les numrer un un dans le sens inverse des aiguilles d'une montre. En effet nous verrons plus tard que cela nous sera utile quand nous voudrons viter de dessiner les faces caches. Encore une fois quelque soit le sens que vous dcidez d'utiliser, le tout est d'tre cohrents et de garder le mme sens. Continuons donc avec la 2e face, disons celle gauche de la premire (quand on regarde le schma) et faisons-la en vert. Code : C++ - Slectionner

1 2 3 4 5

glColor3ub(0,255,0); //face verte glVertex3d(1,-1,1); glVertex3d(1,-1,-1); glVertex3d(1,1,-1); glVertex3d(1,1,1);

Ce qui me donne le rsultat suivant :

Bon maintenant parce que je veux vous montrer un problme important, attaquons-nous la face de derrire que nous ferons... devinez... en bleu ! Rien de compliqu, il suffit de suivre le schma de tout l'heure pour avoir rapidement les coordonnes et crire le code appropri. Code : C++ - Slectionner

1 2 3 4 5

glColor3ub(0,0,255); //face bleue glVertex3d(-1,-1,1); glVertex3d(-1,-1,-1); glVertex3d(1,-1,-1); glVertex3d(1,-1,1);

Et voil le rsultat :

En effet vous ne rvez pas, la face bleue qui tait cense tre derrire, donc en majeure partie cache par la rouge et la verte vient se dessiner par-dessus ces dernires. Et c'est tout fait logique, OpenGL dessine les carrs dans l'ordre dans lequel on les dfinit. Il ne se soucie pour l'instant pas de savoir s'il y a dj quelque chose l o il dessine et vient donc craser les faces prcdentes. La solution ? Le Z-Buffer !

Le Z-Buffer
Le Z-Buffer ou Depth-Buffer (pour tampon de profondeur) sert viter le problme que nous venons de rencontrer.

Principe du Z-Buffer
Le Z-Buffer est un tampon (buffer) qui stocke la profondeur (d'o le Z, X et Y sur l'cran tant la position en pixel) de chaque pixel affich l'cran. Ensuite quand OpenGL demande dessiner un pixel un endroit, il compare la profondeur du point afficher et celle prsente dans le buffer. Si le nouveau pixel est situ devant l'ancien, alors il est dessin et la valeur de la profondeur dans le buffer est mise jour. Sinon, le pixel tait alors situ derrire et n'a donc pas lieu d'tre affich. Pour bien comprendre, suivons le cheminement qui est fait. Au dpart le buffer (ici de taille 3x3 pour l'exemple) est initialis des distances infinies (le plus loin possible vers le fond de votre cran).

OpenGL demande dessiner un pixel en (2,2,5). Valeur demande : 5. Valeur prsente : infini. 5 < infini => OK pour dessin Dessin du pixel + Mise jour du Z-buffer avec la valeur 5.

Demande de dessin d'un pixel en (2,2,10). Valeur demande : 10. Valeur prsente : 5. 10 > 5 => Dessin refus

Application dans OpenGL


Heureusement pour nous OpenGL gre trs bien cette technique, il nous faut juste modifier notre programme pour l'activer et bien l'utiliser !

Pour cela il nous faut : activer son utilisation : aprs la cration de la fentre OpenGL il faut simplement appeler : Code : C++ - Slectionner

glEnable(GL_DEPTH_TEST);

le rinitialiser chaque nouvelle image, en mme temps que le buffer des pixels : Code : C++ - Slectionner

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) ;

Ce qui nous donne un code complet (avec le dbut de notre cube) : Code : C++ - Slectionner

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40

#include #include #include #include

<SDL/SDL.h> <GL/gl.h> <GL/glu.h> <cstdlib>

void Dessiner(); int main(int argc, char *argv[]) { SDL_Event event; SDL_Init(SDL_INIT_VIDEO); atexit(SDL_Quit); SDL_WM_SetCaption("SDL GL Application", NULL); SDL_SetVideoMode(640, 480, 32, SDL_OPENGL); glMatrixMode( GL_PROJECTION ); glLoadIdentity(); gluPerspective(70,(double)640/480,1,1000); glEnable(GL_DEPTH_TEST); Dessiner(); for (;;) { SDL_WaitEvent(&amp;event); switch(event.type) { case SDL_QUIT: exit(0); break; } Dessiner(); } return 0; }

41 void Dessiner() 42 { 43 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); 44 45 glMatrixMode( GL_MODELVIEW ); 46 glLoadIdentity( ); 47 48 gluLookAt(3,4,2,0,0,0,0,0,1); 49 50 glBegin(GL_QUADS); 51 52 glColor3ub(255,0,0); //face rouge 53 glVertex3d(1,1,1); 54 glVertex3d(1,1,-1); 55 glVertex3d(-1,1,-1); 56 glVertex3d(-1,1,1); 57 58 glColor3ub(0,255,0); //face verte 59 glVertex3d(1,-1,1); 60 glVertex3d(1,-1,-1); 61 glVertex3d(1,1,-1); 62 glVertex3d(1,1,1); 63 64 glColor3ub(0,0,255); //face bleue 65 glVertex3d(-1,-1,1); 66 glVertex3d(-1,-1,-1); 67 glVertex3d(1,-1,-1); 68 glVertex3d(1,-1,1); 69 70 glEnd(); 71 72 glFlush(); 73 SDL_GL_SwapBuffers(); 74 }
Et en effet en excutant notre nouveau code nous obtenons ceci :

Ouf ! Nous avons enfin ce que nous dsirions. Bien pratique ce z-buffer !

Finir le cube

ce stade il ne vous reste plus qu' complter le code avec les 3 faces restantes et leur choisir de belles couleurs. N'oubliez pas que vous pouvez utiliser le schma du dbut pour facilement trouver les coordonnes des sommets de la face en cours. Voici mon code pour ceux qui ne veulent pas essayer eux-mmes, ou simplement pour comparer : Code : C++ - Slectionner

1 glBegin(GL_QUADS); 2 3 glColor3ub(255,0,0); //face rouge 4 glVertex3d(1,1,1); 5 glVertex3d(1,1,-1); 6 glVertex3d(-1,1,-1); 7 glVertex3d(-1,1,1); 8 9 glColor3ub(0,255,0); //face verte 10 glVertex3d(1,-1,1); 11 glVertex3d(1,-1,-1); 12 glVertex3d(1,1,-1); 13 glVertex3d(1,1,1); 14 15 glColor3ub(0,0,255); //face bleue 16 glVertex3d(-1,-1,1); 17 glVertex3d(-1,-1,-1); 18 glVertex3d(1,-1,-1); 19 glVertex3d(1,-1,1); 20 21 glColor3ub(255,255,0); //face jaune 22 glVertex3d(-1,1,1); 23 glVertex3d(-1,1,-1); 24 glVertex3d(-1,-1,-1); 25 glVertex3d(-1,-1,1); 26 27 glColor3ub(0,255,255); //face cyan 28 glVertex3d(1,1,-1); 29 glVertex3d(1,-1,-1); 30 glVertex3d(-1,-1,-1); 31 glVertex3d(-1,1,-1); 32 33 glColor3ub(255,0,255); //face magenta 34 glVertex3d(1,-1,1); 35 glVertex3d(1,1,1); 36 glVertex3d(-1,1,1); 37 glVertex3d(-1,-1,1); 38 39 glEnd();
Et le rsultat graphique correspondant :

Comme vous avez le code sous les yeux vous savez que vous n'avez pas trich et que le code fait bien un cube 3D. Mais personnellement je vois 3 quadrilatres de couleur, je peux faire pareil sous Paint en 30 secondes, la preuve : Ok c'est moche et mal fait mais avec un peu de soin j'aurais pu avoir pareil ! Il est donc temps de profiter de la puissance de la 3D temps rel et d'animer notre cube pour le voir sous toutes les coutures !

Animation
Pour animer notre cube nous allons simplement le faire tourner en utilisant ce que vous connaissez dj par coeur : la rotation ! Au niveau du dessin nous n'avons vraiment pas grand chose changer, juste faire tourner le repre avant de dessiner le cube. Nous allons le faire tourner la fois sur Z (la verticale) et X donc nous avons besoin de 2 variables globales* pour chacun des angles contrler.

* En gnral en programmation on essaye d'viter les variables globales mais ici nous faisons en quelque sorte du prototypage pour apprendre et tester les concepts OpenGL, ce n'est donc vraiment pas bien grave. Le code, simplifi, du programme devient alors : Code : C++ - Slectionner

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32

#include #include #include #include

<SDL/SDL.h> <GL/gl.h> <GL/glu.h> <cstdlib>

void Dessiner(); double angleZ = 0; double angleX = 0; int main(int argc, char *argv[]) { }

//le code du main

void Dessiner() { glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); glMatrixMode( GL_MODELVIEW ); glLoadIdentity( ); gluLookAt(3,4,2,0,0,0,0,0,1); glRotated(angleZ,0,0,1); glRotated(angleX,1,0,0);

//dessin du cube
glFlush(); SDL_GL_SwapBuffers(); }

En terme de code SDL nous voulons que ces angles soient modifis automatiquement avec le temps. On ne peut donc plus se permettre d'attendre les vnements avecSDL_WaitEvent et nous allons en consquence utiliser SDL_PollEvent pour rcuprer les vnements s'il y en a puis animer notre cube. Nous modifions donc le code de notre boucle d'affichage pour incrmenter nos angles chaque image : Code : C++ - Slectionner

1 for (;;) 2 { 3 while (SDL_PollEvent(&amp;event)) 4 { 5 6 switch(event.type) 7 { 8 case SDL_QUIT: 9 exit(0); 10 break; 11 } 12 } 13 14 angleZ += 1; 15 angleX += 1; 16 17 Dessiner(); 18 19 }

Grer la vitesse d'animation


En testant le code actuel vous voyez que le cube tourne beaucoup trop vite, nous n'avons franchement rien le temps de voir. Il faut donc introduire des vitesses de rotation. Ces vitesses ne doivent pas dpendre de l'ordinateur sur lequel le programme est lanc et donc doivent prendre en compte le temps rel. Pour ce faire, chaque image (chaque passage dans la boucle donne lieu une image), il faut dterminer combien de temps il s'est pass depuis la dernire image et faire bouger le cube en consquence. Nous avons donc besoin de 3 variables : une pour garder en mmoire le temps qu'il tait lors de la dernire image : last_time ; une pour avoir le temps de l'image actuelle : current_time ; une (par commodit) pour le temps coul : ellapsed_time.

Pour connatre le temps coul, en millisecondes, depuis le lancement de l'application nous utiliserons l'instruction SDL_GetTicks(); Le principe est alors le suivant. 1. On initialise une premire fois, avant de rentrer dans notre boucle d'affichage last_time avec le temps actuel.

2. chaque image on rcupre le temps actuel dans current_time. 3. On utilise la diffrence entre le temps actuel et le temps qu'il tait lors de l'ancien passage pour savoir combien de temps s'est coul. On stocke le rsultat dansellapsed_time. 4. On ralise nos mouvements en fonction du temps coul. 5. On finit par affecter last_time la valeur de current_time car nous passons une nouvelle image et donc le prsent devient du pass ( . En ce qui concerne l'unit de mesure des vitesses, comme le temps coul est donn en millisecondes et que les angles utiliss dans glRotate sont en degrs, il s'agit tout simplement de degrs par milliseconde. Dans notre cas j'utiliserai 0.05 /ms. La traduction en code du principe tout juste voqu est la suivante : Code : C++ - Slectionner c'est beau !)

1 Uint32 last_time = SDL_GetTicks(); 2 Uint32 current_time,ellapsed_time; 3 4 for (;;) 5 { 6 while (SDL_PollEvent(&amp;event)) 7 { 8 9 switch(event.type) 10 { 11 case SDL_QUIT: 12 exit(0); 13 break; 14 } 15 } 16 17 current_time = SDL_GetTicks(); 18 ellapsed_time = current_time - last_time; 19 last_time = current_time; 20 21 angleZ += 0.05 * ellapsed_time; 22 angleX += 0.05 * ellapsed_time; 23 24 Dessiner(); 25 26 }

Ne pas monopoliser le CPU


a rame ! Le programme prend 100% du CPU rien que pour faire tourner un simple cube, je ne vais jamais pouvoir faire un jeu ! En effet si on regarde la charge du processeur impose par notre application on voit qu'il est totalement occup grer notre programme :

Cela ne veut pas dire que votre programme est lent c'est juste que nous bouclons en permanence et que nous ne prenons jamais de pause. En ralit nous n'avons pas vraiment besoin de boucler tout le temps. Nous allons utiliser une technique possible (celle que je prfre et donc souhaite vous expliquer) : limiter les FPS (frames per second - images par seconde). En effet pour avoir une animation trs fluide il nous suffit de 50 images par seconde. En fixant le nombre d'images par secondes dsires par votre application, il est alors possible de la faire s'endormir un certain temps si elle va plus vite que ncessaire, ce qui soulagera (mme s'il ne s'en plaint pas) le processeur. Pour ce faire nous allons calculer chaque image combien de temps nous avons mis pour la dessiner, si nous avons t plus rapides que le temps moyen ncessaire, nous stopperons l'excution pour un certain temps. Par exemple, autoriser 50 images par seconde donne chaque image 20 millisecondes pour s'afficher. Imaginons qu'une image mette 5 ms s'afficher rellement, il reste alors 15 ms tuer. Plutt que de passer directement l'image suivante, nous allons endormir l'application pendant ces 15 ms. En terme de code nous allons utiliser SDL_GetTicks comme auparavant pour dterminer le temps coul entre le dbut et la fin de la cration de l'image. Nous utiliseronsSDL_Delay pour suspendre temporairement l'application. Code : C++ - Slectionner

1 Uint32 start_time; //nouvelle variable 2 3 for (;;) 4 { 5 start_time = SDL_GetTicks(); 6 while (SDL_PollEvent(&amp;event)) 7 { 8 9 switch(event.type) 10 { 11 case SDL_QUIT: 12 exit(0); 13 break; 14 case SDL_KEYDOWN: 15 animation = !animation; 16 break; 17 } 18 } 19 20 current_time = SDL_GetTicks(); 21 ellapsed_time = current_time - last_time;

22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 }

last_time = current_time; angleZ += 0.05 * ellapsed_time; angleX += 0.05 * ellapsed_time; Dessiner(); ellapsed_time = SDL_GetTicks() - start_time; if (ellapsed_time < 10) { SDL_Delay(10 - ellapsed_time); } } return 0;

Notez qu'ici nous ne voulons pas savoir combien de temps il s'est pass depuis la dernire fois mais combien de temps notre image a pris se dessiner (j'y ai inclus la gestion des vnements). Il y a donc un appel SDL_GetTicks au dbut de notre boucle et un appel la toute fin. Je rutilise ellapsed_time par commodit mais pas les autres variables pour ne pas mlanger les 2 concepts : limitation des FPS et gestion du temps dans les animations. Code final : Code : C++ - Slectionner

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

#include #include #include #include

<SDL/SDL.h> <GL/gl.h> <GL/glu.h> <cstdlib>

void Dessiner(); double angleZ = 0; double angleX = 0; int main(int argc, char *argv[]) { SDL_Event event; SDL_Init(SDL_INIT_VIDEO); atexit(SDL_Quit); SDL_WM_SetCaption("SDL GL Application", NULL); SDL_SetVideoMode(640, 480, 32, SDL_OPENGL); glMatrixMode( GL_PROJECTION ); glLoadIdentity(); gluPerspective(70,(double)640/480,1,1000); glEnable(GL_DEPTH_TEST); Dessiner(); Uint32 last_time = SDL_GetTicks(); Uint32 current_time,ellapsed_time; Uint32 start_time;

31 32 for (;;) 33 { 34 start_time = SDL_GetTicks(); 35 while (SDL_PollEvent(&amp;event)) 36 { 37 38 switch(event.type) 39 { 40 case SDL_QUIT: 41 exit(0); 42 break; 43 } 44 } 45 46 current_time = SDL_GetTicks(); 47 ellapsed_time = current_time - last_time; 48 last_time = current_time; 49 50 angleZ += 0.05 * ellapsed_time; 51 angleX += 0.05 * ellapsed_time; 52 53 Dessiner(); 54 55 ellapsed_time = SDL_GetTicks() - start_time; 56 if (ellapsed_time < 10) 57 { 58 SDL_Delay(10 - ellapsed_time); 59 } 60 61 } 62 63 return 0; 64 } 65 66 void Dessiner() 67 { 68 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); 69 70 glMatrixMode( GL_MODELVIEW ); 71 glLoadIdentity( ); 72 73 gluLookAt(3,4,2,0,0,0,0,0,1); 74 75 glRotated(angleZ,0,0,1); 76 glRotated(angleX,1,0,0); 77 78 glBegin(GL_QUADS); 79 80 glColor3ub(255,0,0); //face rouge 81 glVertex3d(1,1,1); 82 glVertex3d(1,1,-1); 83 glVertex3d(-1,1,-1); 84 glVertex3d(-1,1,1); 85 86 glColor3ub(0,255,0); //face verte 87 glVertex3d(1,-1,1); 88 glVertex3d(1,-1,-1);

89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 }

glVertex3d(1,1,-1); glVertex3d(1,1,1); glColor3ub(0,0,255); //face bleue glVertex3d(-1,-1,1); glVertex3d(-1,-1,-1); glVertex3d(1,-1,-1); glVertex3d(1,-1,1); glColor3ub(255,255,0); //face jaune glVertex3d(-1,1,1); glVertex3d(-1,1,-1); glVertex3d(-1,-1,-1); glVertex3d(-1,-1,1); glColor3ub(0,255,255); //face cyan glVertex3d(1,1,-1); glVertex3d(1,-1,-1); glVertex3d(-1,-1,-1); glVertex3d(-1,1,-1); glColor3ub(255,0,255); //face magenta glVertex3d(1,-1,1); glVertex3d(1,1,1); glVertex3d(-1,1,1); glVertex3d(-1,-1,1); glEnd(); glFlush(); SDL_GL_SwapBuffers();

En comparaison, pour une animation de la mme fluidit, la charge moyenne du processeur est ngligeable :

Notes diverses : il est aussi possible d'utiliser des timers pour limiter les FPS (voir la doc de SDL_AddTimer ainsi que son exemple). Ce n'est pas la solution retenue ici ; les adeptes de la doc ne manqueront pas de signaler le problme de la granularit du temps de pause (cf. SDL_Delay). Dans les tests effectus pour rdiger ce tutoriel, le temps de pause rel n'excdait jamais le temps demand de plus de 1 milliseconde. Le nombre rel d'images par seconde est donc gal (ou trs proche) au nombre fix ; en terme d'ergonomie cela peut faire peur certains de faire s'endormir le programme un certain temps. Qu'ils soient rassurs, mme dans une application 3D bien plus complexe, la gestion des vnements n'est en rien altre.

Charger une texture

Grce notre choix d'utiliser OpenGL avec la SDL, vous allez voir que la phase de chargement des textures nous sera hautement simplifie. Mais avant mme de pouvoir charger une quelconque texture, encore faut-il en avoir... Pour cela je vous propose de travailler avec ce pack de textures hautes rsolutions. Il est assez volumineux (124 Mo) mais assez complet et contient des textures photoralistes classes dans diverses catgories : sols, caisses, vgtaux, mtaux, pierres, etc.

Exemples de textures incluses dans le pack Que vous le tlchargiez ou non je fournirai quand mme les textures utilises dans l'exemple en fin de chapitre. Vous pouvez donc vous en contenter.

Format et taille
Nous utiliserons principalement deux formats pour les textures : .jpg et .png. Le format .jpg est parfait pour les textures, car il donne les plus petites tailles de fichier sur des images complexes (comme les textures photoralistes). Le format .png (24 bits) quant lui est utilis, car il gre trs bien la transparence. La largeur et la hauteur des textures doivent imprativement tre des puissances de 2 (64,128,512,1024). En effet si elles ne le sont pas, les textures seront de toute faon redimensionnes en interne pour respecter cette contrainte et vous risquez donc de perdre inutilement en qualit. En terme de qualit visuelle, plus la rsolution de la texture est grande, meilleur est le rsultat. Le pack que je vous fournis contient principalement des textures haute-rsolution (512x512) avec lesquelles vous n'aurez pas de mal avoir des rendus de meilleure qualit que Half-Life premier du nom (effets de lumire mis part pour l'instant).

Utiliser SDL_Image pour charger une texture


Le code ncessaire pour crer une texture OpenGL partir d'un tableau de pixels n'est pas extrmement compliqu. Cependant la phase la plus pnible est le chargement d'un fichier image

(.jpg, .bmp, .tga ou autre). Heureusement pour nous SDL_Image est l ; nous aurons simplement l'utiliser pour qu'elle nous retourne une SDL_Surface partir d'un nom de fichier. Une fois cette surface cre, elle doit tre retourne verticalement car SDL et OpenGL n'ont pas les mmes conventions. Vous avez d le remarquer lors du chapitre sur les transformations car le (0,0) en OpenGL tait en bas gauche alors que celui en SDL (comme indiqu dans le cours de M@teo) est en haut gauche. La dernire chose faire est de convertir le tableau de pixels contenus dans la surface en texture OpenGL par des appels OpenGL appropris. Oh l l ! a a l'air compliqu tout a... Je vais russir faire a moi ?

Savoir coder vous-mmes cette phase n'est pas ncessaire pour la comprhension de la suite du chapitre. Le plus important est l'utilisation des textures cres. Nous reviendrons plus en dtail sur les appels en question lorsque nous verrons les textures procdurales mais pour l'instant je vous donne tout a sur un plateau d'argent, voil :

Tlchargez le code pour charger une texture (3 Ko)


Dans l'archive, je vous fournis deux fichiers : sdlglutils.h et sdlglutils.cpp. J'ai choisi ce nom car j'y rajouterai petit petit des fonctions utiles pour ce tuto qui ne sont l que pour nous simplifier la vie mais que vous auriez pu faire vous-mmes avec un peu plus de connaissances. La fonction qui nous intresse pour l'instant est loadTexture qui s'utilise trs simplement : Code : C++ - Slectionner

1 2

#include "sdlglutils.h" GLuint identifiant_texture = loadTexture("ma_texture.jpg");

Si vous tes curieux vous pouvez regarder le code derrire mais il n'a rien de compliqu, juste ce que je vous ai expliqu plus haut (chargement de l'image, retournement, et cration texture OpenGL). Le type renvoy est GLuint soit l'quivalent d'un unsigned int. Cependant je n'utiliserai jamais le type unsigned int pour les textures pour ne pas tre tent dans mon code de faire des calculs dessus, en effet ce nombre retourn a une signification bien prcise : c'est l'identifiant de la texture OpenGL cre. chaque fois que nous voudrons utiliser cette texture, il suffira d'utiliser cet identifiant.

Activer le texturing
Comme nous avons dj cr des scnes en 3D sans texture nous savons que le texturing n'est pas activ par dfaut. Pour l'activer il suffit donc d'appeler : Code : C++ - Slectionner

glEnable(GL_TEXTURE_2D);

Vous pouvez avoir envie dans vos scnes de faire cohabiter des objets texturs avec des objets non texturs (pourquoi pas aprs tout...). Pour ce faire il suffit de dsactiver le texturing temporairement avec :Code : C++ - Slectionner

glDisable(GL_TEXTURE_2D);

Voil nous sommes prts, nous savons charger une texture, nous savons activer le texturing. Maintenant voyons comment, en reprenant notre cube 3D du chapitre prcdent, appliquer une texture sur un objet.

Plaquage de texture
Partons du code que nous avions pour dfinir la premire face de notre cube : Code : C++ - Slectionner

1 2 3 4 5 6

glBegin(GL_QUADS); glVertex3d(1,1,1); glVertex3d(1,1,-1); glVertex3d(-1,1,-1); glVertex3d(-1,1,1); glEnd();

Nous allons utiliser la texture stainedglass05.jpg que vous trouverez dans le pack dans la catgorie window (ou dans l'archive en fin de chapitre). Commenons donc par la charger au lancement de notre application comme expliqu prcdemment : Code : C++ - Slectionner

1 2 3 4 5 6 7 8 9 10

GLuint texture1; //en variable globale int main(int argc, char *argv[]) {

// ... lancement de l'application

la boucle d'affichage //... boucle d'affichage et de gestion des vnements


}

glEnable(GL_DEPTH_TEST); glEnable(GL_TEXTURE_2D); texture1 = loadTexture("stainedglass05.jpg"); // pendant l'initial

Dans le code du dessin, avant de dfinir les vertices de notre premire face texture, il nous faut dire OpenGL que l'on veut utiliser cette texture en appelantglBindTexture avec l'identifiant de notre texture : Code : C++ - Slectionner

glBindTexture(GL_TEXTURE_2D, texture1);

Et maintenant nous allons raliser le plaquage proprement dit de la texture sur la face en faisant correspondre chaque vertex composant notre face une coordonne sur la texture comme reprsent sur le schma ci-dessous :

Correspondance coordonnes texture / coordonnes relles Pour dfinir les coordonnes du vertex nous savons utiliser glVertex, eh bien pour dfinir les coordonnes de texture nous utiliserons :

glTexCoord2d (double x_texture, double y_texture);


L'espace de coordonnes sur la texture est en 2D, le coin en bas gauche a les coordonnes (0,0) et le coin en haut droite est en (1,1), quelque soit la taille en pixels de l'image. Ici je vais commencer par dfinir le vertex (1,1,1) auquel je veux plaquer le coin haut gauche de la texture soit (0,1). Je fais donc : Code : C++ - Slectionner

1 2

glBegin(GL_QUADS); glTexCoord2d(0,1);

glVertex3d(1,1,1);

glTexCoord2d, comme glColor3ub, s'applique tous les vertices dfinis par la suite. Il ne faut donc pas oublier d'y faire nouveau appel avant chaque vertex. En continuant avec les autres sommets de la face cela donne donc le code complet suivant : Code : C++ - Slectionner

1 2 3 4 5 6 7

glBindTexture(GL_TEXTURE_2D, texture1); glBegin(GL_QUADS); glTexCoord2d(0,1); glVertex3d(1,1,1); glTexCoord2d(0,0); glVertex3d(1,1,-1); glTexCoord2d(1,0); glVertex3d(-1,1,-1); glTexCoord2d(1,1); glVertex3d(-1,1,1); glEnd();

Il n'est pas possible de changer de texture en cours de dfinition d'une face. Pour appliquer une texture diffrente la face suivante il faut donc terminer le bloc de vertices par un appel glEnd(), changer la texture puis dfinir la prochaine face : Code : C++ - Slectionner

glBindTexture(GL_TEXTURE_2D, texture1);

2 3 4 5 6 7 8

glBegin(GL_QUADS); //premire face

//dfinition des vertices

glEnd(); glBindTexture(GL_TEXTURE_2D, texture2); //changement de texture glBegin(GL_QUADS); //deuxime face

//dfinition des vertices


glEnd();

Ce n'est bien sr pas ncessaire si vous souhaitez garder la mme texture.

Erreurs frquentes
Une mauvaise utilisation de glTexCoord2d (mauvaises coordonnes, oubli de redfinir glTexCoord2d pour le vertex suivant) donne lieu de drles de rsultats :

Erreur en oubliant de redfinir glTexCoord2d pour le dernier vertex

Ne rigolez pas ces erreurs sont frquentes au dbut et je suis prt parier que a vous arrivera au moins une fois . Vous saurez au moins d'o vient le problme...

Cas d'un triangle


Dans les jeux vidos, la primitive de base la plus utilise est le triangle (nous verrons pourquoi quand nous importerons des models 3D). Le plaquage de texture fonctionne de la mme manire en ne choisissant que 3 points sur la texture, points qui ne sont donc pas forcment des coins de l'image.

Code : C++ - Slectionner glBindTexture(GL_TEXTURE_2D, texture2); 1 glBegin(GL_TRIANGLES); 2 glTexCoord2d(0,0); 3 glVertex3d(1,1,-1); 4 glTexCoord2d(1,0); 5 glVertex3d(-1,1,-1); 6 glTexCoord2d(0.5,1); glVertex3d(0,0,1); glEnd();

Utilisation d'une partie de la texture


Nous venons de le voir dans le cas du triangle, il n'est pas obligatoire d'utiliser toute la texture. C'est une pratique souvent utilise pour regrouper toutes les textures pour un seul objet dans un seul fichier, comme par exemple ici la skin de Tommy Vercetti dans GTA:Vice City :

Texture du hros (une des skins possibles) de GTA:Vice City Il suffit alors, coup de glTexCoord2d bien penss, de dlimiter la zone de la texture que l'on souhaite utiliser pour la face courante :

Code : C++ - Slectionner glBindTexture(GL_TEXTURE_2D, texture3); glBegin(GL_QUADS); 1 glTexCoord2d(0,1); 2 glVertex3d(1,1,1); 3 glTexCoord2d(0,0); 4 glVertex3d(1,1,-1); 5 glTexCoord2d(0.33,0); 6 glVertex3d(-1,1,-1); 7 glTexCoord2d(0.33,1); glVertex3d(-1,1,1); glEnd();

Comme vous le voyez, la texture utilise ici n'est pas un carr (384x128) et pourtant le coin en haut droite a toujours les coordonnes (1,1). Les coordonnes d'un point sur la texture donnent donc une information relative la taille totale de l'image. Ici on a voulu dcouper une partie de 128 pixels de large sur les 384 totaux soit un rapport de 1/3 (0.33).

Texture rptitive
Jusqu' prsent nous appliquions tout ou une partie de la texture sur nos objets. Mais si on essaye de crer un sol (un simple carr de 20x20) dans la scne avec la mme technique on obtient le rsultat suivant :

Code : C++ - Slectionner glBindTexture(GL_TEXTURE_2D, texture4); glBegin(GL_QUADS); 1 glTexCoord2i(0,0); glVertex3i(2 10,-10,-1); 3 glTexCoord2i(1,0); 4 glVertex3i(10,-10,-1); 5 glTexCoord2i(1,1); 6 glVertex3i(10,10,-1); 7 glTexCoord2i(0,1); glVertex3i(10,10,-1); glEnd();

Comme vous le voyez la texture n'est pas faite pour tre tale sur une si grande surface. Nous devons donc faire en sorte qu'elle se rpte ! Dans les exemples que nous avons vus nous n'utilisions que des coordonnes entre 0 et 1. Mais en ralit l'espace de coordonnes de la texture n'a pas de limite :

Espace des coordonnes de texture Quand nous considrions l'image d'origine, nous nous restreignions une partie de cet espace. Mais ici nous voulons faire rpter la texture 10 fois par exemple donc nous allons prendre des coordonnes entre 0 et 10 tout simplement ! Et en effet en modifiant le code en consquence : Code : C++ - Slectionner

1 2

glBindTexture(GL_TEXTURE_2D, texture4); glBegin(GL_QUADS);

3 4 5 6 7

glTexCoord2i(0,0); glTexCoord2i(10,0); glTexCoord2i(10,10); glTexCoord2i(0,10); glEnd();

glVertex3i(-10,-10,-1); glVertex3i(10,-10,-1); glVertex3i(10,10,-1); glVertex3i(-10,10,-1);

Nous obtenons un bien meilleur rsultat visuel :

Texture du sol rpte 10 fois Toutes les textures ne sont pas faites pour tre rptes. Il faut en effet qu'elles soient conues spcialement pour les bords correspondent quand plusieurs rptitions de l'image sont mises bout bout. Dans le pack de textures, vous pouvez tre quasiment srs que toutes les textures de sol, de mur, de plafond, d'herbe, de rocher sont susceptibles d'tre rptes.

Les couleurs
Je n'ai volontairement pas mentionn les couleurs depuis le dbut de ce chapitre pour rester concentr sur la nouveaut du moment : les textures. Mais nos bonnes vielles couleurs ne sont pas mortes pour autant.

Combiner texture et couleur


Nous savons depuis le dbut de ce tuto dfinir la couleur des vertices venir avec glColor3ub. Rien ne nous interdit de continuer l'utiliser en plus de ce que nous venons d'apprendre sur les textures. La dfinition complte d'un vertex peut donc maintenant contenir jusqu' 3 lignes : dfinition de la couleur avec glColor3ub (facultatif) ; dfinition des coordonnes sur la texture avec glTexCoord2d (obligatoire sur utilisation d'une texture) ; dfinition des coordonnes spatiales du vertex avec glVertex3d.

Il n'est bien sr pas obligatoire de redfinir glColor3ub chaque fois si l'on ne souhaite pas changer de couleur. Appliqu au sol vu prcdemment, en affectant des couleurs chaque sommet on obtient donc :

Code : C++ - Slectionner glBindTexture(GL_TEXTURE_2D, texture4); glBegin(GL_QUADS); 1 glColor3ub(255,0,0); 2 //Nouveau 3 glTexCoord2i(0,0); 4 glVertex3i(-10,-10,-1); 5 glColor3ub(0,255,0); 6 //Nouveau 7 glTexCoord2i(10,0); 8 glVertex3i(10,-10,-1); 9 glColor3ub(255,255,0); 10 //Nouveau 11 glTexCoord2i(10,10); 12 glVertex3i(10,10,-1); 13 glColor3ub(255,0,255); 14 //Nouveau 15 glTexCoord2i(0,10); glVertex3i(-10,10,-1); glEnd();

Comme vous le voyez, la couleur agit comme un filtre et vient donner une teinte locale la texture. Tout semble aller trs bien jusqu' ce que l'on dcide d'afficher autre chose juste aprs avoir dfini notre sol, un cube textur par exemple :

Comme vous le voyez le cube est lui aussi affect par la dernire couleur utilise. C'est tout fait logique car glColor3ub par dfinition est utilis pour tous les vertex dfinis la suite (jusqu'au prochain appel glColor3ub). Il faut donc utiliser une couleur neutre qui, applique comme filtre, ne viendra pas modifier la couleur de la texture. Et cette couleur n'est autre que... le blanc ! Ainsi, pour en quelque sorte annuler l'effet des couleurs , il suffit d'utiliser le blanc comme prochaine couleur avec un appel : Code : C++ - Slectionner

glColor3ub(255,255,255);

Et en effet en interposant un appel glColor3ub(255,255,255); entre la dfinition du sol et celle du cube, on obtient bien un cube vierge de tout effet de couleur :

Code : C++ - Slectionner 1 //... dbut du sol glColor3ub(255,0,255); 2 glTexCoord2i(0,10); 3 glVertex3i(-10,10,-1); 4 glEnd(); //fin du sol 5 6 glColor3ub(255,255,255); //on enlve 7 8 la couleur 9 glBegin(GL_QUADS); //dbut du cube 10 glTexCoord2d(0,1); 11 glVertex3d(1,1,1); 12 //... fin du cube

Voil vous savez maintenant les prcautions qu'il faut prendre lorsque l'on souhaite combiner dans un mme code les couleurs et les textures. Rassurez-vous, il ne vous sera pas rare d'oublier de repasser en blanc de temps en temps et vous crerez souvent de jolis effets de couleur involontairement.

Qui aurait cru que nous passerions aussi rapidement d'objets moches mais joliment colors de somptueux objets texturs ?! Comme vous avez pu le voir il n'y a vraiment rien de sorcier dans l'application des textures, il suffit de bien savoir affecter chaque vertex les bonnes coordonnes sur la texture et le tour est jou. Le meilleur moyen de vous assurer que vous avez bien compris est de vous entraner raliser une petite scne en 3D avec des textures, notamment une caisse de 4x2x2avec la texture de caisse (voir caisse.jpg dans zip plus bas) utilise dans ce chapitre. Je vous en ai fait une rapidement dont vous pouvez tlcharger le code plus bas :

Laissez libre cours votre imagination et n'hsitez pas poster vos crations dans les commentaires du chapitre ou sur le forum.

Tlchargez les textures utilises et un exemple de scne texture (482 Ko)


Principe d'utilisation
Nous le verrons juste aprs, il existe dans OpenGL des fonctions toutes faites pour dessiner une sphre, un cylindre, etc.

Cependant ces fonctions ont besoin d'informations sur les intentions du codeur : faut-il texturer l'objet crer, faut-il l'afficher uniquement avec des traits ? Pour cela nous utiliserons un champ (struct) de paramtres. Ces paramtres sont stocks dans une variable de type GLUquadric que l'on se doit d'utiliser d'une manire un peu particulire.

Cration d'une variable de type GLUquadric


Ce n'est pas nous directement de crer une variable de ce type. On doit utiliser un appel OpenGL qui nous renverra un pointeur sur le GLUquadric cr par OpenGL : Code : C++ - Slectionner

1 2

GLUquadric* params; params = gluNewQuadric();

Nous pouvons donc maintenant utiliser la variable params cre pour paramtrer nos objets et les dessiner.

Paramtrage du GLUquadric
Ce champ n'est pas manipulable directement mais seulement par l'intermdiaire de fonctions. Une fois paramtr il sera utilis pour tous les appels de dessin de quadriques dfinis aprs.

Style d'affichage

Les fonctions pour dfinir des quadriques utilisent tout comme nous des appels glVertex pour dfinir les vertices des objets. Cependant comme nous n'avons pas accs directement au code, et donc au glBegin, nous ne pouvons pas spcifier par exemple par un glBegin(GL_LINES); que nous voulons que l'affichage soit fait avec des lignes. Pour ce faire nous devons utiliser la fonction :

gluQuadricDrawStyle(params,style);
qui permet de dfinir quel sera le style d'affichage. style peut valoir :

Style

Explication

Exemple

Style

Explication

Exemple

GLU_POINT L'objet sera dessin avec des points.

GLU_LINE

L'objet sera dessin avec des lignes.

GLU_FILL

L'objet sera dessin avec des faces pleines.

La valeur par dfaut est GLU_FILL. Pour utiliser des faces pleines il n'est donc pas ncessaire d'appeler gluQuadricDrawStyle si aucun style n'a t prcdemment dfini.

Coordonnes de texture

Nous l'avons vu dans le prcdent chapitre, pour utiliser des textures il faut dfinir des coordonnes de texture avec glTexCoord2d. Si nous souhaitons utiliser des textures avec nos quadriques, il faut indiquer OpenGL qu'il doit lui aussi incorporer les appels glTexCoord2d lorsqu'on demandera de dessiner un quadrique. Nous utilisons donc la fonction :

gluQuadricTexture(params,texture);
o texture peut valoir GL_TRUE (vrai : pour activer les coordonnes de texture) ou GL_FALSE (faux : pour ne pas utiliser les coordonnes de texture). La valeur par dfaut tant GL_FALSE, il n'est ncessaire d'appeler initialement gluQuadricTexture que si nous souhaitons utiliser les textures sur nos quadriques.

Code : C++ - Slectionner

glBindTexture(GL_TEXTURE_2D,texture 1 1); GLUquadric* params = 2 gluNewQuadric(); 3 //dessin de la sphere... ( 4 venir) gluDeleteQuadric(params);

Code : C++ - Slectionner

glBindTexture(GL_TEXTURE_2D,texture 1); 1 GLUquadric* params = 2 gluNewQuadric(); 3 4 gluQuadricTexture(params,GL_TRUE); 5 //dessin de la sphere... (

venir)

gluDeleteQuadric(params);

Sur la premire image c'tait pas cens tre une sphre ? On dirait un simple disque...

Sans texture et sans lumire, on ne peut pas comprendre que c'est une sphre. C'est un principe d'optique : la comprhension de la forme d'un objet sur une image 2D utilise pour beaucoup les diffrences de couleurs dues l'clairage (Shape from Shading). Nous rglerons donc ce problme dans quelques chapitres lorsque nous verrons la lumire. En attendant, avec les textures et le mouvement nous n'aurons vraiment pas de mal discerner la forme de nos objets rassurez-vous.

Suppression du GLUquadric
Mme si nous n'avons pas nous-mmes utilis de malloc (ou new en C++) pour crer le GLUquadric, il faut librer la mmoire quand on ne souhaite plus l'utiliser par le biais de la fonction (entraperue dans les exemples de code plus haut) : Code : C++ - Slectionner

gluDeleteQuadric(params);

Maintenant nous savons crer un champ de paramtres pour nos quadriques, le paramtrer et le supprimer. Il est temps de voir ce qui nous intresse rellement, les quadriques et leurs fonctions de dessin !

Les quadriques
Pour bien comprendre quelles sont les faces et vertices gnrs par les appels suivants, tous les quadriques seront reprsents en filaire. J'inclus aussi une version 3D du repre pour montrer que l'axe Z (bleu) est l'axe principal utilis par les quadriques.

La sphre

gluSphere(GLUquadric* params,radius,slices,stacks);
Le premier paramtre est le champ de type GLUquadric que nous avons paramtr prcdemment. Le deuxime, radius, est le plus simple : c'est le rayon de la sphre. slices est le nombre de tranches verticales qui composeront la sphre. stacks est aussi un nombre de tranches mais pour les tranches horizontales.

L'influence des ces deux paramtres est rsume par l'image ci-dessous :

Plus ces deux nombres sont grands, plus la sphre est prcise et ressemble en effet une sphre. La valeur choisie (20x20) donne un rsultat satisfaisant.

Le cylindre et le cne

gluCylinder(GLUquadric* params,base,top,height,slices,stacks);
Le premier paramtre est toujours le mme champ de type GLUquadric. Base est le rayon du cylindre en bas, top est le rayon du cylindre en haut. Pour avoir un vrai cylindre il faut donc utiliser la mme valeur pour les 2, mais en utilisant des valeurs diffrentes pour base et top, nous aurons un cne ! (voir dessin ci-dessous) slices est, comme pour la sphre, le nombre de divisions autour de l'axe Z et nous choisirons une valeur de l'ordre de 20 pour la mme raison. stacks ici ne sert pas grand chose. Mettre une valeur diffrente de 1 ne changerait rien au niveau de la prcision du cylindre/cne ( part quand on le regarde en filaire).

gluCylinder(params,1,0,2,20,1);

Le disque

gluDisk(GLUquadric* params,inner,outer,slices,loops);
Le premier paramtre est toujours le mme champ de type GLUquadric. inner est le rayon interne du disque, souvent 0 mais peut (comme sur l'image) avoir une valeur diffrente. outer est le rayon externe du disque. slices est, comme prcdemment, le nombre de divisions autour de l'axe Z. loops ici ne sert pas grand chose. Mettre une valeur diffrente de 1 rajouterait des faces l'intrieur du disque mais ne rajoute pas de prcision visible ( part en filaire).

Le disque partiel

gluPartialDisk(GLUquadric* params,inner,outer,slices,loops,start,swe ep);


Le disque partiel est comme le disque normal sauf qu'il ne fait pas ncessairement 360. start est l'angle de dpart du disque partiel. Malheureusement pour nous, les concepteurs d'OpenGL n'ont pas suivi la logique mathmatique qui voudrait que les angles soient exprims dans le sens trigonomtrique (sens inverse des aiguilles d'une montre) avec 0 sur l'axe X. Ici 0 pour start place le dbut du disque sur l'axe Y (vert), 90 sur l'axe X (rouge). sweep est la distance angulaire entre le dbut et la fin du disque partiel (180 sur le dessin plus haut).

La prcision donne par slices s'applique ici au disque partiel uniquement. Cela ne sert donc rien de mettre une grande valeur (20) si on utilise un disque partiel de 90 seulement. Une prcision de 5 peut suffire avoir le mme rsultat de qualit sur un angle de 90 (par rapport une prcision de 20 pour un angle de 360).

Un mme GLUquadric pour dessiner plusieurs quadriques


Vous l'avez compris maintenant, l'objet GLUquadric n'est pas un quadrique mais simplement un champ de paramtres utilis lors de l'appel d'une fonction de dessin de quadrique pour spcifier le mode d'affichage. On peut donc tout fait utiliser le mme GLUquadric pour faire dessiner des quadriques tout en changeant, si on le souhaite, les paramtres en cours de route :

Code : C++ - Slectionner

1 2 3 4 5 6 7 8 9 1 0 1 1 1 2 1 3 gluQuadricDrawStyle(params,GLU_FILL ); gluQuadricTexture(params,GL_TRUE); glTranslated(0,0,1); gluSphere(params,0.75,20,20); gluDeleteQuadric(params); gluQuadricDrawStyle(params,GLU_LINE ); gluCylinder(params,1,1,2,20,1); GLUquadric* params = gluNewQuadric(); glBindTexture(GL_TEXTURE_2D,texture 1);

Exercice : une roquette


Ce chapitre n'a rien de compliqu mais jusqu' prsent j'ai fait tout le boulot en vous dtaillant les fonctions pour utiliser les quadriques. Maintenant vous ! Pour vous faire la main sur ces nouvelles fonctions je vous propose de crer une roquette, base sur celles que l'on trouve dans le jeu Half-Life premier du nom. Mon but n'est pas de faire de vous des apprentis terroristes mais juste des pros des quadriques !

Une roquette inspire d'Half-Life

Les textures
Les textures sont elles aussi tires d'Half-Life et lgrement modifies par mes soins. Vous les trouverez dans le pack ci-dessous (et dans le zip final).

Tlchargez les textures pour la roquette (4.53 Ko)

Schma de la roquette

Comme vous pouvez le voir, la roquette est constitue de quatre lments : 1. 2. 3. 4. un cne suprieur (texture rocket_top.jpg) ; un cne intermdiaire (texture rocket_middle.jpg) ; un cne infrieur (texture rocket_bottom.jpg) ; et enfin, a ne se voit pas trop sur le schma, un disque (texture rocket_motor.jpg).

Mthode pour la coder


Pour coder la roquette il nous faut donc procder en deux tapes : au lancement du programme il faut : charger toutes les textures utilises ; au moment de dessiner il faut : 1. crer un GLUquadric ;

2. paramtrer le quadrique pour qu'il gnre les coordonnes de texture automatiquement ; 3. dessiner le premier objet ; 4. se translater la base du deuxime objet ; 5. dessiner le deuxime objet ; 6. etc. 7. dtruire le GLUquadric.

La phase se translater est importante. Comme nous l'avons vu sur les schmas des diffrents types de quadriques, ils sont toujours dessins partir de (0,0,0) dans le repre local. En utilisant intelligemment les transformations, il est donc possible de placer chaque quadrique o on le souhaite.

N'oubliez pas non plus de changer de texture entre chaque quadrique pour ne pas vous retrouver avec une roquette uniformment... moche. Voil vous avez tous les outils pour dessiner cette roquette. Rfrez-vous bien au schma que je vous donne pour respecter les proportions. Toute roquette dforme ne sera pas accepte pour une utilisation sur le terrain ! (Hum...)

vous donc !
Correction
Je ne vous mets ici que le code intressant. Je pars du principe que vous savez parfaitement charger les textures et initialiser l'application. Je fournis bien entendu le code complet dans l'archive finale. Code : C++ - Slectionner

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

/* J'ai choisi de faire une fonction Dessiner Rocket. Je pourrai ainsi l'appeler plusieurs fois, et dans n'importe quelle position initiale du repre initial */

void DrawRocket() { glPushMatrix(); //pour que les transformations soient rversibles

GLUquadric* params = gluNewQuadric(); //cration du quadrique gluQuadricTexture(params,GL_TRUE); //activation des coordonnes de glBindTexture(GL_TEXTURE_2D,top); //texture du haut gluCylinder(params,0.5,0,1.6,20,1); //cne 1 glBindTexture(GL_TEXTURE_2D,middle); glTranslated(0,0,-1.05); //je descends pour faire le 2me cne gluCylinder(params,0.15,0.5,1.05,20,1); //cne 2

17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48

glBindTexture(GL_TEXTURE_2D,bottom); glTranslated(0,0,-0.25); //je descends enfin tout en bas (sur le s gluCylinder(params,0.3,0.15,0.25,20,1); //cne 3 glBindTexture(GL_TEXTURE_2D,motor); gluDisk(params,0,0.3,20,1); //disque 4

//et la mme position je dessine le disque de sortie des flammes

gluDeleteQuadric(params); //je supprime le quadrique } glPopMatrix(); //hop je remets tout comme je l'ai trouv

void DrawGL() { glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

glMatrixMode( GL_MODELVIEW ); glLoadIdentity( ); gluLookAt(3,4,2,0,0,0,0,0,1); //je place la camra un endroit id DrawRocket(); //je dessine la 1re roquette

glTranslated(2,0,0); //je me dplace pour la 2me roquette glRotated(90,1,0,0); /*je vais tourner celle-l pour que son axe p

soit horizontal */ DrawRocket(); //et je la dessine


glFlush(); SDL_GL_SwapBuffers(); }

Comme vous le voyez j'ai dcid personnellement de faire une fonction pour dessiner la roquette. Je peux ainsi dessiner autant de roquettes que je veux sans alourdir le code.

Amliorations

Vous pouvez, si vous le souhaitez, crer une sphre reprsentant la Terre (avec la texture EarthLow.jpg du pack final) autour de laquelle la roquette tournerait. Conseil : pour faire tourner la roquette autour de la Terre il suffit de bien rflchir l'ordre des transformations faire. Comme ce n'est pas le but de cet exercice, qui se veut simple et rapide, je vous laisse imaginer la solution adquate.

Tlchargez le projet Code::Blocks, l'excutable Windows et le Makefile Unix (391 Ko)

Les quadriques, c'est fantastique et c'est magique ! Loin d'tre mystiques elles sont quand mme vachement pratiques ! C'est une solution trs simple pour ajouter des objets un peu complexes dans vos scnes en attendant de savoir charger de vrais modles 3D crs dans des logiciels 3D externes (Blender, 3dSmax...). Dans le prochain chapitre nous verrons comment contrler la camra de manire plus pousse, et raliserons entre autres un driv de Google Earth sans prtention qui utilisera justement la sphre. Que de bonheur en perspective !

Principe d'une camra TrackBall


Le nom TrackBall vient de ce priphrique bizarre qui remplace la souris o l'on tourne directement une boule. Ici j'ai librement tir le terme du logiciel multiplate-forme Google Earth :

Google Earth

Dans Google Earth en effet on utilise la souris pour tourner autour de la Terre. Nous allons donc reproduire ce principe qui nous permettra d'avoir une camra permettant de regarder un objet / une partie d'une scne sous tous les angles.

Rotation la souris
En maintenant le bouton gauche de la souris enfonc, les mouvements de la souris feront tourner la scne : un mouvement horizontal de la souris donne une rotation horizontale de la scne (donc autour de sa verticale). un mouvement vertical de la souris donne une rotation verticale de la scne.

Ces mouvements sont illustrs par les schmas ci-dessous :

Mouvement horizontal de la souris

Mouvement vertical de la souris

Notez au passage, et nous verrons l'quivalent dans le code, que ce n'est pas vraiment la camra qui tourne mais la scne, mme si cela revient plus ou moins au mme. Ce n'est le cas que pour ce type de camra. Pour la camra FreeFly que nous verrons aprs c'est bel et bien la camra et non la scne qui bougera.

Zoom la molette
Pour prendre du recul ou au contraire nous rapprocher de l'objet / scne que nous souhaitons visualiser, nous allons tout simplement utiliser la molette. Un coup de molette en avant pour zoomer, un coup de molette en arrire pour dzoomer, rien de plus intuitif :

Rotation de la roulette de la souris

Ici c'est bien la camra qui bouge, nous verrons une fois encore comment cela se rpercute sur le code.

Et le clavier ?
Il est possible d'arguer que toutes les souris ne possdent pas de molette. Dans ce cas-l rien ne vous empche d'utiliser le clavier pour dzoomer. Ici nous n'utiliserons le clavier que pour une chose : rintialiser la rotation de la scne avec la

touche

(SDLK_HOME avec SDL).

Quelques bases de C++


Maintenant que nous savons ce que nous voulons faire avec notre camra, il faut faire un petit intermde apprentissage du C++. Nous allons en effet utiliser et regrouper toutes les fonctionnalits de notre camra dans une classe : TrackBallCamera. Le cours de M@teo expliquera le concept des classes en dtail. Voyons pour l'instant a comme une extension d'une structure. Rappelez-vous en C un struct permettait de stocker plusieurs champs dans un mme type : Code : C - Slectionner

1 2 3 4 5

struct NomDeVotreStructure { long variable1; long variable2; int autreVariable;

6 7

double nombreDecimal; };

Une classe possde, en plus des attributs, des mthodes. Ces mthodes sont comme des fonctions qui s'appliquent aux instances de cette classe. Ouh l l beaucoup de mots nouveaux ! Instances par exemple c'est quoi ? Imaginons une classe nomme Chaise. Une chaise a certains attributs : hauteur, nombre de pieds, matire. On crira donc : Code : C++ - Slectionner

1 2 3 4 5 6 7

class Chaise { protected: int hauteur; int nombre_de_pieds; string matiere; }

Une fois la classe dclare, dans le c%u0153ur du programme on veut pouvoir en utiliser (des chaises). On cre donc des instances de la classe Chaise en dclarant simplement une variable de type Chaise. Exemple : Code : C++ - Slectionner

Chaise machaise;

Notez qu'en C++ les structures et les classes sont automatiquement des types. Nul besoin donc d'crire un typedef Class Chaise Chaise; par exemple.

Vous avez pu voir un mot bizarre dans mon exemple : protected. Sans rentrer dans le dtail, cela veut dire que les attributs dclars protected ne sont pas accessibles de l'extrieur de la classe mais uniquement par ses mthodes. Les mthodes justement c'est quoi ?

Une mthode est comme une fonction mais elle s'applique une instance prcise de la classe. Reprenons notre exemple de la chaise. Imaginons que nous voulions enlever un pied notre chaise. En C nous aurions d utiliser une fonction enleverPied en passant en paramtre quelle chaise modifier. En C++ on appelle directement une mthode sur une instance de la classe. Exemple : La dclaration de la classe Chaise dans Chaise.h Code : C++ - Slectionner

class Chaise

2 3 4 5 6 7 8 9

{ public: void enleverPied(); protected: int hauteur; int nombre_de_pieds; string matiere; };

L'implmentation des mthodes de la classe Chaise dans Chaise.cpp Code : C++ - Slectionner

1 2 3 4 5 6

#include "chaise.h" void Chaise::enleverPied() { nombre_de_pieds--; }

Appel dans le corps du programme Et maintenant ce qui nous intresse, l'appel de la mthode enleverPied sur une instance : Code : C++ - Slectionner

1 2

Chaise machaise; machaise.enleverPied();

Comme vous le voyez on appelle une mthode comme on utiliserait un attribut : instance lamthode(); Quand le programme entre dans le code de la mthode il l'applique donc l'instance souhaite, et utilise donc les attributs propres l'instance en question. Notez qu'ici je mets la mthode en public et non protected pour pouvoir l'appeler de l'extrieur de la classe (c'est--dire partir du corps du programme).

Deux mthodes particulires

Il existe deux mthodes particulires qui ne sont pas appeles directement par l'utilisateur : le constructeur et le destructeur. Le constructeur est appel lorsque l'objet est initialis, gnralement sa dclaration. C'est une mthode sans type de retour, qui porte le nom de la classe, et qui permet d'initialiser les attributs des valeurs initiales : Exemple : Dclaration du constructeur dans Chaise.h Code : C++ - Slectionner

1 Class Chaise 2 { 3 public:

4 Chaise(); //un constructeur ne renvoit rien mais peut ventuel 5 paramtres 6 void enleverPied(); 7 protected: 8 int hauteur; 9 int nombre_de_pieds; 10 string matiere; };
Implmentation du constructeur dans Chaise.cpp Code : C++ - Slectionner

1 2 3 4 5 6

Chaise::Chaise() { hauteur = 1; nombre_de_pieds = 4; matiere = "bois"; }

Et donc ce constructeur sera appel ds qu'on instanciera un objet dans le corps principal du programme : Code : C++ - Slectionner

1 2

Chaise machaise; //dclenche l'appel du constructeur machaise.enleverPied(); //je sais donc que maintenant elle en a 3 car

dpart grce au contructeur

Le destructeur quant lui est appel automatiquement quand on dtruit l'objet. Dans le cas prsent je n'ai rien de spcial faire dans le destructeur, mais si nous avions allou de la mmoire dynamiquement (attributs dynamiques de la classe), c'est dans le destructeur qu'il faut les dtruire pour ne pas faire de fuite de mmoire. Comme le constructeur, le destructeur porte le nom de la classe prcd du symbole ~ . Ici je vais me contenter d'afficher un message lors de la destruction : Dclaration du destructeur dans Chaise.h Code : C++ - Slectionner

1 2 3 4 5 6 7 8 9 10 11

Class Chaise { public: Chaise(); //un constructeur ne renvoie rien mais peut ventuel

arguments

du symbole ~

void enleverPied(); ~Chaise(); //un destructeur ne renvoie rien, n'a pas d'argumen

protected: int hauteur; int nombre_de_pieds; string matiere; };

Implmentation du destructeur dans Chaise.cpp Code : C++ - Slectionner

1 2 3 4 5 6

#include <iostream> ... Chaise::~Chaise() { std::cout << "Au revoir petite chaise." << std::endl; }

Dans le corps du programme l'appel au destructeur est automatique la fin du bloc o l'instance est dclare. Exemple : Code : C++ - Slectionner

1 2 3 4 5 6 7

int main() { Chaise machaise; //appel du constructeur machaise.enleverPied(); //la pauvre a doit faire mal return 0; //on quitte le bloc du main, donc on dtruit toutes Chaise sur l'instance machaise. }

Allocation dynamique
Si vous en tes la lecture du tuto OpenGL c'est que vous connaissez srement l'allocation dynamique en C : Code : C - Slectionner

1 2

struct Chaise * machaise; machaise = malloc(sizeof(struct Chaise));

En C++ on utilise gnralement l'oprateur new comme ceci : Code : C++ - Slectionner

1 2

Chaise * machaise; machaise = new Chaise();

On note ici l'utilisation des ( ) aprs Chaise qui montre clairement qu'on cherche construire un objet. Une fois la mmoire alloue, le constructeur de la classe est donc automatiquement appel. Pour la destruction, delete remplace le free que vous connaissez : Code : C++ - Slectionner

1 2 3 4

Chaise * machaise; machaise = new Chaise(); machaise->enleverPied(); delete machaise;

Reprsentation UML
Je ne vais pas vous faire un cours de modlisation UML mais juste vous prsenter une manire graphique de reprsenter une classe. J'utiliserai ce symbolisme tout au long du tuto pour rsumer brivement les fonctionnalits d'une classe :

Ce qui donne par exemple pour reprendre notre chre chaise :

Implmentation de la camra
Nous allons implmenter la camra TrackBall avec le concept de classe que nous venons de voir. Nous l'avons vu plus haut il nous faut grer trois types d'vnements : l'appui sur le bouton gauche de la souris : nous n'activerons le mouvement la souris que si ce bouton est enfonc ; le mouvement de la souris : pour changer l'orientation de la scne ; l'appui sur la touche HOME pour remettre l'orientation de la scne sa valeur initiale.

Dans le corps principal de notre programme SDL (partie suivante) nous devrons donc envoyer les vnements ncessaires au fonctionnement de la camra. Vous savez comment placer une camra manuellement avec gluLookAt. Ici c'est la mthode look de notre classe TrackBallCamera qui s'occupera d'appeler le gluLookAtpour nous. Dans le code d'affichage de la scne nous n'aurons donc qu' appeler cette mthode. Nous allons aussi rajouter deux autres mthodes pour configurer la sensibilit de notre camra : setMotionSensivity : pour dterminer la vitesse de rotation de la scne en fonction du mouvement en pixel du curseur de la souris ; setScrollSensivity : pour dterminer de combien zoomer/dzoomer lorsque l'on utilise la molette de la souris.

Tout cela se traduit donc de la faon suivante en UML et C++ : <tableau> <ligne> <entete>UML simplifi</entete> <entete>Dclaration C++</entete> </ligne> <ligne>

<cellule> <cellule>Code : C++ - Slectionner

</cellule>

1 class TrackBallCamera 2 { 3 public: 4 TrackBallCamera(); 5 6 virtual void OnMouseMotion(const SDL_MouseMotionEvent &amp; event) 7 virtual void OnMouseButton(const SDL_MouseButtonEvent &amp; event) 8 virtual void OnKeyboard(const SDL_KeyboardEvent &amp; event);

9 10 virtual void look(); 11 virtual void setMotionSensivity(double sensivity); 12 virtual void setScrollSensivity(double sensivity); 13 14 virtual ~TrackBallCamera(); 15 protected: 16 double _motionSensivity; 17 double _scrollSensivity; 18 bool _hold; 19 double _distance; 20 double _angleY; 21 double _angleZ; 22 SDL_Cursor * _hand1; 23 SDL_Cursor * _hand2; 24 };;
</cellule> </ligne> </tableau>

J'en ai profit pour rajouter tous les attributs que nous allons utiliser. Une petite explication s'impose donc : double _motionSensivity : utilis pour stocker la sensibilit de la camra aux mouvements de la souris ; double _scrollSensivity : sensibilit de la camra au scroll de la souris ( pas d'un dplacement) ; bool _hold : est-on actuellement en train de maintenir le bouton gauche de la souris enfonc ? double _distance : distance entre la camra et le centre de la scne ; double _angleY : angle de rotation verticale de la scne (en vert sur le schma plus haut) ; double _angleZ : angle de rotation horizontale de la scne (donc autour de la verticale, en bleu sur le schma).

Les deux derniers attributs sont les deux curseurs de la souris que nous utiliserons :

_hand1 en temps normal, _hand2 quand le bouton gauche de la souris est enfonc. Vous remarquerez au passage que je prcde les attributs de la classe du symbole underscore _ . Cela permet dans l'implmentation des mthodes de distinguer plus rapidement variables temporaires (ou paramtres) et attributs. Vous n'tes bien sr pas forcs de suivre cette rgle.

Constructeur

Dans le constructeur nous allons simplement initialiser tous les attributs des valeurs initiales connues. Il ne faut rien laisser qui puisse tre utilis sans avoir t initialis. La partie la moins vidente est peut-tre la cration des deux curseurs. Pour faciliter les choses j'ai relgu tout le travail dans une fonction rajoute sdlglutils :cursorFromXPM (fournie dans l'archive finale). Code : C++ - Slectionner

1 TrackBallCamera::TrackBallCamera() 2 { 3 const char *hand1[] = 4 { 5 /* width height num_colors chars_per_pixel */ 6 " 16 16 3 1 ", 7 /* colors */ 8 "X c #000000", 9 ". c #ffffff", 10 " c None", 11 /* pixels */ 12 " XX ", 13 " XX X..XXX ", 14 " X..XX..X..X ", 15 " X..XX..X..X X ", 16 " X..X..X..XX.X", 17 " X..X..X..X..X", 18 " XX X.......X..X", 19 "X..XX..........X", 20 "X...X.........X ", 21 " X............X ", 22 " X...........X ", 23 " X..........X ", 24 " X.........X ", 25 " X.......X ", 26 " X......X ", 27 " X......X ", 28 "0,0" 29 }; 30 31 const char *hand2[] = 32 { 33 /* width height num_colors chars_per_pixel */ 34 " 16 16 3 1 ", 35 /* colors */ 36 "X c #000000", 37 ". c #ffffff", 38 " c None", 39 /* pixels */ 40 " ", 41 " ", 42 " ", 43 " ", 44 " XX XX XX ", 45 " X..X..X..XX ", 46 " X........X.X ",

47 " X.........X ", 48 " XX.........X ", 49 " X...........X ", 50 " X...........X ", 51 " X..........X ", 52 " X.........X ", 53 " X.......X ", 54 " X......X ", 55 " X......X ", 56 "0,0" 57 }; 58 _hand1 = cursorFromXPM(hand1); //cration du curseur normal 59 _hand2 = cursorFromXPM(hand2); //cration du curseur utilis quand 60 enfonc 61 SDL_SetCursor(_hand1); //activation du curseur normal 62 _hold = false; //au dpart on part du principe que le bouton n'est 63 _angleY = 0; 64 _angleZ = 0; 65 _distance = 2; //distance initiale de la camra avec le centre de 66 _motionSensivity = 0.3; 67 _scrollSensivity = 1; }
Comme vous pouvez le voir, le constructeur fait appel une fonction SDL, SDL_SetCursor, qui ne doit pas tre excute avant la cration de votre fentre SDL. De ce fait la camra ne devra tre cre qu'aprs l'initialisation de l'application.

On Mouse Motion
Cette mthode est la plus importante de la classe et pourtant l'une des plus courtes. Rappelez-vous le principe de la camra : lorsque le curseur de la souris est boug, si le bouton gauche de la souris est maintenu appuy, alors la scne tourne. Voyons donc comment cela se traduit en code : Code : C++ - Slectionner

1 2 3 4 la rotation horizontale 5 _angleY += event.yrel*_motionSensivity; //mouvement sur Y de l 6 la rotation verticale 7 8 et 90 //pour viter certains problmes, on limite la rotation vertic 9 if (_angleY > 90) 10 _angleY = 90; 11 else if (_angleY < -90) 12 _angleY = -90; 13 } }

void TrackBallCamera::OnMouseMotion(const SDL_MouseMotionEvent &amp; e { if (_hold) //si nous maintenons le bouton gauche enfonc { _angleZ += event.xrel*_motionSensivity; //mouvement sur X de l

On Mouse Button
Cette mthode nous permet de grer deux choses : l'appui et le relchement du bouton gauche de la souris ; le mouvement de la molette de la souris.

Lorsque l'on bouge la molette, deux vnements successifs sont gnrs : SDL_MOUSEBUTTONDOWN et SDL_MOUSEBUTTONUP. Nous n'utiliserons donc que le premier.

Code : C++ - Slectionner

1 2 3 4 5 6 7 8 9 10 relch { 11 _hold = true; //le mouvement de la souris fera bouger la s 12 SDL_SetCursor(_hand2); //on met le curseur spcial 13 } 14 } 15 else if ((event.button == SDL_BUTTON_WHEELUP)&amp;&amp;(event.type 16 de molette vers le haut 17 { 18 _distance -= _scrollSensivity; //on zoome, donc rapproche la c 19 if (_distance < 0.1) //distance minimale, changer si besoin 20 _distance = 0.1; 21 } 22 else if ((event.button == SDL_BUTTON_WHEELDOWN)&amp;&amp;(event.ty 23 de molette vers le bas 24 { 25 _distance += _scrollSensivity; //on dzoome donc loigne l 26 } }

void TrackBallCamera::OnMouseButton(const SDL_MouseButtonEvent &amp; e { if (event.button == SDL_BUTTON_LEFT) //l'vnement concerne le bou { if ((_hold)&amp;&amp;(event.type == SDL_MOUSEBUTTONUP)) //rel { _hold = false; //le mouvement de la souris ne fera plus bo SDL_SetCursor(_hand1); //on met le curseur normal } else if ((!_hold)&amp;&amp;(event.type == SDL_MOUSEBUTTONDOWN)

OnKeyboard

La dernire mthode qui vient utiliser les vnements est la gestion du clavier, pour l'appui sur la touche HOME. On se contente d'y remettre la rotation de la scne zro : Code : C++ - Slectionner

1 2 3 4 5 6 7 8

void TrackBallCamera::OnKeyboard(const SDL_KeyboardEvent &amp; event) { if ((event.type == SDL_KEYDOWN)&amp;&amp;(event.keysym.sym == SDLK

la touche HOME
{ } }

_angleY = 0; //remise zro des angles _angleZ = 0;

Look
Tout cela est bien beau, nous savons comment changer des variables avec la souris et le clavier mais a ne fait en rien bouger la camra dans notre scne. En effet nous n'avons pour l'instant pas vu la moindre commande OpenGL ! Il est donc temps de s'y mettre avec la mthode Look qui viendra remplacer, dans votre fonction d'affichage, l'appel gluLookAt. Remplacer gluLookAt ? Parce qu'il y a un truc mieux que tu nous as cach !! ?

Non non. La mthode Look appelle elle-mme gluLookAt mais avec des paramtres qui dpendent de la position de la camra, c'est pour a que vous n'avez plus l'appeler vous-mmes. Si on se rfre aux schmas en dbut de chapitre qui expliquent le principe de la camra TrackBall, on remarque plusieurs choses : elle regarde le centre de la scne ; ce n'est pas la camra mais la scne qui est tourne autour de Y et Z.

Il suffit alors de traduire tout a en code : Code : C++ - Slectionner

1 2 3 4 5 6 7 8

void TrackBallCamera::look() { gluLookAt(_distance,0,0, 0,0,0, 0,0,1); // la camra regarde le centre (0,0,0) et est su

centre donc (_distance,0,0)


}

glRotated(_angleY,0,1,0); //la scne est tourne autour de l'axe Y glRotated(_angleZ,0,0,1); //la scne est tourne autour de l'axe Z

Et voil ce n'tait vraiment pas sorcier.

La dernire chose qu'il nous reste faire dans le code mme de la camra est une destruction propre de ce qui a t allou dynamiquement.

Destructeur
Les seules choses alloues dynamiquement sont les curseurs qu'il nous faut dtruire quand la camra est dtruite : Code : C++ - Slectionner

1 2 3 4 5 6

TrackBallCamera::~TrackBallCamera() { SDL_FreeCursor(_hand1); //destruction du curseur normal SDL_FreeCursor(_hand2); //destruction du curseur spcial SDL_SetCursor(NULL); //on remet le curseur par dfaut. }

Scne de test
Le code de la classe TrackBallCamera est complet et n'a besoin de rien de plus. Cependant un objet camera ne va pas recevoir tout seul les vnements, il faut les lui donner. Nous allons donc voir avec une petite scne de test simple comment utiliser la camra que nous venons de crer. Pour faire original et pas du tout inspir de Google Earth, nous allons crer une sphre avec la texture de de la Terre. Hum hum ! En variables globales nous allons donc utiliser : Code : C++ - Slectionner

1 2

GLuint earth; //l'identifiant de la texture de la Terre TrackBallCamera * camera; //un pointeur vers notre camra

Pourquoi pas directement une camra ?

Rappelez-vous, le constructeur appelle des fonctions SDL qui ncessitent qu'une fentre SDL existe dj. Il ne faut donc pas que la camra soit construite ds le lancement du programme (ce qui serait le cas ici si nous n'utilisions pas de pointeur). Nous la crons donc dynamiquement aprs la fentre : Code : C++ - Slectionner

1 2 3 4 5 6

atexit(stop); //stop() sera appel quand on fera exit(0);

//... //...

SDL_SetVideoMode(width, height, 32, SDL_OPENGL); earth = loadTexture("EarthMap.jpg"); camera = new TrackBallCamera();

camera->setScrollSensivity(0.1);

Comme la camra a t cre dynamiquement c'est nous de la dtruire proprement la fin de l'excution du programme. C'est ce qu'on fait dans la fonction stop, appele quand on fera exit(0); dans le corps du programme : Code : C++ - Slectionner

1 2 3 4 5

void stop() { delete camera; //destruction de la camra alloue dynamiquement SDL_Quit(); }

Comme je vous l'ai dit plus haut, la camra ne recevra pas les vnements clavier/souris si on ne les lui donne pas. C'est pourquoi dans notre partie de gestion des vnements, il faut donner la camra les vnements dont on ne se sert pas : Code : C++ - Slectionner

while(SDL_PollEvent(&amp;event)) 1 { 2 switch(event.type) 3 { 4 case SDL_QUIT: 5 exit(0); 6 break; 7 case SDL_KEYDOWN: 8 switch (event.key.keysym.sym) 9 { 10 case SDLK_p: 11 takeScreenshot("test.bmp"); 12 break; 13 case SDLK_ESCAPE: 14 exit(0); 15 break; 16 default : //on a utilis la touche P et la touche 17 la camra 18 camera->OnKeyboard(event.key); 19 } 20 break; 21 case SDL_MOUSEMOTION: //la souris est bouge, a n'int 22 camera->OnMouseMotion(event.motion); 23 break; 24 case SDL_MOUSEBUTTONUP: 25 case SDL_MOUSEBUTTONDOWN: 26 camera->OnMouseButton(event.button); //tous les vnem 27 sont donns la camra 28 break; 29 } }
La dernire chose qu'il nous reste faire est de dessiner la scne, ici trs basique. On n'appelle plus gluLookAt nous-mmes mais bien la mthode Look de notre objet camera.

Code : C++ - Slectionner

1 void DrawGL() 2 { 3 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); 4 5 glMatrixMode( GL_MODELVIEW ); 6 glLoadIdentity( ); 7 8 camera->look(); 9 10 GLUquadric* params = gluNewQuadric(); 11 gluQuadricTexture(params,GL_TRUE); 12 glBindTexture(GL_TEXTURE_2D,earth); 13 gluSphere(params,1,20,20); 14 gluDeleteQuadric(params); 15 16 glFlush(); 17 SDL_GL_SwapBuffers(); 18 }

Tlchargez la vido au format avi/Xvid (1.17 Mo)

Tlchargez le projet Code::Blocks, l'excutable Windows et le Makefile Unix (1.30 Mo)


Principe d'une camra FreeFly
Une camra FreeFly, comme son nom l'indique, permet de se dplacer/voler librement dans une scne. On retrouve parfois ce type de camra sous des appellations incompltes comme FreeLook, voire errone comme FPS (First Person Shooter). FreeLook ne donne pas la mme ide de dplacement, caractristique importante de la camra FreeFly. Une camra FPS quant elle est plus complexe car assujettie la gravit, des dplacements contraints (celui du personnage que l'on incarne), etc. Quoi qu'il en soit, le principe est exactement le mme que dans Counter Strike, quand on est mort (certains plus souvent que d'autres fin de la partie. ) et qu'on se balade librement dans la map pour regarder la

Camra FreeFly (FreeLook) dans Counter Strike

Gestion du regard la souris

Pour pouvoir regarder tout autour de nous, nous allons utiliser les mouvements de la souris pour orienter la camra : un mouvement horizontal de la souris fait tourner la camra horizontalement autour de la verticale du monde (regard gauche et droite) ; un mouvement vertical de la souris fait tourner la camra verticalement (le regard se lve ou se baisse).

Ces mouvements sont illustrs par les schmas ci-dessous (rutilisation des noms des angles propres aux coordonnes sphriques) :

Mouvement horizontal de la souris (schma vu du dessus)

Mouvement vertical de la souris (schma vu de ct)

Gestion du dplacement au clavier

La souris nous permet d'orienter la camra par rapport aux axes X, Y, Z locaux, le clavier quant lui va nous permettre de dplacer la camra selon l'orientation actuelle : 2 touches nous permettent de faire avancer/reculer la camra ; 2 touches nous permettent de faire strafer (dplacement latral) la camra.

J'ai choisi d'utiliser les touches ZQSD, utilises de faon presque standard pour ce genre de mouvement sur un clavier AZERTY :

Mouvement avant/arrire

Gestion fluide du mouvement

Pour l'instant en suivant mon tutoriel, nous avons utilis les vnements pour : faire bouger notre grue dans le chapitre sur les transformations ; faire tourner la camra dans le chapitre prcdent sur la camra TrackBall.

Nous utiliserons le mme systme de mouvement la souris, donc l'utilisation des SDL_MouseMotionEvent pour orienter la camra. Pour notre grue, la ruse de l'animation tait base sur l'activation de la rptition des touches avec SDL_EnableKeyRepeat. La vitesse du mouvement tait donc dpendante de la vitesse de rptition des touches, ce qui pose trois problmes : si le taux de rptition des touches est trop faible le mouvement est saccad ; utiliser l'vnement d'appui sur une touche son apparition empche de pouvoir utiliser plusieurs touches en mme temps ; le mouvement n'est pas rellement li au temps coul.

Dans le cadre de notre petite grue sans prtention tout cela tait tout fait tolrable, ici nous rentrons dans un domaine diffrent : le contrle de la vue 3D. Les personnes habitues aux FPS ne se rendent peut-tre pas compte que la manire avec laquelle ils se dplacent peut vite donner la nause un spectateur non habitu. La fluidit du mouvement de la camra est donc primordiale ici.

Les KeyStates
Je vous l'ai dit juste avant, utiliser l'vnement clavier juste son apparition n'est pas la bonne solution pour grer un dplacement fluide. Nous allons donc nous contenter de mettre jour un tableau interne de l'tat des touches qui nous intressent. Par exemple si la touche SDLK_z nous intresse et que l'on reoit un vnement SDL_KEYDOWN, nous mettrons la case SDLK_z de notre tableau de boolens true, pour indiquer qu'elle est actuellement enfonce. De mme sur la rception d'un vnement SDL_KEYUP avec la touche SDLK_z, nous mettrons la case correspondante du tableau false pour indiquer qu'elle n'est pas/plus enfonce. Comment faire correspondre un nom de touche avec un indice dans un tableau ? Nous allons pour cela utiliser ce que l'on appelle, comme en PHP, un tableau associatif. Au lieu de faire mon_tableau[indice_numrique] nous ferons mon_tableau_associatif[cl]. L'outil en C++ qui permet de faire cela est appel une map. On la dfinit par les deux types qu'elle utilise, le type pour la cl (la cl est ce qui remplace l'indice numrique, bien qu'elle puisse tre numrique aussi ), et le type de la valeur stocker. Dans notre cas, nous souhaitons stocker des boolens (bool) indiquant si une touche (SDLKey) est actuellement enfonce ou non. Cela correspond donc la map suivante : Code : C++ - Slectionner

typedef std::map<SDLKey,bool> KeyStates;

On peut l'utiliser ainsi : Code : C++ - Slectionner

1 2

Keystates keystates; //dclaration d'une variable de type KeyStates keystates[SDLK_Z] = true; //association bidon, eh oui je n'ai pas recu

je peux savoir ? :p )

Configuration du clavier
Dans l'absolu on se fiche pas mal que la touche pour faire avancer soit la touche SDLK_z. a pourrait tre SDLK_i qu'on s'en ficherait tout autant. Ce que l'on veut savoir n'importe quel moment c'est si la touche qui sert faire avancer en avant (forward) est enfonce ou non, quel que soit le choix du joueur/concepteur en ce qui concerne la configuration des touches. Nous allons donc utiliser un deuxime tableau associatif qui associera une action et la touche utilise pour effectuer cette action. L'action sera nomme textuellement, comme forward pour aller en avant, et la touche sera le code de la SDLKey comme SDLK_z (qui est en fait une valeur numrique mais on n'a pas besoin de le savoir). Cela correspond donc la map suivante : Code : C++ - Slectionner

typedef std::map<std::string,SDLKey> KeyConf;

On peut alors l'utiliser ainsi : Code : C++ - Slectionner

1 2

KeyConf keyconf; //dclaration d'une variable de type KeyConf. keyconf["forward"] = SDLK_z; //ici je choisis d'utiliser la touche z p

forward.

On peut alors tout fait imaginer charger la configuration des touches via un fichier de configuration ou mme demander l'utilisateur quelles touches utiliser. Ces deux techniques ne seront pas enseignes ici car dpassent le cadre du tuto OpenGL. Nous utiliserons juste une affectation action/touche comme dans l'exemple du dessus. Grce la combinaison de KeyStates et de notre configuration des touches, il est alors facile de dfinir si la touche pour raliser une action donne est enfonce : Code : C++ - Slectionner

1 2 3 4

Keystates keystates; KeyConf keyconf; keyconf["forward"] = SDLK_z; keystates[keyconf["forward"]] = true;

La partie keyconf["forward"] vient rcuprer la touche utilise pour l'action forward , cette touche sert de cl pour la map touche/boolen keystates.

Mouvement anim
Nous avons dj ralis un mouvement anim ds notre premire scne 3D avec un cube. La boucle principale de notre programme tait la suivante :

Structure de la boucle principale Ici nous ferons la mme chose : un mouvement de la camra qui dpend du temps coul mais aussi de l'tat des touches. Par exemple nous ne ferons avancer la camra que si la touche correspondante est enfonce, ce qui donne en code : Code : C++ - Slectionner

1 2

if (keystates[keyconf["forward"]]) position += forward * (speed * timestep);

o timestep est le temps coul depuis la dernire image, speed est la vitesse de dplacement, forward est le vecteur d'orientation de la camra (qui pointe l o elle regarde), et position est la position de la camra.

Vector3D
Mathmatiquement je sais ce qu'est un vecteur mais a existe en C++ ? la base non. Mais rien ne nous empche de le crer. D'ailleurs si vous avez dj fait un peu de SDL, j'imagine que vous avez pu tre amens crer des struct ayant comme attributs x et y les positions de vos sprites blitter. Quoiqu'il en soit ici des attributs (X,Y et Z) ne nous suffisent pas. Comme on l'a vu dans le code juste au-dessus, il nous faut pouvoir multiplier un vecteur par un nombre, additionner deux vecteurs, et un peu plus encore. Pour cela on utilise en C++ la surcharge d'oprateurs qui permet de redfinir les oprations lmentaires comme l'addition, la multiplication pour un type (ici notre classe vecteur) non lmentaire. En dfinissant ainsi l'oprateur + comme mthode de notre classe, il est alors tout fait possible dans le code, comme on le fait pour des nombres, de faire vecteur1 + vecteur2. Et mine de rien a simplifiera beaucoup le code de la camra. Voici la dclaration de la classe Vector3D que nous allons utiliser. Je vous la donne pour vous montrer la dclaration des oprateurs. L'implmentation en elle-mme est triviale, car purement mathmatique et je laisse les curieux aller la lire dans l'archive finale. Pour l'utiliser nous n'avons pas besoin de connatre les dtails de l'implmentation. Code : C++ - Slectionner

1 class Vector3D 2 { 3 public:

4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 };

double X; double Y; double Z; Vector3D(); Vector3D(double x,double y,double z); Vector3D(const Vector3D &amp; v); Vector3D(const Vector3D &amp; from,const Vector3D &amp; to); Vector3D &amp; operator= (const Vector3D &amp; v); Vector3D &amp; operator+= (const Vector3D &amp; v); Vector3D operator+ (const Vector3D &amp; v) const; Vector3D &amp; operator-= (const Vector3D &amp; v); Vector3D operator- (const Vector3D &amp; v) const;

Vector3D &amp; operator*= (const double a); Vector3D operator* (const double a)const; friend Vector3D operator* (const double a,const Vector3D &amp; v); Vector3D &amp; operator/= (const double a); Vector3D operator/ (const double a)const; Vector3D crossProduct(const Vector3D &amp; v)const; double length()const; Vector3D &amp; normalize();

Fluidit VS Molette
Il me reste une dernire chose couvrir avant d'entrer dans l'implmentation de la camra et cela concerne la molette. Vous l'avez vu dans la chapitre prcdent, la molette se gre comme un bouton et on dtecte que la molette a t monte si on reoit un vnement de type SDL_MOUSEBUTTONDOWN sur le bouton SDL_BUTTON_WHEELUP. Par consquent si on bougeait sur cet vnement on viendrait remettre en cause toute ma super thorie de fluidit quantique convergence rtroactive ! Nous allons donc faire comme avec le clavier : sur dtection d'un mouvement de la molette on met un boolen true ; quand nous faisons bouger la camra, si ce boolen vaut true nous dplaons verticalement la camra en fonction du temps coul.

Oui mais quand remet-on ce boolen false ? On ne peut pas le remettre false quand on dtecte que la molette ne bouge plus car les vnements SDL_MOUSEBUTTONDOWN et SDL_MOUSEBUTTONUP pour la molette sont conscutifs, on n'aurait donc aucun mouvement. On utilise donc le temps !

Sur dtection d'un mouvement de la molette on met un boolen true et une variable temps restant 250 (ms, c'est un exemple). Quand nous faisons bouger la camra, si ce boolen vaut true : o on dcrmente le temps restant du temps coul ; o si le temps restant est < 0 alors on passe le boolen false.

Nous allons bien sr voir toutes ces techniques en pratique, appliques notre camra FreeFly.

Implmentation de la camra
Tout comme notre camra TrackBall du chapitre prcdent, nous allons implmenter la camra FreeFly en C++ l'aide d'une classe. Nous devons ici aussi grer trois types d'vnements : le mouvement de la souris : pour changer l'orientation de la camra ; le clavier : pour dplacer la camra ; la molette (qui se gre comme un clic de bouton) : pour faire monter/descendre la camra.

Nous aurons une mthode look dont l'appel viendra remplacer dans nos codes l'utilisation du gluLookAt (appel en interne par la mthode look de toute faon). Nous allons aussi crer trois mthodes pour paramtrer la camra : setSpeed : pour dfinir la vitesse de dplacement de la camra ; setSensivity : pour dterminer la vitesse de rotation de la camra en fonction du mouvement en pixels du curseur de la souris ; setPosition : pour placer prcisment la camra quand on le souhaite.

Nous introduirons aussi une dernire mthode, animate, qui viendra grer le mouvement fluide de la camra, comme expliqu dans la partie prcdente. Voici une traduction formelle de ce que je viens de dire rapidement :

UML simplifi

Dclaration C++

UML simplifi

Dclaration C++

Code : C++ - Slectionner class FreeFlyCamera { 1 public: FreeFlyCamera(const Vector3D &amp; positi 2 Vector3D(0,0,0)); 3 4 virtual void OnMouseMotion(const SDL_Mous 5 &amp; event); 6 virtual void OnMouseButton(const SDL_Mous 7 &amp; event); 8 virtual void OnKeyboard(const SDL_Keyboar 9 event); 10 11 virtual void animate(Uint32 timestep); 12 virtual void setSpeed(double speed); 13 virtual void setSensivity(double sensivit 14 15 virtual void setPosition(const Vector3D & 16 position); 17 18 virtual void look(); 19 20 virtual ~FreeFlyCamera(); 21 22 protected: 23 double _speed; 24 double _sensivity; 25 26 Uint32 _timeBeforeStoppingVerticalMotion; 27 bool _verticalMotionActive; 28 int _verticalMotionDirection; 29 30 typedef std::map<SDLKey,bool> KeyStates; 31 KeyStates _keystates; 32 typedef std::map<std::string,SDLKey> Keyc 33 Keyconf _keyconf; 34 35 Vector3D _position; 36 Vector3D _target; 37 Vector3D _forward; 38 Vector3D _left; 39 double _theta; 40 double _phi 41 void VectorsFromAngles(); };

Dcryptons ensemble tous les attributs dont nous allons avoir besoin : double _speed : vitesse de dplacement de la camra ; double _sensivity : sensibilit de la camra aux mouvements de la souris ;

Uint32 _timeBeforeStoppingVerticalMotion : si un mouvement vertical ( la molette) est en cours, combien de temps reste-t-il avant de l'arrter ? bool _verticalMotionActive : y a-t-il un mouvement vertical en cours ? int _verticalMotionDirection : sens du mouvement vertical quand il a t dclench la molette (+1 vers la haut, -1 vers le bas) ; KeyStates _keystates : tableau donnant l'tat des touches utilises ; Keyconf _keyconf : tableau donnant les touches utiliser pour chaque action ; Vector3D _position : position de la camra dans l'absolu ; Vector3D _target : point regard par la camra dans l'absolu ; Vector3D _forward : vecteur donnant la direction du regard (et donc du dplacement vers l'avant) ; Vector3D _left : vecteur perpendiculaire au regard pour le dplacement latral ; double _theta : angle de rotation horizontale de la camra (autour de la verticale) ; double _phi : angle de rotation verticale de la camra.

On remarque aussi, dans le schma UML comme dans la dclaration de la classe, une mthode protge (non publique) : VectorsFromAngles. Cette mthode viendra calculer les vecteurs _forward et _left en fonction des nouvelles valeurs de _theta et _phi.

Constructeur
Vous l'avez vu dans l'implmentation, le constructeur possde un paramtre facultatif : la position initiale de la camra. Si on ne la spcifie pas, elle sera en (0,0,0) au dpart. Le code n'est pas passionnant, juste de l'initialisation des attributs : Code : C++ - Slectionner

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

FreeFlyCamera::FreeFlyCamera(const Vector3D &amp; position) { _position = position; //si aucune position n'est dfinie on reoit

en paramtre

_phi = 0; _theta = 0; VectorsFromAngles(); //dcrit un peu plus loin _speed = 0.01; _sensivity = 0.2; _verticalMotionActive = false;

//Initialisation de la configuration des touches


_keyconf["forward"] = SDLK_z; _keyconf["backward"] = SDLK_s; _keyconf["strafe_left"] = SDLK_q; _keyconf["strafe_right"] = SDLK_d; _keyconf["boost"] = SDLK_LSHIFT;

//Initialisation des KeyStates

_keystates[_keyconf["forward"]] = false; _keystates[_keyconf["backward"]] = false; _keystates[_keyconf["strafe_left"]] = false; _keystates[_keyconf["strafe_right"]] = false; _keystates[_keyconf["boost"]] = false; SDL_WM_GrabInput(SDL_GRAB_ON); SDL_ShowCursor(SDL_DISABLE);

Pourquoi ces deux dernires lignes ? Le but de notre camra est de bouger le regard avec la souris en permanence. Nous utilisons donc l'information de dplacement relatif de la souris pour faire tourner d'autant la camra. Que se passe-t-il si la souris quitte la fentre et re-rentre par un autre endroit ? 1. Tout le temps o la souris sera en dehors de la fentre la camra sera indirigeable, a peut tre pnible quand on est pas loin du bord. 2. Si la camra revient dans la fentre par un autre ct, le dplacement relatif peut tre norme d'un coup et faire trop tourner la camra, on ne comprendra plus ce que l'on regarde (changement trop brusque).

Par la combinaison des deux lignes : Code : C++ - Slectionner

1 2

SDL_WM_GrabInput(SDL_GRAB_ON); SDL_ShowCursor(SDL_DISABLE);

on vient demander SDL d'interdire la souris de quitter la fentre, mais en plus, quand le curseur est juste au bord de la fentre, de gnrer quand mme des informations de dplacement relatif de la souris. Ces deux commandes appeles par le constructeur de notre camra ncessitent que la SDL soit initialise. Il ne faut donc pas construire un objet FreeFlyCamera avant. Dans notre scne de test comme nous utiliserons la camra en variable globale pour l'instant, nous utiliserons l'allocation dynamique (avec un new) pour pallier ce problme.

On Mouse Motion
Comme notre camra est assure de recevoir des vnements de mouvement de la souris corrects, la mthode OnMouseMotion est trs simple : Code : C++ - Slectionner

1 2 3 4 5 6

void FreeFlyCamera::OnMouseMotion(const SDL_MouseMotionEvent &amp; eve { _theta -= event.xrel*_sensivity; _phi -= event.yrel*_sensivity; VectorsFromAngles(); }

Pourquoi des signes - ? Les angles s'expriment dans le sens trigonomtrique (sens inverse des aiguilles d'une montre). Un changement positif de _theta correspond donc un mouvement vers la gauche de la souris, soit un dplacement ngatif sur l'axe des X. Soustraire un nombre ngatif revient additionner son oppos donc nous retombons sur nos pattes. De mme pour _phi, un changement positif correspond un mouvement vers le haut de la souris, soit un dplacement ngatif sur l'axe des Y (pour SDL).

Vectors From Angles


chaque fois que les angles sont changs, il faut recalculer le vecteur d'orientation de la camra, _forward, ainsi que le vecteur latral _left. Le vecteur _forward permet de dfinir la fois o regarder, _target, et dans quelle direction avancer. Le vecteur _left sert pour le mouvement latral. Comme ce sont des vecteurs, ils donnent des directions et ne sont donc pas localiss un endroit prcis de l'espace. On peut donc rflchir comme si nous tions en (0,0,0). De deux angles on veut passer aux coordonnes 3D d'un vecteur, hum... mais comme c'est bizarre... a me fait penser trangement aux coordonnes sphriques de l'annexe de trigonomtrie. Comme c'est bizarre et pas du tout intentionnel...

Rappel de l'utilisation des angles en coordonnes sphriques Nous appliquerons donc le calcul bte et mchant expliqu en annexe pour calculer le vecteur _forward (quivalent du vecteur rouge sur le dessin du dessus). Ici nous rflchissons sur des vecteurs unitaires (de longueur 1) donc le rayon de la sphre vaut 1. Code : C++ - Slectionner

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

void FreeFlyCamera::VectorsFromAngles() { static const Vector3D up(0,0,1); //une constante, le vecteur verti if (_phi > 89) _phi = 89; else if (_phi < -89) _phi = -89;

dans les calculs //On limite les valeurs de _phi, on vole certes, mais on en fait pas d

//passage des coordonnes sphriques aux coordonnes cartsiennes


double r_temp = cos(_phi*M_PI/180); _forward.Z = sin(_phi*M_PI/180); _forward.X = r_temp*cos(_theta*M_PI/180); _forward.Y = r_temp*sin(_theta*M_PI/180); _left = up.crossProduct(_forward);

//diantre mais que fait ce passage ?

17 _left.normalize(); 18 19 //avec la position de la camra et la direction du regard, on calcule 20 regarde la camra (la cible) _target = _position + _forward; }}
Le seul point flou qui peut subsister ici est le calcul du vecteur _left pour le mouvement latral. On utilise ici le produit vectoriel (mthode crossProduct de la classe Vector3D) pour calculer le vecteur _left, perpendiculaire au plan form par le vecteur _forwardavec la verticale. Ce vecteur doit ensuite tre normalis (on lui donne une longueur de 1) pour pouvoir tre utilis dans nos mouvements. Tout cela justifie donc les deux lignes : Code : C++ - Slectionner

1 2

_left = up.crossProduct(_forward); _left.normalize();

_left est perpendiculaire au plan form par_forward avec la verticale

On Mouse Button
La gestion de la molette est assez simple. Comme il a t vu dans la partie Gestion fluide du mouvement , nous ne bougerons pas la camra directement mais signalerons juste qu'il faut bouger. J'ai choisi de faire monter/descendre la camra pendant 250 ms. Ce temps n'est pas changer en fonction de la vitesse de la camra, c'est le mouvement lui-mme qui utilisera l'information de vitesse. Code : C++ - Slectionner

1 void FreeFlyCamera::OnMouseButton(const SDL_MouseButtonEvent &amp; eve 2 { 3 if ((event.button == SDL_BUTTON_WHEELUP)&amp;&amp;(event.type == S 4 molette vers le haut 5 { 6 _verticalMotionActive = true; //on demande activer le mouvem

7 _timeBeforeStoppingVerticalMotion = 250; //pendant 250 ms 8 _verticalMotionDirection = 1; //et vers le haut 9 10 } 11 else if ((event.button == SDL_BUTTON_WHEELDOWN)&amp;&amp;(event.ty 12 de molette vers le bas 13 { 14 _verticalMotionActive = true; //on demande activer le mouvem 15 _timeBeforeStoppingVerticalMotion = 250; //pendant 250 ms 16 _verticalMotionDirection = -1; //et vers le bas } }

On Keyboard
Dans la mthode OnKeyBoard, on va utiliser les KeyStates dcouverts plus haut et initialiss dans le constructeur. On pourrait utiliser des if, ou un switch...case, mais il y a plus simple : Code : C++ - Slectionner

1 2 3 4 5 6 7 8 9 10 11 12 13

void FreeFlyCamera::OnKeyboard(const SDL_KeyboardEvent &amp; event) {

//on parcourt tous les keystates actuels

est celle du keystate ?


{ } } }

for (KeyStates::iterator it = _keystates.begin();it != _keystates. it++) { if (event.keysym.sym == it->first) //est-ce que la touche resp

it->second = (event.type == SDL_KEYDOWN); //true si enfonc break; //la touche responsable de l'vnement a t utilis

La seule difficult ici est le parcours des KeyStates, le tableau associatif qui donne l'tat des touches utilises. L'avantage de cette technique : code trs court, simple, et je n'ai pas cod en dur quelles touches tester (pas d'informations redondantes).

Animate
Jusqu' prsent nous avons vu des mthodes qui taient appeles sur des vnements souris/clavier. Ces mthodes ( part le MouseMotion) ne venaient que dfinir des boolens pour une gestion fluide du mouvement. Mouvement qui va rellement s'effectuer ici, dans la mthode Animate, appele chaque boucle du programme. Le code n'est qu'une simple application des principes vus dans la partie Gestion fluide du mouvement : Code : C++ - Slectionner

1 2 3 4

void FreeFlyCamera::animate(Uint32 timestep) {

//la vitesse relle du dplacement est soit la vitesse de croisire, s //de l'tat enfonc ou non de la touche correspondant l'action "boos

5 double realspeed = (_keystates[_keyconf["boost"]])?10*_speed:_spee 6 if (_keystates[_keyconf["forward"]]) 7 _position += _forward * (realspeed * timestep); //on avance 8 if (_keystates[_keyconf["backward"]]) 9 _position -= _forward * (realspeed * timestep); //on recule 10 if (_keystates[_keyconf["strafe_left"]]) 11 _position += _left * (realspeed * timestep); //on se dplace s 12 if (_keystates[_keyconf["strafe_right"]]) 13 _position -= _left * (realspeed * timestep); //on se dplace s 14 if (_verticalMotionActive) 15 { 16 if (timestep > _timeBeforeStoppingVerticalMotion) 17 _verticalMotionActive = false; 18 else 19 _timeBeforeStoppingVerticalMotion -= timestep; 20 _position += Vector3D(0,0,_verticalMotionDirection*realspeed*t 21 valeur de _verticalMotionDirection 22 } 23 _target = _position + _forward; //comme on a boug, on recalcule l 24 }
On remarque que dans certains cas, il est possible lors du mme appel Animate d'avancer et reculer en mme temps (si les touches forward et backward sontpresses simultanment). De manire tout fait logique les deux mouvements s'annulent et la camra ne bougera pas (en avant ou en arrire).

Look
La dernire mthode intressante est bien sr la mthode Look, appele chaque image, avant de dessiner la scne. Rien de compliqu ici, les autres mthodes ont calcul pour nous la position de la camra et la cible qu'elle regarde. Il suffit de faire un appel propre gluLookAt et tout va pour le mieux dans le meilleur des mondes. Code : C++ - Slectionner

1 2 3 4 5 6

void FreeFlyCamera::look() { gluLookAt(_position.X,_position.Y,_position.Z, _target.X,_target.Y,_target.Z, 0,0,1); }

Destructeur
On finit nanmoins avec le destructeur pour remettre les choses comme on les a trouves . Si l'utilisateur pense qu'il n'a plus besoin de sa camra, il serait de bon ton quand mme de lui rendre son curseur que l'on a masqu pour nos besoins personnels : Code : C++ - Slectionner

1 2 3

FreeFlyCamera::~FreeFlyCamera() { SDL_WM_GrabInput(SDL_GRAB_OFF);

4 5

SDL_ShowCursor(SDL_ENABLE); }

Vous trouverez bien sr l'implmentation complte de la camra dans l'archive en fin de chapitre.

Scne de test
Voil, la camra est prte mais une fois encore, comme pour la camra TrackBall, il faut videmment s'assurer de bien la crer et de lui envoyer tous les vnements dont elle a besoin. Un dilemme cependant se prsentait moi la prparation de ce chapitre. Quelle scne minable allais-je encore crer pour montrer l'intrt d'une camra FreeFly ? Pour vous pargner une rapparition de la scne du chapitre des textures (sol + cube + pyramide) j'ai lanc un appel sur les forums. Deux zros, qui ont appris l'OpenGL grce ce cours, ont rpondu prsents et nous ont donc prpar une petite scne sympathique qui nous servira mme dans les chapitres venir. Merci donc TheDead Master et 42 ! Leur scne est incluse dans un fichier part, scene.cpp, dont les fonctions seront bien sr appeles partir de main.cpp. Leurs fonctions que l'on doit appeler sont : Code : C++ - Slectionner

1 2

void chargerTextures(); //pour initialiser les textures que la scne u void dessinerScene(); //pour dessiner leur scne

Nous pouvons donc nous concentrer sur la cration de la camra. En variable globale nous aurons donc : Code : C++ - Slectionner

FreeFlyCamera * camera;

Encore une fois, sous forme de pointeur pour ne pas la crer avant que la SDL ne soit initialise. Nous allouons la camra dynamiquement aprs la cration de la fentre : Code : C++ - Slectionner

1 2 3 4 5 6 7 8 9 10 11 12

int main(int argc, char *argv[]) {

//... //... //...

atexit(stop); SDL_SetVideoMode(width, height, 32, SDL_OPENGL);

chargerTextures(); //ncessaire pour que les textures de leur scn

camra ds le dpart //...


}

camera = new FreeFlyCamera(Vector3D(0,0,2)); //pour les besoins de

La camra sera automatiquement dtruite la fin du programme grce la fonction stop :

Code : C++ - Slectionner

1 2 3 4 5

void stop() { delete camera; SDL_Quit(); }

Une fois la camra cre, dans notre boucle principale il ne faut pas oublier : d'envoyer les vnements que la camra attend ; d'appeler la mthode FreeFlyCamera::animate pour que la camra bouge.

Cela se traduit donc par le code suivant : Code : C++ - Slectionner

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

while(SDL_PollEvent(&amp;event)) { switch(event.type) { case SDL_QUIT: exit(0); break; case SDL_KEYDOWN: switch (event.key.keysym.sym) { case SDLK_p: takeScreenshot("test.bmp"); break; case SDLK_ESCAPE: exit(0); break; default : //on a utilis la touche P et la touche

est donn la camra

la camra
} }

camera->OnKeyboard(event.key); } break; case SDL_KEYUP: //on n'utilise pas de keyup, on donne camera->OnKeyboard(event.key); break; case SDL_MOUSEMOTION: //la souris est bouge, a n'int camera->OnMouseMotion(event.motion); break; case SDL_MOUSEBUTTONUP: case SDL_MOUSEBUTTONDOWN: //tous les venements bouton camera->OnMouseButton(event.button); break;

image

current_time = SDL_GetTicks(); elapsed_time = current_time - last_time; //on calcule le temps last_time = current_time;

40

camera->animate(elapsed_time); //et on fait bouger la camra DrawGL();

Le dtail de la fonction d'affichage ne nous intresse pas vraiment ici. En effet la majeure partie se fait dans le scene.cpp pour utiliser la scne de The Dead Master et 42. Par contre, il ne faut pas oublier d'appeler la mthode look() de notre camra : Code : C++ - Slectionner

1 void DrawGL() 2 { 3 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); 4 5 glMatrixMode( GL_MODELVIEW ); 6 glLoadIdentity( ); 7 8 camera->look(); //remplace un appel manuel gluLookAt 9 10 dessinerScene(); 11 12 glFlush(); 13 SDL_GL_SwapBuffers(); 14 }
Et voil le travail !

Tlchargez la vido au format avi/Xvid (2.11 Mo) Tlchargez le projet Code::Blocks, l'excutable Windows et le Makefile Unix (1.23 Mo)

On arrive enfin au bout de ce chapitre ! Rappelez-vous que si vous n'avez pas tout compris des dtails de l'implmentation de la camra, comprendre son principe (mouvement) et comment l'utiliser peut suffire. Maintenant grce notre camra FreeFly nous pouvons enfin nous balader librement dans nos scnes et les contempler sous vraiment tous les angles. Nous garderons cette scne pour le prochain chapitre, la transparence, dans lequel nous verrons comment rendre l'eau et les fentres transparentes. Tout un programme !

Vous aimerez peut-être aussi