játékfejlesztés.hu
FórumGarázsprojectekCikkekSegédletekJf.hu versenyekKapcsolatokEgyebek
Legaktívabb fórumozók:
Asylum:    5448
FZoli:    4892
Kuz:    4455
gaborlabor:    4449
kicsy:    4304
TPG:    3402
monostoria:    3284
DMG:    3172
HomeGnome:    2919
Matzi:    2520

Pretender:    2498
szeki:    2440
Seeting:    2306
Geri:    2188
Orphy:    1893
Joga:    1791
Bacce:    1783
MaNiAc:    1735
ddbwo:    1625
syam:    1491
(C++) Egyszerű 3D-s effektek részecskerendszerrel 2006.04.21 17:14


Ezen írás témája a részecske rendszerek, konkrétabban egy egyszerű általános részecske rendszer, majd abból kiindulva többféle robbanás és egy „spray” effekt megvalósítása. Az itt leírtakat elsősorban kezdő, ám a C++ programnyelvet és az OpenGL-t legalább alapszinten ismerő programozóknak ajánlom. Az ismertetőhöz tartozik egy példaprogram, mely az alul található linken letölthető.

A természetben sok olyan jelenség található, mely megjelenítése, modellezése egy 3D-s számítógépes programban meglehetősen bonyolult lenne. Gondoljunk például egy tábortűzre; a lobogó láng geometriáját modellezni elég macerás lenne, és az eredmény is csúnyán nézne ki. Számtalan ilyen jelenség található: eső, hó, víz, tűz, robbanás vagy az űrhajó hajtóművéből kiáramló plazma… Ezek mindegyikében közös, hogy sok kis hasonló, apró részecskéből állnak, melyek azért több tulajdonságukban különböznek, és együtt alkotják az adott jelenséget. Az ilyen jelenségeket tudjuk könnyen részecskerendszerrel (ismertebb nevén: „particle system”) megvalósítani.


A négy effekt a példaprogramból


Lényegében tehát a részecskerendszer az adott részecskékből és a részecskék viselkedését meghatározó szabályokból áll. A részecske maga bármi lehet és a szabályok is tetszés szerint felépíthetők, így egy elég tág fogalomhoz jutunk: vegyünk például egy akváriumot és a benne úszkáló halakat. Ezt közelíthetjük ebből a nézőpontból, ekkor a halak maguk a részecskék amik egy egyszerű szabályt tartanak be, vagyis bármerre mozoghatnak de nem hagyhatják el a vizet. A halak lehetnek különböző, komplex 3D-s modellek és a mozgásukat is bonyolíthatjuk, nyilván két hal nem fog átúszni egymáson vagy egyhelyben megfordulni. A szabályokhoz tartoznak még a részecskék létrehozásának, elhalásának, és egyéb tulajdonságaik változásának szabályozása is; ezek a halas példánál elég egyértelműek, amennyiben a halak nem születnek, öregednek és pusztulnak el az akváriumban.

A részecskerendszerek nagy része azonban az egyszerűség (és a gyorsaság) kedvéért több egyszerűsítést is bevezet, ezeket a példaprogramban ki is használom. Egyrészt, az egyes részecskék egymástól függetlenek, nem ütköznek egymással. Aztán az itt megvalósított és az egyéb felsorolt effektek megvalósításához fölösleges az egyes részecskéket részletében modellezni (gondoljunk bele mennyire pazarló lenne ez például egy hópehelynél) hanem egy egyszerű alakzattal helyettesítjük azokat. Ez lehet egy pont, sokszög (általában három- vagy négyszög) vagy egy egyszerű test. Ebből is következik, hogy a részecskék néhány tulajdonsága, például az orientáció egyszerűen fölösleges.

Egy részecskének általában a következő jellemzőit adjuk meg:
- pozíció
- méret
- szín, átlátszóság
- élettartam
- tömeg
- az egyes értékek változásának nagysága

A tömeget itt most nem használom, de fontos lehet fizikai szimulációknál, ha a részecskéinkre erők hatnak. Mivel a legtöbb effektben ezek az értékek változhatnak, ezért érdemes megadni mindegyiknek a változásának gyorsaságát (sebességét), vagy bonyolultabb esetben magát a változás függvényt a részecske feldolgozása során. Ha a részecskéink különbözőek, akkor a részecske alakját is tárolnunk kell.

