21-08-2015, 09:05 PM
Tutorial 20: Managed Lights
Scritto da Colin MacDonald. Questo tutorial spiega l'uso del Light Manager di Irrlicht (gestore delle luci). Permette l'uso di più luci dinamiche rispetto a quante ne possa gestire l'hardware Non tratteremo per semplicità le ulteriori applicazioni per il Light Manager, come i nodi di scena per le callbacks.
Normalmente abbiamo un limite di 8 luci dinamiche per scena: si tratta di un limite hardware. Se però volessimo usare più luci dinamiche nella scena possiamo farlo registrando un light manager pozionale che ci permette di spegnere e accendere le luci in determinati punti durante il rendering. Così facendo il limite delle 8 luci rimarrà sempre ma sarà superato per ciascuna scena.
Questa cosa è completamente opzionale: se non creassimo questo light manager, di default Irrlicht userebbe comunque uno schema basato sulla distanza che attiverebbe le luci hardware dando priorità a quelle più vicine.
Con NO_MANAGEMENT disabilitiamo il light manager per far si chee Irrlicht utilizzi la sua modalità di default per la gestione delle luci. Le 8 luci più vicine alla camera verrebbero accese, mentre le restanti verrebbero spente. Questo, nel nostro esempio, produrrà un comportamento curioso ma comunque incoerente.
LIGHTS_NEAREST_NODE mostra una implementazione dove si spengono solo un limitato numero di luci per nodo mesh di scena. Accende le 3 luci più vicine alla camera per il rendering ma spenge le restanti. La cosa funziona ma siccome lavoriamo su ogni singola luce, in caso di molte luci non scala di conseguenza. Il flickering (sfarfallio) che si può vedere è causato dal continuo scambio di luci che is accendono/spengono in base alla loro posizione rispetto ai cubi (si tratta di una dimostrazione voluta appositamente per dimostrare i limiti di questa tecnica).
LIGHTS_IN_ZONE mostra una tecnica basata su 'zone' intere. Ogni nodo di scena vuoto viene considerato come parente di una zona. Quando i nodi sono renderizzati, tutte le luci vengono spente, viene trovata la zona con cui è imparentata la nostra scena e vengono accese le luci che vi si trovano all'interno, e tutti i rispettivi discendenti nel grafo della scena. Questo genera un effetto più reale nella illuminazione di ciascun cubo del nostro esempio. Analogamente possiamo usare una simile per illuminare tutte le mesh che (ad esempio) si trovano in una stanza, senza considerare le luci che si trovano in altre stanze.
Nel nostro light manager gestiamo anche l'event receiver; non è necessario né raccomandato per una applicazione reale, è solo per semplificare l'esempio.
Aggiungiamo altre 'zone'. Questa tecnica la possiamo usare per accendere singole stanze ad esempio.
Versione pdf scaricabile da QUI
Scritto da Colin MacDonald. Questo tutorial spiega l'uso del Light Manager di Irrlicht (gestore delle luci). Permette l'uso di più luci dinamiche rispetto a quante ne possa gestire l'hardware Non tratteremo per semplicità le ulteriori applicazioni per il Light Manager, come i nodi di scena per le callbacks.
Codice PHP:
#include <irrlicht.h>
#include "driverChoice.h"
using namespace irr;
using namespace core;
#if defined(_MSC_VER)
#pragma comment(lib, "Irrlicht.lib")
#endif // MSC_VER
Questa cosa è completamente opzionale: se non creassimo questo light manager, di default Irrlicht userebbe comunque uno schema basato sulla distanza che attiverebbe le luci hardware dando priorità a quelle più vicine.
Con NO_MANAGEMENT disabilitiamo il light manager per far si chee Irrlicht utilizzi la sua modalità di default per la gestione delle luci. Le 8 luci più vicine alla camera verrebbero accese, mentre le restanti verrebbero spente. Questo, nel nostro esempio, produrrà un comportamento curioso ma comunque incoerente.
LIGHTS_NEAREST_NODE mostra una implementazione dove si spengono solo un limitato numero di luci per nodo mesh di scena. Accende le 3 luci più vicine alla camera per il rendering ma spenge le restanti. La cosa funziona ma siccome lavoriamo su ogni singola luce, in caso di molte luci non scala di conseguenza. Il flickering (sfarfallio) che si può vedere è causato dal continuo scambio di luci che is accendono/spengono in base alla loro posizione rispetto ai cubi (si tratta di una dimostrazione voluta appositamente per dimostrare i limiti di questa tecnica).
LIGHTS_IN_ZONE mostra una tecnica basata su 'zone' intere. Ogni nodo di scena vuoto viene considerato come parente di una zona. Quando i nodi sono renderizzati, tutte le luci vengono spente, viene trovata la zona con cui è imparentata la nostra scena e vengono accese le luci che vi si trovano all'interno, e tutti i rispettivi discendenti nel grafo della scena. Questo genera un effetto più reale nella illuminazione di ciascun cubo del nostro esempio. Analogamente possiamo usare una simile per illuminare tutte le mesh che (ad esempio) si trovano in una stanza, senza considerare le luci che si trovano in altre stanze.
Nel nostro light manager gestiamo anche l'event receiver; non è necessario né raccomandato per una applicazione reale, è solo per semplificare l'esempio.
Codice PHP:
class CMyLightManager : public scene::ILightManager, public IEventReceiver
{
typedef enum
{
NO_MANAGEMENT,
LIGHTS_NEAREST_NODE,
LIGHTS_IN_ZONE
}
LightManagementMode;
LightManagementMode Mode;
LightManagementMode RequestedMode;
// Con questi dati otteniamo tutte le informazioni del light manager che
// stiamo usando.
scene::ISceneManager * SceneManager;
core::array<scene::ISceneNode*> * SceneLightList;
scene::E_SCENE_NODE_RENDER_PASS CurrentRenderPass;
scene::ISceneNode * CurrentSceneNode;
public:
CMyLightManager(scene::ISceneManager* sceneManager)
: Mode(NO_MANAGEMENT), RequestedMode(NO_MANAGEMENT),
SceneManager(sceneManager), SceneLightList(0),
CurrentRenderPass(scene::ESNRP_NONE), CurrentSceneNode(0)
{ }
// Il gestore dell'input utente per cambiare le strategie del light management
bool OnEvent(const SEvent & event)
{
bool handled = false;
if (event.EventType == irr::EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown)
{
handled = true;
switch(event.KeyInput.Key)
{
case irr::KEY_KEY_1:
RequestedMode = NO_MANAGEMENT;
break;
case irr::KEY_KEY_2:
RequestedMode = LIGHTS_NEAREST_NODE;
break;
case irr::KEY_KEY_3:
RequestedMode = LIGHTS_IN_ZONE;
break;
default:
handled = false;
break;
}
if(NO_MANAGEMENT == RequestedMode)
SceneManager->setLightManager(0); // Show that it's safe to register the light manager
else
SceneManager->setLightManager(this);
}
return handled;
}
// Viene richiamato prima che il primo nodo della scena venga renderizzato.
virtual void OnPreRender(core::array<scene::ISceneNode*> & lightList)
{
// Aggiorniamo la modalità da applicare al renderer
Mode = RequestedMode;
// Salviamo una lista delle luci modificabile prima del OnPostRender().
SceneLightList = &lightList;
}
// Richiamato dopo che l'ultimo nodo di scena è stato renderizzato.
virtual void OnPostRender()
{
// Finché spengiamo le luci con con ilg estore di eventi, giriamo lungo
// tutte le luci per assicurarci che siano tutte gestite. Normalmente non
// occorrerebbe farlo se usassimo un light manager, ora però lo facciamo da
// noi.
for (u32 i = 0; i < SceneLightList->size(); i++)
(*SceneLightList)[i]->setVisible(true);
}
virtual void OnRenderPassPreRender(scene::E_SCENE_NODE_RENDER_PASS renderPass)
{
// Non c'è da fare nulla qua tranne ricordarmi quale render ho passato.
CurrentRenderPass = renderPass;
}
virtual void OnRenderPassPostRender(scene::E_SCENE_NODE_RENDER_PASS renderPass)
{
// Illumino solo i corpi solidi, spengo le luci rimanenti.
if (scene::ESNRP_SOLID == renderPass)
{
for (u32 i = 0; i < SceneLightList->size(); ++i)
(*SceneLightList)[i]->setVisible(false);
}
}
// Lo richiamiamo prima che la scena venga renderizzata
virtual void OnNodePreRender(scene::ISceneNode* node)
{
CurrentSceneNode = node;
// Questo light manager considera solo oggetti solidi, ma possiamo
// cambiarlo in base alle nostre necessità.
if (scene::ESNRP_SOLID != CurrentRenderPass)
return;
// Infatti in questo esempio: voglio considerare solo i cubi della scena
// Vogliamo l'illuminazione per (almeno) le i nodi mesh animati e non
// presenti nella scena.
if (node->getType() != scene::ESNT_CUBE)
return;
if (LIGHTS_NEAREST_NODE == Mode)
{
// E' una implementazione un po' ingenua che privilegia tutte le luci
// presenti nella scena in base alla vicinanza al nodo da illuminare.
// Questo produce uno sfarfallio quando le luci orbitano vicino ai
// cubi che sono vicini alle 'zone' da illuminare.
const vector3df nodePosition = node->getAbsolutePosition();
// Ordino le luci in base alla loro distanza dal nodo che vado a
// renderizzare.
array<LightDistanceElement> sortingArray;
sortingArray.reallocate(SceneLightList->size());
u32 i;
for(i = 0; i < SceneLightList->size(); ++i)
{
scene::ISceneNode* lightNode = (*SceneLightList)[i];
const f64 distance = lightNode->getAbsolutePosition().getDistanceFromSQ(nodePosition);
sortingArray.push_back(LightDistanceElement(lightNode, distance));
}
sortingArray.sort();
// La lista ora è ordinata per vicinanza al nodo.
// Accendo le tre luci più vicine e spengo le altre.
for(i = 0; i < sortingArray.size(); ++i)
sortingArray[i].node->setVisible(i < 3);
}
else if(LIGHTS_IN_ZONE == Mode)
{
// Usiamo un nodo di scena vuoto per rappresentare la 'zona'. Per
// ciascuna mesh solida, spengo le luci, trovo la 'zona' parente e
// accendo le luci che si trovano nel nodo di quella scena.
// Questo è un sistema generico che non necessita di una particolare
// conoscenza di come è organizzato il grafo delle scena.
for (u32 i = 0; i < SceneLightList->size(); ++i)
{
if ((*SceneLightList)[i]->getType() != scene::ESNT_LIGHT)
continue;
scene::ILightSceneNode* lightNode = static_cast<scene::ILightSceneNode*>((*SceneLightList)[i]);
video::SLight & lightData = lightNode->getLightData();
if (video::ELT_DIRECTIONAL != lightData.Type)
lightNode->setVisible(false);
}
scene::ISceneNode * parentZone = findZone(node);
if (parentZone)
turnOnZoneLights(parentZone);
}
}
// Chiamato dopo che il nodo specidifico è stato renderizzato
virtual void OnNodePostRender(scene::ISceneNode* node)
{
// Non serve altro light management dopo il rendering dei singoli nodi.
}
private:
// Trovo la il nodo di scena vuoto che è parente del nodo specificato
scene::ISceneNode * findZone(scene::ISceneNode * node)
{
if (!node)
return 0;
if (node->getType() == scene::ESNT_EMPTY)
return node;
return findZone(node->getParent());
}
// Accendo tutte le luci figlie (dirette o indirette) del nodo specificato.
void turnOnZoneLights(scene::ISceneNode * node)
{
core::list<scene::ISceneNode*> const & children = node->getChildren();
for (core::list<scene::ISceneNode*>::ConstIterator child = children.begin();
child != children.end(); ++child)
{
if ((*child)->getType() == scene::ESNT_LIGHT)
(*child)->setVisible(true);
else // Si assume che le luci non abbiano figli che sono luci
turnOnZoneLights(*child);
}
}
// Una classe utility per ordinare i nodi in base ad una distanza
class LightDistanceElement
{
public:
LightDistanceElement() {};
LightDistanceElement(scene::ISceneNode* n, f64 d)
: node(n), distance(d) { }
scene::ISceneNode* node;
f64 distance;
// Gli elementi con distanza minore sono ordinati in cima all'array
bool operator < (const LightDistanceElement& other) const
{
return (distance < other.distance);
}
};
};
int main(int argumentCount, char * argumentValues[])
{
// richiesta utente del driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
IrrlichtDevice *device = createDevice(driverType,
dimension2d<u32>(640, 480), 32);
if(!device)
return -1;
f32 const lightRadius = 60.f; // Enough to reach the far side of each 'zone'
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
gui::IGUIEnvironment* guienv = device->getGUIEnvironment();
gui::IGUISkin* skin = guienv->getSkin();
if (skin)
{
skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255, 255, 255, 255));
gui::IGUIFont* font = guienv->getFont("../../media/fontlucida.png");
if(font)
skin->setFont(font);
}
guienv->addStaticText(L"1 - No light management", core::rect<s32>(10,10,200,30));
guienv->addStaticText(L"2 - Closest 3 lights", core::rect<s32>(10,30,200,50));
guienv->addStaticText(L"3 - Lights in zone", core::rect<s32>(10,50,200,70));
Codice PHP:
for(f32 zoneX = -100.f; zoneX <= 100.f; zoneX += 50.f)
for(f32 zoneY = -60.f; zoneY <= 60.f; zoneY += 60.f)
{
// Per fare la zona uso un nodo di scena vuoto.
scene::ISceneNode * zoneRoot = smgr->addEmptySceneNode();
zoneRoot->setPosition(vector3df(zoneX, zoneY, 0));
// Ogni zona contiene un cubo rotante
scene::IMeshSceneNode * node = smgr->addCubeSceneNode(15, zoneRoot);
scene::ISceneNodeAnimator * rotation = smgr->createRotationAnimator(vector3df(0.25f, 0.5f, 0.75f));
node->addAnimator(rotation);
rotation->drop();
// Ogni cubo ha tre luci agganciate. Le luci sono agganciate alla
// billboard così da vedere dove sono. Le billboards sono agganciate
// ai cubi, così luci e cubi sono discendenti dallo stesso nodo.
scene::IBillboardSceneNode * billboard = smgr->addBillboardSceneNode(node);
billboard->setPosition(vector3df(0, -14, 30));
billboard->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR );
billboard->setMaterialTexture(0, driver->getTexture("../../media/particle.bmp"));
billboard->setMaterialFlag(video::EMF_LIGHTING, false);
smgr->addLightSceneNode(billboard, vector3df(0, 0, 0), video::SColorf(1, 0, 0), lightRadius);
billboard = smgr->addBillboardSceneNode(node);
billboard->setPosition(vector3df(-21, -14, -21));
billboard->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR );
billboard->setMaterialTexture(0, driver->getTexture("../../media/particle.bmp"));
billboard->setMaterialFlag(video::EMF_LIGHTING, false);
smgr->addLightSceneNode(billboard, vector3df(0, 0, 0), video::SColorf(0, 1, 0), lightRadius);
billboard = smgr->addBillboardSceneNode(node);
billboard->setPosition(vector3df(21, -14, -21));
billboard->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR );
billboard->setMaterialTexture(0, driver->getTexture("../../media/particle.bmp"));
billboard->setMaterialFlag(video::EMF_LIGHTING, false);
smgr->addLightSceneNode(billboard, vector3df(0, 0, 0), video::SColorf(0, 0, 1), lightRadius);
// Ogni cubo ha un cubetto orbitante in modo da notare che sono
// illuminati anche dalle luci circostanti, non solo luci figlie.
node = smgr->addCubeSceneNode(5, node);
node->setPosition(vector3df(0, 21, 0));
}
smgr->addCameraSceneNode(0, vector3df(0,0,-130), vector3df(0,0,0));
CMyLightManager * myLightManager = new CMyLightManager(smgr);
smgr->setLightManager(0); // Avviamo il light management solo quando vogliamo.
device->setEventReceiver(myLightManager);
int lastFps = -1;
while(device->run())
{
driver->beginScene(true, true, video::SColor(255,100,101,140));
smgr->drawAll();
guienv->drawAll();
driver->endScene();
int fps = driver->getFPS();
if(fps != lastFps)
{
lastFps = fps;
core::stringw str = L"Managed Lights [";
str += driver->getName();
str += "] FPS:";
str += fps;
device->setWindowCaption(str.c_str());
}
}
myLightManager->drop(); // Cancello il riferimento esplicito ogni volta
device->drop();
return 0;
}
Versione pdf scaricabile da QUI