IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Ogre 3d

Ce tutorial a pour but de vous aider a commencer avec le moteur 3d Ogre.

Article lu   fois.

L'auteur

Profil Pro

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Il convient de faire la distinction entre moteur 3d et moteur de jeux. Les novices en programmation de jeux vidéo confondent souvent les 2. Un moteur de jeux permet de créer un jeux, il gère l'affichage, les collisions, les sons,l'IA etc, tandis ce que le moteur 3d ne s'occupe que de l'affichage. Un moteur 3d n'est donc pas suffisant pour faire un jeu, et n'est pas forcement le composent le plus essentiel d'un moteur de jeux.

Ogre est un moteur 3d open source et performant. Il possède de nombreux add-on que nous utiliserons plus tard pour la physique et le son. Ogre est peut-être plus compliquer que d'autres moteurs tels que irrlicht au premier abord, mais il est extrêmement flexible et possède une grande communauté qui vous aideras des que vous aurez un problème. Mais il est nécessaire de parler un minimum anglais : le forum est en anglais et la majeur partie de la documentation est en anglais.

Ce cours n'a pas la prétention de faire le tour de toutes les fonctionnalités de Ogre, vous serez donc emmenés à lire la documentation de Ogre pour des usages plus spécifiques, mais il entend vous donner de bonnes bases pour commencer(et pour pouvoir lire la doc sereinement).

I-A. Moteur 3d,kesako?

Un moteur 3d sert à simplifier la partie graphique et l'utilisation de la carte graphique. On peut utiliser la carte graphique directement via DirectX ou OpenGL, mais c'est long fastidieux et compliqué. Un moteur 3d vous permeteras par exemple d'afficher simplement un personnage a l'écran, faire une explosion, etc.

Bien sur, rien ne vous empêche de faire votre propre moteur 3D, mais il faut avoir a l'esprit que cela peut prendre plusieurs années, et qu'il seras probablement moins performent que les moteurs 3D open source déjà existants.

II. Installation

Je ne suis pas un pro de linux, je décrirais donc ici uniquement l'installation de Ogre pour windows avec le compilateur Visual C++. Bien entendue, rien ne vous empêche d'utiliser MinGW, mais bon nombre d'addon sont bien plus simple a installer avec Visual C++ et PhysX n'est disponible que pour VC.

II-A. Installation de Visual C++ express 2008

Visual C++ express est disponible en téléchargement gratuitement sur le site de Microsoft ici :
http://msdn2.microsoft.com/fr-fr/express/aa975050.aspx
Sélectionnez Visual C++, téléchargez puis installez.
Des fois il peut-être pratique d'avoir une édition en anglais notamment pour ne pas avoir a traduire les erreurs du compilateur sur les forums non francophones.

II-B. Installation de Ogre

Pour télécharger Ogre, allez à cette adresse
Et téléchargez la version " SDK for Visual C++ .Net 2008 (9.0) " .Une fois le téléchargement terminé, installez Ogre. Aucune étape supplémentaire n'est nécessaire comme avec Visual Studio 2005.

III. Ogre, première approche

III-A. Projet minimal

La première étape consiste à créer un projet win32 vide si vous êtes sous windows. Ajoutez un fichier main.cpp, et ajoutez dans le linker OgreMain.lib OIS.lib (en release).

Ajoutez ce code dans main.cpp :

 
Sélectionnez
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"

INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
#else
int main(int argc, char **argv)
#endif
{
    try {
    } catch( Ogre::Exception &e ) { 
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 
        MessageBox( NULL, e.what(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
        fprintf(stderr, "An exception has occurred: %s\n",
                e.what());
#endif
    }

    return 0;
}

Ce code sert a maintenir la compatiblitée entre les différents systèmes (windows,linux et mac), et lève une exception en cas d'erreur dans le bloc try.

III-B. Classe de base

Nous allons maintenant batire une class qui nous serviras tout le temps par la suite.

Créez une classe Application :

 
Sélectionnez
class Application
{
public :
	Application() ;
	~Application() ;
} ;

Ajoutez ensuite les fonction Run() et Start() .

Ensuite ajoutez les membres Ogre ::Root*, Ogre ::SceneManager* , Ogre ::Camera* , Ogre ::Viewport* , Ogre ::RenderWindow.

Cela donne ceci :

 
Sélectionnez
class Application
{
public :
	Application() ;
	~Application() ;

public :
	Ogre::Root* pRoot ;
	Ogre::SceneManager* pSceneManager ;
	Ogre::RenderWindow* pRenderWindow;
	Ogre::Viewport* pViewport ;
	Ogre::Camera* pCamera ;
} ;

Nous verrons les utilités de ces différents membres par la suite.

III-C. Fonction Start

III-C-1. Root

Ajoutez :

 
Sélectionnez
pRoot = new Ogre::Root() ;

Root est l'objet de base de Ogre, c'est la première chose a initialiser car nous aurons besoins de Root pour initialiser le reste de Ogre.

III-C-2. Fenêtre

Ensuite rajoutez cela:

 
Sélectionnez
pRenderWindow = pRoot->initialise(true,"Ma premiere application Ogre");

Cette ligne ouvre une fenêtre.

III-C-3. SceneManager

Ensuite,il faut initialiser le SceneManager. Le SceneManager est un composent fondamentale dans l'affichage. Il détermine la façon dont les données de votre Scene(vos objets) vont être organisés (certaines organisations seront plus adaptées a des scenes extérieurs , tandis que d'autres seront mieux adaptées pour des scenes intérieurs, bien choisir sont sceneManager est donc primordiale en terme de performance). C'est d'ailleurs par l'intermédiaire du SceneManager que nous créerons des objets 3d.

Pour créer le SceneManager ajoutez la ligne suivante :

 
Sélectionnez
pSceneManager = pRoot->createSceneManager(Ogre::ST_GENERIC, "MonGestionnaireDeScene");

Il existe d'autres SceneManager tel que:
ST_EXTERIOR_CLOSE
ST_EXTERIOR_FAR
ST_INTERIOR_BSP

Il existe bien sur d'autres SceneManager dans les add-ons entre autre, et vous pouvez créer votre propre SceneManager si vous avez un besoin particulier.

III-C-4. Camera

Maintenant il s'agit d'ajouter une camera a la scene. Ce qui seras afficher a l'écran seras vu par la camera.

Pour créer la camera il faut écrire les lignes suivantes :

 
Sélectionnez
pCamera = pSceneManager->createCamera("MaCamera");

III-C-5. Viewport

Il s'agit maintenant " d'associer " la camera que nous venons de créer avec la fenêtre(nous pouvons avoir plusieurs cameras et plusieurs fenêtres), pour cela, ajoutez les lignes suivantes :

 
Sélectionnez
pViewport = pRenderWindow->addViewport(pCamera);

III-C-6. Fonction finale

Cela nous donne une fonction Start comme ceci :

 
Sélectionnez
void Application::Start()
{
	pRoot = new Ogre::Root() ;

	pRenderWindow = pRoot->initialise(true,"Ma premiere application Ogre");

	pSceneManager = pRoot->createSceneManager(Ogre::ST_GENERIC, "MonGestionnaireDeScene");

	pCamera = pSceneManager->createCamera("MaCamera");

	pViewport = pRenderWindow->addViewport(pCamera);

}

III-D. Fonction Run

Cette fonction a pour rôle de lancer le rendue de Ogre.
Et cela donne sa :

 
Sélectionnez
void Application::run();
{
 	pRoot->startRendering();
};

Cette fonction peut être un peu déroutante au début. En fait, Ogre peut remplacer la boucle " infinie " que l'utilisateur fait dans les jeux. On pourrait remplacer startRendering par le code suivant :

 
Sélectionnez
while(1==1)
{
	pRoot->renderOneFrame() ;
}

Evidement vous vous doutez bien que ce n'est pas aussi simple mais le principe est la. Vous posez peut-être une question a ce stade : comment insérer une fonction dans la boucle que Ogre fait a notre place ? En effet, Ogre continuera indéfiniment le programme?
Nous verrons comment placer notre code par la suite grâce au frameListener.

III-E. Fonction Exit

Nous voulons pouvoir quitter le programme proprement en liberant la memoire. Pour cela, il faut supprimer l'objet Root. Le destructeur de Root supprimeras tous les objets d'Ogre(SceneNode,SceneManager,etc). Créez donc la fonction suivante:

 
Sélectionnez
void Application::Exit()
{
	if (pRoot!=0) delete pRoot;
}

III-F. La class final

Voila à quoi devrait ressembler votre class à ce stade :

 
Sélectionnez
class Application
{
public :
	Application();
	~Application();
	
	void Start();
	void Run();
	void Exit();
	
public :
	Ogre::Root* pRoot;
	Ogre::SceneManager* pSceneManager;
	Ogre::RenderWindow* pRenderWindow;
	Ogre::Viewport* pViewport;
	Ogre::Camera* pCamera;
};

Application::Application()
{
	pRoot = 0;
	pSceneManager = 0;
	pRenderWindow = 0;
	pViewport = 0;
	pCamera = 0;
}

Application::~Application()
{
	Exit();
}

void Application::Run()
{
 	pRoot->startRendering();
}

void Application::Start()
{
	pRoot = new Ogre ::Root();

	pRenderWindow = pRoot->initialise(true,"Ma premiere application Ogre");

	pSceneManager = pRoot->createSceneManager(Ogre::ST_GENERIC, "MonGestionnaireDeScene");

	pCamera = pSceneManager->createCamera("MaCamera");

	pViewport = pRenderWindow->addViewport(pCamera);

}

void Application::Exit()
{
	if (pRoot!=0) delete pRoot;
}

III-G. Le programme principale

Maintenant que nous avons créer notre class, il faut l'intégrer dans notre fonction main :

 
Sélectionnez
Application MonApplication ;
MonApplication.Start();
MonApplication.Run();
MonApplication.Exit();

Voila le programme finale :

 
Sélectionnez
#include <Ogre.h>

class Application
{
public :
	Application();
	~Application();
	
	void Start();
	void Run();
	void Exit();
	
public :
	Ogre::Root* pRoot;
	Ogre::SceneManager* pSceneManager;
	Ogre::RenderWindow* pRenderWindow;
	Ogre::Viewport* pViewport;
	Ogre::Camera* pCamera;
};

Application::Application()
{
	pRoot = 0;
	pSceneManager = 0;
	pRenderWindow = 0;
	pViewport = 0;
	pCamera = 0;
};

Application::~Application()
{
	Exit();
}

void Application::Run()
{
 	pRoot->startRendering();
}

void Application::Start()
{
	pRoot = new Ogre ::Root();

	pRenderWindow = pRoot->initialise(true,"Ma premiere application Ogre");

	pSceneManager = pRoot->createSceneManager(Ogre::ST_GENERIC, "MonGestionnaireDeScene");

	pCamera = pSceneManager->createCamera("MaCamera");

	pViewport = pRenderWindow->addViewport(pCamera);

}

void Application::Exit()
{
	if (pRoot!=0) delete pRoot;
}
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"

INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
#else
int main(int argc, char **argv)
#endif
{
    try {
	Application MonApplication ;
	MonApplication.Start ();
	MonApplication.Run() ;
	MonApplication.Exit();
	
    } catch( Ogre::Exception &e ) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 
        MessageBox( NULL, e.what(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
        fprintf(stderr, "An exception has occurred: %s\n",
                e.what());
#endif
    }

    return 0;
}

Vous pouvez maintenant compiler et lancer le programme. Quand vous lancerez le programme vous aurez probablement une erreur vous disant que OgreMain.dll n'est pas trouver. Pour remédier a ce problème : soit vous déplacez toutes les dlls se trouvant dans OgreSDK\bin\release, soit vous déplacez votre .exe dans OgreSDK\bin\release.

Comme vous pouvez vous en apercevoir, vous ne pouvez pas quitter sans utiliser ctrl+alt+supr, mais nous remédierons à ce problème dans la suite de ce cours.

IV. Les Entités,Le Framlistener et Les Entrées

IV-A. Les Entités et les noeuds de scène

Les entités sont en gros des objets. Par exemple le sol va être une entitée, les personnages sont eux aussi des entitées,...

Ces entités sont elles mêmes attachées à un SceneNode(un noeud de scène pour les anglophobes).Ce noeud de scène contient des informations telles que la taille, l'orientation, le facteur d'échelle... Sachez aussi que plusieurs entitées peuvent être attachées à un même noeud de scène. Les noeuds de scène sont attachés à un noeud de scène parent. Le premier noeud de scène est appeler RootSceneNode. Si le noeud 1 est attaché au noeud Root et que le noeud 2 est attaché au noeud 1, si je bouge le noeud 1, le noeud 2 bougera aussi, mais si je bouge le noeud 2,le noeud 1 ne bougera pas. Les noeud de scène ont des coordonnées relatives à celles de leurs parents, et le noeud Root ne peut-être déplacé.

Mais reparlons un peu des entités: à chaque entité est associée une mesh.
Une mesh est l'ensemble des points qui constituent un objet. Prenez un cube. La mesh va contenir les coordonnées de ses 8 sommets et des triangles qui sont composés avec ses sommets (ce ne sont pas toujours des triangles, mais c'est une des figures les plus utilisées). Il y a différents types de fichiers contenant des mesh (.3ds,.obj,...) celui de Ogre étant le .mesh (bizarre non?).La mesh de Ogre contient d'autres données que les points et les triangles. Elle comporte aussi des informations sur le material de la mesh(texture,shader,etc). C'est pourquoi les meshes de Ogre sont composées d'un 2ème fichier .material plus les textures. On peut aussi associer un fichier .skelton sur le même modele qu'un .material, le fichier .skelton définissant lui des animations.

Je vous vois bailler d'ici! Mais la partie théorique est terminée et le code arrive.
Reprenez le code précédent ( je vous conseille de garder le code minimal dans un fichier que vous réutiliserez pour chaque nouveau projet ). Rajoutez dans la classe Application:

 
Sélectionnez
Ogre::Entity* pEntity;
Ogre::SceneNode* pNode;

Puis créez une nouvelle fonction CreateObject (dans la class Application):

 
Sélectionnez
Void Application::CreateObject(Ogre ::String Name)
{
	pEntity = pSceneManager->createEntity( Name, "robot.mesh" );
	pNode = pSceneManager->getRootSceneNode()->createChildSceneNode(Name);
	pNode->attachObject(pEntity);
}

D'abord, nous créons une entity, ensuite nous créons un SceneNode, puis nous attachons l'entity au SceneNode. Vous remarquez que l'on donne un nom a l'entity et au SceneNode, nous pouvons donc récupérer a tout moment le SceneNode et l'Entity via leurs noms (il n'est donc pas obliger de garder un pointeur vers le SceneNode et l'Entity, mais c'est plus commode et rechercher les objets par leurs noms a un impacte sur les performances).


