Creare Videogiochi - Game Developer
Tutorial 11 - Per-Pixel Lighting - Versione stampabile

+- Creare Videogiochi - Game Developer (https://www.making-videogames.net/giochi)
+-- Forum: Altri Programmi per la Creazione di Videogames (https://www.making-videogames.net/giochi/Forum-Altri-Programmi-per-la-Creazione-di-Videogames)
+--- Forum: Irrlicht Engine (https://www.making-videogames.net/giochi/Forum-Irrlicht-Engine)
+--- Discussione: Tutorial 11 - Per-Pixel Lighting (/thread-Tutorial-11-Per-Pixel-Lighting)



Tutorial 11 - Per-Pixel Lighting - Chip - 17-08-2015

Tutorial 11: Per-Pixel Lighting

[Immagine: 011shot.jpg]

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 
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).
Codice PHP:
class MyEventReceiver : public IEventReceiver
{
public:

    
MyEventReceiver(scene::ISceneNoderoom,scene::ISceneNodeearth,
        
gui::IGUIEnvironmentenvvideo::IVideoDriverdriver)
    {
        
// store pointer to room so we can change its drawing mode
        
Room room;
        
Earth earth;
        
Driver driver;

        
// set a nicer font
        
gui::IGUISkinskin env->getSkin();
        
gui::IGUIFontfont env->getFont("../../media/fonthaettenschweiler.bmp");
        if (
font)
            
skin->setFont(font);

        
// add window and listbox
        
gui::IGUIWindowwindow env->addWindow(
            
core::rect<s32>(460,375,630,470), falseL"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::IMaterialRendererrenderer =
            
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 SEventevent)
    {
        
// 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 2sel 0;
            if (
sel 0sel 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 
0type video::EMT_SOLID;
            break;
        case 
1type video::EMT_NORMAL_MAP_SOLID;
            break;
        case 
2type video::EMT_PARALLAX_MAP_SOLID;
            break;
        }

        
Room->setMaterialType(type);

        
// change material setting
        
switch(ListBox->getSelected())
        {
        case 
0type video::EMT_TRANSPARENT_VERTEX_ALPHA;
            break;
        case 
1type video::EMT_NORMAL_MAP_TRANSPARENT_VERTEX_ALPHA;
            break;
        case 
2type video::EMT_PARALLAX_MAP_TRANSPARENT_VERTEX_ALPHA;
            break;
        }

        
Earth->setMaterialType(type); 
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.
Codice PHP:
video::IMaterialRendererrenderer Driver->getMaterialRenderer(type);

        
// display some problem text when problem
        
if (!renderer || renderer->getRenderCapability() != 0)
            
ProblemText->setVisible(true);
        else
            
ProblemText->setVisible(false);
    }

private:

    
gui::IGUIStaticTextProblemText;
    
gui::IGUIListBoxListBox;

    
scene::ISceneNodeRoom;
    
scene::ISceneNodeEarth;
    
video::IVideoDriverDriver;
}; 
Ora arriva il divertimento. Creiamo il device di Irrlicht Device ed iniziamo il rendering.
Codice PHP:
int main()
{
    
// ask user for driver
    
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
    if (
driverType==video::EDT_COUNT)
        return 
1;

    
// create device
    
IrrlichtDevicedevice createDevice(driverType,
            
core::dimension2d<u32>(640480));

    if (
device == 0)
        return 
1// could not create selected driver. 
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.
Codice PHP:
video::IVideoDriverdriver device->getVideoDriver();
    
scene::ISceneManagersmgr device->getSceneManager();
    
gui::IGUIEnvironmentenv device->getGUIEnvironment();

    
driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BITtrue);
    
// add irrlicht logo
    
env->addImage(driver->getTexture("../../media/irrlichtlogo3.png"),
        
core::position2d<s32>(10,10));
    
// add camera
    
scene::ICameraSceneNodecamera smgr->addCameraSceneNodeFPS();
    
camera->setPosition(core::vector3df(-200,200,-200));

    
// disable mouse cursor
    
device->getCursorControl()->setVisible(false); 
Poiché vogliamo che la scena sembri un po' più spaventosa aggiungiamo un po' di nebbia. Questo si fa richiamando IVideoDriver:ConfusedetFog(). 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.
Codice PHP:
driver->setFog(video::SColor(0,138,125,81), video::EFT_FOG_LINEAR2501000.003ftruefalse); 
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().
Codice PHP:
scene::IAnimatedMeshroomMesh smgr->getMesh("../../media/room.3ds");
    
scene::ISceneNoderoom 0;
    
scene::ISceneNodeearth 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); 
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.
Codice PHP:
video::ITexturenormalMap =
            
driver->getTexture("../../media/rockwall_height.bmp");

        if (
normalMap)
            
driver->makeNormalMapTexture(normalMap9.0f); 
La Normal Map e la displacement map/height map nel canale alpha (credo ci sia un errore di formattazione nel tutorial ndt)
Codice PHP:
video::ITexturenormalMap driver->getTexture(../../media/rockwall_NRM.tga”); 
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.
Codice PHP:
scene::IMeshtangentMesh smgr->getMeshManipulator()->
                
createMeshWithTangents(roomMesh->getMesh(0));

        
room smgr->addMeshSceneNode(tangentMesh);
        
room->setMaterialTexture(0,
                
driver->getTexture("../../media/rockwall.jpg"));
        
room->setMaterialTexture(1normalMap);

        
// Stones don't glitter..
        
room->getMaterial(0).SpecularColor.set(0,0,0,0);
        
room->getMaterial(0).Shininess 0.f;

        
room->setMaterialFlag(video::EMF_FOG_ENABLEtrue);
        
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();
    } 
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.
Codice PHP:
// add earth sphere

    
scene::IAnimatedMeshearthMesh 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::IMeshtangentSphereMesh =
            
manipulator->createMeshWithTangents(earthMesh->getMesh(0));

        
// set the alpha value of all vertices to 200
        
manipulator->setVertexColorAlpha(tangentSphereMesh200);

        
// scale the mesh by factor 50
        
core::matrix4 m;
        
m.setScale core::vector3df(50,50,50) );
        
