Profil

  • Damien
  • 3dworks
  • Paris étudiant informatique animation journal
  • Je travaille chez Wyde Corporation dans le secteur R&D. je reste passionné de technologie et de tous ce qui s'applique à la réalité virtuelle et à la 3D. damien.leroux.pro@gmail.com

Liens

Lundi 4 octobre 2010 1 04 /10 /Oct /2010 21:51

Genèse


g-2010-10-04-22-06-50-13-copy.JPG

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Vidéo:

 

 

 

Information : C++ + openGL + GLSL sous Visual Studio

 

Génèse est un logiciel de simulation visant à créer son propre système planétaire de manière intuitive.



 


Etape 1 : l'implémentation d’un contexte openGL et SDL.


Comment faire fonctionner SDL et openGL ensemble ? Rien de plus simple : SDL offre un contexte pour openGL. Ainsi, l’architecture de base de mon programme est celle d’un programme SDL avec une boucle de rendu openGL. Cette boucle contient une fonction d’affichage avec les traitements openGL correspondants. Pour mieux comprendre comment cela fonctionne et afin de ne pas répéter ce que d’autres ont très bien expliqué, je vous invite à aller sur : http://www.siteduzero.com/tutoriel-3-5014-creez-des-programmes-en-3d-avec-opengl.html

 


 


Etape 2 : les shaders


Mon désir de travailler réellement l’aspect visuel de mes programmes m’a emmené sur la piste des shaders. Et c’est donc ce point qui m’a demandé le plus travail. L’utilisation des shaders conjointement avec openGL s’appelle le GLSL (openGL Shading Language) .

Les shaders sont le « plus » de la 3D : ils permettent de reconfigurer le pipeline de la carte graphique pour en modifier son fonctionnement. De ce fait, ils interviennent sur tous les rendus en fonction de ce qui est activé ou non : affichage de formes, de lumière, de profondeur, ect…. Deux types de shader existent : le pixel shader et le vertex shader. Pour faire simple : le vertex shader intervient sur les sommets qui composent une forme et le pixel shader intervient sur chaque pixel de cette même forme. Le but étant de reconfigurer la manière dont la carte graphique intervient sur le rendu final, ces deux types de shaders sont indissociables. Ils sont donc linkés entre eux dans ce qui s’appelle un program. Afin d’avoir une explication complète du fonctionnement des shaders, je vous conseille fortement ces adresses : http://www.siteduzero.com/tutoriel-3-8894-les-shaders-en-glsl.html et http://www.lighthouse3d.com/opengl/glsl/ (très intéressant pour les exemples qui y sont fournis). 

Une autre particularité des shaders est qu’ils ne sont pas compilés au même moment que votre programme. Ils sont compilés au lancement de l’exécutable (et non à sa compilation). Cela implique qu’il faut implémenter leur chargement et leur compilation (même si pour ca il y a pas mal de fonctions)


Ou est la difficulté ?

  • Ce n’est pas l’implémentation pour la compilation : la compilation des shaders répond à une méthodologie très précise et très bien expliquée (conf. lien plus haut).
  • Ce n’est pas non plus l’utilisation du shader : on l’active et le désactive à souhait.

 

  • Et enfin, le codage du shader pose peu de problèmes. En effet, 4 lignes de code dans votre shader peuvent suffire à affecter grandement vos rendus openGL. Il est cependant vrai que la programmation d’un shader demande de bonnes notions mathématiques, logiques et physiques.

La grosse difficulté du codage des shaders réside dans l’interprétation par openGL du shader. En effet, le shader agit de manière globale sur le rendu final, mais cela dépend grandement de ce qui est activé ou non dans votre programme.
Dans la vidéo servant d’exemple, j’ai implémenté des shaders pour l’affichage des boutons et menus (effets de fondu) et pour le soleil (effet de magma et de flamme).

