www.codeworx.org/opengl-tutorials/codeworx.org - Partikelsysteme mit OpenGL - Teil 2 – Dynamische Partikel

Partikelsysteme mit OpenGL - Teil 2 – Dynamische Partikel

In diesem Tutorial werden wir unsere Partikel um einige Eigenschaften erweitern.
Erschienen die Partikel in Tutorial 1 noch wie „aus dem Nichts“, so sollen die Partikel
nun langsam erscheinen (d. h. an Intensität zunehmen) und wenn sie nicht mehr
benötigt werden langsam verblassen.
Die Zeichenoperation mit Quads werden wir durch die effizienteren Trianglestrips
Um ein gewisses Eigenleben der Partikel zu ermöglichen, sollen diese nun ihre
Größe und Farbe verändern können.
Die Erweiterungen auf einen Blick:
• Größenänderung möglich
• Farbänderung möglich
• „Geburts“- und „Sterbeprozess“
• Trianglestrips

Um diese Änderungen in der Partikelklasse zu realisieren fügen wir entsprechende
Attribute ein und ändern die Set() Funktion.
Der neue Teil von CParticle:

   float m_fChangeSize;             // Größenänderung
   bool m_bChangeSize;           // bei true wird die Größe des
                                 // Partikels geändert
   float m_fMaxSize;             // maximale Größe
   float m_clrChangeColor[4];    // Farbänderung
   bool m_bBorn;                 // true: Partikel wird "geboren"
   bool m_bIsDying;              // true: Partikel loest sich auf
   float m_fFadeSpeed;           // so schnell verschwindet ein Partikel
   float m_fFadeInSpeed;         // so schnell erscheint ein Partikel
void Set(VECTOR vPosition,VECTOR vVelocity, float clrColor[4],float clrChangeColor[4],float fSize, float fChangeSize,float fLifetime,float fFadeSpeed);

Im Konstruktor von CParticle setzen wir alle neuen Attribute auf Standardwerte,
zum Beispiel setzen wir m_bIsDying auf false, m_bLife auf true und m_bBorn
ebenfalls auf true. m_clrColor[3] wählen wir nun sehr klein, d. h. die Intensität des
Partikels ist zu Beginn sehr gering, wichtig ist dass m_clrColor[3] nicht gleich Null
ist, da sonst kein „Geburtsprozess“ stattfinden kann.

Die Render() Funktion ändern wir nun von Quads zu Trianglestrips, dazu zeichnen
wir ganz einfach zwei Dreiecke, die das ursprüngliche Rechteck gebildet haben und
achten dabei darauf, dass wir die Punkte entgegen dem Uhrzeigersinn durchlaufen
(wegen des Backface-Cullings).

Die neue Render() Funktion sieht dann folgendermaßen aus:

void CParticle::Render()
{
   glColor4fv(m_clrColor);     // Farbe des Partikel setzen
   glBegin(GL_TRIANGLE_STRIP); // Begin der Zeichenoperation,
                               // mit Trianglestrips
   // oben rechts
   glTexCoord2i(1,1);
   glVertex3f(m_vPosition[0]+m_fSize,m_vPosition[1]+m_fSize,
   m_vPosition[2]);
// oben links glTexCoord2i(0,1); glVertex3f(m_vPosition[0]-m_fSize,m_vPosition[1]+m_fSize, m_vPosition[2]);
// unten rechts glTexCoord2i(1,0); glVertex3f(m_vPosition[0]+m_fSize,m_vPosition[1]-m_fSize, m_vPosition[2]);
// unten links glTexCoord2i(0,0); glVertex3f(m_vPosition[0]-m_fSize,m_vPosition[1]-m_fSize, m_vPosition[2]);
glEnd(); // Ende der Zeichenoperation }

Kurze Anmerkung zu Trianglestrips:
Das erste Dreieck besteht aus den Punkten oben rechts, oben links und unten
rechts, das zweite aus unten rechts an, oben links und unten links.
Die Texturkoordinaten bleiben dieselben, da wir in diesem Fall ja die Punkte garnicht
geändern haben.

Die Update() Funktion haben wir ebenfalls entsprechend den Erweiterungen
ergänzt:

void CParticle::Update()
{
   if(m_bBorn)
   {
      // Auftauchen des Partikels durch erhöhen des
      // Alpha-Wertes
      m_clrColor[3] += (m_pEmitter->m_fFrameTime*
      m_fFadeInSpeed);
      // Prozess der "Geburt" beendet,
      // vollen Alpha-Wert 1 setzen

      if(m_clrColor[3] >= 0.99f)
      {
         m_clrColor[3] = 1.0f;
         m_bBorn = false;
      }

   } 

   else if(m_bIsDying)
   {
      // Partikel verblassen lassen,
      // durch verringen des Alpha-Werts
      m_clrColor[3] -= (m_pEmitter->m_fFrameTime
      *m_fFadeSpeed);
      if(m_clrColor[3] < 0)
         m_bLife = false; // Partikel wird beim nächsten
                          // Schleifendruchlauf gelöscht

   } 
else if(m_pEmitter->m_fCurTime - m_fBirthday > m_fLifetime) m_bIsDying = true; // Startet den "Sterbeprozess" // neue Position anhand der Geschwindigkeit und // der Zeit seit dem letzen Update berechnen m_vPosition[0] += (m_vVelocity[0]*m_pEmitter->m_fFrameTime); m_vPosition[1] += (m_vVelocity[1]*m_pEmitter->m_fFrameTime); m_vPosition[2] += (m_vVelocity[2]*m_pEmitter->m_fFrameTime);
//*NEU* Farbänderung m_clrColor[0]+=m_clrChangeColor[0]*m_pEmitter->m_fFrameTime; m_clrColor[1]+=m_clrChangeColor[1]*m_pEmitter->m_fFrameTime; m_clrColor[2]+=m_clrChangeColor[2]*m_pEmitter->m_fFrameTime;
// *NEU* Größenänderung if(m_bChangeSize && m_fSize <= m_fMaxSize ) { m_fSize += m_fChangeSize; if(m_fSize <0) m_fSize = 0; } }

An der Emitterklasse brauchen wir keine Änderungen vorzunehmen. Nun können wir
das Hauptprogramm schreiben um unsere neuen Partikel auszuprobieren.
Dazu erzeugen wir in BuildParticles() Partikel mit zufälligen Attributen, die ihre
Größe und Farbe ändern:

void BuildParticles()
{
   for(int i = 0; i < 30 + (rand() % 30) ; i++)
   {
      CParticle* p = new CParticle(g_pEmitter);
      VECTOR vPos = { -10 + ((rand() % 80)-40),
      -10+((rand() % 80)-40),
      -150+((rand() % 20)-10)};

      VECTOR vVel= { ( rand()%10)-5*0.001 ,
      ( rand()%10)-5*0.001 ,
      ( rand()%20)-10 *0.0001};

      float clrCol[4] = { ((rand() % 100)+1) *0.02,
      ((rand() % 100)+1) *0.02,0,1 };
      float s1 = (( rand() % 16)-8)*0.1;
      float s2 = (( rand() % 12)-5)*0.1;
      float clrChangeCol[4] = {s1,s2,0};
      float fFade = 0.5f + ((rand() % 5)*0.1);
      float fLife = (rand() % 15 )*0.1 + 4.5 ;
      float fSize = 0.1f + ( (rand() % 10) -5)*0.1;
      float fChangeSize = ((rand() % 6)-3)*0.1;
      p->Set(vPos,vVel,clrCol,clrChangeCol,
      fSize,fChangeSize,fLife,fFade);
      g_pEmitter->Add(p);
   }
}


Die RenderScene() Methode ist ziemlich knapp, wir zeichnen einfach einen
Hintergrund und updaten dann den Emitter:

void RenderScene()
{
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glLoadIdentity();
   DrawBackground(); // Hintergrund zeichnen
   if(g_Timer.GetCurrentTime() - g_fLastUpdate > 1.0f)
   {
      BuildParticles();
      g_fLastUpdate = g_Timer.GetCurrentTime();
   }

   g_pEmitter->Update(); // Partikel zeichnen
   SwapBuffers(g_hDC);
}

Nun haben wir also die Partikelklasse aus dem ersten Tutorial erweitert und optimiert
und können nun schon eine ganze Reihe von netten Effekten erzielen. Einige davon
werden wir in folgenden Tutorials behandeln.
Da CParticle nun schon eine ganze Reihe von Attributen enthält erhöht sich
natürlich der Speicherbedarf einzelner Partikel-Objekte. Je nachdem aus wie vielen
Partikeln ein System besteht, kann hier leicht die Speicherkapazität von so manchem
Rechner erreicht werden (beispielsweise ein System mit über Hunderttausend
Partikeln) obwohl vielleicht nicht immer alle Attribute bei einem Effekt gebraucht
werden.

Dem könnte man entgegenwirken, indem man einzelne, nicht so häufig verwendete
Attribute aus der ursprünglichen Partikelklasse herausnimmt und in eine separate
Klasse steckt und diese Effektklasse von CParticle ableitet.
Auf diese Weise kann man auch leicht neue Effekte erzeugen ohne die
Partikelklasse zu verändern, was wiederum den Vorteil hat, das der Emitter alle
Objekte die CParticle als Oberklasse haben aufnehmen und verwalten kann. Dies
wollen wir uns im nächsten Tutorial einmal genauer anschauen.
Feedback erwünscht!

Den VC++ 6.0-Arbeitsbereich dazu gibts hier.

rick@diamond-productions.de, diamond-productions.de

codeworx.org