Mais il reste encore un léger problème: Ogre ne sait pas où trouver la mesh Robot.mesh et son fichier material associé. Il faut donc lui dire ou sont les répertoires. Créez donc une fonction LoadResource comme cela(dans la class Application) :

 
Sélectionnez
void Application::LoadResource()
{
	Ogre::ResourceGroupManager::getSingleton().addResourceLocation("../../media/models","FileSystem","General");
	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");
	Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
}

Ensuite, ajoutez à votre fonction Start :

 
Sélectionnez
LoadResource();
CreateObject(" Robot ");

Maintenant, quand vous lancez le programme, vous devriez voir un robot.

IV-B. Les Framelistener

Le programme qui affiche un robot est bien beau, mais il ne nous permet toujours pas de quitter sans passer par alt+f4 ou ctrl+alt+supr. Nous allons voir dans cette partie comment remédier à ce problème et en plus,rajouter du mouvement a nos objets qui sont bien statiques.
Ogre fonctionne de ma manière suivante: il appelle une fonction appelée framestarted, fait le rendu, puis appelle la fonction framended, et ainsi de suite.

Vous vous souvenez de ce petit bout de code :

 
Sélectionnez
while(1==1)
{
	pRoot->renderOneFrame() ;
}

Que nous avions donner pour illustrer startRendering, et bien nous pourions ajouter le code suivant pour illustrer les fonctions frameStarted et frameEnded :

 
Sélectionnez
while(1==1)
{
	frameStarted() ;
	pRoot->renderOneFrame() ;
	frameEnded() ;
}

