Ogre 3d

Date de publication : 23 mai 2009

Par Augustin Chevallier
 

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

            

I. Introduction
I-A. Moteur 3d, kesako ?
II. Installation
II-A. Installation de Visual C++ express 2008
II-B. Installation de Ogre
III. Ogre, première approche
III-A. Projet minimal
III-B. Classe de base
III-C. Fonction Start
III-C-1. Root
III-C-2. Fenêtre
III-C-3. SceneManager
III-C-4. Camera
III-C-5. Viewport
III-C-6. Fonction finale
III-D. Fonction Run
III-E. Fonction Exit
III-F. La classe finale
III-G. Le programme principal
IV. Les Entités, Le Framlistener et Les Entrées
IV-A. Les Entités et les noeuds de scène
IV-B. Les Framelistener
IV-C. Les Entrées
IV-D. Code final
V. Les Lumières, les ombres et le ciel
V-A. Les lumières
V-A-1. La lumière ambiante
V-A-2. Les lumières dynamiques
V-B. Les ombres
V-B-1. Stencil Shadows
V-B-2. Texture-based Shadows
V-B-3. Modulative et Additive
V-C. Le Ciel
V-C-1. SkyPlane
V-C-2. SkyCube
V-C-3. SkyDome


I. Introduction

Il convient de faire la distinction entre moteur 3d et moteur de jeux. Les novices en programmation de jeu 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 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 composant 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 compliqué que d'autres moteurs tels que irrlicht au premier abord, mais il est extrêmement flexible et possède une grande communauté qui vous aidera dès que vous aurez un problème. Mais il est nécessaire de parler un minimum d'anglais : le forum est en anglais et la majeure 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 permettra par exemple d'afficher simplement un personnage a l'écran, faire une explosion, etc.

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


II. Installation

Je ne suis pas un pro de linux, je décrirai donc ici uniquement l'installation de Ogre pour windows avec le compilateur Visual C++. Bien entendu, rien ne vous empêche d'utiliser MinGW, mais bon nombre d'add-ons sont bien plus simples 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 à 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 :

#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 à 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 bâtir une classe qui nous servira tout le temps par la suite.

Créez une classe Application :

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 :

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 :

pRoot = new Ogre::Root() ;
					
Root est l'objet de base de Ogre, c'est la première chose à initialiser car nous aurons besoin de Root pour initialiser le reste de Ogre.


III-C-2. Fenêtre

Ensuite rajoutez cela:

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 composant fondamental dans l'affichage. Il détermine la façon dont les données de votre Scene (vos objets) vont être organisées (certaines organisations seront plus adaptées a des scenes extérieures, tandis que d'autres seront mieux adaptées pour des scenes intérieures, bien choisir sont sceneManager est donc primordial 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 :

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 autres, 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 à la scene. Ce qui sera affiché à l'écran sera vu par la caméra.

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

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 :

pViewport = pRenderWindow->addViewport(pCamera);
					

III-C-6. Fonction finale

Cela nous donne une fonction Start comme ceci :

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 rendu de Ogre.
Et cela donne ça :

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 :

while(1==1)
{
	pRoot->renderOneFrame() ;
}
				
Evidement vous vous doutez bien que ce n'est pas aussi simple mais le principe est là. Vous posez peut-être une question à ce stade : comment insérer une fonction dans la boucle que Ogre fait à 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:

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

III-F. La classe finale

Voila à quoi devrait ressembler votre classe à ce stade :

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 principal

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

Application MonApplication ;
MonApplication.Start();
MonApplication.Run();
MonApplication.Exit();
				
Voila le programme final :

#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 trouvé. Pour remédier à 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é, les personnages sont eux aussi des entités,...

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és 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 modèle 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:

Ogre::Entity* pEntity;
Ogre::SceneNode* pNode;
				
Puis créez une nouvelle fonction CreateObject (dans la classe Application):

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 à 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 obligé de garder un pointeur vers le SceneNode et l'Entity, mais c'est plus commode et rechercher les objets par leurs noms a un impact 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 où sont les répertoires. Créez donc une fonction LoadResource comme cela (dans la class Application) :

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 :

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 à nos objets qui sont bien statiques.
Ogre fonctionne de la 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 :

while(1==1)
{
	pRoot->renderOneFrame() ;
}
				
Que nous avons donner pour illustrer startRendering, et bien nous pouvons ajouter le code suivant pour illustrer les fonctions frameStarted et frameEnded :

while(1==1)
{
	frameStarted() ;
	pRoot->renderOneFrame() ;
	frameEnded() ;
}
				