A megvalósítás konkrétabb leírása előtt szót kell még ejteni az úgynevezett plakátokról (angolul billboard). Ez is egy népszerű egyszerűsítő módszer, a lényeg, hogy egy objektumot annak a 2D-s képével helyettesítünk. Nagyon sok régebbi játékban így oldották meg a növényzetet (fák vagy bokrok lombjait) vagy a távoli, a játékos számára elérhetetlen objektumok kirajzolásán spóroltak vele. Fontos, hogy ez általában olyan tárgyakra működik melyek minden irányból egyformán néznek ki. Ekkor vagy a plakátot mindig a néző felé forgatjuk, vagy – mint a már említett fák esetében, melyek furán néznének ki ha forognának – két vagy több merőleges sík plakáttal közelítünk. Ezen kívül szükség van arra, hogy a plakátok textúrái átlátszóak legyenek, ugyanis a plakát általában egy téglalap, viszont elég kevés objektum valóban téglalap alakú. Egyszerű esetben elég csak egy logikai értékkel megadnunk az átlátszóságot (pl fa) de átlátszó objektumoknál (mondjuk egy üveglap) szükség lehet valamilyen összemosó (blending) módszerre, ami később megbonyolítja a kirajzolást.


A programban használt részecskék képei


Mi most a részecskéinket kis átlátszó plakátokként fogjuk kirajzolni, melyek mindig a szemlélő felé fordulnak, mivel a robbanások képe többnyire független az iránytól. Mivel most a részecskéink nem szilárdak, ezért szürkeárnyalatos képekkel dolgozunk, melyek az átlátszóságot adják meg, míg a konkrét színt majd kirajzoláskor állítjuk be. Ilyen képek képszerkesztő-, vagy 3D-s modellező programokkal könnyen készíthetőek. A részecskék képét animálhatjuk is, ennek egy gyors módszere, hogy több képet az átmenetek során súlyozva váltogatunk, így még szebb eredményt kaphatunk.


A részecskerendszer osztály megvalósítása

A korábban tárgyaltak alapján itt egy általános részecske struktúra.

Kód:
struct sParticle
{
      sParticle *lpsNext;
      sParticle *lpsBack;

      float       fLife;      // élettartam
      float       fAlpha;     // átlátszóság
      float       fFade;      // elhalványulás mértéke     
      float       fColor[3];  // szín
      float       fdColor[3]; // a szín változása
      float       fSize;      // méret
      float       fdSize;     // a méret megváltozása
      Vector      vPos;       // helyzet
      Vector      vSpeed;     // sebesség
};


Szükség lesz még egy részecske osztályra, ami ezeket a részecskéket tárolja, és megvalósítja a részecskék feldolgozásának szabályait. Megjegyzés: a példaprogramban többféle kirajzolási mód közül lehet választani, így ott több Draw függvény található (0-3-ig). A módok közül (lásd később) a Draw(cCamera*,int); függvénnyel lehet választani, az int paraméter az aktuális kirajzolási mód.

Kód:
class cBaseParticleSystem
{
public:
                        cBaseParticleSystem();
                        ~cBaseParticleSystem();

      virtual void      Init();                 // kezdeti állapot beállítás
      virtual void      Process(float&) = 0;    // a rendszer feldolgozása az eltelt idõ függvényében
      void              Draw(cCamera*);         // kirajzolás

      void              Add(sParticle*);        // egy részecske hozzáadása
      void              Remove(sParticle*);     // egy részecske törlése
      void              DeleteList();           // az összes törlése
      bool              LoadTexture(char*);     // textúra betöltése

      bool              bActive;                // a részecserendszer állapota     
      sParticle         *lpsParticles;          // a részecskék egy dinamkus listában
      UINT              uiTexID;                // textúra ID
      Vector            vPos;                   // a rendszer helyzete
      Vector            vSpeed;                 // a rendszer sebessége
      UINT              uiParticles;            // a részecskék száma
};