Pour utiliser les frameListener, il faut que notre class Application hérite de la class frameListener.
Cette classe contient les 2 fonctions virtuelles frameStarted et frameEnded.
Nous allons donc créer nos fonctions frameStarted et frameEnded dans la classe Application.

Dans la classe Application, rajoutez dans public:

 
Sélectionnez
bool frameStarted(const Ogre::FrameEvent &evt);
bool frameEnded(const Ogre::FrameEvent &evt);

Les fonctions sont de type bool car si l'une d'elle renvoie false, la boucle s'arrête. La constante Ogre::FrameEvent garde en mémoire le temps qu'il y a eu entre le dernier appel de frameStarted ou de frameEnded et l'appel de frameStarted ou de frameEnded.

Ensuite il faut enregistrer le frameListener dans pRoot. Sachez aussi que vous pouvez enregistrer autant de frameListner que vous voulez. Vous pouvez donc avoir un frameListner qui s'occupe de l'IA, l'autre des déplacements du joueur... Pour enregistrer le frameListner dans pRoot,utilisez cette fonction dans la fonction Start de application:

 
Sélectionnez
pRoot->addFrameListener(this);

Vous l'aurez compris, cette fonction prend en paramètre un pointeur vers la classe du frameListener.

Occupons nous maintenant du code des fonctions FrameStarted et FrameEnded. Allez dans main.cpp et créez les fonctions comme ceci:

 
Sélectionnez
bool Application::frameStarted(const Ogre::FrameEvent &evt)
{
	return true;
}
 
bool Application::frameEnded(const Ogre::FrameEvent &evt)
{
	return true;
}

