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