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 connaissances 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 quelque 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 introduira les bases d'Ogre : les noeuds de scène, les gestionnaires de scène et les entités d'objets. Nous ne verrons pas beaucoup de code; en fait, nous nous focaliserons sur les concepts généraux pour commencer à apprendre et à utiliser Ogre.

En même temps que vous parcourerez ce tutoriel, vous devriez ajouter tranquillement le code dans votre propre projet et observer les résultats au fur et à mesure que nous le construirons. Il n'y a pas vraiment d'autres solutions pour vous familiariser avec ces concepts ! Résistez à l'envie de simplement lire ce texte en diagonale .

Dans ce premier tutoriel, je ne m'interesserai qu'à vous faire découvrir quelques possibilités d'Ogre, principalement l'affichage d'objet 3D. On utilisera pour tout ce premier tutoriel, une classe ExampleApplication qui existe déjà (Répertoire:(OgreSDK)\samples\include) et qui nous fera l'essentiel du travail. Nous n'étudierons pas en détail cette classe, ce sera l'objet du prochain tutoriel.

III. C'est parti !

On va utiliser du code de base préconstruit pour ce tutoriel. Vous ne devez pas, pour l'instant, vous préoccuper du code autre que celui de la fonction createScene() que nous allons ajouter. Dans un prochain tutoriel, nous étudierons plus en profondeur comment fonctionne une application Ogre, mais pour l'instant nous nous focaliserons sur des choses plus simples. Créez un projet nommé Tutoriel1 à l'aide de l'assistant (voir ici pour l'installation avec VS), sélectionnez les options suivantes:

  • Application minimale,
  • à partir de l'application exemple
  • copie après la compilation (l'exécutable sera ainsi directement copié dans le répertoire bin de la librairie Ogre pour pouvoir accéder aux DLLs).

Vous devriez obtenir le code suivant dans un fichier nommé Tutoriel1.cpp:

Tutoriel1.cpp
Sélectionnez
 
/*
-----------------------------------------------------------------------------
This source file is part of OGRE
(Object-oriented Graphics Rendering Engine)
For the latest info, see http://www.ogre3d.org/
 
Copyright (c) 2000-2005 The OGRE Team
Also see acknowledgements in Readme.html
 
You may use this sample code for anything you like, it is not covered by the
LGPL like the rest of the engine.
-----------------------------------------------------------------------------
*/
 
#include <Ogre.h>
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#endif
 
#ifdef __cplusplus
extern "C" {
#endif
 
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
   INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
#else
   int main(int argc, char *argv[])
#endif
   {
      // Create application object
      //Tutoriel1App app;
 
      SET_TERM_HANDLER;
 
      try {
         //app.go();
      } catch( Ogre::Exception& e ) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
         MessageBox( NULL, e.getFullDescription().c_str(), "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
         std::cerr << "An exception has occured: " <<
            e.getFullDescription().c_str() << std::endl;
#endif
      }
 
      return 0;
   }
 
#ifdef __cplusplus
}
#endif
 

Vous remarquerez qu'un objet app correspondant à la classe Tutoriel1App est déclaré (Tutoriel1App app;) puis la méthode go de cet objet est appelé (app.go();). Ces deux lignes sont commentées ce qui les rends inactives. De plus la classe Tutoriel1App n'existe pas encore, nous allons la créer de façon propre, c'est-à-dire dans des fichiers séparés.

Commençons par créer une nouvelle classe que vous nommerez Tutoriel1App. Cette classe sera dérivée d'une classe ExampleApplication fournie dans les exemples du SDK d'Ogre. Nous étudierons la conception (plus ou moins bien faite) de cette classe, et verrons comment créer la notre dans un prochain tutoriel. Dans VS, vous pouvez le faire en sélectionnant le projet et en cliquant sur ajouter une classe dans le menu contextuel puis sélectionnez Classe C++. Nommez votre classe Tutoriel1App, définissez le chemin du fichier .h dans le répertoire include, et celui du .cpp dans src. N'oubliez pas de spécifier la classe de base, à savoir ExampleApplication (Ne tenez pas compte de la fenêtre d'avertissement), vous devriez obtenir la fenêtre suivante:

Image non disponible

Cliquer sur Terminer. Vous obtiendrez donc deux fichiers:

Tutoriel1App.h
Sélectionnez

#pragma once
 
class Tutoriel1App :
	public ExampleApplication
{
public:
	Tutoriel1App(void);
public:
	~Tutoriel1App(void);
};
 
Tutoriel1App.cpp
Sélectionnez

#include "..\include\Tutoriel1App.h"
 
Tutoriel1App::Tutoriel1App(void)
{
}
 
Tutoriel1App::~Tutoriel1App(void)
{
}

Il faut faire appel à cette classe dans notre fichier de départ: Tutoriel1.cpp. Pour cela rajouter la ligne suivante:

Tutoriel1.cpp
Sélectionnez

#include "..\include\Tutoriel1App.h"

Décommentez les deux lignes où apparait Tutoriel1App. Dorénavant, nous ne manipulerons plus que la classe Tutoriel1App. Pour cela revenons au fichier Tutoriel1App.h, juste après la ligne qui permet d'éviter les appels mutiples à ce fichier, rajoutez:

Tutoriel1App.h
Sélectionnez

#include "ExampleApplication.h"

Cette classe ExampleApplication, contient le canevas par défaut d'une classe d'application pour le moteur Ogre. Cette classe est une classe abstraite. Nous devons donc définir la fonction virtuelle afin de pouvoir instancier notre nouvelle classe. Cette fonction virtuelle est createScene (en réalité, nous ne ferons que modifier son contenu dans ce premier tutoriel). L'étude de l'architecture de la classe ExampleApplication sera l'objet du deuxième tutoriel.

Rajouter la déclaration de cette fonction dans le fichier d'entête:

Tutoriel1App.h
Sélectionnez

void createScene(void);

Rajouter la définition dans le fichier de code. Pour l'instant, ce sera simple, cette fonction ne fera absolument rien:

Tutoriel1App.h
Sélectionnez

void Tutoriel1App::createScene(void){
}

IV. Comment Ogre fonctionne ?

Nous allons commencer par étudier le gestionnaire de Scène puis nous aborderons les entités et, enfin, les noeuds de scène. Ces trois classes sont les structures fondamentales des applications Ogre.

IV-A. Les bases du gestionnaire de scène

Tout ce qui apparaît à l'écran est géré par le gestionnaire de scène (original, non ?). Lorsque l'on place un objet sur la scène, le gestionnaire de scène est la classe qui garde la trace de sa localisation. Lorsque l'on crée des caméras sur la scène (chose que l'on abordera dans un prochain tutoriel), le gestionnaire de scène garde leur trace. Il en va de même pour les plans, les lumières ... et ainsi de suite.