Ces fonctions ne servent à rien pour l'instant. Mais un moyen simple pour quitter est de lui dire: quand le temps total d'exécution est supérieur à tant, return faux. Pour faire cela nous allons créer une variable qui enregistrera le temps total. A chaque fois que frameStarted ou frameEnded sera appelée, nous ajouterons à cette variable le temps depuis le dernier frame.

Rajoutez donc dans la classe application (dans public: )

 
Sélectionnez
float pTime;

Dans le constructeur:

 
Sélectionnez
pTime=0;

et enfin dans frameStarted et/ou dans frameEnded:

 
Sélectionnez
pTime += evt.timeSinceLastFrame;
if (pTime>100.0)
{
	return false;
};

Maintenant, dès que le temps dépassera 100, le programme quittera.
Nous allons nous attaquer à quelque chose de nettement plus intéressant maintenant: nous allons faire bouger nos objets.

Essayez de rajouter dans FrameStarted():

 
Sélectionnez
pNode->yaw(Degree(1));

Vous voyez, votre robot tourne.

Une autre fonction très utile est translate(Ogre::Vector3()). Rajoutez dans frameStarted

 
Sélectionnez
pNode->translate(Ogre::Vector3(1,0,0));

Compilez. Votre robot devrait maintenant tourner sur lui même et se déplacer.

IV-C. Les Entrées

Ogre fournit OIS avec le SDK et celui-ci est utilisé par les démos fournient avec Ogre. OIS gère aussi bien les joysticks que la sourie ou le clavier.
Incluez OIS/OIS.h au début de votre programme, rajoutez OIS.lib dans le linker, puis créez une fonction InitOIS(dans la class Application) :

 
Sélectionnez
Void Application::InitOIS()
{
	OIS::paramList pl;size_t windowHnd = 0;
	std::ostringstream windowHndStr;
	pRenderWindow->getCustomAttribute("WINDOW", &windowHnd); 
	windowHndStr << windowHnd;
	pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str()));
	pInputManager = OIS::InputManager::createInputSystem( pl );
}

Vous vous apercevrez que pInputManager n'est pas définie. Rajoutez donc dans les membres publiques de la class :

 
Sélectionnez
OIS::InputManager *pInputManager;

Le système d'entrée est correctement initialisé. Maintenant, il faut créer un objet pour le clavier et pour la sourie. Nous laisserons les joysticks pour le moment.

Rajoutez à la suite dans la fonction :

 
Sélectionnez
pMouse = static_cast<OIS::Mouse*>(pInputManager->createInputObject(OIS::OISMouse, false));
pKeyboard = static_cast<OIS::Keyboard*>(pInputManager->createInputObject(OIS::OISKeyboard, false));

Et déclarez pMouse et pKeyboard dans la classe application:

 
Sélectionnez
OIS::Mouse *pMouse;
OIS::Keyboard *pKeyboard;

Ensuite, il faut définir la taille de la fenêtre pour que OIS puisse par exemple garder la sourie dans la fenêtre.
Toujours dans la fonction InitOIS et à la suite, ajoutez :

 
Sélectionnez
unsigned int width, height, depth;
int top, left;
pRenderWindow->getMetrics(width, height, depth, left, top);
const OIS::MouseState &ms = pMouse->getMouseState();
ms.width = width;
ms.height = height;

Ajoutez un appelle a InitOIS a la fin de la function Start.

Maintenant, ajoutez dans frameStarted:

 
Sélectionnez
if (pKeyboard->isKeyDown(OIS::KC_ESCAPE)) return false;

Si vous appuyez sur la touche échappe, le programme quittera. Fini les alt+f4...


Maintenant que le programme quitte, on peut enlever pTime!Enlevez donc de frameEnded et/ou frameStarted:

 
Sélectionnez
pTime += evt.timeSinceLastFrame;
if (pTime>10.0)
{
        return false;
};

IV-D. Code finale

 
Sélectionnez
#include <Ogre.h>
#include <OIS\OIS.h>

class Application:public Ogre::FrameListener
{
public :
	Application();
	~Application();
	void Start();
	void Run();
	void CreateObject(Ogre::String Name);
	void LoadResource();
	void InitOIS();
	void Exit();
	
	bool frameStarted(const Ogre::FrameEvent &evt);
	bool frameEnded(const Ogre::FrameEvent &evt);


public :
	Ogre ::Root* pRoot ;
	Ogre ::SceneManager* pSceneManager ;
	Ogre ::RenderWindow* pRenderWindow;
	Ogre ::Viewport* pViewport ;
	Ogre ::Camera* pCamera ;
	
	Ogre::Entity* pEntity;
	Ogre::SceneNode* pNode;
	
