1 /** Example 011 Per-Pixel Lighting
2 
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 do not need any experience with shaders to use these materials in Irrlicht.
7 
8 At first, we need to include all headers and do the stuff we always do, like in
9 nearly all other tutorials.
10 */
11 #include <irrlicht.h>
12 #include "driverChoice.h"
13 
14 using namespace irr;
15 
16 #ifdef _MSC_VER
17 #pragma comment(lib, "Irrlicht.lib")
18 #endif
19 
20 /*
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.
26 */
27 class MyEventReceiver : public IEventReceiver
28 {
29 public:
30 
MyEventReceiver(scene::ISceneNode * room,scene::ISceneNode * earth,gui::IGUIEnvironment * env,video::IVideoDriver * driver)31 	MyEventReceiver(scene::ISceneNode* room,scene::ISceneNode* earth,
32 		gui::IGUIEnvironment* env, video::IVideoDriver* driver)
33 	{
34 		// store pointer to room so we can change its drawing mode
35 		Room = room;
36 		Earth = earth;
37 		Driver = driver;
38 
39 		// set a nicer font
40 		gui::IGUISkin* skin = env->getSkin();
41 		gui::IGUIFont* font = env->getFont("../../media/fonthaettenschweiler.bmp");
42 		if (font)
43 			skin->setFont(font);
44 
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");
48 
49 		ListBox = env->addListBox(
50 			core::rect<s32>(2,22,165,88), window);
51 
52 		ListBox->addItem(L"Diffuse");
53 		ListBox->addItem(L"Bump mapping");
54 		ListBox->addItem(L"Parallax mapping");
55 		ListBox->setSelected(1);
56 
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));
62 
63 		ProblemText->setOverrideColor(video::SColor(100,255,255,255));
64 
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);
70 
71 		// set the material which is selected in the listbox
72 		setMaterial();
73 	}
74 
OnEvent(const SEvent & event)75 	bool OnEvent(const SEvent& event)
76 	{
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)
80 		{
81 			// change selected item in listbox
82 
83 			int sel = ListBox->getSelected();
84 			if (event.KeyInput.Key == irr::KEY_KEY_R)
85 				++sel;
86 			else
87 			if (event.KeyInput.Key == irr::KEY_KEY_E)
88 				--sel;
89 			else
90 				return false;
91 
92 			if (sel > 2) sel = 0;
93 			if (sel < 0) sel = 2;
94 			ListBox->setSelected(sel);
95 
96 			// set the material which is selected in the listbox
97 			setMaterial();
98 		}
99 
100 		return false;
101 	}
102 
103 private:
104 
105 	// sets the material of the room mesh the the one set in the
106 	// list box.
setMaterial()107 	void setMaterial()
108 	{
109 		video::E_MATERIAL_TYPE type = video::EMT_SOLID;
110 
111 		// change material setting
112 		switch(ListBox->getSelected())
113 		{
114 		case 0: type = video::EMT_SOLID;
115 			break;
116 		case 1: type = video::EMT_NORMAL_MAP_SOLID;
117 			break;
118 		case 2: type = video::EMT_PARALLAX_MAP_SOLID;
119 			break;
120 		}
121 
122 		Room->setMaterialType(type);
123 
124 		// change material setting
125 		switch(ListBox->getSelected())
126 		{
127 		case 0: type = video::EMT_TRANSPARENT_VERTEX_ALPHA;
128 			break;
129 		case 1: type = video::EMT_NORMAL_MAP_TRANSPARENT_VERTEX_ALPHA;
130 			break;
131 		case 2: type = video::EMT_PARALLAX_MAP_TRANSPARENT_VERTEX_ALPHA;
132 			break;
133 		}
134 
135 		Earth->setMaterialType(type);
136 
137 		/*
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 		rendered 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
145 		the case.
146 		*/
147 		video::IMaterialRenderer* renderer = Driver->getMaterialRenderer(type);
148 
149 		// display some problem text when problem
150 		if (!renderer || renderer->getRenderCapability() != 0)
151 			ProblemText->setVisible(true);
152 		else
153 			ProblemText->setVisible(false);
154 	}
155 
156 private:
157 
158 	gui::IGUIStaticText* ProblemText;
159 	gui::IGUIListBox* ListBox;
160 
161 	scene::ISceneNode* Room;
162 	scene::ISceneNode* Earth;
163 	video::IVideoDriver* Driver;
164 };
165 
166 
167 /*
168 Now for the real fun. We create an Irrlicht Device and start to setup the scene.
169 */
main()170 int main()
171 {
172 	// ask user for driver
173 	video::E_DRIVER_TYPE driverType=driverChoiceConsole();
174 	if (driverType==video::EDT_COUNT)
175 		return 1;
176 
177 	// create device
178 
179 	IrrlichtDevice* device = createDevice(driverType,
180 			core::dimension2d<u32>(640, 480));
181 
182 	if (device == 0)
183 		return 1; // could not create selected driver.
184 
185 	/*
186 	Before we start with the interesting stuff, we do some simple things:
187 	Store pointers to the most important parts of the engine (video driver,
188 	scene manager, gui environment) to safe us from typing too much, add an
189 	irrlicht engine logo to the window and a user controlled first person
190 	shooter style camera. Also, we let the engine know that it should store
191 	all textures in 32 bit. This necessary because for parallax mapping, we
192 	need 32 bit textures.
193 	*/
194 
195 	video::IVideoDriver* driver = device->getVideoDriver();
196 	scene::ISceneManager* smgr = device->getSceneManager();
197 	gui::IGUIEnvironment* env = device->getGUIEnvironment();
198 
199 	driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, true);
200 
201 	// add irrlicht logo
202 	env->addImage(driver->getTexture("../../media/irrlichtlogo3.png"),
203 		core::position2d<s32>(10,10));
204 
205 	// add camera
206 	scene::ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS();
207 	camera->setPosition(core::vector3df(-200,200,-200));
208 
209 	// disable mouse cursor
210 	device->getCursorControl()->setVisible(false);
211 
212 	/*
213 	Because we want the whole scene to look a little bit scarier, we add
214 	some fog to it. This is done by a call to IVideoDriver::setFog(). There
215 	you can set various fog settings. In this example, we use pixel fog,
216 	because it will work well with the materials we'll use in this example.
217 	Please note that you will have to set the material flag EMF_FOG_ENABLE
218 	to 'true' in every scene node which should be affected by this fog.
219 	*/
220 	driver->setFog(video::SColor(0,138,125,81), video::EFT_FOG_LINEAR, 250, 1000, .003f, true, false);
221 
222 	/*
223 	To be able to display something interesting, we load a mesh from a .3ds
224 	file which is a room I modeled with anim8or. It is the same room as
225 	from the specialFX example. Maybe you remember from that tutorial, I am
226 	no good modeler at all and so I totally messed up the texture mapping
227 	in this model, but we can simply repair it with the
228 	IMeshManipulator::makePlanarTextureMapping() method.
229 	*/
230 
231 	scene::IAnimatedMesh* roomMesh = smgr->getMesh("../../media/room.3ds");
232 	scene::ISceneNode* room = 0;
233 	scene::ISceneNode* earth = 0;
234 
235 	if (roomMesh)
236 	{
237 		// The Room mesh doesn't have proper Texture Mapping on the
238 		// floor, so we can recreate them on runtime
239 		smgr->getMeshManipulator()->makePlanarTextureMapping(
240 				roomMesh->getMesh(0), 0.003f);
241 
242 		/*
243 		Now for the first exciting thing: If we successfully loaded the
244 		mesh we need to apply textures to it. Because we want this room
245 		to be displayed with a very cool material, we have to do a
246 		little bit more than just set the textures. Instead of only
247 		loading a color map as usual, we also load a height map which
248 		is simply a grayscale texture. From this height map, we create
249 		a normal map which we will set as second texture of the room.
250 		If you already have a normal map, you could directly set it,
251 		but I simply didn't find a nice normal map for this texture.
252 		The normal map texture is being generated by the
253 		makeNormalMapTexture method of the VideoDriver. The second
254 		parameter specifies the height of the heightmap. If you set it
255 		to a bigger value, the map will look more rocky.
256 		*/
257 
258 		video::ITexture* normalMap =
259 			driver->getTexture("../../media/rockwall_height.bmp");
260 
261 		if (normalMap)
262 			driver->makeNormalMapTexture(normalMap, 9.0f);
263 /*
264 		// The Normal Map and the displacement map/height map in the alpha channel
265 		video::ITexture* normalMap =
266 			driver->getTexture("../../media/rockwall_NRM.tga");
267 */
268 		/*
269 		But just setting color and normal map is not everything. The
270 		material we want to use needs some additional informations per
271 		vertex like tangents and binormals. Because we are too lazy to
272 		calculate that information now, we let Irrlicht do this for us.
273 		That's why we call IMeshManipulator::createMeshWithTangents().
274 		It creates a mesh copy with tangents and binormals from another
275 		mesh. After we've done that, we simply create a standard
276 		mesh scene node with this mesh copy, set color and normal map
277 		and adjust some other material settings. Note that we set
278 		EMF_FOG_ENABLE to true to enable fog in the room.
279 		*/
280 
281 		scene::IMesh* tangentMesh = smgr->getMeshManipulator()->
282 				createMeshWithTangents(roomMesh->getMesh(0));
283 
284 		room = smgr->addMeshSceneNode(tangentMesh);
285 		room->setMaterialTexture(0,
286 				driver->getTexture("../../media/rockwall.jpg"));
287 		room->setMaterialTexture(1, normalMap);
288 
289 		// Stones don't glitter..
290 		room->getMaterial(0).SpecularColor.set(0,0,0,0);
291 		room->getMaterial(0).Shininess = 0.f;
292 
293 		room->setMaterialFlag(video::EMF_FOG_ENABLE, true);
294 		room->setMaterialType(video::EMT_PARALLAX_MAP_SOLID);
295 		// adjust height for parallax effect
296 		room->getMaterial(0).MaterialTypeParam = 1.f / 64.f;
297 
298 		// drop mesh because we created it with a create.. call.
299 		tangentMesh->drop();
300 	}
301 
302 	/*
303 	After we've created a room shaded by per pixel lighting, we add a
304 	sphere into it with the same material, but we'll make it transparent.
305 	In addition, because the sphere looks somehow like a familiar planet,
306 	we make it rotate. The procedure is similar as before. The difference
307 	is that we are loading the mesh from an .x file which already contains
308 	a color map so we do not need to load it manually. But the sphere is a
309 	little bit too small for our needs, so we scale it by the factor 50.
310 	*/
311 
312 	// add earth sphere
313 
314 	scene::IAnimatedMesh* earthMesh = smgr->getMesh("../../media/earth.x");
315 	if (earthMesh)
316 	{
317 		//perform various task with the mesh manipulator
318 		scene::IMeshManipulator *manipulator = smgr->getMeshManipulator();
319 
320 		// create mesh copy with tangent informations from original earth.x mesh
321 		scene::IMesh* tangentSphereMesh =
322 			manipulator->createMeshWithTangents(earthMesh->getMesh(0));
323 
324 		// set the alpha value of all vertices to 200
325 		manipulator->setVertexColorAlpha(tangentSphereMesh, 200);
326 
327 		// scale the mesh by factor 50
328 		core::matrix4 m;
329 		m.setScale ( core::vector3df(50,50,50) );
330 		manipulator->transform( tangentSphereMesh, m );
331 
332 		earth = smgr->addMeshSceneNode(tangentSphereMesh);
333 
334 		earth->setPosition(core::vector3df(-70,130,45));
335 
336 		// load heightmap, create normal map from it and set it
337 		video::ITexture* earthNormalMap = driver->getTexture("../../media/earthbump.jpg");
338 		if (earthNormalMap)
339 		{
340 			driver->makeNormalMapTexture(earthNormalMap, 20.0f);
341 			earth->setMaterialTexture(1, earthNormalMap);
342 			earth->setMaterialType(video::EMT_NORMAL_MAP_TRANSPARENT_VERTEX_ALPHA);
343 		}
344 
345 		// adjust material settings
346 		earth->setMaterialFlag(video::EMF_FOG_ENABLE, true);
347 
348 		// add rotation animator
349 		scene::ISceneNodeAnimator* anim =
350 			smgr->createRotationAnimator(core::vector3df(0,0.1f,0));
351 		earth->addAnimator(anim);
352 		anim->drop();
353 
354 		// drop mesh because we created it with a create.. call.
355 		tangentSphereMesh->drop();
356 	}
357 
358 	/*
359 	Per pixel lighted materials only look cool when there are moving
360 	lights. So we add some. And because moving lights alone are so boring,
361 	we add billboards to them, and a whole particle system to one of them.
362 	We start with the first light which is red and has only the billboard
363 	attached.
364 	*/
365 
366 	// add light 1 (more green)
367 	scene::ILightSceneNode* light1 =
368 		smgr->addLightSceneNode(0, core::vector3df(0,0,0),
369 		video::SColorf(0.5f, 1.0f, 0.5f, 0.0f), 800.0f);
370 
371 	light1->setDebugDataVisible ( scene::EDS_BBOX );
372 
373 
374 	// add fly circle animator to light 1
375 	scene::ISceneNodeAnimator* anim =
376 		smgr->createFlyCircleAnimator (core::vector3df(50,300,0),190.0f, -0.003f);
377 	light1->addAnimator(anim);
378 	anim->drop();
379 
380 	// attach billboard to the light
381 	scene::IBillboardSceneNode* bill =
382 		smgr->addBillboardSceneNode(light1, core::dimension2d<f32>(60, 60));
383 
384 	bill->setMaterialFlag(video::EMF_LIGHTING, false);
385 	bill->setMaterialFlag(video::EMF_ZWRITE_ENABLE, false);
386 	bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
387 	bill->setMaterialTexture(0, driver->getTexture("../../media/particlegreen.jpg"));
388 
389 	/*
390 	Now the same again, with the second light. The difference is that we
391 	add a particle system to it too. And because the light moves, the
392 	particles of the particlesystem will follow. If you want to know more
393 	about how particle systems are created in Irrlicht, take a look at the
394 	specialFx example. Maybe you will have noticed that we only add 2
395 	lights, this has a simple reason: The low end version of this material
396 	was written in ps1.1 and vs1.1, which doesn't allow more lights. You
397 	could add a third light to the scene, but it won't be used to shade the
398 	walls. But of course, this will change in future versions of Irrlicht
399 	where higher versions of pixel/vertex shaders will be implemented too.
400 	*/
401 
402 	// add light 2 (red)
403 	scene::ISceneNode* light2 =
404 		smgr->addLightSceneNode(0, core::vector3df(0,0,0),
405 		video::SColorf(1.0f, 0.2f, 0.2f, 0.0f), 800.0f);
406 
407 	// add fly circle animator to light 2
408 	anim = smgr->createFlyCircleAnimator(core::vector3df(0,150,0), 200.0f,
409 			0.001f, core::vector3df(0.2f, 0.9f, 0.f));
410 	light2->addAnimator(anim);
411 	anim->drop();
412 
413 	// attach billboard to light
414 	bill = smgr->addBillboardSceneNode(light2, core::dimension2d<f32>(120, 120));
415 	bill->setMaterialFlag(video::EMF_LIGHTING, false);
416 	bill->setMaterialFlag(video::EMF_ZWRITE_ENABLE, false);
417 	bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
418 	bill->setMaterialTexture(0, driver->getTexture("../../media/particlered.bmp"));
419 
420 	// add particle system
421 	scene::IParticleSystemSceneNode* ps =
422 		smgr->addParticleSystemSceneNode(false, light2);
423 
424 	// create and set emitter
425 	scene::IParticleEmitter* em = ps->createBoxEmitter(
426 		core::aabbox3d<f32>(-3,0,-3,3,1,3),
427 		core::vector3df(0.0f,0.03f,0.0f),
428 		80,100,
429 		video::SColor(10,255,255,255), video::SColor(10,255,255,255),
430 		400,1100);
431 	em->setMinStartSize(core::dimension2d<f32>(30.0f, 40.0f));
432 	em->setMaxStartSize(core::dimension2d<f32>(30.0f, 40.0f));
433 
434 	ps->setEmitter(em);
435 	em->drop();
436 
437 	// create and set affector
438 	scene::IParticleAffector* paf = ps->createFadeOutParticleAffector();
439 	ps->addAffector(paf);
440 	paf->drop();
441 
442 	// adjust some material settings
443 	ps->setMaterialFlag(video::EMF_LIGHTING, false);
444 	ps->setMaterialFlag(video::EMF_ZWRITE_ENABLE, false);
445 	ps->setMaterialTexture(0, driver->getTexture("../../media/fireball.bmp"));
446 	ps->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
447 
448 	MyEventReceiver receiver(room, earth, env, driver);
449 	device->setEventReceiver(&receiver);
450 
451 	/*
452 	Finally, draw everything. That's it.
453 	*/
454 
455 	int lastFPS = -1;
456 
457 	while(device->run())
458 	if (device->isWindowActive())
459 	{
460 		driver->beginScene(true, true, 0);
461 
462 		smgr->drawAll();
463 		env->drawAll();
464 
465 		driver->endScene();
466 
467 		int fps = driver->getFPS();
468 
469 		if (lastFPS != fps)
470 		{
471 			core::stringw str = L"Per pixel lighting example - Irrlicht Engine [";
472 			str += driver->getName();
473 			str += "] FPS:";
474 			str += fps;
475 
476 			device->setWindowCaption(str.c_str());
477 			lastFPS = fps;
478 		}
479 	}
480 
481 	device->drop();
482 
483 	return 0;
484 }
485 
486 /*
487 **/
488