Petites astuces :

  • L’implémentation d’une variable Uniform (variable qui permet de transmettre une valeur de l’application openGl vers le shader) qui est initialisée mais pas utilisée déclenche une erreur.
  • On ne peut pas recoder qu’une partie de ce que fais la carte graphique : Si l’on souhaite reconfigurer la couleur d’un objet avec un shader : il faut aussi reconfigurer les textures et les lumières, sinon elles ne seront pas présentes. Pour ce qui est du fonctionnement des textures, c’est presque facile. Pour ce qui est des lumières, cela demande de bien comprendre le fonctionnement de la lumières, en openGL d’abord, puis dans la théorie et enfin, dans son utilisation par la carte graphique.
  • L’utilisation des shaders présente l’avantage de ne pas ralentir l’ordinateur : c’est la carte graphique qui effectue les calculs et non pas le processeur. Cet avantage est un réel inconvénient lorsque l’ordinateur ne possède pas de carte graphique performante ou que celle-ci ne possède pas une version d’openGL récente. J’ai eu le problème et j’ai mis ma carte graphique Ati Radeon à jour sur ce site : http://www.touslesdrivers.com/index.php?v_page=23&v_code=26826 .
  • Pour transmettre plusieurs textures à un shader (multitexturing),  il faut bien sûr utiliser les variables « uniform ». Si vous avez appris à utiliser le GLSL, vous savez aussi que la fonction linkUniformVariableGLint() vous permet d’envoyer un entier au shader. Cette fonction va donc nous permettre d’envoyer nos textures. 
    Il faut vous être assuré d’avoir au préalable liés vos textures au program ainsi : U=glGetUniformLocationARB(nomDuProgram, varUniform) où :
  • U est l’entier servant d’identifiant dans le programme openGL,
  • nomDuProgram : le program (conf cour GLSL)  
  • varUniform, la variable Uniform dans le shader.


Ainsi, il faut pour envoyer plusieurs textures au shader :

 

Activer le numéro de texture 0 :
glActiveTexture(GL_TEXTURE0);
Appliquer la texture A voulu (appliqué en tant que texture 0):
glBindTexture(GL_TEXTURE_2D,A);
Activer le numéro de texture 1 :
glActiveTexture(GL_TEXTURE1);
Appliquer la texture B voulu (appliqué en tant que texture 1):
glBindTexture(GL_TEXTURE_2D,B);
On fait à ce moment appel à note shader :
_shader.active();
Puis on envoi enfin au shader la texture A au shader :
glUniform1iARB(TexA, 0);
Puis on envoi enfin au shader la texture B au shader :
glUniform1iARB(TexB, 1);

  

 
J’attire votre attention sur les 2 dernières lignes. Contrairement à se qu’on croit, il ne faut pas envoyer les textures A et B au shader mais le numéro correspondant en openGL. Ainsi A étant affecté à GL_TEXTURE0, on envoi 0 et B étant affecté à GL_TEXTURE1, on envoi 1.


Mais que se passe-t-il du coter de notre shader ?
uniform sampler2D texA récupère 0
uniform sampler2D texB récupère 1.
Ainsi la fonction GLSL texture2D(texA,gl_TexCoord[0].st) saura exactement qu’elle travaille sur la texture 0, soit GL_TEXTURE0 soit A.


Afin de pouvoir aider ceux qui se lancent dans l’utilisation des shaders, vous pouvez télécharger ces différents shaders que j'ai créé :

 

magma.jpg

Le magma du soleil : http://dl.dropbox.com/u/5275341/3Dworks/shaders/shadersMagma.7z .

 

 

apparitionProgressive.jpg

Apparition progressive d’une image : http://dl.dropbox.com/u/5275341/3Dworks/shaders/shaderApparitionProgressive.7z .
 

 

 

fondu2images.jpg

Transition fondu entre 2 images : http://dl.dropbox.com/u/5275341/3Dworks/shaders/TransitionEntreDeuxImages.7z .
 

 

 

rotation.jpg

Rotation d’une image sur un plan : http://dl.dropbox.com/u/5275341/3Dworks/shaders/shaderRotation.7z .

 


 


Etape 3 : la skybox


 

skybox-copie-2.jpg