	OIS::InputManager *pInputManager;
	OIS::Mouse *pMouse;
	OIS::Keyboard *pKeyboard;


} ;
Application::Application()
{
	pRoot = 0;
	pSceneManager = 0;
	pRenderWindow = 0;
	pViewport = 0;
	pCamera = 0;
}

Application::~Application()
{
	Exit();
}


void Application::InitOIS()
{
	OIS::ParamList pl;size_t windowHnd = 0;
	std::ostringstream windowHndStr;
	pRenderWindow->getCustomAttribute("WINDOW", &windowHnd); 
	windowHndStr << windowHnd; 
	pl.insert(std::make_pair(std::string("WINDOW"), windowHndStr.str()));
	pInputManager = OIS::InputManager::createInputSystem( pl );
	
	pMouse = static_cast<OIS::Mouse*>(pInputManager->createInputObject(OIS::OISMouse, false));
	pKeyboard = static_cast<OIS::Keyboard*>(pInputManager->createInputObject(OIS::OISKeyboard, false));
	
	unsigned int width, height, depth;
	int top, left;
	pRenderWindow->getMetrics(width, height, depth, left, top);
	const OIS::MouseState &ms = pMouse->getMouseState();
	ms.width = width;
	ms.height = height;

}

bool Application::frameStarted(const Ogre::FrameEvent &evt)
{
	if (pKeyboard->isKeyDown(OIS::KC_ESCAPE)) return false;
	return true;
}
 
bool Application::frameEnded(const Ogre::FrameEvent &evt)
{
	return true;
}
				
void Application::CreateObject(Ogre::String Name)
{
	pEntity = pSceneManager->createEntity( Name, "robot.mesh" );
	pNode = pSceneManager->getRootSceneNode()->createChildSceneNode(Name);
	pNode->attachObject(pEntity);
}

void Application::LoadResource()
{
	Ogre::ResourceGroupManager::getSingleton().addResourceLocation("../../media/models","FileSystem","General");
	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");
	Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
}

void Application::Run()
{
 	pRoot->startRendering();
}

void Application::Start()
{
	pRoot = new Ogre ::Root() ;

	pRenderWindow = pRoot->initialise(true,"Ma premiere application Ogre");

	pSceneManager = pRoot->createSceneManager(Ogre::ST_GENERIC, "MonGestionnaireDeScene");

	pCamera = pSceneManager->createCamera("MaCamera");

	pViewport = pRenderWindow->addViewport(pCamera);
	
	LoadResource();
	CreateObject(" Robot ");
	
	InitOIS();
	
	pRoot->addFrameListener(this);

}

void Application::Exit()
{
	delete pRoot;
}	

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"

INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
#else
int main(int argc, char **argv)
#endif
{
    try {
	Application MonApplication ;
	MonApplication.Start();
	MonApplication.Run();
	MonApplication.Exit();

	
    } catch( Ogre::Exception &e ) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 
        MessageBox( NULL, e.what(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
        fprintf(stderr, "An exception has occurred: %s\n",
                e.what());
#endif
    }

    return 0;
}

V. Les Lumières,les ombres et le ciel

V-A. Les lumières

V-A-1. La lumière ambiante

La lumière ambiante est contrôlée par la fonction:

 
Sélectionnez
Ogre::SceneManager::setAmbienteLight(Ogre::ColourValue));

Ogre::ColourValue est donnée en RGB(rouge,vert,bleu). Par exemple une lumière rouge s'écrirait comme sa : Ogre::ColourValue(100,0,0);
Il y a une valeur limite que l'on peut donner a chaque couleur : 255;on ne peut pas mettre Ogre::ColourValue(300,300,300);(en fait si , mais sa sera comme s'il y avait (250,250,250)).