Il existe plusieurs types de gestionnaires de scène. Certains gèrent le rendu de terrain, d'autres de texture BSP ... Vous pouvez avoir une liste de ces gestionnaires ici. On abordera ces différents gestionnaires au fur et à mesure des tutoriels.

IV-B. Les bases des entités

Une entité est un objet dont on peut avoir un rendu à l'écran. On peut voir comme entité tout ce qui peut être représenté par un maillage en 3D. Un robot peut être une entité, un poisson est une entité, le terrain sur lequel se déplace un personnage est un objet très vaste. Les choses comme les lumières, les particules, les cameras ... ne sont pas des entités.

Une chose à noter à propos d'Ogre est qu''il sépare les objets, dont on peut faire un rendu, et, leur localisation et leur orientation. Cela signifie que l'on ne peut pas placer directement un objet sur la scène. En fait, il faut attacher un objet à un noeud de scène, ce noeud de scène contiendra les informations concernant la position et de l'orientation de l'entité.

IV-C. Les bases des noeuds de scène

Comme déjà dit, les noeuds de scène contiennent les informations de position et d'orientation de tous les objets qui y sont rattachés. Lorsque l'on crée une entité, elle ne sera pas affichée à l'écran tant qu''elle ne sera pas rattachée à un noeud de scène. De la même façon, un noeud de scène n'est pas un objet visible à l'écran. Il n'y a qu''en créant un noeud de scène ET en lui rattachant un objet que quelque chose sera visible à l'écran.