Pour utiliser les frameListener, il faut que notre classe Application hérite de la classe 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:

bool frameStarted(const Ogre::FrameEvent &evt);
bool frameEnded(const Ogre::FrameEvent &evt);
				
Les fonctions sont de type bool car si l'une d'elles 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:

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:

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)

float pTime;
				
Dans le constructeur:

pTime=0;
				
et enfin dans frameStarted et/ou dans frameEnded:

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():

pNode->yaw(Degree(1));
				
Vous voyez, votre robot tourne.

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

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 fournies 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) :

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 classe :

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 :

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:

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 :

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 à InitOIS à la fin de la function Start.

Maintenant, ajoutez dans frameStarted:

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 le donc de frameEnded et/ou frameStarted:

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

IV-D. Code final


#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:

Ogre::SceneManager::setAmbienteLight(Ogre::ColourValue));
					
Ogre::ColourValue est donnée en RGB(rouge,vert,bleu). Par exemple une lumière rouge s'écrirait comme ça : Ogre::ColourValue(100,0,0);
Il y a une valeur limite que l'on peut donner à 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 valeurs que vous voulez (faite des tests...):

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:

Ogre::Light* light = mSceneMgr->createLight( "Light1" );
light->setType( Light::LT_POINT );
					
Il existe 3 types différents de lumière. La première éclaire dans toutes les directions. C'est celle qui est 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

pSceneManager->setAmbienteLight(Ogre::ColourValue(rouge,vert,bleu));
					
par:

pSceneManager->setAmbienteLight(Ogre::ColourValue(0,0,0));
					
Ensuite ajoutez juste après la déclaration de la lumière ambiante la commande suivante:

Ogre::Light* light = pSceneManager->createLight( "Light1" );
					

light->setType( Light::LT_POINT );
					
Si vous voulez une lumière directionnelle ajoutez :

light->setType( Light::LT_ SPOTLIGHT);
					
Ensuite ajoutez la commande suivante pour spécifier la position de votre lumière:

light->setPosition(Ogre::Vector3 ( 300,300,0));
					
Pour les lumières directionnelles il faut aussi préciser une direction.

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: 

light->setSpotlightRange( Ogre::Degree(25), Ogre::Degree(40) );
					
Pourquoi deux paramètres ? Comme vous l'avez 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 omnidirectionelles

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

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'ombre différentes (hors shader bien sur):SHADOWTYPE_TEXTURE_MODULATIVE,SHADOWTYPE_STENCIL_MODULATIVE,SHADOWTYPE_STENCIL_ADDITIVE et SHADOWTYPE_TEXTURE_ADDITIVE. Nous verrons les differences entre 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:

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:

pSceneManager->setShadowTechnique(SHADOWTYPE_STENCIL_ADDITIVE) ;
				
Ogre créera maintenant des ombres.

Rajoutez après la création du robot :

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ée 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 valeurs possibles.

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 méthode a des avantages et des inconvénients: on ne peut pas utiliser de Shader, les bords des ombres sont très durs, et vu que c'est une methode "géometrique", plus les objets sont compliqués, plus cela demande de la puissance. Cela dit, tant que les meshs ne sont pas trop compliquées, cette méthode est plus rapide que les texture-based shadows et permet d'avoir une ombre sur l'objet projetant l'ombre (self-shadowing) sur des machines peu puissantes.

exemple de stencil shadow

V-B-2. Texture-based Shadows

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

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 en rendant plus foncés 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 scène plusieurs fois avec à chaque fois une seule lumière active. Une 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 coût en terme de performance est très élevé comparé au modulative shadow.

exemple de modulative shadow
exemple d'additive shadow

V-C. Le Ciel

Le fond monochrome d'ogre par défaut n'est pas vraiment magnifique, mais Ogre inclut 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:

Plane plan;
plan.d=900;
plan.normal = Vector3::NEGATIVE_UNIT_Y;
					
La première ligne crée le plan. La 2ème définit la hauteur à 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éé, il nous faut créer le ciel. Ajoutez donc à la suite:

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 quelques cas où il ne faut pas voir le ciel en dessous par exemple. Pour créer un SkyCube, ajoutez la ligne suivante :

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 par rapport à la camera et le dernier si tous 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 à false, il ne sera pas affiché).


V-C-3. SkyDome

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

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

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 tiled.



            

Valid XHTML 1.0 TransitionalValid CSS!

Copyright © 2009 Augustin Chevallier. Aucune reproduction, même partielle, ne peut être faite de ce site et 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. Cette page est déposée.

 
 
 
 
Partenaires

Hébergement Web