Ajoutez donc dans la fonction Start après avoir déclaré et initialisé le SceneManager le code suivant en remplaçant les valeurs rouge bleu et vert par les valeur que vous voulez(faite des tests...):

 
Sélectionnez
pSceneManager->setAmbienteLight(Ogre::ColourValue(rouge,vert,bleu);

V-A-2. Les lumières dynamiques

En plus de la lumière ambiante, Ogre propose un système complet de lumière qui permet de créer des lampes , ...

On initialise une lumière grâce aux fonctions suivantes:

 
Sélectionnez
Ogre::Light* light = mSceneMgr->createLight( "Light1" );
light->setType( Light::LT_POINT );

Il existe 3 types différents de lumières. La première éclaire dans toutes les directions. C'est celle utilisée dans l'exemple si dessus déclarée avec light::LT_POINT. La deuxième est une lumière directionnelle déclarée avec LT_SPOTLIGHT. Elle fonctionne comme une lampe torche et comme un spot. La troisième est un peu spéciale et nous ne nous attarderons pas dessus.

La première chose à faire est d'enlever la lumière ambiante.
Remplacez

 
Sélectionnez
pSceneManager->setAmbienteLight(Ogre::ColourValue(rouge,vert,bleu));

par:

 
Sélectionnez
pSceneManager->setAmbienteLight(Ogre::ColourValue(0,0,0));

Ensuite ajoutez juste après la déclaration de la lumière ambiante la commande suivante:

 
Sélectionnez
Ogre::Light* light = pSceneManager->createLight( "Light1" );
 
Sélectionnez
light->setType( Light::LT_POINT );

Si vous voulez une lumière directionnelle ajoutez :

 
Sélectionnez
light->setType( Light::LT_ SPOTLIGHT);

Ensuite ajoutez la commande suivante pour spécifier la position de votre lumière:

 
Sélectionnez
light->setPosition(Ogre::Vector3 ( 300,300,0));

Pour les lumières directionnelles il faut aussi préciser une direction.

 
Sélectionnez
light->setDirection( -1, -1, 0 );

La lumière pointera donc sur le point(-1,-1,0).Pour cette lumière , on peut aussi donner un angle d'ouverture au faisceau de lumière: 

 
Sélectionnez
light->setSpotlightRange( Ogre::Degree(25), Ogre::Degree(40) );

Pourquoi deux paramètres ? Comme vous l'avez vous sans doute remarqué, avec une lampe de poche, le centre est bien éclairé et le bord l'est nettement moins. Cette commande prend comme premier paramètre l'angle d'ouverture du faisceau à intérieur où il est plus puissant et comme deuxième celui du faisceau à extérieur ou il est moins puissant. Bien entendu cela ne concerne pas les lumières omnidirectionel

Un paramètre important pour une lumière est sa couleur. Pour cela rajoutez le code suivant:

 
Sélectionnez
light->setDiffuseColour( Ogre::ColourValue(0,10,0));
light->setSpecularColour( Ogre::ColourValue(0,10,0));

Ces deux commandes ont une signification précise que nous n'aborderons pas dans ce tutorial, mais je vous invite à regarder les liens suivants si vous voulez plus de précision :
http://www.webreference.com/3d/glossary/diffuse.html
http://www.webreference.com/3d/glossary/specular.html

V-B. Les ombres

Ogre intègre 4 types d'ombres différentes(hors shader bien sur):SHADOWTYPE_TEXTURE_MODULATIVE,SHADOWTYPE_STENCIL_MODULATIVE,SHADOWTYPE_STENCIL_ADDITIVE et SHADOWTYPE_TEXTURE_ADDITIVE. Nous verrons les differences entres ces differentes ombres un peu plus tard.
La première chose à faire pour voir une ombre est de créer un sol. Rajoutez donc avec la déclaration de l'objet robot le code suivant:

 
Sélectionnez
Ogre::Plane plane( Vector3::UNIT_Y, 0 );
 
MeshManager::getSingleton().createPlane("ground", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, plane,1500,1500,20,20,true,1,5,5,Vector3::UNIT_Z);
 
ent = pSceneManager->createEntity( "GroundEntity", "ground" );
pSceneManager->getRootSceneNode()->createChildSceneNode()->attachObject(ent);
 
ent->setMaterialName("Examples/Rockwall");
ent->setCastShadows(false);

Nous ne verrons ici aucune des commandes suivantes car elles n'entrent pas vraiment dans l'optique du tutorial, sauf la dernière qui dit à Ogre que l'objet ne créera pas d'ombre.

Rajoutez aussi dans Application::Start:

 
Sélectionnez
pSceneManager->setShadowTechnique(SHADOWTYPE_STENCIL_ADDITIVE) ;

Ogre créera maintenant des ombres.

Rajoutez après la création du robot :

 
Sélectionnez
pEntity->setCastShadows(true);

Voila le robot devrait maintenant créer une ombre.

V-B-1. Stencil Shadows

Pour créer les Stencil Shadows,on créer un "masque" sur l'écran grace au stencil buffer. Ce masque permet de dire si une partie de l'écran est dans l'ombre ou pas. Cela provoque des bords très "dur" vu que le stencil buffer n'a que 2 valeur possible.

Pour créer l'ombre,les objets sont extrudés et on calcule les points d'intersection avec les autres objets pour avoir l'ombre.
Cette methode a des avantages et des inconvegnants: on ne peut pas utiliser de Shader, les bords des ombres sont très dur, et vu que c'est une methode "géometrique", plus les objets sont compliqués,plus cela demande de puissance. Cela dit, tant que les meshs ne sont pas trop compliquer,cette methode est plus rapide que les texture-based shadow et permet d'avoir une ombre sur l'objet projetant l'ombre(self-shadowing) sur des machines peu puissantes.

Image non disponible
exemple de stencil shadow

V-B-2. Texture-based Shadows

Pour créer ce type d'ombre,l'objet projetant une ombre est rendue du point de vue de la lumière émetrice dans une texture,puis cette texture est "projeté" sur les objets récepteurs. Contrairement au Stencil Shadows, on peut utiliser des shader pour rendre les bords plus doux par exemple. De plus, ce type d'ombre est beacoup moins affecter en terme de performances par l'augmentation de la complexité de la géometrie que les stencile shadows.
Par contre, Ogre ne supporte pas les "Point light" si elles se trouvent dans le champs de la camera,et cette méthode est peu adapté au vieux hardware.

Image non disponible
exemple de texture-based shadow

V-B-3. Modulative et Additive

En plus d'avoir des ombres "stencil" ou "texture-based", elles sont aussi modulatives ou additives.
Les modulatives shadows fonctionnent rendant plus foncé les endroits touchés par une ombre. Cette méthode est rapide, mais n'est pas correcte physiquement et peut donner des ombres trop sombres aux endroits ou les ombres se coupent.
Les additives fonctionnent en rendant la scene plusieurs fois avec a chaque fois une seul lumière active. Un fois la scene rendue pour toute les lumières on combine tout les rendus. Cela donne une lumière ayant l'air très réaliste, mais le cout en terme de performance est très élevé comparé au modulative shadow.

Image non disponible
exemple de modulative shadow
Image non disponible
exemple d'additive shadow

V-C. Le Ciel

Le fond monochrome d'ogre par défaut n'est pas vraiment magnifique, mais Ogre inclue des fonctions qui permettent d'ajouter un ciel très facilement.
Il existe 3 types de ciel: SkyDome,SkyCube et SkyPlane.

V-C-1. SkyPlane

Les SkyPlane sont les ciels les plus simples. Ils consistent en de simples plans texturés. Ce type de ciel est adapté dans des situations montagneuses par exemple,car on ne voit pas les "limites" du ciel, alors que dans une plaine parfaitement plate, ce ciel n'est pas adapté...Pour créer un SkyPlane, nous devons d'abord créer un plan.
Pour cela, entrez le code suivant dans la fonction Start de la classe application:

 
Sélectionnez
Plane plan;
plan.d=900;
plan.normal = Vector3::NEGATIVE_UNIT_Y;

La première ligne crée le plan. La 2eme définie la hauteur a laquelle se trouve le plan. La dernière ajuste la normale du plan. La normale est utilisée notamment pour les lumières.

Une fois le plan crée, il nous faut créer le ciel. Ajoutez donc à la suite:

 
Sélectionnez
pSceneManager->setSkyPlane(true, plan,"Examples/SpaceSkyPlane",1500,75);

V-C-2. SkyCube

Un SkyCube créera un cube qui jouera le rôle de ciel. Ce type de ciel est adapté à presque toutes les situations, sauf quelque cas ou il ne faut pas voir le ciel en dessous par exemple. Pour créer un SkyCube, ajoutez la ligne suivante :

 
Sélectionnez
pSceneManager->setSkyBox(true, "Examples/SpaceSkyBox", 5000, false);

Le premier paramètre indique si on utilise le SkyCube(on pourra le désactiver en mettant faux), le second indique quel material utiliser(et donc la texture), le troisième la distance du SkyCube a la camera et le dernier si les touts les objets sont rendus avant le SkyCube ou pas(par exemple si un objet se trouve derrière la SkyCube, si le paramètre est a false,il ne sera pas afficher).

V-C-3. SkyDome

Les SkyDomes sont semblables au SkyCube si ce n'est que ce n'est pas un cube mais une demis sphère (même si en réalité il s'agit toujours d'un cube, mais la texture y est placé de telle sorte qu'on n'est l'impression que ce soit une sphère). Mais vu que c'est une demis sphère, il faut " cacher " le dessous, il est donc adaté a bon nombre de situations,mais un relief négatif sur le bord d'un terrain peut donner des "trous" ou le ciel n'apparait pas.

Pour créer un SkyDome,ajoutez la ligne suivante :

 
Sélectionnez
pSceneManager->setSkyDome(true, "Examples/CloudySky", 5, 8);

Les 2 premiers paramètres sont les mêmes que pour le SkyCube.
Le troisième indique la courbure du ciel (donnez une valeur entre 2 et 64), et le quatrième combien de fois votre texture est tilled.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

Copyright © 2009 Augustin Chevallier. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.