A Vector osztály egy szimpla 3D-s vektort jelent, a cCamera osztály a kamerát adja meg a térben. A kamera osztály megvalósítására nem térnék most ki, amire szükségünk lesz belőle a részecskék kirajzolásához, az a képzeletbeli kamera orientációja és pozíciója, valamint a modellnézeti mátrix.

Az egyszerűség kedvvért az osztály minden tagja publikus, így nem kell majd a lekérdező és beállító függvényekkel bajlódni. Az Init és Process függvényeket most nem adjuk meg, hiszen az alap osztályt nem is lenne értelme példányosítani, hanem majd az egyes effektekhez származtatunk külön osztályokat.

A részecskéket itt egy dinamikus listában tároltam. Másik megoldás egy dinamikus tömb megadása lenne ami egyszerűbbnek tűnhet, ám így menet közben bármikor könnyen hozzáadhatunk részecskéket valamint törölhetjük az elhaltakat. Megjegyzem, az elhalt részecskék törlését a legtöbb esetben nem kell elvégezni, hanem elég nem kirajzolni az adott részecskét és szükség esetén kezdőállapotba állítani, így gazdaságosabb lehet a program mintha folyton memóriát foglalnánk és felszabadítanánk.

Az alaposztály leglényegesebb függvénye talán a Draw. Ebben valósítjuk meg a részecske kirajzolását, a plakátoknál ismertetett elv szerint. Először pár szót ennek az elméletéről: ugye a részecskéink átlátszóak, ezért szükség lesz alpha blendingre. A részecskékhez egy darab szürkeárnyalatos textúrát használunk, ami a részecske átlátszóságát - vagyis az alakját - jelenti, és ennek alapján rajzoljuk ki a részecskét a saját aktuális színével. A szín megadásánál alpha értékként a részecske fAlpha értékét adjuk meg, ennek függvényében az egész részecskét elhalványíthatjuk. Persze a színt és az alpha értéket össze is vonhatnánk de így kényelmesebb kezelni őket. Például, ha a szín állandó (vagy fekete) és csak halványítani szeretnénk a részecskét. A megfelelő összemosó függvény lehet például a glBlendFunc(GL_SRC_ALPHA,GL_ONE); ebben az esetben a háttérre rámossuk a részecskét az alpha értékkel súlyozva. Ez teljesen jó most megoldást ad, viszont probléma lehet, hogy sok részecskénél a szín összeadódik és fehér lesz. Ez most nem zavaró, de pl egy másik függvénnyel ((GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA)) kiküszöbölhető. A blending miatt az átlátszó részecskéket muszáj a többi szilárd objektum után kirajzolni, ezért is jó, ha a részecskerendszereinket külön tároljuk és a renderelés vége felé egyben rajzoljuk ki őket.

Mivel a textúra nagy része átlátszó, ezért a sebesség növelése miatt a teljesen átlátszó pixeleket még bármilyen más feldolgozási lépés előtt eldobhatjuk. Ehhez szükséges az alpha testing bekapcsolása és egy alpha teszt függvény megadása: glAlphaFunc(GL_NOTEQUAL,0.0f); Ebben az esetben a nem 0 alpha értékű pixeleket engedi át a teszt.

Ezen túl fontos még a z-teszt és a z-buffer működésének megfelelő beállítása. Mivel a részecskék átlátszóak, ezért nem takarják el a mögöttük lévő objektumokat, valamint két részecske sem takarja el egymást. Viszont, ha egy részecskét takar egy szilárd test, akkor nem szabad kirajzolni. Ezért kell a részecskéket utoljára hagyni: rendesen engedélyezzük a z-tesztet, így egy részecskét takarhat egy már kirajzolt objektum, viszont kikapcsoljuk a z-buffer írását, így a részecskéink mélységi adatai nem kerülnek be a bufferbe, nem takarják el egymást. Az egyes részecskéket nem szükséges távolság szerint rendezni, ezeknél az effekteknél így is kielégítő eredményt kapunk.