Un noeud de scène peut avoir un nombre quelconque d'objets attachés. Par exemple, si l'on souhaite avoir un personnage se déplaçant à l'écran avec une lumière autour de lui. Il suffit donc de créer en premier un noeud de scène, créer une entité pour le personnage et l'attacher au noeud de scène. On crée ensuite une lumière que l'on attache au même noeud de scène. Les noeuds de scène peuvent eux-mêmes être attachés à d'autres noeuds de scène, permettant ainsi de créer une hiérarchie complète de noeuds. Nous utiliserons cela plus tard dans ce tutoriel.

Un point fondamental à propos des noeuds de scène est que la position d'un noeud de scène est TOUJOURS relative à son noeud de scène parent, et chaque gestionnaire de scène contient un noeud racine auquel sont attachés tous les noeuds de scène.

V. Première application Ogre

Revenons maintenant au code précédent. Cherchons la fonction membre Tutoriel1App::createScene. Nous ne manipulerons que le contenu de cette fonction dans ce tutoriel. La première chose que nous allons faire est de définir la lumière ambiante pour la scène afin que nous puissions voir ce que nous ferons. Nous ferons cela en appelant la fonction SetAmbientLight et spécifier la couleur que nous souhaitons. On notera que le constructeur de ColourValue attend une valeur pour le rouge, le vert et le bleu chacune étant dans un intervalle compris entre 0 et 1. Ajouter cette ligne à createScene:

Tutoriel1App.cpp
Sélectionnez

	mSceneMgr->setAmbientLight(ColourValue(1.0f,1.0f,1.0f));

La chose suivante que nous devons faire est d'ajouter une entité. Nous le faisons en appelant la fonction membre createEntity de SceneManager:

Tutoriel1App.cpp
Sélectionnez

	Entity* ent1=mSceneMgr->createEntity("Robot 1","robot.mesh");

Bien. Plusieurs questions doivent se poser. Premièrement, d'où provient mSceneMgr et quels sont les paramètres que nous devons passer à cette fonction ? La variable mSceneMgr contient l'objet SceneManager actuel (cela est réalisé par la classe ExampleApplication). Le premier paramètre de createEntity est le nom de l'entité que nous créons. Toutes les entités doivent avoir un nom unique. Vous obtiendrez une erreur à l'exécution si vous essayez de créer deux entités avec le même nom. Le second paramètre indique l'objet maillé que nous souhaitons utilisé pour cette entité. Celui-ci se trouve dans le dossier (OgreSDK)\media\models. Comment Ogre a-t-il trouvé cet objet ? Il existe dans le répertoire (OgreSDK)\bin, un fichier nommé resources.cfg qui contient une liste de répertoires et de packages (fichiers compressés) dans lequel Ogre pourra aller chercher les différents objets, textures ... que l'on appellera dans le programme. Pour plus d'informations, consultez le document intitulé "Gestion des ressources avec Ogre". Encore une fois, cette initialisation a été effectuée pour nous par la classe ExampleApplication.

Maintenant que nous avons créé notre entité, nous devons créer un noeud de scène qui la contiendra. Puisque chaque gestionnaire de scène a un noeud de scène racine, nous allons créer un enfant à ce noeud:

Tutoriel1App.cpp
Sélectionnez

	SceneNode* node1 = mSceneMgr->getRootSceneNode()->createChildSceneNode("Noeud Robot 1");

Cette longue instruction appelle pour commencer la fonction getRootScene du gestionnaire de scène actuel. Puis elle appelle la fonction createChildSceneNode du noeud de scène racine. Le paramètre de cette fonction est le nom du noeud de scène que nous créons. De même que pour les noms d'entités, deux noeuds de scène ne peuvent pas porter le même nom.

Pour finir, nous devons attacher l'entité au noeud de scène de manière à ce que le robot puisse être dessiné à une position particulière:

Tutoriel1App.cpp
Sélectionnez

	node1->attachObject(ent1);

