I. Prérequis▲
Pour commencer, nous avons besoin d'inclure tous les en-têtes et faire les autres choses que nous faisons toujours comme dans presque tous les autres tutoriels.
#include
<irrlicht.h>
#include
"driverChoice.h"
using
namespace
irr;
#ifdef _MSC_VER
#pragma comment(lib,
"Irrlicht.lib"
)
#endif
Pour cet exemple, nous avons besoin d'un receveur d'événements pour que l'utilisateur puisse choisir dynamiquement entre trois types de matériels disponibles. De plus, le receveur d'événements créera une petite interface utilisateur qui montrera quel matériel est utilisé. Il n'y a rien de spécial dans cette classe donc peut-être que vous souhaitez passer directement à la suite.
class
MyEventReceiver : public
IEventReceiver
{
public
:
MyEventReceiver(scene::
ISceneNode*
room,scene::
ISceneNode*
earth,
gui::
IGUIEnvironment*
env, video::
IVideoDriver*
driver)
{
// Stocke un pointeur sur la salle ainsi nous pourrons changer son mode de rendu.
Room =
room;
Earth =
earth;
Driver =
driver;
// Met une jolie police.
gui::
IGUISkin*
skin =
env->
getSkin();
gui::
IGUIFont*
font =
env->
getFont("../../media/fonthaettenschweiler.bmp"
);
if
(font)
skin->
setFont(font);
// Ajoute une fenêtre et une liste déroulante.
gui::
IGUIWindow*
window =
env->
addWindow(
core::
rect<
s32>
(460
,375
,630
,470
), false
, L"Use 'E' + 'R' to change"
);
ListBox =
env->
addListBox(
core::
rect<
s32>
(2
,22
,165
,88
), window);
ListBox->
addItem(L"Diffuse"
);
ListBox->
addItem(L"Bump mapping"
);
ListBox->
addItem(L"Parallax mapping"
);
ListBox->
setSelected(1
);
// Crée un texte en cas de problèmes.
ProblemText =
env->
addStaticText(
L"Your hardware or this renderer is not able to use the "
\
L"needed shaders for this material. Using fall back materials."
,
core::
rect<
s32>
(150
,20
,470
,80
));
ProblemText->
setOverrideColor(video::
SColor(100
,255
,255
,255
));
// Prépare le matériel (préférez le « parallax mapping » lorsque disponible).
video::
IMaterialRenderer*
renderer =
Driver->
getMaterialRenderer(video::
EMT_PARALLAX_MAP_SOLID);
if
(renderer &&
renderer->
getRenderCapability() ==
0
)
ListBox->
setSelected(2
);
// Met le matériel sélectionné dans la liste.
setMaterial();
}
bool
OnEvent(const
SEvent&
event)
{
// Vérifie si l'utilisateur a pressé les touches 'E' ou 'R'.
if
(event.EventType ==
irr::
EET_KEY_INPUT_EVENT &&
!
event.KeyInput.PressedDown &&
Room &&
ListBox)
{
// Change l'élément sélectionné dans la liste.
int
sel =
ListBox->
getSelected();
if
(event.KeyInput.Key ==
irr::
KEY_KEY_R)
++
sel;
else
if
(event.KeyInput.Key ==
irr::
KEY_KEY_E)
--
sel;
else
return
false
;
if
(sel >
2
) sel =
0
;
if
(sel <
0
) sel =
2
;
ListBox->
setSelected(sel);
// Met le matériel sélectionné dans la liste.
setMaterial();
}
return
false
;
}
private
:
// Affecte le matériel de la liste à l'objet de la salle.
void
setMaterial()
{
video::
E_MATERIAL_TYPE type =
video::
EMT_SOLID;
// Change la configuration du matériel.
switch
(ListBox->
getSelected())
{
case
0
: type =
video::
EMT_SOLID;
break
;
case
1
: type =
video::
EMT_NORMAL_MAP_SOLID;
break
;
case
2
: type =
video::
EMT_PARALLAX_MAP_SOLID;
break
;
}
Room->
setMaterialType(type);
// Change les paramètres du matériel.
switch
(ListBox->
getSelected())
{
case
0
: type =
video::
EMT_TRANSPARENT_VERTEX_ALPHA;
break
;
case
1
: type =
video::
EMT_NORMAL_MAP_TRANSPARENT_VERTEX_ALPHA;
break
;
case
2
: type =
video::
EMT_PARALLAX_MAP_TRANSPARENT_VERTEX_ALPHA;
break
;
}
Earth->
setMaterialType(type);
Nous avons besoin d'ajouter un avertissement si le matériel ne peut pas être affiché correctement à 100 %. Ce n'est pas un problème, ils seront dessinés avec d'autres matériels, mais l'utilisateur doit au moins savoir que cela serait plus joli sur une meilleure machine. Nous vérifions simplement que le moteur de rendu du matériel est capable de dessiner à la qualité maximale sur le matériel utilisé. ImaterialRenderer::getRenderCapability() retourne 0 dans ce cas-là.
video::
IMaterialRenderer*
renderer =
Driver->
getMaterialRenderer(type);
// Affiche un message d'erreur en cas de problèmes.
if
(!
renderer ||
renderer->
getRenderCapability() !=
0
)
ProblemText->
setVisible(true
);
else
ProblemText->
setVisible(false
);
}
private
:
gui::
IGUIStaticText*
ProblemText;
gui::
IGUIListBox*
ListBox;
scene::
ISceneNode*
Room;
scene::
ISceneNode*
Earth;
video::
IVideoDriver*
Driver;
}
;
Maintenant pour vraiment nous amuser, nous créons le moteur Irrlicht et démarrons la configuration de la scène.
int
main()
{
// Demande à l'utilisateur un pilote.
video::
E_DRIVER_TYPE driverType=
driverChoiceConsole();
if
(driverType==
video::
EDT_COUNT)
return
1
;
// Crée le moteur.
IrrlichtDevice*
device =
createDevice(driverType,
core::
dimension2d<
u32>
(640
, 480
));
if
(device ==
0
)
return
1
; // Ne peut pas créer le pilote sélectionné.
Avant de commencer les choses intéressantes, nous allons faire des choses simples : stocker les pointeurs des parties les plus importantes du moteur (pilote vidéo, gestionnaire de scène, environnement de l'interface utilisateur) pour nous éviter de trop écrire, ajouter le logo du moteur Irrlicht à la fenêtre ainsi qu'une caméra FPS contrôlée par l'utilisateur. Aussi nous faisons savoir au moteur s'il doit stocker toutes les textures en 32 bits. Ceci est nécessaire : pour le « parallax mapping » nous avons besoin de textures 32 bits.
video::
IVideoDriver*
driver =
device->
getVideoDriver();
scene::
ISceneManager*
smgr =
device->
getSceneManager();
gui::
IGUIEnvironment*
env =
device->
getGUIEnvironment();
driver->
setTextureCreationFlag(video::
ETCF_ALWAYS_32_BIT, true
);
// Ajoute le logo d'Irrlicht.
env->
addImage(driver->
getTexture("../../media/irrlichtlogo3.png"
),
core::
position2d<
s32>
(10
,10
));
// Ajoute une caméra.
scene::
ICameraSceneNode*
camera =
smgr->
addCameraSceneNodeFPS();
camera->
setPosition(core::
vector3df(-
200
,200
,-
200
));
// Cache le curseur de la souris.
device->
getCursorControl()->
setVisible(false
);
II. Brouillard▲
Nous voulons rendre la scène un peu plus effrayante nous y ajoutons donc un peu de brouillard. Ceci est fait en appelant IvideoDriver::setFog(). Ici vous pouvez définir de nombreuses options pour le brouillard. Dans cet exemple, nous utilisons un brouillard au niveau du pixel, parce qu'il fonctionne bien avec les matériels que nous utilisons ici. Veuillez noter que vous devez mettre le fanion EMF_FOG_ENABLE à true dans chaque nœud de scène devant être affecté par ce brouillard.
driver->
setFog(video::
SColor(0
,138
,125
,81
), video::
EFT_FOG_LINEAR, 250
, 1000
, .003
f, true
, false
);
III. Chargement du modèle▲
Pour être capable d'afficher quelque chose d'intéressant, nous chargeons un objet à partir d'un fichier .3ds d'une salle que j'ai modélisée avec anim8or. Il s'agit de la même salle de l'exemple spécial FX. Peut-être que vous vous souvenez de ce tutoriel et que je ne suis pas un très bon modélisateur et donc que j'ai totalement raté le placage de texture dans ce modèle, mais nous pouvons le réparer avec la méthode ImeshManipulator::makePlanarTextureMapping().
scene::
IAnimatedMesh*
roomMesh =
smgr->
getMesh("../../media/room.3ds"
);
scene::
ISceneNode*
room =
0
;
scene::
ISceneNode*
earth =
0
;
if
(roomMesh)
{
// L'objet de la salle n'a pas de placage de texture propre sur le sol
// donc nous pouvons la recréer pendant l'exécution du programme.
smgr->
getMeshManipulator()->
makePlanarTextureMapping(
roomMesh->
getMesh(0
), 0.003
f);
IV. Texture de relief▲
Maintenant la première chose excitante : si nous avons correctement chargé l'objet, nous avons besoin d'y appliquer des textures dessus. Parce que nous voulons que cette salle soit affichée avec des jolis matériels, nous devons faire un peu plus que de simplement poser les textures. Au lieu de ne charger qu'une seule texture de couleur comme d'habitude, nous chargeons aussi une texture de hauteurs qui est simplement une texture de nuance de gris. À partir de ces hauteurs, nous créons une texture de normales qui sera mise comme seconde texture pour la salle. Si vous avez déjà une texture de normales, vous pouvez directement la poser, mais je n'ai pas trouvé de texture de normales correspondant à cette texture. La texture des normales est générée avec la méthode makeNormalMapTexture de VideoDriver. Le second paramètre indique la taille de la texture des hauteurs. Si vous mettez une valeur plus élevée, la carte sera plus rocailleuse.
video::
ITexture*
normalMap =
driver->
getTexture("../../media/rockwall_height.bmp"
);
if
(normalMap)
driver->
makeNormalMapTexture(normalMap, 9.0
f);
// La texture des normales et les hauteurs dans le canal alpha.
video::
ITexture*
normalMap =
driver->
getTexture("../../media/rockwall_NRM.tga"
);
Définir uniquement la couleur et la normale ne suffit pas. Le matériel que nous voulons utiliser a besoin d'informations supplémentaires pour chaque vertex comme la tangente et la binormale. Parce que nous sommes trop fainéants pour calculer ces informations maintenant, laissons Irrlicht le faire pour nous. C'est pourquoi nous appelons ImeshManipulator::createMeshWithTangents(). Il crée une copie de l'objet avec les tangentes et les binormales. Après que nous avons fait cela, nous créons simplement un nœud de scène d'objet standard avec notre copie d'objet, définissons les textures de couleurs et de normales et définissons d'autres options du matériel. Notez que nous mettons EMF_FOG_ENABLE à true pour activer le brouillard dans la salle.
scene::
IMesh*
tangentMesh =
smgr->
getMeshManipulator()->
createMeshWithTangents(roomMesh->
getMesh(0
));
room =
smgr->
addMeshSceneNode(tangentMesh);
room->
setMaterialTexture(0
,
driver->
getTexture("../../media/rockwall.jpg"
));
room->
setMaterialTexture(1
, normalMap);
// Les pierres ne scintillent pas...
room->
getMaterial(0
).SpecularColor.set(0
,0
,0
,0
);
room->
getMaterial(0
).Shininess =
0.
f;
room->
setMaterialFlag(video::
EMF_FOG_ENABLE, true
);
room->
setMaterialType(video::
EMT_PARALLAX_MAP_SOLID);
// Ajuste la taille pour l'effet parallaxe.
room->
getMaterial(0
).MaterialTypeParam =
1.
f /
64.
f;
// Jette le mesh parce que nous en avons créé un avec l'appel à createXXX.
tangentMesh->
drop();
}
Après avoir créé la salle possédant un éclairage par pixel, nous ajoutons une sphère à l'intérieur avec le même matériel, mais nous le rendons transparent. De plus, parce que la sphère ressemble d'une certaine manière à une planète familière, nous la faisons tourner. La procédure est similaire à la précédente. La différence est que nous chargeons l'objet à partir d'un fichier .x qui contient déjà une texture de couleurs nous n'avons donc pas besoin de la charger manuellement. Mais la sphère est un peu trop petite pour nos besoins, donc nous la redimensionnons avec un facteur de 50.
// Ajoute la sphère Terre.
scene::
IAnimatedMesh*
earthMesh =
smgr->
getMesh("../../media/earth.x"
);
if
(earthMesh)
{
// Réalise plusieurs tâches avec le manipulateur de mesh.
scene::
IMeshManipulator *
manipulator =
smgr->
getMeshManipulator();
// Crée la copie de mesh avec les informations de la tangente du mesh original earth.x.
scene::
IMesh*
tangentSphereMesh =
manipulator->
createMeshWithTangents(earthMesh->
getMesh(0
));
// Fixe la valeur alpha pour chaque vertex à 200.
manipulator->
setVertexColorAlpha(tangentSphereMesh, 200
);
// Redimensionne le mesh avec un facteur 50.
core::
matrix4 m;
m.setScale ( core::
vector3df(50
,50
,50
) );
manipulator->
transform( tangentSphereMesh, m );
earth =
smgr->
addMeshSceneNode(tangentSphereMesh);
earth->
setPosition(core::
vector3df(-
70
,130
,45
));
// Charge la texture des hauteurs et crée la texture des normales à partir de celle-ci et l'affecte au mesh.
video::
ITexture*
earthNormalMap =
driver->
getTexture("../../media/earthbump.jpg"
);
if
(earthNormalMap)
{
driver->
makeNormalMapTexture(earthNormalMap, 20.0
f);
earth->
setMaterialTexture(1
, earthNormalMap);
earth->
setMaterialType(video::
EMT_NORMAL_MAP_TRANSPARENT_VERTEX_ALPHA);
}
// Ajuste les options du matériel.
earth->
setMaterialFlag(video::
EMF_FOG_ENABLE, true
);
// Ajoute l'animateur de rotation.
scene::
ISceneNodeAnimator*
anim =
smgr->
createRotationAnimator(core::
vector3df(0
,0.1
f,0
));
earth->
addAnimator(anim);
anim->
drop();
// Jette le mesh parce que nous l'avons crée avec un appel à createXXX.
tangentSphereMesh->
drop();
}
V. Ajout des lumières▲
Les matériels éclairés au niveau du pixel ne sont cool que lorsqu'on a des lumières dynamiques. Donc nous en ajoutons une. Comme les lumières qui se déplacent seules sont ennuyantes, nous leur ajoutons des panneaux et tout un système de particules à l'une d'entre elles. Nous commençons par la première lumière qui est rouge et qui n'a qu'un panneau attaché.
// Ajoute la première lumière (plutôt verte).
scene::
ILightSceneNode*
light1 =
smgr->
addLightSceneNode(0
, core::
vector3df(0
,0
,0
),
video::
SColorf(0.5
f, 1.0
f, 0.5
f, 0.0
f), 800.0
f);
light1->
setDebugDataVisible ( scene::
EDS_BBOX );
// Ajoute un animateur de vol en cercle à la lumière 1.
scene::
ISceneNodeAnimator*
anim =
smgr->
createFlyCircleAnimator (core::
vector3df(50
,300
,0
),190.0
f, -
0.003
f);
light1->
addAnimator(anim);
anim->
drop();
// Attache un panneau à la lumière.
scene::
IBillboardSceneNode*
bill =
smgr->
addBillboardSceneNode(light1, core::
dimension2d<
f32>
(60
, 60
));
bill->
setMaterialFlag(video::
EMF_LIGHTING, false
);
bill->
setMaterialFlag(video::
EMF_ZWRITE_ENABLE, false
);
bill->
setMaterialType(video::
EMT_TRANSPARENT_ADD_COLOR);
bill->
setMaterialTexture(0
, driver->
getTexture("../../media/particlegreen.jpg"
));
VI. Système de particules▲
Maintenant, la même chose avec la seconde lumière. La différence est que nous lui ajoutons un système de particules. Et parce que la lumière bouge, les particules du système de particules suivront. Si vous souhaitez en savoir un peu plus sur la création des systèmes de particules par Irrlicht, regardez l'exemple Spécial FX. Peut-être remarquerez-vous que nous avons ajouté seulement deux lumières, il y a une raison simple : La version de ce matériel a été écrite en ps1.1 et vs1.1 qui ne permettent pas d'utiliser plus de lumières. Vous pouvez ajouter une troisième lumière à la scène, mais elle ne sera pas utilisée pour nuancer les murs. Mais bien sûr, cela changera dans les futures versions d'Irrlicht où des versions plus performantes seront implémentées.
// Ajoute la lumière 2 (rouge).
scene::
ISceneNode*
light2 =
smgr->
addLightSceneNode(0
, core::
vector3df(0
,0
,0
),
video::
SColorf(1.0
f, 0.2
f, 0.2
f, 0.0
f), 800.0
f);
// Ajoute l'animateur de vol en cercle à la lumière 2.
anim =
smgr->
createFlyCircleAnimator(core::
vector3df(0
,150
,0
), 200.0
f,
0.001
f, core::
vector3df(0.2
f, 0.9
f, 0.
f));
light2->
addAnimator(anim);
anim->
drop();
// Attache le panneau à la lumière.
bill =
smgr->
addBillboardSceneNode(light2, core::
dimension2d<
f32>
(120
, 120
));
bill->
setMaterialFlag(video::
EMF_LIGHTING, false
);
bill->
setMaterialFlag(video::
EMF_ZWRITE_ENABLE, false
);
bill->
setMaterialType(video::
EMT_TRANSPARENT_ADD_COLOR);
bill->
setMaterialTexture(0
, driver->
getTexture("../../media/particlered.bmp"
));
// Ajoute le système de particules.
scene::
IParticleSystemSceneNode*
ps =
smgr->
addParticleSystemSceneNode(false
, light2);
// Crée et fixe l'émetteur.
scene::
IParticleEmitter*
em =
ps->
createBoxEmitter(
core::
aabbox3d<
f32>
(-
3
,0
,-
3
,3
,1
,3
),
core::
vector3df(0.0
f,0.03
f,0.0
f),
80
,100
,
video::
SColor(10
,255
,255
,255
), video::
SColor(10
,255
,255
,255
),
400
,1100
);
em->
setMinStartSize(core::
dimension2d<
f32>
(30.0
f, 40.0
f));
em->
setMaxStartSize(core::
dimension2d<
f32>
(30.0
f, 40.0
f));
ps->
setEmitter(em);
em->
drop();
// Crée et fixe l'affecteur.
scene::
IParticleAffector*
paf =
ps->
createFadeOutParticleAffector();
ps->
addAffector(paf);
paf->
drop();
// Ajuste quelques options du matériel.
ps->
setMaterialFlag(video::
EMF_LIGHTING, false
);
ps->
setMaterialFlag(video::
EMF_ZWRITE_ENABLE, false
);
ps->
setMaterialTexture(0
, driver->
getTexture("../../media/fireball.bmp"
));
ps->
setMaterialType(video::
EMT_TRANSPARENT_ADD_COLOR);
MyEventReceiver receiver(room, earth, env, driver);
device->
setEventReceiver(&
receiver);
Et finalement, dessinons le tout. C'est tout.
int
lastFPS =
-
1
;
while
(device->
run())
if
(device->
isWindowActive())
{
driver->
beginScene(true
, true
, 0
);
smgr->
drawAll();
env->
drawAll();
driver->
endScene();
int
fps =
driver->
getFPS();
if
(lastFPS !=
fps)
{
core::
stringw str =
L"Per pixel lighting example - Irrlicht Engine ["
;
str +=
driver->
getName();
str +=
"] FPS:"
;
str +=
fps;
device->
setWindowCaption(str.c_str());
lastFPS =
fps;
}
}
device->
drop();
return
0
;
}
VII. Conclusion▲
Vous pouvez désormais créer des matériels plus compliqués avec des surfaces éclairées par pixel, créer un brouillard et déplacer des systèmes de particules avec Irrlicht.
Dans le prochain tutoriel Rendu de terrain, nous verrons comment utiliser le dessinateur de terrain d'Irrlicht.
VIII. Remerciements▲
Merci à Nikolaus Gebhardt de nous permettre de traduire ce tutoriel.
Merci à LittleWhite pour sa relecture technique ainsi qu'à ClaudeLELOUP pour sa relecture orthographique.