Ezek a beállítások a példában szereplő összes részecske osztályra megegyeznek, így ezt fölösleges minden Draw függvényben beállítani, elég csak az összes részecske kirajzolása előtt egyszer. Ha csak egyféle textúrájú részecskéink vannak, akkor a textúra kiválasztását is elvégezhetjük itt, különben meg a Draw elején célszerű. Az egyforma tulajdonságú részecske rendszereket érdemes lehet egymás után, rendezve kirajzolni, így minél kevesebb beállítást kell megváltoztatni a kirajzolások között.

Kód:
// render() :

// „szilárd” objektumok kirajzolása
// részecskerendszerek kirajzolásának felkészítése:
glDisable(GL_LIGHTING);
glEnable(GL_TEXTURE_2D);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glEnable(GL_BLEND);
glAlphaFunc(GL_NOTEQUAL,0.0f);     
glEnable(GL_ALPHA_TEST);     
glBlendFunc(GL_SRC_ALPHA,GL_ONE);
glDepthMask(GL_FALSE); 
// az összes részecske kirajzolása
// egyéb kirajzolások, pl szövegek


Maga a kirajzolás egyszerű, mivel csak polygonokat kell kirajzolni, viszont a kamera irányába történő pozícionálás problémás lehet. Erre többféle módszer létezik, én itt röviden kettőt ismertetnék.

Az egyik elképzelés az, hogy kirajzoljuk rendesen a plakát téglalapját úgy, hogy a normálvektora (0,0,1) irányú, azaz az identikus modellnézeti transzformáció után felénk néz. Ezt az aktuális modellnézeti transzformáció eltolja a megfelelő pozícióba, viszont az orientációja nyilván rossz lesz, hiszen úgy látjuk mintha továbbra is a (0,0,1) világ koordinátarendszerbeli irányba nézne. Ezen úgy segíthetünk, hogy a modellnézeti transzformáció forgatási részének inverzével újra a helyes irányba (vagyis a kamera irányába) forgatjuk a téglalapot. Vázlatosan elképzelhetjük így: forgatás, eltolás (immár a kamerának megfelelő irányba), majd visszaforgatás.

A másik módszer szerint beállítjuk a nézeti transzformációt, így a részecskék helyzete jó lesz, viszont az irányát (pontosabban a téglalap vertexeinek helyzetét) manuálisan számoljuk ki a kamera iránya alapján. Ez rendkívül egyszerű a példában szereplő négyzet alakú plakátoknál. Ismerjük a részecske helyét, méretét, a kamera nézet vertikális és horizontális irányú egységvektorait (pl „föl” és „jobb” irányt). Ha a két egységvektor által kifeszített irányokba képzeljük el magunkkal szembe a téglalapot, akkor pl a jobb felső koordinátát megkaphatjuk következő módon:
vJobbfelső = (vKameraJobb + vKameraFel) * fRészecskeméret;
Ha ezt hozzáadjuk a pozícióhoz mint középponthoz, akkor megkapjuk a tényleges helyzetet a világ koordináta rendszerben, amit majd a kirajzolásnál a kamera koordinátarendszerbe transzformálva pont a megfelelő helyzetet kapjuk. Ezt ki kell számolni mind a 4 pontra.


A jobbfelső vertex meghatározása 2D-ben (felülnézet) és ugyanez 3D-ben


Ezt a módszert másként is megfogalmazhatjuk. Amikor a kamera irányairól beszélünk, lényegében a világ koordinátarendszer egységvektorairól van szó, melyeket elforgattunk a kameranézeti transzformáció inverzével. Szemléletesen: ha a kamera jobbra forog, akkor körülöttünk a világ balra fog. Tehát amikor a részecske vertexeinek a helyzetét adjuk meg a kamera vektoraival, akkor előforgatjuk azokat a kameranézeti transzformáció forgatásának inverzével. Ezután a kameranézeti transzformációt alkalmazva egyértelmű, hogy a részecskénk a kamera felé néz majd.

Tapasztalataim szerint a második módszer gyorsabb, mivel nem kell változtatnunk a nézeti mátrixot minden részecskénél, ráadásul így azokat egy glBegin - glEnd között kirajzolhatjuk. A példaprogramban ezeken kívül még két hasonló módszer szerepel, rövid leírással és kommentekkel..