manipulator->transformtangentSphereMesh);

        
earth smgr->addMeshSceneNode(tangentSphereMesh);

        
earth->setPosition(core::vector3df(-70,130,45));

        
// load heightmap, create normal map from it and set it
        
video::ITextureearthNormalMap driver->getTexture("../../media/earthbump.jpg");
        if (
earthNormalMap)
        {
            
driver->makeNormalMapTexture(earthNormalMap20.0f);
            
earth->setMaterialTexture(1earthNormalMap);
            
earth->setMaterialType(video::EMT_NORMAL_MAP_TRANSPARENT_VERTEX_ALPHA);
        }
        
// adjust material settings
        
earth->setMaterialFlag(video::EMF_FOG_ENABLEtrue);
        
// add rotation animator
        
scene::ISceneNodeAnimatoranim =
            
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();
    } 
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.
Codice PHP:
// add light 1 (more green)
    
scene::ILightSceneNodelight1 =
        
smgr->addLightSceneNode(0core::vector3df(0,0,0),
        
video::SColorf(0.5f1.0f0.5f0.0f), 800.0f);

    
light1->setDebugDataVisible scene::EDS_BBOX );

    
// add fly circle animator to light 1
    
scene::ISceneNodeAnimatoranim =
        
smgr->createFlyCircleAnimator (core::vector3df(50,300,0),190.0f, -0.003f);
    
light1->addAnimator(anim);
    
anim->drop();

    
// attach billboard to the light
    
scene::IBillboardSceneNodebill =
        
smgr->addBillboardSceneNode(light1core::dimension2d<f32>(6060));

    
bill->setMaterialFlag(video::EMF_LIGHTINGfalse);
    
bill->setMaterialFlag(video::EMF_ZWRITE_ENABLEfalse);
    
bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
    
bill->setMaterialTexture(0driver->getTexture("../../media/particlegreen.jpg")); 
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).
Codice PHP:
// add light 2 (red)
    
scene::ISceneNodelight2 =
        
smgr->addLightSceneNode(0core::vector3df(0,0,0),
        
video::SColorf(1.0f0.2f0.2f0.0f), 800.0f);

    
// add fly circle animator to light 2
    
anim smgr->createFlyCircleAnimator(core::vector3df(0,150,0), 200.0f,
            
0.001fcore::vector3df(0.2f0.9f0.f));
    
light2->addAnimator(anim);
    
anim->drop();

    
// attach billboard to light
    
bill smgr->addBillboardSceneNode(light2core::dimension2d<f32>(120120));
    
bill->setMaterialFlag(video::EMF_LIGHTINGfalse);
    
bill->setMaterialFlag(video::EMF_ZWRITE_ENABLEfalse);
    
bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
    
bill->setMaterialTexture(0driver->getTexture("../../media/particlered.bmp"));

    
// add particle system
    
scene::IParticleSystemSceneNodeps =
        
smgr->addParticleSystemSceneNode(falselight2);

    
// create and set emitter
    
scene::IParticleEmitterem 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.0f40.0f));
    
em->setMaxStartSize(core::dimension2d<f32>(30.0f40.0f));

    
ps->setEmitter(em);
    
em->drop();

    
// create and set affector
    
scene::IParticleAffectorpaf ps->createFadeOutParticleAffector();
    
ps->addAffector(paf);
    
paf->drop();

    
// adjust some material settings
    
ps->setMaterialFlag(video::EMF_LIGHTINGfalse);
    
ps->setMaterialFlag(video::EMF_ZWRITE_ENABLEfalse);
    
ps->setMaterialTexture(0driver->getTexture("../../media/fireball.bmp"));
    
ps->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);

    
MyEventReceiver receiver(roomearthenvdriver);
    
device->setEventReceiver(&receiver); 
Finalmente disegniamo e questo è tutto.
Codice PHP:
int lastFPS = -1;

    while(
device->run())
    if (
device->isWindowActive())
    {
        
driver->beginScene(truetrue0);
        
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;

1 ndt (nota del traduttore)

Versione in pdf scaricabile da QUI