Tutoriel 2 - Les bases - Création d'une application à partir de ... rien
Date de publication : 30/06/2006 , Date de mise à jour : 30/06/2006
Par
Nicolas Bauland (Autres articles)
Dans le tutoriel précédent, vous avez créé une scène avec des objets maillés
que nous appellerons désormais "entités". vous avez également appris à placer
ces objets en les déplaçant ou en les orientant. Pour faire tout cela, vous
vous étiez appuyés sur une classe déjà existante, la classe
ExampleApplication. Nous ne nous étions pas tellement interessés au
fonctionnement de cette classe là.
Nous allons, ici, nous intéresser à la construction d'une application Ogre
sans nous appuyer sur quoi que ce soit. Ce qui vous permettra de choisir entre
créer votre propre application Ogre, ou utiliser tout ou partie de la classe
ExampleApplication en sachant ce qu'elle réalise.
I. Prérequis
II. Introduction
III. C'est parti !
I-A. L'application ogre
I-B. L'objet root
I-C. Création de la fenêtre de rendu
I-D. Création de la scène
I-E. Les ressources
I-E-1. Code en dur !
I-E-2. Utilisation d'un script de configuration
I-F. Création de la caméra d'observation
I-G. Création du "Viewport"
I-H. La boucle de rendu
V. Conclusion
Téléchargements
Remerciements
I. Prérequis
Ce tutoriel présume que vous ayez des connaissances de programmation en
C++ et que vous êtes capable de configurer et compiler une application
Ogre (si vous avez des difficultés à configurer votre application, voir
ce guide pour les configurations
spécifiques au compilateur). Aucune connaissance sur Ogre n'est nécessaire,
en dehors de celles concernant l'installation, pour aborder ce tutoriel.
Bien que je détaille la procédure pour les compilateurs Microsoft Visual
Studio 2005 et Microsoft Visual C++ Express 2005 (nommé dans la suite VS),
tout le code présenté fonctionne quel que soit le compilateur et
l'environnement (Windows ou linux), vous aurez juste à taper le code
et à créer les fichiers à la main si vous utilisez un autre environnement
de programmation.
II. Introduction
Dans ce tutoriel, on va chercher à créer une application Ogre à partir de rien. Bien que cet objectif semble très simple, vous verrez qu'il met en oeuvre beaucoup de choses : vous pouvez regarder la table des matières de cet article pour vous en convaincre.
III. C'est parti !
Nous nous contenterons de créer une application minimale qui ne fera rien d'autre
que d'afficher une tête d'Ogre !
I-A. L'application ogre
Créez un projet Ogre, si vous utilisez l'assistant pensez bien à
choisir un projet vide, si ce n'est pas le cas et que vous êtes
sous Windows créez bien un projet Windows Application et non un
projet Console Application. Créez dans ce projet un fichier nommé Tutoriel2.cpp
et tapez pour l'instant le code suivant :
| Tutoriel2.cpp | #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
#else
int main(int argc, char *argv[])
#endif
{
return 0;
} |
Bien, rapidement que fait ce code ? Pas grand chose à vrai dire : il
crée une application qui ne fait rien, passionnant ! Les #if, #ifdef ...
permettent de vérifier si on fonctionne sous Windows ou non afin de charger
l'entête windows.h si nécessaire.
I-B. L'objet root
Ajoutez la prise en charge d'Ogre. Pour cela ajoutez, en tête du fichier, la directive :
| Tutoriel2.cpp | #include <Ogre.h> |
Créez à présent l'application Ogre proprement dite, ajoutez donc dans la fonction main :
| Tutoriel2.cpp | try{
Ogre::Root* root=new Ogre::Root();
delete root;
}catch(Ogre::Exception e){
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
MessageBox(NULL,e.getFullDescription(),
"Erreur d'initialisation de l'objet Root",MB_OK|MB_ICONERROR|MB_TASKMODAL);
#else
std::cerr << "Une exception a été levée: " << e.getFullDescription().c_str() << std::endl;
#endif
} |
Une application Ogre est susceptible de lever des exceptions, c'est pourquoi
les deux instructions sont entourées dans un bloc try-catch. Celui-ci
ne fait qu'afficher à l'écran (soit par une boîte de message si vous êtes
sous Windows, soit dans la console sinon) la description de l'erreur.
Je n'expliquerai pas ici les exceptions, cela l'étant dans tout bon
livre de référence sur le C++.
La première ligne crée l'objet indispensable
à toute application Ogre : l'objet Root. C'est à partir de cet objet
que se construira tout le reste, cet objet est donc indispensable
à notre application. La deuxième ligne se contente de nettoyer
correctement la mémoire. Si vous compilez et lancez l'application,
le résultat sera quelque peu décevant : il ne se passera rien !
Etoffons maintenant cette application.
 |