C'est fini ! Compilez et lancer l'application. Vous devriez voir un robot sur l'écran.

Image non disponible

Vous pouvez utilisez la souris pour faire pivoter la vue, les flèches pour vous déplacer, et enfin, un appui sur ESC termine l'application. Qu'est-ce qui permet cette interacitivité ? La classe ExampleApplication fait appel à un "FrameListener" ou écouteurs d'image. Cet objet est appelé pour chaque image lorsqu'un événement se déclenche: appui sur une touche, déplacement de la souris, clic de souris ... Il permettra aussi de réaliser l'animation des scènes, mais chaque chose en son temps, gardons cela pour les deuxième et troisième tutoriaux.

VI. Coordonnées et vecteurs

Avant que nous n'allions plus loin, nous devons parler des coordonnées d'écran et objets vecteurs d'Ogre. Ogre (comme la plupart des moteurs graphiques) utilise les axes z et x pour définir le plan horizontal, et l'axe y comme axe vertical. Tel que vous regardez votre écran en ce moment, l'axe x va du coté gauche au coté droit de votre écran, le coté droit étant la direction positive de x. L'axe y va du bas en haut de l'écran, le haut étant la direction positive de y. L'axe z va du "fond" de l'écran vers l'"avant" de l'écran, l'avant étant la direction positive de z.

Avez-vous noter comme le robot fait face à la direction positive de l'axe x ? C'est une propriété intrinsèque à l'objet maillé et la façon dont il a été conçu. Ogre ne fait aucune présomption quant à l'orientation de vos modèles. Chaque objet maillé que vous chargez peut avoir une orientation de départ différente.

Ogre utilise la classe Vector pour représenter, et la position, et la direction (il n'y a pas de classe Point). Il y a des vecteurs pour 2 (Vector2), 3 (Vector3) et 4(Vector4) dimensions, bien que Vector3 soit le plus utilisé. Si vous n'êtes pas familiarisé avec les vecteurs, je vous conseille de vous plonger dans leur étude, car sans, vous ne pourrez rien faire de bien sérieux avec Ogre. Les mathématiques qui se cachent derrière les vecteurs va devenir rapidement utile dans les programmes un peu plus complexes.

VII. Ajouter un autre objet

Maintenant que vous avez compris comment fonctionne le système de coordonnées, nous pouvons revenir à notre code. Dans les trois lignes que nous avons écrites, nous n'avons spécifié nulle part la position à laquelle nous voulons apparaitre notre Robot. Une grande partie des fonctions dans Ogre utilisent des paramètres par défaut. Par exemple, la fonction membre createChildSceneNode dans Ogre a 3 paramètres: le nom du noeud de scène, la position du noeud de scène, et la rotation initiale (l'orientation) à laquelle on fait face. La position, comme vous pouvez le voir, a été définie pour nous aux coordonnées (0,0,0). Créons un autre noeud de scène, mais cette fois, nous allons spécifier la position initiale de telle sorte que ce ne soit pas l'origine:

Tutoriel1App.cpp
Sélectionnez

	Entity* ent2=mSceneMgr->createEntity("Robot 2","robot.mesh");
	SceneNode* node2=mSceneMgr->getRootSceneNode()->createChildSceneNode("Noeud Robot 2",Vector3(50,0,0));
	node2->attachObject(ent2);

Ce code devrait vous sembler familier. Nous avons fait exactement la même chose qu'avant, à deux exceptions. Premièrement, nous avons nommer l'entité et le noeud de scène de façon légèrement différente. La deuxième chose est que nous avons spécifier que la position de départ devait être de 50 unités selon l'axe x par rapport au noeud de scène (Rappelez-vous que toutes les positions des noeuds de scène sont relatives à celles de leurs parents). Compilez et lancer. Maintenant il devrait y avoir deux robots cote à cote.

VIII. Les entités en profondeur

La classe des entités - Entity - est très fournie, et on ne va pas passer en revue chaque fonctionnalité. On verra uniquement ce qui permettra de débuter. Il y a juste quelques fonctions vraiment très importantes à étudier.