Itt említeném meg a „plakátos” kirajzolási módszer egyik hátrányát, hogy ha közelről nézzük a részecskéket, akkor nagyon belassulhat a program. Ezt próbáljuk kerülni, és törekedjünk a minimális részecskeszámra és méretre.

Kód:
// a kirajzolás
void cBaseParticleSystem::Draw(cCamera *cView)
{     
      sParticle   *lpsP = lpsParticles;   

      glLoadMatrixf(&cView->mView[0][0]);

      // két szomszédos sarokba mutató vektor a kamera alapján
      // vTopRight = vCamX + vCamY, vTopLeft = -vCamX + vCamY
      Vector vTopRight(cView->vUp+cView->vHrzn),vTopLeft(cView->vUp + cView->vHrzn * -1);     

      // végigmegyünk a részecskék listáján, megkezdjük a kirajzolást
      glBegin(GL_QUADS);
      while (lpsP)
      {     
            if (lpsP->fLife > 0.0f)
            {
                  // beállítjuk a részecske színét
                  glColor4f(lpsP->fColor[0],lpsP->fColor[1],lpsP->fColor[2],lpsP->fAlpha);
                 
                  // kirajzoljuk a részecskét, ügyelve a körüljárási irányra
                  // a vertex pozíciója:
                  // a kiszámolt irányok szorozva a mérettel majd hozzáadva a részecske pozíciójához
                  glTexCoord2i(0,0); glVertex3fv((lpsP->vPos + vTopRight * -lpsP->fSize).GetArray());
                  glTexCoord2i(1,0); glVertex3fv((lpsP->vPos + vTopLeft * -lpsP->fSize).GetArray());
                  glTexCoord2i(1,1); glVertex3fv((lpsP->vPos + vTopRight * lpsP->fSize).GetArray());
                  glTexCoord2i(0,1); glVertex3fv((lpsP->vPos + vTopLeft * lpsP->fSize).GetArray());
            }           
            lpsP = lpsP->lpsNext;
      }     
      glEnd();
}




Konkrét effektek megvalósítása

A példaprogramban 4 effekt szerepel, ezek közül egyet fogok részletesebben ismertetni. Az effektek megvalósításához az alap részecske osztály Init és Process függvényeket kell megvalósítani.

Kezdjük a spray effekttel. Lényegében itt annyit csinálunk, hogy egy megadott irányba, egy megadott eltérési szögön belül kilőjük a részecskéket. A részek idővel elhalványodnak és a színük is változik, jelen esetben világosról sötét kékre és a méretük is nő. Ilyen elven valósíthatunk meg tűzszerű effekteket, füstöt, vagy akár egy gejzírszerű feltörő vízoszlopot.


Spray effekt


Az osztály maga:

Kód:
class cPSSpray : public cBaseParticleSystem
{
public:
                        cPSSpray();
                        ~cPSSpray();

      virtual void      Init(char*,UINT,Vector,int);
      virtual void      Process(float&);
      void              CreateP();
                 
      float             fvStartColor[3], fvEndColor[3], fSize;
      Vector            vNormal;
      int               iSpread;
      UINT              uiNum;
};


Az uiNum a részecskék maximális száma, a Start- és EndColor a részek kezdeti és végső színe, az iSpread a kiáramlás szöge, végül a vNormal a kiáramlás iránya. Az Init függvény roppant egyszerű lesz, csak a kezdeti értéket kell beállítanunk és be kell töltenünk a textúrát. Az EndColor változóban innentől a szín megváltozását tároljuk, ugyanis később erre lesz szükségünk.

A Process függvény a következő:

