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
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 )
Dans le constructeur:
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():
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;
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.


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.