Accueil
Rechercher:
sur developpez.com sur les forums
Forums | Tutoriels | F.A.Q's | Participez | Hébergement | Contacts
Accueil Conception Java DotNET Visual Basic  C  C++ Delphi Eclipse MS-Office SQL & SGBD Oracle  4D  Business Intelligence
Club Emploi Blogs   TV   Dév. Web PHP XML Python Autres 2D-3D-Jeux Sécurité Windows Linux PC Mac
FORUMS LES FAQs TUTORIELS OUTILS BIBLIOTHEQUES MEDIAS LIVRES SOURCES TV BLOG

Ogre 3d

Date de publication : 31/07/2008 , Date de mise à jour : 31/07/2008

Par gusgus (gusgus.developpez.com)
 

L'apprentissage d'un moteur 3d tel que Ogre peut paraître difficile. Ce tutorial a pour but de vous initier à 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. La classe finale
III-F. 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
VI. Remerciements


I. Introduction

Il convient de faire la distinction entre moteur 3d et moteur de jeu. Les novices en programmation de jeux vidéo confondent souvent les 2. Un moteur de jeu permet de créer un jeu, 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 jeu.

Ogre est un moteur 3d open source et performant. Il possède de nombreux add-ons 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 permettras 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 à 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
http://www.ogre3d.org/index.php?option=com_content&task=view&id=411&Itemid=131
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( 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() ;
	
	void Start();
	void Run();

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:

pRoot->showConfigDialog();
pRenderWindow = pRoot->initialise(true,"Ma premiere application Ogre");
					
La première ligne ouvre une boite de dialogue pour configurer la fenêtre et le RenderSysteme. Elle n'est pas nécéssaire si vous avez le fichier Ogre.cfg.
La seconde ligne ouvre la 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 à des scenes extérieures, tandis que d'autres seront mieux adaptées pour des scènes 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 scène. Ce qui sera affiché à l'écran sera vu par la camera.

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

	pRoot->showConfigDialog();
	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 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. La classe finale

Voila à quoi devrait ressembler votre classe à ce stade :

class Application
{
public :
	Application();
	~Application();
	
	void Start();
	void Run();

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

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

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

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

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

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

	pViewport = pRenderWindow->addViewport(pCamera);

}
				

III-F. 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() ;
				
Voila le programme final :

#include <Ogre.h>

class Application
{
public :
	Application() ;
	~Application() ;

	void Start();
	void Run();

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

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

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

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

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

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

	pViewport = pRenderWindow->addViewport(pCamera);

}


#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() ;

	
    } catch( 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 class 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)
{
	pRoot->renderOneFrame() ;
}
				
Que nous avons donné 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 FrameEvent &evt)
{
	return true;
}
 
bool Application::frameEnded(const 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 souris 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 classe 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 souris. 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 souris 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 appel à InitOIS() à la fin de la function Start.

Maintenant, ajoutez dans frameStarted:

pKeyboard->capture();
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:

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

IV-D. Code final


#include <Ogre.h>

class Application: public Ogre::frameListener
{
public :
	Application() ;
	~Application() ;
	
	void Run();
	void Start();
	void LoadResource();
	void InitOIS();

	void CreateObject(Ogre ::String Name);

	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 ;
	OIS::InputManager* pInputManager;
	OIS::Mouse* pMouse;
	OIS::Keyboard* pKeyboard;
	
	Ogre::Entity* pEntity;
	Ogre::SceneNode* pNode;
} ;

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

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

	pRoot->showConfigDialog();
	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 ");

	pRoot->addFrameListener(this);
	
	InitOIS();

}

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();
}

bool Application::frameStarted(const FrameEvent &evt)
{
	pNode->yaw(Degree(1));

	pKeyboard->capture();
	if (pKeyboard->isKeyDown(OIS::KC_ESCAPE)) return false;

	return true;
}
 
bool Application::frameEnded(const FrameEvent &evt)
{
	return true;
}

void Application::InitOIS()
{
	OIS::paramList pl;size_t windowHnd = 0;
	std::ostringstream windowHndStr;
	pRenderWindow->getCustomAttribute("WINDOW", &windowHnd); 
	windowHndStr << windowHnd; // :: egale inf inf
	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;

}

#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() ;

	
    } catch( 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 ci-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 tutoriel, 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) les 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 tutoriel, 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.

De plus,cette méthode demande une phase de préprocess consistant a générer une liste d'arêtes. Cette génération est automatique quand on convertit un fichier xml vers un .mesh, mais lors d'un génération procédurale d'objet, il faut faire appelle a la fonction Mesh::buildEdgeList().

L'utilisation de Stencil Shadows est aussi coûteuses lors de l'utilisation de meshs skinnés car le skining doit donc être effectué 2 fois: une sur le CPU (génération de l'ombre) et une autre sur le GPU (affichage). De même, elles ne peuvent pas s'accomoder de transformation arbitraire. Seul les mesh statiques et le skinning sont supportés. L'extrusion se fera sur les arêtes initiales dans les autres cas d'animation.
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 beaucoup 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. De plus, la précision de l'ombre n'est pas infinie car limitée par la taille de la texture utilisée, on peu donc avoir ombres pixelisées.

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é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 scène 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 n'ait 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 tilled.


VI. Remerciements

Un grand merci à IrmatDen et Melem pour leurs relectures, et à loka pour son soutient.



Valid XHTML 1.1!Valid CSS!

Copyright © 2014 gusgus. 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'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts. Cette page est déposée à la SACD.

Responsables bénévoles de la rubrique 2D - 3D - Jeux : loka et Cyril Doillon - Contacter par EMail :
Vos questions techniques : forum d'entraide 2D - 3D - Jeux - Publiez vos articles, tutoriels et cours
et rejoignez-nous dans l'équipe de rédaction du club d'entraide des développeurs francophones
Nous contacter - Copyright © 2000-2008 www.developpez.com - Legal informations.