Dans Ogre, tous les types d'objets définis dans la bibliothèque Ogre sont
placés dans un espace de nom (namespace) appelé Ogre. Ainsi
tous ces types doivent être précédés de Ogre:: pour pouvoir y faire
référence. Cette notation que l'on peut parfois trouver lourde a,
entre autre, l'avantage de bien mettre en évidence l'appartenance de
ces objets à Ogre. Si toutefois vous souhaitez vous évitez cette
"décoration", vous pouvez spécifier en début de fichier, juste après la
directive d'inclusion de l'entête Ogre, la directive :
|
I-C. Création de la fenêtre de rendu
Vous devez ajouter à votre programme une chose essentielle qui
apparaissait à chaque lancement de votre application : la boîte
de dialogue de configuration de l'écran. Ceci va vous permettre
d'obtenir un pointeur vers une fenêtre de rendu : RenderWindow.
Ajoutez les lignes suivantes entre la création de l'objet Ogre et sa
destruction :
| Tutoriel2.cpp | if(!root->restoreConfig()){
if(!root->showConfigDialog()){
delete root;
return -1;
}
}
Ogre::RenderWindow* pRenderWindow;
pRenderWindow = mRoot->initialise(true,"Tutoriel 2 App"); |
Que font ces lignes ? Le premier if, vérifie qu'un fichier de configuration
d'écran existe (ogre.cfg par défaut; si vous souhaitez modifier ce nom de fichier,
utilisez le deuxième paramètre du constructeur de l'objet Root).
Si ce fichier existe, les paramètres sont chargés et la fonction renvoie
true; si le fichier n'existe pas ou si les paramètres ne sont
pas valides, la méthode restoreConfig() renvoie false,
dans ce cas, on affiche la boite de dialogue de configuration d'écran
avec showConfigDialog(). Si cette fonction renvoie false,
l'utilisateur a alors cliqué sur Annuler, dans ce cas on nettoie l'objet
root et on ressort de l'application en envoyant le code d'erreur -1.
Sinon la fonction a renvoyé true, on crée alors un pointeur vers
un objet "fenêtre de rendu", ou RenderWindow que l'on stocke dans
pRenderWindow avec la fonction initialise. Cette fonction prend 2
arguments : le premier est booléen et, permet de spécifier si la création
de la fenêtre est immédiate ou non; le deuxième est une chaîne permettant
de spécifier le titre de la fenêtre. Vous pouvez compiler et lancer
l'application : une fenêtre de configuration s'ouvrira selon que vous avez
déjà ou non un fichier ogre.cfg (vous pouvez l'effacer pour voir la
conséquence sur le lancement de l'application). Un écran noir s'affiche
puis l'application se termine.
I-D. Création de la scène
Que faire si vous souhaitez à présent afficher quelques objets comme
on l'a déjà vu dans le précédent tutoriel ? Il nous faut un gestionnaire
de scène, ajoutez donc :
| Tutoriel2.cpp | Ogre::SceneManager* pSceneManager=NULL;
pSceneManager=root->createSceneManager(Ogre::ST_GENERIC,"SceneManager Generique"); |
Vous avez votre gestionnaire de scène, ici, un gestionnaire générique.
Il existe d'autres types de gestionnaires, notamment certains qui
permettent de gérer les grandes scènes d'extérieur avec terrain, ciel
et grands espaces. Je vous renvoie à la documentation si cela vous
intéresse car ce n'est pas le but de ce tutoriel. Pour votre petite
scène, un générique suffira amplement. Pour le reste du code, vous
devriez être assez familier avec, je ne le détaillerai pas :
| Tutoriel2.cpp | Ogre::Entity* pEntity=pSceneManager->createEntity("Ogre Head","OgreHead.mesh");
Ogre::SceneNode* pNode=pSceneManager->getRootSceneNode()->createChildSceneNode("Node");
pNode->attachObject(pEntity); |
Verifier si vous pouvez compiler, mais ne lancez surtout pas votre programe ! Celui-ci se planterait !
I-E. Les ressources
Pourquoi planterait-il ? Une exception serait levée car Ogre ne saurait pas
où trouver le fichier ogrehead.mesh. Ah ?!? Mais dans le tutoriel 1, cela n'avait
pas posé de problème, vous ne vous étiez d'ailleurs même pas poser la question ! Qu'est ce qui a donc changé ?
Dans le tutoriel 1, votre application héritait de la classe ExampleApplication
qui faisait une grande partie du travail pour vous et, entre autre, celui
d'indiquer à Ogre où résidaient les fichiers des objets maillés et des
textures grâce au fichier resources.cfg dans lequel vous pouvez
jeter une oeil pour voir ce qu'il contient. Il va vous falloir réaliser cette opération à la main, ici.
I-E-1. Code en dur !
Il vous faut ajouter le chemin qui permettra de trouver le fichier ogrehead.mesh,
puis initialiser les ressources. Ajoutez les deux lignes suivantes juste avant la création
de votre scène :
| Tutoriel2.cpp | Ogre::ResourceGroupManager::getSingleton().addResourceLocation("../../media/models","FileSystem","General");
Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups(); |
La première fonction ajoute le chemin, indique qu'il s'agit un répertoire (FileSystem)
et non d'une archive Zip, le dernier paramètre, "Général" ici, permet de définir
différents groupes de ressources. Ici vous n'en utiliserez qu'un compte tenu du
faible nombre d'entités différentes : 1 ici !
Compilez puis lancez le programme. Celui va se terminer sans rien afficher, cela est
parfaitement normal : vous avez créé notre scène, mais vous ne demandez jamais de
l'afficher ! Là encore la classe ExampleApplication le faisait pour vous ...
Mais avant de s'occuper de cela, examinons ensemble un autre problème plus "vicieux" ...
Allez jeter un coup d'oeil au fichier ogre.log dans le dossier bin du SDK : parcourez-le
et cherchez attentivement vers la fin du fichier les lignes suivantes :
| Ogre.log | Mesh: Loading OgreHead.mesh
Can't assign material ... |
Argh ! Qu'est-ce donc ? Les entités dans Ogre sont
composées d'un modèle de maillage 3D, ce sont les fichiers .mesh qui
peuvent être construits avec 3DS Max, Maya, blender ... avec l'exporteur
adéquat. Ces fichiers ne contiennent que la description géométrique de l'objet,
c'est-à-dire, l'emplacement des points dans l'espace. Pour réaliser un objet
en 3D, la tête d'ogre dans notre cas, il faut ajouter également une ou
des textures, qui sont les images à superposer sur le maillage pour
donner l'impression d'une matière. Ces matières (ou material) sont contenues dans
des fichiers .material qui peuvent être également construits par les
logiciels 3DS, Maya ou blender avec l'exporteur correspondant.
Les quatres textures que ne trouvent pas Ogre sont : Ogre/Eyes, Ogre/Skin,
Ogre/Earring et Ogre/Tusks. Ces 4 "material" sont, en réalité, contenus
dans le fichier Ogre.material qui se situe dans le répertoire
media/materials/scripts du SDK. Ce fichier fait lui-même appel à quatre
textures qui se trouvent dans media/materials/textures.
Ajoutez donc les lignes suivantes dans l'ajout des ressources juste avant l'initialisation des groupes de ressources :
| Tutoriel2.cpp | Ogre::ResourceGroupManager::getSingleton().addResourceLocation("../../media/materials/scripts","FileSystem","General");
Ogre::ResourceGroupManager::getSingleton().addResourceLocation("../../media/materials/textures","FileSystem","General");
Ogre::ResourceGroupManager::getSingleton().addResourceLocation("../../media/materials/programs","FileSystem","General"); |
Tiens, mais n'a-t-on pas parlé que des deux premières lignes ? Or,
il y en une de plus : media/materials/programs. Dans le répertoire
scripts, il n'y a pas que
notre matière pour l'ogre; il y a d'autres exemples, et parmi ceux-ci, certains
font appels à des shaders. Or ceux-ci nécessitent les fichiers se trouvant
dans media/materials/programs, d'où la présence de cette troisième ligne.
Recompilez, lancez à nouveau, il ne devrait plus y avoir d'erreur ni apparentes
ni dans le fichier Ogre.log. Mais cette programmation est-elle vraiment envisageable ?
Dès que l'on souhaitera ajouter un objet ou une texture qui ne sera pas dans
les répertoires précédents, ça risque d'être compliqué. Et surtout, on devra
recompiler tout le code ! Cette solution fonctionne mais nous pourrions trouver mieux.
I-E-2. Utilisation d'un script de configuration
Pourquoi ne pas reprendre le principe utilisé dans ExampleApplication,
qui consiste à aller mettre nos différents dossiers et/ou archives dans
un fichier appelé par exemple resources.cfg ? Cette méthode sera souple : on
pourra modifier les répertoires sans tout recompiler, et on va voir qu'Ogre
va beaucoup nous faciliter le travail !
Le fichier resources.cfg contient des entêtes de sections; dans chaque
section, on trouvera des lignes contenant le type de ressources, puis le chemin
de celle-ci, bref, les paramètres de la fonction addResourcesLocation().
Ouvrez donc votre fichier ressources :
| Tutoriel2.cpp | Ogre::ConfigFile cf;
cf.load("resources.cfg"); |
Créez un itérateur qui vous permettra de parcourir les différentes sections du fichier :
| Tutoriel2.cpp | Ogre::ConfigFile::SectionIterator si=cf.getSectionIterator(); |
Maintenant pour chaque section (vous stockerez le nom de la section dans secNom),
il vous faut prendre chaque ligne; et pour chaque ligne, prendre le type d'archive,
que vous stockerez dans typeArch et son chemin que vous stockerez dans archChemin :
| Tutoriel2.cpp | Ogre::String secNom, typeArch, cheminArch;
while(si.hasMoreElements()){
secNom = si.peekNextKey();
Ogre::ConfigFile::SettingsMultiMap *settings = si.getNext();
Ogre::ConfigFile::SettingsMultiMap::iterator it;
for (it=settings->begin(); it!=settings->end();++it){
typeArch = it->first;
cheminArch = it->second;
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(cheminArch,typeArch,secNom);
}
}
Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups(); |
Au final, par rapport au six lignes précédentes, ça ne vous fait guère plus de code,
si l'on compare aux possibilités de ces lignes. Il faudra, en revanche, prendre soin
de placer un fichier resources.cfg (ou un autre nom si vous modifiez le
code précédent) dans le répertoire de l'application. Pour tous mes tutoriaux, le
fichier par défaut (resources.cfg)sera amplement suffisant.
Mais toujours pas d'Ogre en vue !
I-F. Création de la caméra d'observation
Ajoutez donc une camera, c'est par celle-ci que vous pourrez observer
votre scène. L'ajout ressemble beaucoup à celui d'une entité, si ce n'est
que vous n'aurez pas besoin de noeuds :
| Tutoriel2.cpp | Ogre::Camera* pCamera=pSceneManager->createCamera("Camera"); |
Il faut, bien évidemment, spécifier la position de la caméra, et le point
qu'elle observe. Plaçons la en (0,50,200), et faisons la observer le point
d'origine (0,0,0). De cette façon, nous n'avons pas à préciser la focale
de la caméra, bien que cela puisse être fait également :
| Tutoriel2.cpp | pCamera->setPosition(Ogre::Vector3(0,50,200));
pCamera->lookAt(Ogre::Vector3(0,0,0)); |
I-G. Création du "Viewport"
Il ne faut pas seulement créer la caméra : ce qui est vu par la camera peut
n'occuper qu'une partie de l'écran. Il faut qu'on précise à Ogre que nous
souhaitons voir la scène par l'intermédiaire de la caméra et que cette vue
occupe tout l'écran ! C'est ce que l'on nomme le Viewport. Imaginez
un jeu de courses de voitures où deux joueurs s'affrontent; l'écran pourrait
être divisé en deux parties : la partie gauche serait une vue consacrée à la
caméra suivant la voiture du joueur 1, et à droite, la caméra suivant la
voiture du joueur 2. Ici, faisons simple : une caméra, une vue ! Il faut
donc ajouter un Viewport, on définira au passage la couleur de fond
ici un bleu foncé (0.0,0.0,0.3) en couleur RGB :
| Tutoriel2.cpp | Ogre::Viewport* pViewport=pRenderWindow->addViewport(pCamera);
pViewport->setBackgroundColour(Ogre::ColourValue(0.0,0.0,0.3));
pCamera->setAspectRatio(Ogre::Real(pViewport->getActualWidth())/Ogre::Real(pViewport->getActualHeight())); |
I-H. La boucle de rendu
Il ne nous reste plus qu'à ajouter la fonction qui indique à Ogre d'afficher
à proprement parler la scène, c'est-à-dire d'effectuer le rendu à l'écran :
| Tutoriel2.cpp | root->startRendering(); |
Ouf! Enfin fini! Vite, vite compilez. Pendant ce temps, réfléchissez : que
fait startRendering() ? Cette méthode fait le rendu puis l'affiche,
refait un rendu puis l'affiche, refait ... Stop ! Cette fonction s'arrête
quand ? JAMAIS ! Enfin presque. Si vous exécutiez votre programme en l'état
actuel des choses, le programme ne se terminerait pas. Il va donc falloir
travailler encore ! On va commencer par enlever cette instruction de boucle
sans fin. On verra dans le prochain tutoriel que l'on peut, sous certaines
conditions de programmation, utiliser cette fonction bien utile.
Pour l'instant, malheureusement, nous devrons nous en passer.
Il faut que vous rajoutiez ce qu'il faut pour qu'à chaque rendu de votre scène,
Ogre vérifie que l'utilisateur ait appuyé ou non sur la touche ECHAP. Si c'est
le cas, il faut stopper l'application. Créez l'objet qui vous permettra de lire
le clavier :
| Tutoriel2.cpp | Ogre::InputReader* pInputReader = Ogre::PlatformManager::getSingleton().createInputReader();
pInputReader->initialise(pRenderWindow,true,false); |
La boucle de rendu doit commencer par afficher le rendu de la scène. Enfin
elle lit le clavier et si la touche ECHAP est enfoncée, elle doit sortir de
la boucle pour terminer le programme. Ajoutez maintenant le code de la
boucle de rendu proprement dite :
| Tutoriel2.cpp | while(1){
root->_updateAllRenderTargets();
pInputReader->capture();
if(pInputReader->isKeyDown(Ogre::KC_ESCAPE))
break;
}
} |
Voilà, c'est fini cette fois ! Vous devriez pouvoir observer la belle tête
d'Ogre ! Que de travail juste pour une tête !
V. Conclusion
Dans ce tutoriel, vous avez mis les mains dans le cambouis : vous
avez fait votre application de A à Z. On notera que dans le premier
tutoriel, vous aviez pu vous passer de tout cela, grâce à une classe
d'application Tutoriel1App que vous aviez dérivé d'une classe
existante : ExampleApplication. N'oubliez pas, en effet, qu'Ogre
est avant tout un moteur 3D orienté objet. Il vous faut donc penser à
la réutilisation du code. D'où l'intérêt de la classe ExampleApplication.
Pourquoi dans ces conditions avez vous fait tout cela ? Parce que comme
beaucoup vous le diront, la classe ExampleApplication, qui est
d'une praticité indéniable pour débuter, est écrite de manière très
contestable : les fichiers d'entêtes et de codes ne sont absolument pas
séparés. La conception de la classe peut aussi être criticable en fonction
de vos besoins.
Grâce à ce tutoriel, j'espère que vous pourrez vous servir de cette classe tout
en étant critique par rapport à sa construction. Elle est très bien pour se focaliser
sur des points précis comme les entités, les noeuds de scène, les animations
(que nous verrons plus tard) ... sans se soucier de la gestion de l'application.
Elle est en revanche horrible pour servir de base à un véritable Framework d'application
Ogre.
Téléchargements
Voici les fichiers relatifs à ce tutoriel :
Remerciements
Un grand merci à Laurent Gomilla pour la relecture et les conseils, ainsi qu'à Khayyam90 pour sa grande et précieuse liste de corrections.
 
|