Pour pouvoir représenter l’espace autour des astres, j’ai opté pour une skybox. Ce procédé nécessite l’utilisation d’extensions openGL. Glew permet de facilement vérifier si l’implémentation de votre système les supporte. La skybox est en fait un cube (GL_TEXTURE_CUBE_MAP_ARB) dans lequel la caméra se trouve, toujours au centre, ce qui donne la sensation d’être dans l’espace. 


  

 

J’ai donc créé moi-même les textures qui représentent l’espace : http://dl.dropbox.com/u/5275341/3Dworks/Skybox.7z .
Il fallait ensuite les charger. (Pour plus d’informations sur l’utilisation des skybox : http://3dworks.over-blog.com/article-systeme-solaire-47907606.html étape 6)





Etapes 4 : les textures
 

Pour ce qui est des textures, j’ai choisi le format jpeg.  C’est donc en utilisant SDL que je procède à ce chargement puis en utilisant openGL que je convertie ces surfaces en textures. (Pour plus d’informations sur l’utilisation des textures : http://3dworks.over-blog.com/article-systeme-solaire-47907606.html étape 3)

 


 


Etape 5 : caméra


Afin de pouvoir se déplacer dans l’espace, on utilise un trackball. Ce trackball réagit fonction des mouvements de la souris et du scrolling (pour se rapprocher et s’éloigner).

  • Ce trackball permet de se déplacer dans l’espace en ciblant différent astres mais ce n’est pas tout. En effet, la camera est programmé pour changer de comportement en fonction de l’astre qu’elle cible. De ce fait, lorsque l’on clique sur un astre, on fige l’astre qui sert de point référent. Par exemple, si l’on clique sur la terre : le soleil ne bouge pas ou si l’on clique sur la lune, la terre ne bouge pas.
  • Lorsque la caméra change de cible, la transition entre la cible précédente A et la suivante B se fait graduellement d’un point à un autre. Ce procédé est vraiment simple :  
    coordonneesA x (1-t) + coordonneesB x (t) = positionVoulu (t allant de 0 à 1). 
    Je m’explique. Si on veut aller d’un point A à un point B alors : 
    Au début, on est au point A et t=0 donc coordonneesA x (1-0)+ coordonneesB x 0 = coordonneesA. On est bien en A. 
    A la fin, on doit être au point B et t=1 donc coordonneesA x (1-1)+ coordonneesB x 1 = coordonneesB. On est bien en B. Faire varier t de 0 à 1 nous assure de partir de A pour arriver à B de manière linéaire.

(Pour plus d’informations sur l’utilisation des textures : http://3dworks.over-blog.com/article-systeme-solaire-47907606.html étape 5)

 



 


Etape 6 : les lumières


lumiere.jpg

L’éclairage est orienté afin de simuler la lumière de l’étoile sur les planètes. Il est aussi régler pour éclairer les éléments chimiques servant à construire une planète dans le menu du choix des éléments. Cette même lumière est désactivée sur le soleil (il représente lui-même la lumière) et sur les parties « menu » (Pour plus d’informations sur l’utilisation des lumières : http://3dworks.over-blog.com/article-systeme-solaire-47907606.html étape 4).


 




Etape 7 : Le mouse picking


Le mouse picking est en deux parties. La première partie est dédiée à la reconnaissance des formes openGL. Par exemple, savoir si la sourie est sur une planète (une sphère). La deuxième partie est dédiée à la reconnaissance de formes voulues. Par exemple, sur un quad on plaque une texture dont l’image est un rond et bien que la sourie soit sur un quad (un carré), on souhaite que la détection ne se fasse que sur le rond.

  • Pour ce qui est de la reconnaissance des formes openGL, il faut d’abord dessiner une première fois la scène pour que le mouse picking enregistre dans un pile tous les objets qui se trouve en dessous du pointeur de souris (inutile de mettre des couleurs, textures ou lumières : seul les formes comptent). On récupère alors dans cette pile l’identifiant de l’objet avec la profondeur la plus faible. Puis on redessine la scène pour l’afficher à l’écran cette fois ci. Pour connaitre la manière dont procédé, ce tutoriel est très bien expliqué (en anglais) : http://www.lighthouse3d.com/opengl/picking/ .
  • Pour ce qui est de la reconnaissance de formes voulues, le mouse picking est gérer manuellement en fonction de nombreux calculs selon la forme à détecter. Par exemple un bouton rond est en fait une texture avec un rond dessus qui est placé sur un quad. Il faut donc calculer la position de la souris par rapport au centre de ce cercle pour savoir si la souris est bien sur le bouton.

 


Etape 8 : La création de menus


g-2010-10-04-22-55-26-74.jpg L’implémentation des menus est segmentée en différentes classes: une partie « menu » et une partie « bouton ». Les boutons ont des propriétés et formes distinctes tandis que les menus regroupent plusieurs boutons. Dans l’exemple fourni, on affiche les boutons à l’aide des shaders.


 

  • Chaque bouton possède un état. Cet état peut être : « ouvert », « fermé», « survolé », « enfoncé », « en cours d’ouverture » ou « en cours de fermeture.
  • Chaque bouton peut donc déclencher :
    • un changement de son état. Par exemple, le survole d’un bouton modifie son état de « ouvert » à « survolé », ce qui s’accompagne de l’appel d’un shader.  
    • un changement des états des autres boutons et/ou menus. Par exemple, cliquer sur un bouton pour afficher un menu modifie l’état du menu à afficher de « fermé » à « en cours d’ouverture ».  Lorsqu’un menu est« en cours d’ouverture », un variable évolue graduellement de 0 à 1. Lorsque 1 est atteint, l’état du menu passe de « en cours d’ouverture » à « ouvert ».  
    • des actions diverses dans le programme. Par exemple, cliquer sur le bouton de validation de création d’une planète édite automatiquement un fichier data afin de sauvegarder, en temps réel, l’apparition d’une nouvelle planète.



Etape 9 : La création d’astre.


La classe mère ASTRE peut être dérivée en classes filles : soit une PLANETE, soit une ETOILE.
Un astre possède : positions, trajectoire, identifiant, forme, taille, couleurs, textures, date de création, composition chimique et durée de vie et un nom. Une planète possède tous ce que possède un astre avec en plus de la vie Une étoile possède en plus une force de rayonnement. La création d’un astre constitue essentiellement l’enregistrement de nouvelles données au sein du programme. Pour des raisons pratiques, dans la démonstration que je vous ai faite, le choix du nom d’un astre nouvellement créé est généré automatiquement, de même que sa taille et sa vitesse. Par contre, la définition de la trajectoire et de la composition chimique de l’astre est paramétrable.


La création d'un astre fonctionne ici:

g-2010-10-04-22-07-40-18.jpg
Déterminer la composition chimique d’un astre n’a ici aucun effet, si ce n’est que la couleur est une moyenne des couleurs des différents éléments chimiques choisis.

 
Déterminer la trajectoire de l'astre consiste en 3 étapes :

 

g-2010-10-04-22-06-59-57.jpg Choisir un astre qui va servir de référentiel : un simple clic sur un astre fait de cet astre un point référent.
skybox-copie-1.jpg Choisir où positionner notre nouvel astre.
g-2010-10-04-22-08-29-12.jpg Choisir l’inclinaison et le sens de parcours de l’astre nouvellement créé.

  


 


Etape 10 : afficher du texte


Ce n’est pas aussi simple que l’on pourrait le croire. En effet, SDL permet facilement  d’afficher grâce à sa librairie TTF du texte à l’écran mais pas dans un contexte openGL. Si vous ne connaissez pas encore cette librairie, je vous invite à vous rendre sur : http://www.siteduzero.com/tutoriel-3-14144-ecrire-du-texte-avec-sdl-ttf.html .

Pour pouvoir afficher du texte, il faut :

  • Générer une SDL_ surface avec le texte voulu : 
  • Plaquer  la SDL_ surface obtenue que une surface openGL (de type quad) :


Voici la fonction setNom() permettant tout cela

GLuint _texte; //soit la texture qui contient le nom de l’astre: ce qui permet l'affichage de celui-ci.

 

//nom est le texte que l’on souhait afficher
//police est la police que l’on souhaite utiliser pour afficher le texte
//couleur est la couleur du texte.
void setNom(string nom,TTF_Font *police,SDL_Color couleur){
    if(_texte!=0) //si une texture openGL n'est pas déjà affectée à _texte
    {
        glDeleteTextures(1,&_texte); //on vide _texte
        _texte=0; //_texte est vidée.
    }


    glEnable(GL_TEXTURE_2D); //on active la gestion de texture 2D openGL
    _texte= charge_texte(nom,police,couleur);
    glDisable(GL_TEXTURE_2D); //on desactive la gestion de texture 2D openGL

}

//cette fonction permet de récupérer une texture composée de texte afin de l'afficher en openGL
//nom est le texte que l’on souhait afficher
//police est la police que l’on souhaite utiliser pour afficher le texte
//couleur est la couleur du texte.
GLuint charge_texte(const char * nom,TTF_Font *police, SDL_Color couleur){
    SDL_Surface* gl_fliped_surface=NULL; //la surface SDL qui reçoit notre texte SDL
    GLuint glID=0; //la texture openGL qui va être renvoyé.

// on applique sur la surface SDL notre texte et on vérifie que cela a fonctionné.
    if((gl_fliped_surface=TTF_RenderText_Blended(police,nom,couleur)) !=NULL)
    {

        glGenTextures(1, &glID); //génère un entier pour la texture glID à renvoyer
       

        //on spécifie la texture sur laquelle agiront les opérations futures
        glBindTexture(GL_TEXTURE_2D, glID);  


        //création de la texture openGL grâce à la surface SDL
        glTexImage2D(GL_TEXTURE_2D, 0, 4, gl_fliped_surface->w,gl_fliped_surface->h, 0, GL_RGBA,GL_UNSIGNED_BYTE, gl_fliped_surface->pixels);

        //paramétrage de l’affichage de la texture
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);

        //libération des surfaces
        SDL_FreeSurface(gl_fliped_surface);
    }

    return glID;
}


 

Utilisation :
GLfloat taille_texte[2]; //les dimensions du texte à afficher ( à vous de choisir)
glEnable(GL_TEXTURE_2D); //on active la gestion de texture 2D openGL
glBindTexture(GL_TEXTURE_2D,_texte); //on souhaite utiliser notre texture qui contient notre texte
    glBegin(GL_QUADS);
    glTexCoord2d(1,0);
    glVertex3f(0, taille_texte[1],-taille_texte[0]);
    glTexCoord2d(0,0);
    glVertex3f(0, taille_texte[1],taille_texte[0]);
    glTexCoord2d(0,1);
    glVertex3f(0,-taille_texte[1],taille_texte[0]);
    glTexCoord2d(1,1);
    glVertex3f(0,-taille_texte[1],-taille_texte[0]);
    glEnd();

glDisable(GL_TEXTURE_2D); //on désactive la gestion de texture 2D openGL



 
Etape 11 : la gestion du temps


J’ai créé une variable statique dans une classe de gestion du temps qui se calle sur la variable de gestion du temps de boucle.


Qui plus est, grâce à la gestion du temps de boucle et du FPS, le temps est en seconde. Ainsi, lorsque j’indique comme vitesse de trajectoire à un astre : 1, il fait 1 tour en 1 seconde. Lorsque je lui indique 1/10, il met 10 secondes pour faire un tour.


Afin d’implémenter ce genre de système je vous conseille : http://www.siteduzero.com/tutoriel-3-14136-maitrisez-le-temps.html .

 




Etape 12 : La musique


Mettre en place de la musique dans une application est simple grâce à SDL et plus précisément grâce à la bibliothèque : SDL_mixer : http://www.siteduzero.com/tutoriel-3-80307-jouer-du-son-avec-sdl-mixer.html .

 


 

Les résultats en images:

 

fin1.jpg

fin3.jpg

 

Damien

Par Damien - Publié dans : Articles francais
Ecrire un commentaire - Voir les 0 commentaires
Retour à l'accueil
 
Créer un blog gratuit sur over-blog.com - Contact - C.G.U. - Rémunération en droits d'auteur - Signaler un abus