Particle System
Brevi cenni su come implementare un particle system in DirectX
Una delle tante applicazioni del texture blending è la creazione dei cosiddetti “sistemi particellari”. I particle system sono semplicissimi da implementare ma allo stesso tempo hanno un gradevolissimo impatto grafico. Potrei esordire con delle lunghe dissertazioni sui sistemi “fuzzy” e cose del genere, ma ho deciso di dare un taglio molto pratico a quest’argomento.
Cominciamo col dire che una particella non è altro che un punto nello spazio (2D o 3D, è lo stesso) che ha alcune proprietà tra le quali un “ciclo di vita”: nasce, si sviluppa e, come è naturale che sia, muore. Una particella appartiene ad un “sistema” di altre particelle ed obbedisce a determinate “leggi” imposte da tale sistema: potremmo chiamarle le “regole del gioco”. Una particella può interagire con altre particelle ma può anche ignorarne l’esistenza. Potremmo per esempio usare le particelle per simulare un corpo rigido, imponendo a tutte le particelle la contemporanea osservanza delle leggi della gravità (potremmo pensare ogni particella come un punto materiale) e dei cosiddetti vincoli di rigidità, dove ogni particella è costretta a mantenersi a distanza fissata da tutte le altre.
Potremmo descrivere tantissime altre applicazioni dei particle system, ma non basterebbe una vita. Focalizziamo quindi la nostra attenzione sui particle system “classici”, quelli che sono utilizzati da gran parte dei giochi in circolazione. Con questo tipo di particle system possiamo abbastanza facilmente simulare effetti tipo fontane, fuochi, colonne di fumo etc.
Ogni particella ha tra le sue proprietà principali almeno la posizione, la velocità, la dimensione ed un colore assocuiato, insomma è fatta a questa maniera:
struct SimpleParticle
{
D3DXVECTOR3 pos;
D3DXVECTOR3 vel;
float size;
DWORD color;
DWORD life;
};
Il particle “manager” che gestisce tutte le singole particelle, provvederà ad aggiornare/animare nel tempo le proprietà di ogni singola particella. Per esempio potremmo animare il colore, facendo in modo che partendo da un colore qualsiasi “random”, la particella sfumi quando sta per “morire” (variabile life<=0), modificando ad ogni frame il suo canale alpha (trasparenza). In questo modo si avrebbe l’effetto della particella che “perde energia” man mano che il suo ciclo vitale sta per terminare. Lo stesso discorso può valere per le dimensioni della particella, facendo in modo che sembri “consumarsi” nel tempo, facendo tendere la sua dimensione a zero.
L’esempio “Particles_1” fa queste cose animando solamente un parametro (la posizione) ed assegnando valori a casaccio al colore e alla dimensione.
bool FillParticles()
{
particles.clear();
D3DXVECTOR3 zero(0,0,0);
for(int i = 0; i < 50; i++)
particles.push_back(SimpleParticle(zero,RandVect(),RandSize(),RandColor()));
return true;
}
bool OnAnimate()
{
for(int i = 0; i < particles.size(); i++)
particles.pos+=particles.vel*wrapper->fElapsedTime;
static float lastfTime=wrapper->fTime;
//Ogni 1,5 secondi resettiamo...
if((wrapper->fTime-lastfTime)>1.5f)
{
lastfTime = wrapper->fTime;
FillParticles();
}
return true;
}
bool OnDraw()
{
unsigned int fv;
MyVertex* vertices = vb->Lock(6*particles.size(),fv);
if(!vertices)
return false;
for(int i = 0; i < particles.size(); i++)
{
D3DXVECTOR3 pos = particles.pos;
float hsize = particles.size/2.0f;
DWORD color = particles.color;
vertices[0]=MyVertex( pos+D3DXVECTOR3(-hsize,-hsize,0),
D3DXVECTOR2(0,0),color);
vertices[1]=MyVertex( pos+D3DXVECTOR3(-hsize,+hsize,0),
D3DXVECTOR2(0,1),color);
vertices[2]=MyVertex( pos+D3DXVECTOR3(+hsize,+hsize,0),
D3DXVECTOR2(1,1),color);
vertices[3]=MyVertex( pos+D3DXVECTOR3(+hsize,+hsize,0),
D3DXVECTOR2(1,1),color);
vertices[4]=MyVertex( pos+D3DXVECTOR3(+hsize,-hsize,0),
D3DXVECTOR2(1,0),color);
vertices[5]=MyVertex( pos+D3DXVECTOR3(-hsize,-hsize,0),
D3DXVECTOR2(0,0),color);
vertices+=6;
}
vb->Unlock();
LPDIRECT3DDEVICE8 lpDev=GetDevice();
lpDev->SetTexture(0,lpTex);
lpDev->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
lpDev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
lpDev->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_ONE);
lpDev->SetRenderState(D3DRS_LIGHTING,FALSE);
lpDev->SetRenderState(D3DRS_ZENABLE, TRUE);
lpDev->SetRenderState(D3DRS_ZWRITEENABLE,FALSE);
lpDev->SetStreamSource(0,vb->GetInterface(),sizeof(MyVertex));
lpDev->SetVertexShader(MyVertex_FVF);
lpDev->DrawPrimitive(D3DPT_TRIANGLELIST,fv,particles.size()*2);
lpDev->SetRenderState(D3DRS_ZWRITEENABLE,TRUE);
return true;
}
Come potete ben vedere in OnAnimate() utilizziamo la semplicissima legge del moto rettilineo uniforme. Utilizziamo però questa legge in versione infinitesima dato che integriamo incrementalmente la posizione con la velocità istantanea, data dalla velocità per wrapper->fElapsedTime che sta per il “dt” di tutti i vostri libri di fisica/analisi). La velocità dà sostanzialmente la direzione dello spostamento in questo caso, mentre il modulo è dato dal “dt”, e questo ci va benissimo per temporizzare il nostro particle system su macchine diverse. Questo “dt” è di solito chiamato in gergo “time step”.
Per il disegno delle particelle (alle quali associamo una texture) utilizziamo due triangoli che hanno un lato in comune. Non abbiamo ancora parlato di TRIANGLE ma per ora prendete per buono il fatto che stiamo semplicemente riempiendo un vertex buffer con tanti triangoli, a due a due disposti per formare dei quadrati. Per ogni triangolo specifichiamo tutte le tre coordinate (6 vertici per particella in totale). Dopo aver fatto questo, disegnamo con DrawPrimitive(…) tutti questi triangoli (che sono in numero uguale al numero di particelle per due, dato che ne servono appunto due per ogni particella).
Scriviamo la posizione del vertice, la coordinata texture ed il suo colore. Da notare che da come specifichiamo tali posizioni, la particella sarà centrata all’interno del quadrato formato dai due triangoli.
I flag che impostiamo sono quelli per il multipassing, dato che vogliamo far “fondere” tutte queste particelle tra loro, disabilitiamo l’illuminazione (potrebbe falsare i colori delle particelle) e disabilitiamo lo Zwrite (putroppo anche questo argomento è rimandato al capitolo sul 3D).
Il “grosso” del nostro particle system è sostanzialmente questo. Particle system più avanzati non fanno altro che aggiungere altre proprietà e le animano in maniera opportuna, ma la sostanza del loro funzionamento è la stessa.
Giusto per far capire che esistono anche altri tipi di particle system, ho creato l’esempio “Particles_2” dove le posizioni iniziali delle particelle vengono prese leggendo i vertici di una mesh tridimensionale in formato .x.
Voto medio articolo: 4.9 Numero Voti: 7
Stefano Cristiano
3D Coder. Sto lavorando ad un engine chiamato "Muse Engine" di cui potete leggere al mio sito http://pagghiu.gameprog.it
Profilo completo