15-08-2015, 04:26 PM
Tutorial 8: SpecialFX
Questo tutorial descrive come creare effetti speciali. Vedremo l'uso delle ombre tramite lo stencil buffer, i sistemi particellari, le billboards, le luci dinamiche e il nodo di scena per creare le superfici d'acqua.
Cominciamo come già fatto in altri tutorials. Notiamo che stavolta imposteremo a true il flag 'shadows' nella chiamata a createDevice() questo perché vorremo che il modello stavolta proietti un'ombra dinamica. Se l'esempio dovesse girare troppo lentamente impostate a false il flag (con le schede video odierni non dovrebbero esserci problemi). L'Irrlicht Engine comunque controlla sempre che il vostro hardware supporti lo stencil buffer e disabiliterà le ombre dinamiche automaticamente.
Creiamo il nostro device e usciamo in caso di fallimento. Rendiamo la scelta dello stencil shadow opzionale in modo da girare comunque l'esempio con hardware più datati senza ombre.
Per il nostro ambiente carichiamo un file .3ds. E' una piccola stanza modellata con Anim8or ed esportata nel formato 3ds perché il formato .an8 non è riconosciuto dal Irrlicht Engine. Sono un pessimo grafico 3d e il texture mapping non è granché. Fortunatamente sono migliore come programmatore che come artista e Irrlicht Engine può creare dei texture mapping molto belli per noi: utilizziamo un mesh manipulator e creiamo un mapping texture planare per la mesh della stanza. Se volete vedere il mio mapping fatto con Anim8or, decommentate la linea e lo vedrete. Non so nemmeno come si impostano i materiali correttamente con Anim8or, c'è una luce speculare che proprio non mi piace. La toglieremo via codice.
Ora il primo effetto speciale: l'animazione dell'acqua. Funziona così: Il WaterSurfaceSceneNode prende come input una mesh e la fa oscillare come la superficie dell'acqua. Se poi le applichiamo un bel materiale come EMT_REFLECTION_2_LAYER, viene fuori un bel effetto. Questo lo facciamo con le righe di codice che seguono. Come mesh di input creiamo una mesh piana, senza alcuna incurvatura all'interno. Potevamo usare qualsiasi altra mesh, volendo potremmo usare anche la stessa stanza room.3ds (il che darebbe a tutta la scena un aspetto veramente strano).
Il secondo effetto speciale è molto semplice, scommetto che l'avete già visto in qualche demo del Irrlicht Engine: una billboard (una mesh quadrata, texturizzata e che punta sempre la camera) trasparente combinata con una luce dinamica. Semplicemente creiamo un nodo di scena di tipo luce, lo facciamo volare in tondo e per farlo sembrare più bello gli attacchiamo un nodo di tipo billboard.
Il prossimo effetto speciale è più interessante: un sistema particellare. Il sistema particellare in Irrlicht è abbastanza modulare ed estensibile ma anche molto facile da usare. Esiste un nodo di tipo sistema particellare nel quale si può piazzare un emettitore di particelle che farà partire particelle oppure no. Questi emettitori sono piuttosto flessibili e tipicamente hanno un sacco di parametri come direzione, intensità e colore della particella creata.
Ci sono diversi tipi di emettitori, per esempio c'è l'emettitore puntiforme che emette le particelle da un solo punto fisso. Se i tipi di emettitori nell'engine non fossero comunque abbastanza per voi, potete sempre crearne di vostri semplicemente derivando una vostra classe dalla classe interfaccia IParticleEmitter agganciandola poi al sistema particellare tramite setEmitter(). In questo esempio creeremo un emettitore a cubo (box), dove le particelle nasceranno casualmente da dentro la superficie del box. I parametri definiscono il box, la direzione delle particelle, il minimo e il massimo numero di particelle generate al secondo, il colore, il minimo e il massimo tempo di durata della particella.
Poiché con solo gli emettitori il sistema particellare sarebbe un po' noioso, possiamo utilizzare i modificatori che variano i parametri delle particelle come il loro movimento durante il loro volo. Questi modificatori possono essere aggiunti per simulare la gravità oppure il vento. Il modificatore di particelle che utilizzeremo, ad esempio, va a modificare il colore delle particelle: le farà scomparire lentamente con un “fade out”. Come per gli emettitori anche i modificatori possono essere creati e personalizzati da voi, basta semplicemente derivare la classe dall'interfaccia IParticleAffector ed aggiungerli con addAffector().
Dopo aver applicato un materiale adatto al sistema particellare avremo un bel fuoco da campo nella scena. Aggiustando il materiale, la texture o intervenendo sui parametri degli emettitori e dei modificatori si possono creare facilmente diversi effetti come fumo, pioggia, esplosioni, neve e così via.
Ora aggiungiamo un nodo per la luce volumetrica, che aggiunge una finta area luminescente nella scena. Come per le billboard e i sistemi particellari assegnamo una texture per l'effetto desiderato, stavolta utilizzeremo un texture animator per ricreare l'illusione di una area magicamente luminescente.
Come ultimo effetto speciale vogliamo un'ombra dinamica proiettata dal personaggio animato. Per farlo andiamo a caricare un modello animato da un file DirectX .x e lo piazziamo nella stanza. Per creare l'ombra semplicemente chiamiamo addShadowVolumeSceneNode(). Il colore delle ombre può essere impostato solo a livello globale e varrà per tutte le ombre presenti, chiamando il metodo ISceneManager:etShadowColor(). Voila, ecco la nostra ombra dinamica.
Siccome il modello è un po' troppo piccolo per la scena lo andiamo a scalare ingrandendolo con setScale(). Poiché è anche illuminato da una luce dinamica dobbiamo normalizzare le sue normali per rendere l'illuminazione corretta. Questo si rende sempre necessario ogni qual volta la scala di un modello illuminato dinamicamente non è più (1,1,1). Altrimenti sembrerà troppo scuro o troppo chiaro a causa della scalatura del modello e di conseguenza delle normali che servono per ll'illuminazione.
Finalmente possiamo andare a disegnare ogni cosa e questo è tutto.
Versione in pdf scaricabile da QUI
Questo tutorial descrive come creare effetti speciali. Vedremo l'uso delle ombre tramite lo stencil buffer, i sistemi particellari, le billboards, le luci dinamiche e il nodo di scena per creare le superfici d'acqua.
Cominciamo come già fatto in altri tutorials. Notiamo che stavolta imposteremo a true il flag 'shadows' nella chiamata a createDevice() questo perché vorremo che il modello stavolta proietti un'ombra dinamica. Se l'esempio dovesse girare troppo lentamente impostate a false il flag (con le schede video odierni non dovrebbero esserci problemi). L'Irrlicht Engine comunque controlla sempre che il vostro hardware supporti lo stencil buffer e disabiliterà le ombre dinamiche automaticamente.
Codice PHP:
#include <irrlicht.h>
#include <iostream>
#include "driverChoice.h"
using namespace irr;
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif
int main()
{
// ask if user would like shadows
char i;
printf("Please press 'y' if you want to use realtime shadows.\n");
std::cin >> i;
const bool shadows = (i == 'y');
// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
Codice PHP:
IrrlichtDevice *device =
createDevice(driverType, core::dimension2d<u32>(640, 480),
16, false, shadows);
if (device == 0)
return 1; // could not create selected driver.
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
Codice PHP:
scene::IAnimatedMesh* mesh = smgr->getMesh("../../media/room.3ds");
smgr->getMeshManipulator()->makePlanarTextureMapping(mesh->getMesh(0), 0.004f);
scene::ISceneNode* node = 0;
node = smgr->addAnimatedMeshSceneNode(mesh);
node->setMaterialTexture(0, driver->getTexture("../../media/wall.jpg"));
node->getMaterial(0).SpecularColor.set(0,0,0,0);
Codice PHP:
mesh = smgr->addHillPlaneMesh( "myHill",
core::dimension2d<f32>(20,20),
core::dimension2d<u32>(40,40), 0, 0,
core::dimension2d<f32>(0,0),
core::dimension2d<f32>(10,10));
node = smgr->addWaterSurfaceSceneNode(mesh->getMesh(0), 3.0f, 300.0f, 30.0f);
node->setPosition(core::vector3df(0,7,0));
node->setMaterialTexture(0, driver->getTexture("../../media/stones.jpg"));
node->setMaterialTexture(1, driver->getTexture("../../media/water.jpg"));
node->setMaterialType(video::EMT_REFLECTION_2_LAYER);
Codice PHP:
// create light
node = smgr->addLightSceneNode(0, core::vector3df(0,0,0),
video::SColorf(1.0f, 0.6f, 0.7f, 1.0f), 800.0f);
scene::ISceneNodeAnimator* anim = 0;
anim = smgr->createFlyCircleAnimator (core::vector3df(0,150,0),250.0f);
node->addAnimator(anim);
anim->drop();
// attach billboard to light
node = smgr->addBillboardSceneNode(node, core::dimension2d<f32>(50, 50));
node->setMaterialFlag(video::EMF_LIGHTING, false);
node->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
node->setMaterialTexture(0, driver->getTexture("../../media/particlewhite.bmp"));
Ci sono diversi tipi di emettitori, per esempio c'è l'emettitore puntiforme che emette le particelle da un solo punto fisso. Se i tipi di emettitori nell'engine non fossero comunque abbastanza per voi, potete sempre crearne di vostri semplicemente derivando una vostra classe dalla classe interfaccia IParticleEmitter agganciandola poi al sistema particellare tramite setEmitter(). In questo esempio creeremo un emettitore a cubo (box), dove le particelle nasceranno casualmente da dentro la superficie del box. I parametri definiscono il box, la direzione delle particelle, il minimo e il massimo numero di particelle generate al secondo, il colore, il minimo e il massimo tempo di durata della particella.
Poiché con solo gli emettitori il sistema particellare sarebbe un po' noioso, possiamo utilizzare i modificatori che variano i parametri delle particelle come il loro movimento durante il loro volo. Questi modificatori possono essere aggiunti per simulare la gravità oppure il vento. Il modificatore di particelle che utilizzeremo, ad esempio, va a modificare il colore delle particelle: le farà scomparire lentamente con un “fade out”. Come per gli emettitori anche i modificatori possono essere creati e personalizzati da voi, basta semplicemente derivare la classe dall'interfaccia IParticleAffector ed aggiungerli con addAffector().
Dopo aver applicato un materiale adatto al sistema particellare avremo un bel fuoco da campo nella scena. Aggiustando il materiale, la texture o intervenendo sui parametri degli emettitori e dei modificatori si possono creare facilmente diversi effetti come fumo, pioggia, esplosioni, neve e così via.
Codice PHP:
// create a particle system
scene::IParticleSystemSceneNode* ps =
smgr->addParticleSystemSceneNode(false);
scene::IParticleEmitter* em = ps->createBoxEmitter(
core::aabbox3d<f32>(-7,0,-7,7,1,7), // emitter size
core::vector3df(0.0f,0.06f,0.0f), // initial direction
80,100, // emit rate
video::SColor(0,255,255,255), // darkest color
video::SColor(0,255,255,255), // brightest color
800,2000,0, // min and max age, angle
core::dimension2df(10.f,10.f), // min size
core::dimension2df(20.f,20.f)); // max size
ps->setEmitter(em); // this grabs the emitter
em->drop(); // so we can drop it here without deleting it
scene::IParticleAffector* paf = ps->createFadeOutParticleAffector();
ps->addAffector(paf); // same goes for the affector
paf->drop();
ps->setPosition(core::vector3df(-70,60,40));
ps->setScale(core::vector3df(2,2,2));
ps->setMaterialFlag(video::EMF_LIGHTING, false);
ps->setMaterialFlag(video::EMF_ZWRITE_ENABLE, false);
ps->setMaterialTexture(0, driver->getTexture("../../media/fire.bmp"));
ps->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
Codice PHP:
scene::IVolumeLightSceneNode * n = smgr->addVolumeLightSceneNode(0, -1,
32, // Subdivisions on U axis
32, // Subdivisions on V axis
video::SColor(0, 255, 255, 255), // foot color
video::SColor(0, 0, 0, 0)); // tail color
if (n)
{
n->setScale(core::vector3df(56.0f, 56.0f, 56.0f));
n->setPosition(core::vector3df(-120,50,40));
// load textures for animation
core::array<video::ITexture*> textures;
for (s32 g=7; g > 0; --g)
{
core::stringc tmp;
tmp = "../../media/portal";
tmp += g;
tmp += ".bmp";
video::ITexture* t = driver->getTexture( tmp.c_str() );
textures.push_back(t);
}
// create texture animator
scene::ISceneNodeAnimator* glow = smgr->createTextureAnimator(textures, 150);
// add the animator
n->addAnimator(glow);
// drop the animator because it was created with a create() function
glow->drop();
}
Siccome il modello è un po' troppo piccolo per la scena lo andiamo a scalare ingrandendolo con setScale(). Poiché è anche illuminato da una luce dinamica dobbiamo normalizzare le sue normali per rendere l'illuminazione corretta. Questo si rende sempre necessario ogni qual volta la scala di un modello illuminato dinamicamente non è più (1,1,1). Altrimenti sembrerà troppo scuro o troppo chiaro a causa della scalatura del modello e di conseguenza delle normali che servono per ll'illuminazione.
Codice PHP:
// add animated character
mesh = smgr->getMesh("../../media/dwarf.x");
scene::IAnimatedMeshSceneNode* anode = 0;
anode = smgr->addAnimatedMeshSceneNode(mesh);
anode->setPosition(core::vector3df(-50,20,-60));
anode->setAnimationSpeed(15);
// add shadow
anode->addShadowVolumeSceneNode();
smgr->setShadowColor(video::SColor(150,0,0,0));
// make the model a little bit bigger and normalize its normals
// because of the scaling, for correct lighting
anode->setScale(core::vector3df(2,2,2));
anode->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, true);
Finalmente possiamo andare a disegnare ogni cosa e questo è tutto.
Codice PHP:
scene::ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS();
camera->setPosition(core::vector3df(-50,50,-150));
camera->setFarValue(10000.0f); // this increase a shadow visible range.
// disable mouse cursor
device->getCursorControl()->setVisible(false);
s32 lastFPS = -1;
while(device->run())
if (device->isWindowActive())
{
driver->beginScene(true, true, 0);
smgr->drawAll();
driver->endScene();
const s32 fps = driver->getFPS();
if (lastFPS != fps)
{
core::stringw str = L"Irrlicht Engine - SpecialFX example [";
str += driver->getName();
str += "] FPS:";
str += fps;
device->setWindowCaption(str.c_str());
lastFPS = fps;
}
}
device->drop();
return 0;
}
Versione in pdf scaricabile da QUI