17-08-2015, 07:44 PM
(Questo messaggio è stato modificato l'ultima volta il: 17-08-2015, 07:45 PM da Chip.)
Tutorial 11: Per-Pixel Lighting
In questo tutorial mostreremo l'utilizzo di uno dei materiali shader più complessi e preimpostati in Irrlicht: Il Per pixel lighted surfaces (illuminazione delle superfici a livello di pixel) utilizzando le normal maps e il parallax mapping (non traduco perché ormai sono di uso comune ndt1). Mostreremo anche l'ultilizzo dell'effetto nebbia e come creare sistemi particellari in movimento. E comunque niente panico: non vi serve alcuna esperienza (che comunque non fa mai male ndt) con la programmazione degli shaders per usare i materiali in Irrlicht.
Come prima cosa includiamo l'header e le solite cose necessarie come abbiamo fatto fino ad ora in tutti i precedenti tutorials.
Per questo esempio ci serve un event receiver, per rendere l'utente in grado di switchare tra i tre tipi di materiali (Diffuse, BumpMap, Parallax). Oltre all'event receiver creiamo anche una piccola finestra con la GUI che mostri quale materiale è usato. Non c'è niente di speciale in questa classe, volendo potete saltarne la lettura (cosa che vi sconsiglio di fare ndt).
Nel caso i materiali non venissero processati correttamente, ci servirebbe aggiungere un messaggio di warning. Non c'è problema, i materiali in Irrlicht hanno un materiale per il fallback, se falliscono vengono attivati come sostitutivi, ma è giusto indicare all'utente che con un hardware migliore potrebbe ottenere una qualità migliore. Semplicemente controlliamo se il materiale può essere gestito al massimo della qualità con questo hardware. In questo caso la classe che usiamo IMaterialRenderer::getRenderCapability() ritornerebbe 0.
Ora arriva il divertimento. Creiamo il device di Irrlicht Device ed iniziamo il rendering.
Prima di iniziare con la parte interessante dobbiamo però fare alcune cose: registrare i puntatori alle parti più importanti dell'engine (video driver, scene manager, ambiente gui) per evitarci di dover scrivere troppe derivazioni nel codice, aggiungiamo un logo di irrlicht alla finestra ed una camera controllata dall'utente di tipo FPS. Dobbiamo anche indicare ad Irrlicht che deve registrare tutte le texture in 32 bit. Questo è necessario sopratutto per il parallax mapping che richiede texture a 32 bit.
Poiché vogliamo che la scena sembri un po' più spaventosa aggiungiamo un po' di nebbia. Questo si fa richiamando IVideoDriver:etFog(). Qui possiamo impostare molti valori per la nebbia. In questo esempio useremo la pixel fog, perché è l'ideale per il tipo di materiali che andremo ad usare nell'esempio. Vorrei che notaste che impostiamo a 'true' il flag EMF_FOG_ENABLE per ogni materiale di ciascun nodo della scena che vogliamo venga colpito dalla nebbia.
Per mostrare qualcosa di interessante carichiamo una mesh da file .3ds di una piccola stanza che ho medellato con Anim8or. Si tratta della stessa stanza usata nel tutorial specialFX. Forse vi ricorderete che sono un pessimo modellatore infatti nel modello avevo sbagliato il texture mapping, ma possiamo riparare attraverso il metodo IMeshManipulator::makePlanarTextureMapping().
Ora la prima cosa eccitante: Se la mesh è stata caricata correttamente dobbiamo texturizzarla. Poiché vogliamo che la stanza abbia un bel aspetto con un buon materiale, non possiamo limitarci ad applicare la texture e basta. Invece di caricare la sola texture come al solito, andiamo anche a caricare una mappa di altezze (height map) che è una semplice texture a scala di grigi. Da questa height map, andiamo a crearci una normal map che impostiamo come seconda texture nella stanza. Se già abbiamo una normal map ovviamente possiamo impostarla direttamente, è solo che non ne ho trovata una adatta per questa texture. La normal map texture viene generata attraverso il metodo makeNormalMapTexture del VideoDriver. Il secondo parametro indica l'altezza della height map (cioè tra il punto più scuro e il punto più chiaro della mappa c'è un salto di 9 punti). Se aumentate il valore, la mappa avrà un aspetto più roccioso, con più scanalatura.
La Normal Map e la displacement map/height map nel canale alpha (credo ci sia un errore di formattazione nel tutorial ndt)
Ma impostare i colori e la normal map non è tutto. Il materiale che stiamo usando richiede altre informazioni per ogni singolo vertex come le tangenti e le binormali. E visto che siamo troppo pigri per calcolarcele, lasciamo che sia Irrlicht a farlo per noi. Ecco perché andiamo a richiamare IMeshManipulator::createMeshWithTangents(). Che crea una copia della nostra mesh con le tangenti e le binormali a partire dalla nostra mesh (la prima e unica di indice 0 ndt). Fatto questo semplicemente creiamo un nodo di scena di tipo mesh standard con questa mesh appena copiata, impostiamo il colore e la normal map e aggiustiamo altri settaggi del materiale. Notare che anche questa volta andiamo ad attivare il flag EMF_FOG_ENABLE per rendere la mesh sensibili rispetto alla nebbia.
Dopo che abbiamo creato la nostra stanza con l'effetto di illuminazione per pixel da shader, andiamo a creare una sfera con lo stesso materiale ma stavolta lo renderemo trasparente. Inoltre, visto che la sfera ci ricorda qualcosa di un pianeta a noi familiare, la facciamo ruotare. La procedura è simile a quella già vista. La differenza è che stavolta carichiamo una mesh da un file .x che già contiene una mappatura dei colori che quindi non necessita di essere impostata manualmente. Ma la sfera è un po' piccola per le nostre necessità, quindi la scaliamo di un fattore 50.
I materiali per l'illuminazione dei pixel ovviamente mostrano il meglio di se quando ci sono in giro luce dinamiche che si muovono. Quindi aggiungiamone un pò. E siccome le luci in movimento non sono granché da sole, allora ci aggiungiamo una bella billboard (uno sprite 3d) ed un intero sistema particellare che ne segue una. Cominciamo con la prima luce che sarà rossa e avrà soltanto una billboard attaccata.
Ora facciamo lo stesso con la seconda luce. La differenza è che stavolta ci attacchiamo anche un sistema particellare. E siccome la luce si muove anche le particelle si muoveranno con essa. Se volete sapere di più sui sistemi particellari date un'occhiata al tutorial specialFx. Avrete notato che non abbiamo aggiunto più di due luci, la ragione è semplice: lo shader di questi materiali è stato scritto con una versione piuttosto bassa la ps1.1 e la vs1.1, che non consentono l'uso di più di due luci alla volta. Potreste comunque aggiungere una terza luce alla scena ma questa non verrebbe utilizzata dallo shader del muro comunque. Naturalmente questo cambierà non appena in futuro le nuove versioni di Irrlicht avranno pixel/vertex shader più moderni e potenti (ad oggi con la versione Irrlicht 1.8.1 sono supportati gli shader HLSL fino alle DX11 tramite un'estensione ad hoc ed la OpenGL GLSL 4.0 mentre gli shader per DX8 sono stati dismessi ndt).
Finalmente disegniamo e questo è tutto.
1 ndt (nota del traduttore)
Versione in pdf scaricabile da QUI
In questo tutorial mostreremo l'utilizzo di uno dei materiali shader più complessi e preimpostati in Irrlicht: Il Per pixel lighted surfaces (illuminazione delle superfici a livello di pixel) utilizzando le normal maps e il parallax mapping (non traduco perché ormai sono di uso comune ndt1). Mostreremo anche l'ultilizzo dell'effetto nebbia e come creare sistemi particellari in movimento. E comunque niente panico: non vi serve alcuna esperienza (che comunque non fa mai male ndt) con la programmazione degli shaders per usare i materiali in Irrlicht.
Come prima cosa includiamo l'header e le solite cose necessarie come abbiamo fatto fino ad ora in tutti i precedenti tutorials.
Codice PHP:
#include <irrlicht.h>
#include "driverChoice.h"
using namespace irr;
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif
Codice PHP:
class MyEventReceiver : public IEventReceiver
{
public:
MyEventReceiver(scene::ISceneNode* room,scene::ISceneNode* earth,
gui::IGUIEnvironment* env, video::IVideoDriver* driver)
{
// store pointer to room so we can change its drawing mode
Room = room;
Earth = earth;
Driver = driver;
// set a nicer font
gui::IGUISkin* skin = env->getSkin();
gui::IGUIFont* font = env->getFont("../../media/fonthaettenschweiler.bmp");
if (font)
skin->setFont(font);
// add window and listbox
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);
// create problem text
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));
// set start material (prefer parallax mapping if available)
video::IMaterialRenderer* renderer =
Driver->getMaterialRenderer(video::EMT_PARALLAX_MAP_SOLID);
if (renderer && renderer->getRenderCapability() == 0)
ListBox->setSelected(2);
// set the material which is selected in the listbox
setMaterial();
}
bool OnEvent(const SEvent& event)
{
// check if user presses the key 'E' or 'R'
if (event.EventType == irr::EET_KEY_INPUT_EVENT &&
!event.KeyInput.PressedDown && Room && ListBox)
{
// change selected item in listbox
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);
// set the material which is selected in the listbox
setMaterial();
}
return false;
}
private:
// sets the material of the room mesh the the one set in the
// list box.
void setMaterial()
{
video::E_MATERIAL_TYPE type = video::EMT_SOLID;
// change material setting
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 material setting
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);
Codice PHP:
video::IMaterialRenderer* renderer = Driver->getMaterialRenderer(type);
// display some problem text when problem
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;
};
Codice PHP:
int main()
{
// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
// create device
IrrlichtDevice* device = createDevice(driverType,
core::dimension2d<u32>(640, 480));
if (device == 0)
return 1; // could not create selected driver.
Codice PHP:
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
gui::IGUIEnvironment* env = device->getGUIEnvironment();
driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, true);
// add irrlicht logo
env->addImage(driver->getTexture("../../media/irrlichtlogo3.png"),
core::position2d<s32>(10,10));
// add camera
scene::ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS();
camera->setPosition(core::vector3df(-200,200,-200));
// disable mouse cursor
device->getCursorControl()->setVisible(false);
Codice PHP:
driver->setFog(video::SColor(0,138,125,81), video::EFT_FOG_LINEAR, 250, 1000, .003f, true, false);
Codice PHP:
scene::IAnimatedMesh* roomMesh = smgr->getMesh("../../media/room.3ds");
scene::ISceneNode* room = 0;
scene::ISceneNode* earth = 0;
if (roomMesh)
{
// The Room mesh doesn't have proper Texture Mapping on the
// floor, so we can recreate them on runtime
smgr->getMeshManipulator()->makePlanarTextureMapping(
roomMesh->getMesh(0), 0.003f);
Codice PHP:
video::ITexture* normalMap =
driver->getTexture("../../media/rockwall_height.bmp");
if (normalMap)
driver->makeNormalMapTexture(normalMap, 9.0f);
Codice PHP:
video::ITexture* normalMap = driver->getTexture(“../../media/rockwall_NRM.tga”);
Codice PHP:
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);
// Stones don't glitter..
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);
// adjust height for parallax effect
room->getMaterial(0).MaterialTypeParam = 1.f / 64.f;
// drop mesh because we created it with a create.. call.
tangentMesh->drop();
}
Codice PHP:
// add earth sphere
scene::IAnimatedMesh* earthMesh = smgr->getMesh("../../media/earth.x");
if (earthMesh)
{
//perform various task with the mesh manipulator
scene::IMeshManipulator *manipulator = smgr->getMeshManipulator();
// create mesh copy with tangent informations from original earth.x mesh
scene::IMesh* tangentSphereMesh =
manipulator->createMeshWithTangents(earthMesh->getMesh(0));
// set the alpha value of all vertices to 200
manipulator->setVertexColorAlpha(tangentSphereMesh, 200);
// scale the mesh by factor 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));
// load heightmap, create normal map from it and set it
video::ITexture* earthNormalMap = driver->getTexture("../../media/earthbump.jpg");
if (earthNormalMap)
{
driver->makeNormalMapTexture(earthNormalMap, 20.0f);
earth->setMaterialTexture(1, earthNormalMap);
earth->setMaterialType(video::EMT_NORMAL_MAP_TRANSPARENT_VERTEX_ALPHA);
}
// adjust material settings
earth->setMaterialFlag(video::EMF_FOG_ENABLE, true);
// add rotation animator
scene::ISceneNodeAnimator* anim =
smgr->createRotationAnimator(core::vector3df(0,0.1f,0));
earth->addAnimator(anim);
anim->drop();
// drop mesh because we created it with a create.. call.
tangentSphereMesh->drop();
}
Codice PHP:
// add light 1 (more green)
scene::ILightSceneNode* light1 =
smgr->addLightSceneNode(0, core::vector3df(0,0,0),
video::SColorf(0.5f, 1.0f, 0.5f, 0.0f), 800.0f);
light1->setDebugDataVisible ( scene::EDS_BBOX );
// add fly circle animator to light 1
scene::ISceneNodeAnimator* anim =
smgr->createFlyCircleAnimator (core::vector3df(50,300,0),190.0f, -0.003f);
light1->addAnimator(anim);
anim->drop();
// attach billboard to the light
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"));
Codice PHP:
// add light 2 (red)
scene::ISceneNode* light2 =
smgr->addLightSceneNode(0, core::vector3df(0,0,0),
video::SColorf(1.0f, 0.2f, 0.2f, 0.0f), 800.0f);
// add fly circle animator to light 2
anim = smgr->createFlyCircleAnimator(core::vector3df(0,150,0), 200.0f,
0.001f, core::vector3df(0.2f, 0.9f, 0.f));
light2->addAnimator(anim);
anim->drop();
// attach billboard to light
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"));
// add particle system
scene::IParticleSystemSceneNode* ps =
smgr->addParticleSystemSceneNode(false, light2);
// create and set emitter
scene::IParticleEmitter* em = ps->createBoxEmitter(
core::aabbox3d<f32>(-3,0,-3,3,1,3),
core::vector3df(0.0f,0.03f,0.0f),
80,100,
video::SColor(10,255,255,255), video::SColor(10,255,255,255),
400,1100);
em->setMinStartSize(core::dimension2d<f32>(30.0f, 40.0f));
em->setMaxStartSize(core::dimension2d<f32>(30.0f, 40.0f));
ps->setEmitter(em);
em->drop();
// create and set affector
scene::IParticleAffector* paf = ps->createFadeOutParticleAffector();
ps->addAffector(paf);
paf->drop();
// adjust some material settings
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);
Codice PHP:
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;
}
Versione in pdf scaricabile da QUI