La première est Entity::setVisible et Entity::isVisible. Vous pouvez définir n'importe quel object comme étant visible ou non. Si l'on souhaite cacher un objet, mais l'afficher de nouveau plus tard, il vaut mieux appeler cette fonction plutôt que de détruire l'objet et de le recréer plus tard. Remarquez que vous n'avez pas à essayer "regrouper" vos entités. Une seule copie de chaque maillage et chaque texture sont chargée en mémoire, vous n'avez donc pas à vous préoccuper de sauvegarder la mémoire en chargeant les modèles. La seule chose que vous économiserez sera la construction et la destruction des entités, ce qui a un coût relativement bas en terme d'utilisation processeur.

La fonction getName retourne le nom de l'entité et la fonction getParentSceneNode retourne le noeud de scène auquel est rattaché l'objet.

IX. Les noeuds de scène en profondeur

La classe SceneNode est très complexe. Il y a beaucoup de choses qui peuvent être faites avec les noeuds de scène, aussi, on se contentera de passer en revue les fonctions que l'on utilisera le plus.

Vous pouvez obtenir et définir la position d'un noeud de scène par les fonctions getPosition et setPosition (la position est toujours définie par rapport au noeud de scène parent). Vous pouvez déplacer un objet par rapport à sa position initiale en utilisant la fonction translate.

Les noeuds de scène ne définissent pas que la position d'un objet, ils contrôlent également le facteur d'échelle et la rotation des objets. Vous pouvez modifier le facteur d'échelle par la méthode scale. Vous pouvez utiliser les méthodes yaw, roll et pitch pour faire une rotation de l'objet. Vous utiliserez resetOrientation pour annuler toutes les rotations sur un objet. Vous pouvez également utiliser les fonctions setOrientation, getOrientation et rotate pour utiliser des rotations plus avancées. Nous n'étudierons pas les quaternions avant plusieurs tutoriaux.

Vous avez déjà vu la fonction attachObject. Les fonctions similaires sont aussi très utiles si vous comptez manipuler les objets attachés à un noeud de scène: numAttachedObjects, getAttachedObject (il y a plusieurs versions de cette fonction) et detachAllObjects (également plusieurs versions). Il y a également une multitude de fonction permettant de manipuler les noeuds enfants ou parents.

Puisque toutes les positions/translations qui sont faites sont relatives au noeud parent, nous pouvons faire que deux noeuds de scène se déplacent en même temps très facilement. Nous avons actuellement ce code dans notre application:

Tutoriel1App.cpp
Sélectionnez

	Entity* ent1=mSceneMgr->createEntity("Robot 1","robot.mesh");
	SceneNode* node1=mSceneMgr->getRootSceneNode()->createChildSceneNode("Robot noeud 1",Vector3(50,0,0));
	node1->attachObject(ent1);
	Entity* ent2=mSceneMgr->createEntity("Robot2","robot.mesh");
	SceneNode* node2=mSceneMgr->getRootSceneNode()->createChildSceneNode("Robot noeud 2",Vector3(50,0,0));
	node2->attachObject(ent2);

Si nous changeons la cinquième ligne de cela :

Tutoriel1App.cpp
Sélectionnez

	SceneNode* node2=mSceneMgr->getRootSceneNode()->createChildSceneNode("Robot noeud 2",Vector3(50,0,0));

En cela :

Tutoriel1App.cpp
Sélectionnez

	SceneNode* node2=node1->createChildSceneNode("Robot noeud 2",Vector3(50,0,0));
<

Alors RobotNode2 est devenu un noeud enfant de RobotNode. Le fait de déplacer le noeud 1 va déplacer le noeud 2 avec lui, mais déplacer le noeud 2 n'aura aucun effet sur le noeud 1. Par exemple, ce code ne déplacera que le noeud 2:

Tutoriel1App.cpp
Sélectionnez

	node2->translate(Vector3(10,0,10));

Le code suivant va déplacer Noeud Robot, et puisque Noeud Robot 2 est l'enfant de Noeud Robot, Noeud Robot 2 va se déplacer également:

