1
/** Example 011 Per-Pixel Lighting
3
This tutorial shows how to use one of the built in more complex materials in
4
irrlicht: Per pixel lighted surfaces using normal maps and parallax mapping. It
5
will also show how to use fog and moving particle systems. And don't panic: You
6
dont need any experience with shaders to use these materials in Irrlicht.
8
At first, we need to include all headers and do the stuff we always do, like in
9
nearly all other tutorials.
12
#include "driverChoice.h"
17
#pragma comment(lib, "Irrlicht.lib")
21
For this example, we need an event receiver, to make it possible for the user
22
to switch between the three available material types. In addition, the event
23
receiver will create some small GUI window which displays what material is
24
currently being used. There is nothing special done in this class, so maybe you
25
want to skip reading it.
27
class MyEventReceiver : public IEventReceiver
31
MyEventReceiver(scene::ISceneNode* room,scene::ISceneNode* earth,
32
gui::IGUIEnvironment* env, video::IVideoDriver* driver)
34
// store pointer to room so we can change its drawing mode
40
gui::IGUISkin* skin = env->getSkin();
41
gui::IGUIFont* font = env->getFont("../../media/fonthaettenschweiler.bmp");
45
// add window and listbox
46
gui::IGUIWindow* window = env->addWindow(
47
core::rect<s32>(460,375,630,470), false, L"Use 'E' + 'R' to change");
49
ListBox = env->addListBox(
50
core::rect<s32>(2,22,165,88), window);
52
ListBox->addItem(L"Diffuse");
53
ListBox->addItem(L"Bump mapping");
54
ListBox->addItem(L"Parallax mapping");
55
ListBox->setSelected(1);
57
// create problem text
58
ProblemText = env->addStaticText(
59
L"Your hardware or this renderer is not able to use the "\
60
L"needed shaders for this material. Using fall back materials.",
61
core::rect<s32>(150,20,470,80));
63
ProblemText->setOverrideColor(video::SColor(100,255,255,255));
65
// set start material (prefer parallax mapping if available)
66
video::IMaterialRenderer* renderer =
67
Driver->getMaterialRenderer(video::EMT_PARALLAX_MAP_SOLID);
68
if (renderer && renderer->getRenderCapability() == 0)
69
ListBox->setSelected(2);
71
// set the material which is selected in the listbox
75
bool OnEvent(const SEvent& event)
77
// check if user presses the key 'E' or 'R'
78
if (event.EventType == irr::EET_KEY_INPUT_EVENT &&
79
!event.KeyInput.PressedDown && Room && ListBox)
81
// change selected item in listbox
83
int sel = ListBox->getSelected();
84
if (event.KeyInput.Key == irr::KEY_KEY_R)
87
if (event.KeyInput.Key == irr::KEY_KEY_E)
94
ListBox->setSelected(sel);
96
// set the material which is selected in the listbox
105
// sets the material of the room mesh the the one set in the
109
video::E_MATERIAL_TYPE type = video::EMT_SOLID;
111
// change material setting
112
switch(ListBox->getSelected())
114
case 0: type = video::EMT_SOLID;
116
case 1: type = video::EMT_NORMAL_MAP_SOLID;
118
case 2: type = video::EMT_PARALLAX_MAP_SOLID;
122
Room->setMaterialType(type);
124
// change material setting
125
switch(ListBox->getSelected())
127
case 0: type = video::EMT_TRANSPARENT_VERTEX_ALPHA;
129
case 1: type = video::EMT_NORMAL_MAP_TRANSPARENT_VERTEX_ALPHA;
131
case 2: type = video::EMT_PARALLAX_MAP_TRANSPARENT_VERTEX_ALPHA;
135
Earth->setMaterialType(type);
138
We need to add a warning if the materials will not be able to
139
be displayed 100% correctly. This is no problem, they will be
140
renderered using fall back materials, but at least the user
141
should know that it would look better on better hardware. We
142
simply check if the material renderer is able to draw at full
143
quality on the current hardware. The
144
IMaterialRenderer::getRenderCapability() returns 0 if this is
147
video::IMaterialRenderer* renderer = Driver->getMaterialRenderer(type);
149
// display some problem text when problem
150
if (!renderer || renderer->getRenderCapability() != 0)
151
ProblemText->setVisible(true);
153
ProblemText->setVisible(false);
158
gui::IGUIStaticText* ProblemText;
159
gui::IGUIListBox* ListBox;
161
scene::ISceneNode* Room;
162
scene::ISceneNode* Earth;
163
video::IVideoDriver* Driver;
168
Now for the real fun. We create an Irrlicht Device and start to setup the scene.
172
// let user select driver type
173
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
174
if (driverType==video::EDT_COUNT)
179
IrrlichtDevice* device = createDevice(driverType,
180
core::dimension2d<u32>(640, 480));
183
return 1; // could not create selected driver.
187
Before we start with the interesting stuff, we do some simple things:
188
Store pointers to the most important parts of the engine (video driver,
189
scene manager, gui environment) to safe us from typing too much, add an
190
irrlicht engine logo to the window and a user controlled first person
191
shooter style camera. Also, we let the engine know that it should store
192
all textures in 32 bit. This necessary because for parallax mapping, we
193
need 32 bit textures.
196
video::IVideoDriver* driver = device->getVideoDriver();
197
scene::ISceneManager* smgr = device->getSceneManager();
198
gui::IGUIEnvironment* env = device->getGUIEnvironment();
200
driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, true);
203
env->addImage(driver->getTexture("../../media/irrlichtlogo3.png"),
204
core::position2d<s32>(10,10));
207
scene::ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS();
208
camera->setPosition(core::vector3df(-200,200,-200));
210
// disable mouse cursor
211
device->getCursorControl()->setVisible(false);
214
Because we want the whole scene to look a little bit scarier, we add
215
some fog to it. This is done by a call to IVideoDriver::setFog(). There
216
you can set various fog settings. In this example, we use pixel fog,
217
because it will work well with the materials we'll use in this example.
218
Please note that you will have to set the material flag EMF_FOG_ENABLE
219
to 'true' in every scene node which should be affected by this fog.
221
driver->setFog(video::SColor(0,138,125,81), video::EFT_FOG_LINEAR, 250, 1000, .003f, true, false);
224
To be able to display something interesting, we load a mesh from a .3ds
225
file which is a room I modeled with anim8or. It is the same room as
226
from the specialFX example. Maybe you remember from that tutorial, I am
227
no good modeler at all and so I totally messed up the texture mapping
228
in this model, but we can simply repair it with the
229
IMeshManipulator::makePlanarTextureMapping() method.
232
scene::IAnimatedMesh* roomMesh = smgr->getMesh("../../media/room.3ds");
233
scene::ISceneNode* room = 0;
234
scene::ISceneNode* earth = 0;
238
// The Room mesh doesn't have proper Texture Mapping on the
239
// floor, so we can recreate them on runtime
240
smgr->getMeshManipulator()->makePlanarTextureMapping(
241
roomMesh->getMesh(0), 0.003f);
244
Now for the first exciting thing: If we successfully loaded the
245
mesh we need to apply textures to it. Because we want this room
246
to be displayed with a very cool material, we have to do a
247
little bit more than just set the textures. Instead of only
248
loading a color map as usual, we also load a height map which
249
is simply a grayscale texture. From this height map, we create
250
a normal map which we will set as second texture of the room.
251
If you already have a normal map, you could directly set it,
252
but I simply didn't find a nice normal map for this texture.
253
The normal map texture is being generated by the
254
makeNormalMapTexture method of the VideoDriver. The second
255
parameter specifies the height of the heightmap. If you set it
256
to a bigger value, the map will look more rocky.
259
video::ITexture* normalMap =
260
driver->getTexture("../../media/rockwall_height.bmp");
263
driver->makeNormalMapTexture(normalMap, 9.0f);
265
// The Normal Map and the displacement map/height map in the alpha channel
266
video::ITexture* normalMap =
267
driver->getTexture("../../media/rockwall_NRM.tga");
270
But just setting color and normal map is not everything. The
271
material we want to use needs some additional informations per
272
vertex like tangents and binormals. Because we are too lazy to
273
calculate that information now, we let Irrlicht do this for us.
274
That's why we call IMeshManipulator::createMeshWithTangents().
275
It creates a mesh copy with tangents and binormals from another
276
mesh. After we've done that, we simply create a standard
277
mesh scene node with this mesh copy, set color and normal map
278
and adjust some other material settings. Note that we set
279
EMF_FOG_ENABLE to true to enable fog in the room.
282
scene::IMesh* tangentMesh = smgr->getMeshManipulator()->
283
createMeshWithTangents(roomMesh->getMesh(0));
285
room = smgr->addMeshSceneNode(tangentMesh);
286
room->setMaterialTexture(0,
287
driver->getTexture("../../media/rockwall.jpg"));
288
room->setMaterialTexture(1, normalMap);
290
// Stones don't glitter..
291
room->getMaterial(0).SpecularColor.set(0,0,0,0);
292
room->getMaterial(0).Shininess = 0.f;
294
room->setMaterialFlag(video::EMF_FOG_ENABLE, true);
295
room->setMaterialType(video::EMT_PARALLAX_MAP_SOLID);
296
// adjust height for parallax effect
297
room->getMaterial(0).MaterialTypeParam = 1.f / 64.f;
299
// drop mesh because we created it with a create.. call.
304
After we've created a room shaded by per pixel lighting, we add a
305
sphere into it with the same material, but we'll make it transparent.
306
In addition, because the sphere looks somehow like a familiar planet,
307
we make it rotate. The procedure is similar as before. The difference
308
is that we are loading the mesh from an .x file which already contains
309
a color map so we do not need to load it manually. But the sphere is a
310
little bit too small for our needs, so we scale it by the factor 50.
315
scene::IAnimatedMesh* earthMesh = smgr->getMesh("../../media/earth.x");
318
//perform various task with the mesh manipulator
319
scene::IMeshManipulator *manipulator = smgr->getMeshManipulator();
321
// create mesh copy with tangent informations from original earth.x mesh
322
scene::IMesh* tangentSphereMesh =
323
manipulator->createMeshWithTangents(earthMesh->getMesh(0));
325
// set the alpha value of all vertices to 200
326
manipulator->setVertexColorAlpha(tangentSphereMesh, 200);
328
// scale the mesh by factor 50
330
m.setScale ( core::vector3df(50,50,50) );
331
manipulator->transform( tangentSphereMesh, m );
333
earth = smgr->addMeshSceneNode(tangentSphereMesh);
335
earth->setPosition(core::vector3df(-70,130,45));
337
// load heightmap, create normal map from it and set it
338
video::ITexture* earthNormalMap = driver->getTexture("../../media/earthbump.jpg");
341
driver->makeNormalMapTexture(earthNormalMap, 20.0f);
342
earth->setMaterialTexture(1, earthNormalMap);
343
earth->setMaterialType(video::EMT_NORMAL_MAP_TRANSPARENT_VERTEX_ALPHA);
346
// adjust material settings
347
earth->setMaterialFlag(video::EMF_FOG_ENABLE, true);
349
// add rotation animator
350
scene::ISceneNodeAnimator* anim =
351
smgr->createRotationAnimator(core::vector3df(0,0.1f,0));
352
earth->addAnimator(anim);
355
// drop mesh because we created it with a create.. call.
356
tangentSphereMesh->drop();
360
Per pixel lighted materials only look cool when there are moving
361
lights. So we add some. And because moving lights alone are so boring,
362
we add billboards to them, and a whole particle system to one of them.
363
We start with the first light which is red and has only the billboard
367
// add light 1 (more green)
368
scene::ILightSceneNode* light1 =
369
smgr->addLightSceneNode(0, core::vector3df(0,0,0),
370
video::SColorf(0.5f, 1.0f, 0.5f, 0.0f), 800.0f);
372
light1->setDebugDataVisible ( scene::EDS_BBOX );
375
// add fly circle animator to light 1
376
scene::ISceneNodeAnimator* anim =
377
smgr->createFlyCircleAnimator (core::vector3df(50,300,0),190.0f, -0.003f);
378
light1->addAnimator(anim);
381
// attach billboard to the light
382
scene::ISceneNode* bill =
383
smgr->addBillboardSceneNode(light1, core::dimension2d<f32>(60, 60));
385
bill->setMaterialFlag(video::EMF_LIGHTING, false);
386
bill->setMaterialFlag(video::EMF_ZWRITE_ENABLE, false);
387
bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
388
bill->setMaterialTexture(0, driver->getTexture("../../media/particlegreen.jpg"));
391
Now the same again, with the second light. The difference is that we
392
add a particle system to it too. And because the light moves, the
393
particles of the particlesystem will follow. If you want to know more
394
about how particle systems are created in Irrlicht, take a look at the
395
specialFx example. Maybe you will have noticed that we only add 2
396
lights, this has a simple reason: The low end version of this material
397
was written in ps1.1 and vs1.1, which doesn't allow more lights. You
398
could add a third light to the scene, but it won't be used to shade the
399
walls. But of course, this will change in future versions of Irrlicht
400
where higher versions of pixel/vertex shaders will be implemented too.
404
scene::ISceneNode* light2 =
405
smgr->addLightSceneNode(0, core::vector3df(0,0,0),
406
video::SColorf(1.0f, 0.2f, 0.2f, 0.0f), 800.0f);
408
// add fly circle animator to light 2
409
anim = smgr->createFlyCircleAnimator(core::vector3df(0,150,0), 200.0f,
410
0.001f, core::vector3df(0.2f, 0.9f, 0.f));
411
light2->addAnimator(anim);
414
// attach billboard to light
415
bill = smgr->addBillboardSceneNode(light2, core::dimension2d<f32>(120, 120));
416
bill->setMaterialFlag(video::EMF_LIGHTING, false);
417
bill->setMaterialFlag(video::EMF_ZWRITE_ENABLE, false);
418
bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
419
bill->setMaterialTexture(0, driver->getTexture("../../media/particlered.bmp"));
421
// add particle system
422
scene::IParticleSystemSceneNode* ps =
423
smgr->addParticleSystemSceneNode(false, light2);
425
// create and set emitter
426
scene::IParticleEmitter* em = ps->createBoxEmitter(
427
core::aabbox3d<f32>(-3,0,-3,3,1,3),
428
core::vector3df(0.0f,0.03f,0.0f),
430
video::SColor(10,255,255,255), video::SColor(10,255,255,255),
432
em->setMinStartSize(core::dimension2d<f32>(30.0f, 40.0f));
433
em->setMaxStartSize(core::dimension2d<f32>(30.0f, 40.0f));
438
// create and set affector
439
scene::IParticleAffector* paf = ps->createFadeOutParticleAffector();
440
ps->addAffector(paf);
443
// adjust some material settings
444
ps->setMaterialFlag(video::EMF_LIGHTING, false);
445
ps->setMaterialFlag(video::EMF_ZWRITE_ENABLE, false);
446
ps->setMaterialTexture(0, driver->getTexture("../../media/fireball.bmp"));
447
ps->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
449
MyEventReceiver receiver(room, earth, env, driver);
450
device->setEventReceiver(&receiver);
453
Finally, draw everything. That's it.
459
if (device->isWindowActive())
461
driver->beginScene(true, true, 0);
468
int fps = driver->getFPS();
472
core::stringw str = L"Per pixel lighting example - Irrlicht Engine [";
473
str += driver->getName();
477
device->setWindowCaption(str.c_str());