Kód:
void cPSSpray::Process(float& dt) // dt az előző frissítés óta eltelt idő
{
      // ha kevesebb részecske van mint a megadott létrehozunk egyet
      if (uiNum < uiParticles) CreateP();

      sParticle   *lpsP = lpsParticles;   
      while (lpsP) // végigmegyünk az összes részecskén
      {     
            lpsP->vPos += (vSpeed * dt);       
            lpsP->vPos += lpsP->vSpeed * dt;   // frissítjük a pozíciót (a példákban a részek csak egyenletesen mozognak)
            lpsP->fLife -= lpsP->fFade * dt;   // a részecske öregszik
            lpsP->fAlpha = lpsP->fLife;        // és egyre átlátszóbb lesz
            lpsP->fSize += lpsP->fdSize * dt;  // a mérete egyenletesen nő

            // a szín az életének függvényében változik: szín = kezdeti + teljes változás * (1 – hátralévő élet)
            lpsP->fColor[0] = fvStartColor[0] + fvEndColor[0] * (1 - lpsP->fLife);
            lpsP->fColor[1] = fvStartColor[1] + fvEndColor[1] * (1 - lpsP->fLife);
            lpsP->fColor[2] = fvStartColor[2] + fvEndColor[2] * (1 - lpsP->fLife);

            if (lpsP->fLife <= 0)   // ha a részecske meghal töröljük, és újat hozunk létre
            {
                  sParticle   *lpsTmp = lpsP->lpsNext;
                  Remove(lpsP); uiNum--;
                  if (uiNum < uiParticles) CreateP();
                  lpsP = lpsTmp; // a következő elemtől folytatjuk a feldolgozást
            }
            else
                  lpsP = lpsP->lpsNext;
      }     
}


Itt most a részecske rendszer szabályai elég egyszerűek. Például ha a részecskékre erő hatna, például fújná őket a szél vagy vonzaná a gravitáció a talaj felé, akkor ezeket a hatásokat is itt lehetne érvényesíteni. Bonyolultabb a helyzet, ha a részecskék lepattanhatnak egy felületről, vagy elnyelődhetnek rajta, illetve ha egymást is befolyásolják; ezekkel most nem foglalkozom.

Az új részecske létrehozását a CreateP függvényben adjuk meg. Röviden: létrehozunk egy új részecskét és beállítjuk a kezdeti értékeit, majd hozzáadjuk a részecske listához.
A részecskék irányát gömbi koordinátákban adjuk meg két véletlen nagyságú szöggel, melyek abszolút értéke maximum iSpread nagyságú lehet (fokban). Lényegében a vektort úgy határozzuk meg, hogy a rendszer normális irányát elforgatjuk két arra (és egymásra is merőleges) vektor körül a maximum iSpread nagyságú szögekkel.

A véletlen számokat a rand() függvénnyel állítom elő a következőképpen: rand() % n az 0 és n között fog egy egész számot adni, ebből pár művelettel egyszerűen elő lehet törteket is állítani. A rand megfelelő használatához meg kell hívni a program elején egy srand függvényt például srand(GetTickCount()); formában.

Kód:
void cPSSpray::CreateP()
{
      sParticle   *lpsTmp;
      float       fTmpX = 0, fTmpY = 0;
      Vector      vTrg, vHrzn, vUp;

      uiNum++;
      lpsTmp = new sParticle;
      lpsTmp->fLife = 1.0f;
      lpsTmp->vPos = this->vPos;
      lpsTmp->fColor[0] = fvStartColor[0];
      lpsTmp->fColor[1] = fvStartColor[1];
      lpsTmp->fColor[2] = fvStartColor[2];     
      // a halványodás értékét véletlenszerűre állítjuk 0.1 és 0.2 között   
      lpsTmp->fFade = float((rand()%200+200))/2000.0f;
      lpsTmp->fSize = fSize;
      lpsTmp->fdSize = 0.35f; // a méret növekedés mértéke állandó     
           
      fTmpX = float(rand()%(2*iSpread)-iSpread);     // -iSpread és iSpread közötti float
      fTmpY = float(rand()%(2*iSpread)-iSpread);     // ezek a forgatás egyik szögei

      // felveszünk egy tetszőleges irányt
      vTrg = Vector(0,0,1);   
     vUp = vNormal;
     // meghatározunk egy, a kibocsátás irányára merőleges vektort   
      vHrzn = vTrg % vNormal; vHrzn.Normalize();
      // és még egyet, ami erre is merőleges
      vTrg = vUp % vHrzn;  vTrg.Normalize();
      // végül lesz egy koordinátarendszerünk, aminek az egyik iránya a kibocsátás iránya

      // a fel vektort elforgatjuk a két másik irány körül az előzőleg véletlenszerűen megkapott szögekkel
      vUp = vUp.Rotate(vHrzn,fTmpX < 0.0f ? fTmpX + 360.0f : fTmpX);
      vUp = vUp.Rotate(vTrg,fTmpY < 0.0f ? fTmpY + 360.0f : fTmpY);   

      lpsTmp->vSpeed = vUp;         // a részecske sebessége a kiszámolt irányba mutat
      lpsTmp->vSpeed.Normalize(); 
      fTmpX = 0.8f + float((rand()%20000))/10000.0f; // a nagysága 0.8 és 2.8 között véletlen
      lpsTmp->vSpeed*=fTmpX;
      lpsTmp->fFade *= lpsTmp->vSpeed.Length();      // az elhalványulást függővé tesszük a sebességtől
      Add(lpsTmp);      // hozzáadjuk a részecskét a listához
}