Tutoriel1App.cpp
Sélectionnez

	node1->translate(Vector3(25,0,0));
 

Si vous avez des difficultés avec ce que l'on vient de voir, la chose la plus simple que vous ayez à faire est de partir de la racine des noeuds de scène et de descendre en parcourant chaque noeud. Dans ce cas, on commence par le noeud 1 de coordonnées (0,0,0) qui est translaté de (25,0,0), donc la position du noeud 1 devient (vis à vis du noeud parent) (25,0,0). Le noeud 2 est initialisé avec (50,0,0) et on le translate de (10,0,10) donc sa nouvelle position est (60,0,10) relativement à son noeud parent.

Ceci dit il est assez rare d'avoir besoin de connaitre les positions absolues des noeuds. Si tel était toutefois le cas, vous pouvez utiliser les fonctions qui appellent les noeuds et les entités par leur nom : getSceneNode et getEntity. Ceci vous permet également de ne pas garder des pointeurs sur chaque objet que vous gardez, seuls les objets fréquemment utilisés pourront faire l'objet de pointeurs afin d'assurer la rapidité des accès.

X. Choses à tester

Maintenant vous devez avoir les connaissances de bases nécessaires sur les entités, les noeuds de scène et les gestionnaires de scène. Je vous suggère de reprendre le code ci-dessus, d'ajouter des robots et de les enlever de la scène. Ensuite effacez tout et essayer les concepts suivants:

X-A. Echelle

Vous pouvez modifier le facteur d'échelle d'un objet maillé en appelant la méthode scale dans le noeud de scène correspondant. Essayez le code suivant:

Tutoriel1App.cpp
Sélectionnez

	Entity* ent=mSceneMgr->createEntity("Robot 1","robot.mesh");
	SceneNode* node=mSceneMgr->getRootSceneNode()->createChildSceneNode("Robot noeud 1",Vector3(50,0,0));
	node->attachObject(ent);
 
	node->scale(0.5,1.0,2.0);
 
	ent=mSceneMgr->createEntity("Robot 2","robot.mesh");
	node=mSceneMgr->getRootSceneNode()->createChildSceneNode("Robot noeud 2",Vector3(50,0,0));
	node->attachObject(ent);
 
	node->scale(1.0,2.0,1.0);

X-B. Rotation

Vous pouvez faire pivoter les objets maillés en utilisant les méthodes yaw, pitch et roll et en spécifiant la valeur de l'angle en radians (par défaut) ou en degrés (en utilisant la fonction Degree). Essayez le code suivant:

Tutoriel1App.cpp
Sélectionnez

	Entity* ent=mSceneMgr->createEntity("Robot 1","robot.mesh");
	SceneNode* node=mSceneMgr->getRootSceneNode()->createChildSceneNode("Robot noeud 1",Vector3(50,0,0));
	node->attachObject(ent);
 
	node->yaw(Degree(-90));
 
	ent=mSceneMgr->createEntity("Robot 2","robot.mesh");
	node=mSceneMgr->getRootSceneNode()->createChildSceneNode("Robot noeud 2",Vector3(50,0,0));
	node->attachObject(ent);
 
	node->pitch(Degree(90));
 
	ent=mSceneMgr->createEntity("Robot 3","robot.mesh");
	node=mSceneMgr->getRootSceneNode()->createChildSceneNode("Robot noeud 3",Vector3(50,0,0));
	node->attachObject(ent);
 
	node->pitch(Degree(-90));

XI. Conclusion

Nous voilà arriver au terme de ce premier tutoriel. Vous devriez avoir les bases vous permettant de manipuler les objets entités, noeuds de scène et gestionnaire de scène. Ne vous inquiétez pas si vous ne retenez pas toutes les fonctions utilisées ici, vous finirez par les utiliser si souvent quelles finiront par devenir naturelles.

Pour la suite, c'est par ici.

Téléchargements

Remerciements

Je remercie Laurent Gomila (loulou) pour pour son aide. Je remercie également tous les correcteurs de cet article : Khayyam90, Ovh et Loka.

Image non disponible Image non disponible Image non disponible