A megadott kezdő adatokkal és függvényekkel érdemes trükközni és érzésre beállítani őket, változtatásukkal rengetegféle különböző részecske rendszer alakítható ki. A továbbiakban még bemutatom a példaprogramban található 3 robbanást, ám már kevésbé részletesen, csak a legfontosabbakat kiemelve.

A robbanás effekt annyiban tér el a spraytől, hogy kezdetben kell kilőni az összes részecskét, minden irányban. Ezek különböző sebességgel kilövődnek, majd lassan elhalványodnak, hasonlóan a sprayhez. A textúra is más, itt a robbanásszerű képet használtam. Tehát az Init függvény beállítja a kezdőértékeket és kezdeti állapotba állítja az összes a részecskét. Egy nagyobb robbanásnál érdemes lehet nem egyszerre, hanem egy adott idő alatt folyamatosan kibocsátani a részecskéket, az eltelt időtől függő paraméterekkel. Az első robbanás kevesebb nagyobb részből áll, melyeknek más a méretük és az elhalványodásuk sebessége, míg a második több kisebb egyforma részecskét lő ki minden irányba. A harmadik, lökéshullám (shockwave) hasonló a másodikhoz, ám a részecskék sebessége a robbanás normálisára merőleges síkba esik. Ehhez egy normálisra merőleges vektort forgattam el véletlenszerű szöggel. A Process függvény már csak frissíti a részecskék adatait, nem bocsát ki újabbakat, ez mindegyik robbanásnál megegyezik.

A példaprogram tartalmazza az alap részecske osztályt és a négy származtatott effektet. A kódban csak minimálisan kommentáltam, a kritikus részek szerepelnek a leírásban. Bővebb információ a program „readme”-jében található.

Mind a programmal, mind a leírással kapcsolatban szívesen várom az észrevételeket e-mailben vagy a prometheusgames.hu oldalon keresztül!

Huszár Tamás

E-mail: th.pg@freemail.hu
Web: www.prometheusgames.atw.hu


A program a forráskóddal elérhető itt: Visual C++ .NET


Felhasznált irodalom:
Dr. Szirmay-Kalos László, Antal György, Csonka Ferenc: Háromdimenziós grafika, animáció és játékfejlesztés című könyv
NeHe OpenGL tutorial, (nehe.gamedev.net)

Értékelés: 9.44

Új hozzászólás
h_thomas          2006.04.27 01:08
Nos, a point spriteok valóban kimaradtak. Asszem ezen a cikken már nem fogok változtatni, a hiány pótlásául itt van két link azoknak, akiket érdekel a téma.

Egy angol leírás a point sprite kiterjesztés használatáról

Egy példaprogram egy másik angol oldalról
MaNiAc          2006.04.24 17:16
Egyetértek Warlock-al...
Orphy          2006.04.24 01:45
Szerintem, akit érdekel a téma, most kapott egy jól használható, érthető, és érdekes útmutatót...
És ez a lényeg, nem?

Nekem nagyon tetszett, 10-est adok.
Így tovább!
warlock          2006.04.22 03:15
Alapos csak a pointspriteokról nincs szó(vagy csak nem vettem észre).