NeHe - Lektion 19 - Partikel Engine

Lektion 19



Willkommen zu Tutorial 19. Sie haben viel gelernt und nun wollen Sie anfangen zu spielen. Ich werden Ihnen einen neuen Befehl in diesem Tutorial vorstellen... Den Triangle Strip. Er ist sehr einfach zu benutzen und kann Ihnen helfen, Ihre Programme schneller zu machen, wenn Sie viele Dreiecke zeichnen.

In diesem Tutorial werde ich Ihnen beibringen, wie Sie eine semi-komplexe Partikel Engine machen. Wenn Sie erst einmal verstanden haben, wie Partikel Engines arbeiten, ist es ein Leichtes, Effekte wie Feuer, Rauch, Wasser-Fontänen und mehr zu erstellen!

Ich muss Sie dennoch warnen! Bis heute habe ich bisher nie eine Partikel Engine geschrieben. Ich hatte immer die Vorstellung, dass die 'bekannte' Partikel Engine ein sehr komplexes Stück Code ist. Ich habe mehrere Versuche in der Vergangenheit unternommen, aber in der Regel aufgegeben, nachdem ich festgestellt habe, dass ich die ganzen Punkte nicht kontrollieren konnte, ohne verrückt zu werden.

Sie mögen mir das vielleicht nicht glauben, wenn ich Ihnen sage, dass dieses Tutorial zu 100 % von der Pieke auf geschrieben wurde. Ich habe mir keine Ideen abgeguckt und ich hatte keine technischen Informationen vorliegen. Ich habe angefangen über Partikel nachzudenken und wie aus dem nichts war mein Kopf gefüllt mit Ideen (Gehirn eingeschaltet?). Anstatt jeden Partikel als einen Pixel zu sehen, der von Punkt 'A' zu Punkt 'B' gehen muss und dieses oder jenes machen muss, habe ich mich entschlossen, dass es besser wäre, jeden Partikel als ein individuelles Objekt zu sehen, dass auf die Umwelt um es herum reagiert. Ich habe jedem Partikel Leben, zufälliges altern, Farbe, Geschwindigkeit, Gravitations-Einflüsse und mehr gegeben.

Bald hatte ich ein fertiges Projekt. Ich habe auf die Uhr geschaut und gemerkt, dass die Zeit wieder im Nu verflogen war. Weitere 4 Stunden vergangen! Ich erinnere mich, wie ich kurz pausiert habe und Kaffe getrunken habe und zack, aber 4 Stunden... ?

So, obwohl dieses Programm meiner Meinung nach gut aussieht und genauso funktioniert wie ich es haben wollte, mag es vielleicht nicht der richtige Weg sein, eine Partikel Engine zu schreiben. Mir persönlich ist das egal, so lange es gut funktioniert und ich es in meinen Projekten verwenden kann! Wenn Sie zu der Art von Personen gehören, die wissen müssen, ob Sie es richtig machen, dann verbringen Sie Stunden damit, im Netz nach Informationen ausschau zu halten. Aber seien Sie gewarnt. Die paar Code-Schnippsel die Sie finden werden, werden wahrscheinlich sehr kryptisch wirken :)

Dieses Tutorial benutzt den Base Code aus Lektion 1. Es gibt vielen neuen Code, weswegen ich jeden Code-Abschnitt, der Änderungen enthält, komplett hier noch mal aufzeigen werde (damit es einfacher zu verstehen ist).

Wir benutzen den Code aus Lektion 1 und fügen gleich am Anfang des Codes 5 neue Zeilen ein. Die erste Zeile (stdio.h) erlaubt es uns, Daten aus Dateien zu lesen. Es ist die selbe Zeile, die wir in vorherigen Tutorials eingefügt haben, um Textur-Mapping zu verwenden. Die zweite Zeile definiert, wie viele Partikel wir erzeugen und am Screen anzeigen werden. Define teilt unserem Programm mit, dass MAX_PARTICLES gleich dem Wert sein wird, den wir angeben. In diesem Fall 1000. Die dritte Zeile wird verwendet, um den 'Regenbogen-Modus' aus und anzuschalten. Wir schalten ihn standardmäßig ein. sp und rp sind Variablen, die wir verwenden, um zu verhindern, dass die Leertaste oder Enter-Taste mehrmals wiederholt werden, wenn sie gedrückt werden.
 
#include <windows.h>            // Header Datei für Windows
#include <stdio.h>            // Header Datei für Standard Input/Output ( HINZUGEFÜGT )
#include <gl\gl.h>            // Header Datei für die OpenGL32 Library
#include <gl\glu.h>            // Header Datei für die GLu32 Library
#include <gl\glaux.h>            // Header Datei für die GLaux Library

#define    MAX_PARTICLES    1000        // Anzahl der zu erzeugenden Partikel ( NEU )

HDC        hDC=NULL;        // Privater GDI Device Context
HGLRC        hRC=NULL;        // Permanenter Rendering Context
HWND        hWnd=NULL;        // Enthält unser Fenster-Handle
HINSTANCE    hInstance;        // Enthält die Instanz der Applikation

bool    keys[256];            // Array das für die Tastatur Routine verwendet wird
bool    active=TRUE;            // Fenster Aktiv Flag standardmäßig auf TRUE gesetzt
bool    fullscreen=TRUE;        // Fullscreen Flag ist standardmäßig auf TRUE gesetzt
bool    rainbow=true;            // Regenbogen-Modus?    ( HINZUGEFÜGT )
bool    sp;                // Leertaste gedrückt?    ( HINZUGEFÜGT )
bool    rp;                // Entertaste gedrückt?    ( HINZUGEFÜGT )

Die nächsten 4 Zeilen enthalten diverse Variablen. Die Variable slowdown kontrolliert, wie schnell sich die Partikel bewegen. Je höher die Zahl, desto langsamer bewegen sie sich. Je niedriger die Zahl, desto schneller bewegen sie sich. Wenn der Wert zu niedrig angesetzt ist, werden sich die Partikel auf jeden Fall zu schnell bewegen! Die Geschwindigkeit mit der die Partikel reisen, beeinflusst die Art wie sie sich auf dem Bildschirm bewegen. Langsame Partikel werden nicht so schnell wieder verblassen. Behalten Sie das im Hinterkopf.

Die Variablen xspeed und yspeed erlauben es uns die Richtung des Schweifs zu kontrollieren. xspeed wird zur aktuellen Geschwindigkeit addiert, mit der ein Partikel entlängs der X-Achse reist. Wenn xspeed ein positiver Wert ist, wird unser Partikel sich mehr nach rechts bewegen. Wenn xspeed ein negativer Wert ist, wird unser Partikel sich mehr nach links bewegen. Je höher der Wert ist, umso mehr wird es in diese Richtung wandern. yspeed funktioniert in der selben Weise, allerdings auf der Y-Achse. Der Grund warum ich 'MEHR' in eine bestimmte Richtung gesagt habe, ist der, dass andere Faktoren die Richtung unseres Partikels mitbestimmen. xspeed und yspeed helfen den Partikel in die von uns gewünschte Richtung zu lenken.

Zu letzt haben wir die Variable zoom. Wir benutzen diese Variable um an unsere heran- und wieder heraus zu zoomen. Bei Partikel-Engines ist es manchmal nett, wenn man mehr auf dem Screen sieht und ein anderes mal ist es cool, wenn man wirklich nah ran geht.
 
float    slowdown=2.0f;            // Verlangsame Partikel
float    xspeed;                // Basis X Geschwindigkeit (um die Richtung des Schweifs mittels Tastatur zu steuern)
float    yspeed;                // Basis Y Geschwindigkeit (um die Richtung des Schweifs mittels Tastatur zu steuern)
float    zoom=-40.0f;            // wird benutzt um wegzuzoomen

Nun führen wir eine Schleifenvariable namens loop ein. Wir benutzen diese, um die Partikel vorher zu definieren und um die Partikel auf dem Screen zu zeichnen. col wird benutzt um zu verfolgen, welche Farbe die Partikel haben. delay wird benutzt um die Farben im Regenbogenmodus zu wechseln.

Zu letzt reservieren wir noch etwas Speicherplatz für eine Textur (die Partikel-Textur). Ich habe mich dazu entschlossen, eine Textur zu verwenden, anstatt OpenGL Punkte, aus diversen Gründen. Der wichtigste Grund ist, dass Punkte (Points) nicht so schnell sind und sie ziemlich blah aussehen. Zweitens sind Texturen wesentlich cooler :) Sie können ein Rechteck Partikel verwenden, ein Bild Ihres Gesichtes, ein Bild von einem Stern, etc. Mehr Kontrolle!
 
GLuint    loop;                // Schleifen-Variable
GLuint    col;                // aktuelle Farbe
GLuint    delay;                // Regenbogen-Effekt-Verzögerung
GLuint    texture[1];            // Speicherplatz für unsere Partikel Textur

Ok, nun zum spaßigen Teil. Der nächste Codeabschnitt erzeugt eine Struktur, die einen einzelnen Partikel beschreibt. Dort geben wir den Partikeln bestimmte Charakteristika.

Wir fangen mit der booleschen Variable active an. Wenn die Variable gleich TRUE ist, lebt unser Partikel und wird angezeigt. Wenn sie FALSE ist, ist unser Partikel tod oder wir haben ihn ausgeschaltet. In diesem Programm benutze ich active nicht, aber es ist nett zu haben.

Die Variablen life und fade kontrollieren, wie lange der Partikel angezeigt wird und wie hell der Partikel ist, solange er lebt. Die Variable life wird ständig um den Wert, der in fade gespeichert ist, dekrementiert. In diesem Programm lässt das einige Partikel länger leben als andere.
 
typedef struct                        // erzeuge eine Struktur für Partikel
{
    bool    active;                    // Aktiv (Ja/Nein)
    float    life;                    // Partikel Leben
    float    fade;                    // Fade Geschwindigkeit

Die Variablen r, g und b enthält die Rot-Intensität, Grün-Intensität und Blau-Intensität unseres Partikels. Je näher r an 1.0f ist, desto roter wird der Partikel sein. Setzen Sie alle 3 Variablen auf 1.0f, wird ein weißer Partikel erzeugt. The variables r, g and b hold the red intensity, green intensity and blue intensity of our particle. The closer r is to 1.0f, the more red the particle will be. Making all 3 variables 1.0f will create a white particle.
 
    float    r;                    // Rot Wert
    float    g;                    // Grün Wert
    float    b;                    // Blau Wert

Die Variablen x, y und z konrollieren wo unser Partikel auf dem Screen angezeigt wird. x enthält den Ort unseres Partikels auf der x-Achse. y enthält den Ort unseres Partikels auf der Y-Achse und z enthält den Ort unseres Partikels auf der Z-Achse.
 
    float    x;                    // X Position
    float    y;                    // Y Position
    float    z;                    // Z Position

Die nächsten drei Variablen sind wichtig. Diese drei Variablen kontrollieren, wie schnell der Partikel auf einer bestimmten Achse und in welche Richtung es sich bewegt. Wenn xi ein negativer Wert ist, bewegt sich unser Partikel nach links. Ist er positiv wird nach rechts bewegt. Wenn yi negativ ist, bewegt sich unser Partikel nach unten. Ist er positiv nach oben. Und wenn zi negativ ist, bewegt sich unser Artikel in den Screen hinein und ist er positiv auf den Betrachter hin.
 
    float    xi;                    // X Richtung
    float    yi;                    // Y Richtung
    float    zi;                    // Z Richtung

Zu letzt 3 weitere Variablen! Jede dieser Variablen können als Gravitation betrachtet werden. Wenn xg ein positiver Wert ist, wird unser Partikel nach rechts abgelenkt. Wenn sie negativ ist, wird unser Partikel nach links abgelenkt. Wenn unser Partikel sich also nach links (negativ) bewegt und wir eine positive Gravitation anwenden, wird die Geschwindigkeit gegebenenfalls so sehr verlangsamt, dass unser Partikel ggf. sich in die andere Richtung bewegt. yg lenkt nach oben oder unten ab und zg zum Betrachter hin oder weg.
 
    float    xg;                    // X Gravitation
    float    yg;                    // Y Gravitation
    float    zg;                    // Z Gravitation

particles ist der Name unserer Struktur.
 
}
particles;                        // Particles Struktur

Als nächstes erzeugen wir ein Array namens particle. Dieses Array wird MAX_PARTICLES speichern. Auf deutsch übersetzt erzeugen wir Speicherplatz für 1000 (MAX_PARTICLES) Partikel. Dieser Speicherplatz speichert die Informationen für jeden individuellen Partikel.
 
particles particle[MAX_PARTICLES];            // Particle Array (Platz für Partikel Info)

Wir schränken die Menge des benötigten Code für dieses Programm etwas ein, indem wir 12 verschiedene Farben in einem Farb Array speicher. Für jede Farbe von 0 bis 11 speichern wir die rot Intensität, grün Intensität und zu letzt die blau Intensität. Die folgende Farbtabelle speichert 12 verschiedene Farben, die von rot ins violette übergehen.
 
static GLfloat colors[12][3]=                // Regenbogenfarben
{
    {1.0f,0.5f,0.5f},{1.0f,0.75f,0.5f},{1.0f,1.0f,0.5f},{0.75f,1.0f,0.5f},
    {0.5f,1.0f,0.5f},{0.5f,1.0f,0.75f},{0.5f,1.0f,1.0f},{0.5f,0.75f,1.0f},
    {0.5f,0.5f,1.0f},{0.75f,0.5f,1.0f},{1.0f,0.5f,1.0f},{1.0f,0.5f,0.75f}
};

LRESULT    CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);    // Deklaration für WndProc

Unser Bitmap-Lade-Code hat sich nicht geändert.
 
AUX_RGBImageRec *LoadBMP(char *Filename)        // Lädt ein Bitmap Image
{
    FILE *File=NULL;                // Datei Handle
    if (!Filename)                    // gehe sicher, dass ein Dateiname übergeben wurde
    {
        return NULL;                // wenn nicht, gebe NULL zurück
    }

    File=fopen(Filename,"r");            // überprüfe, ob die Datei existiert
    if (File)                    // Existiert die Datei?
    {
        fclose(File);                // Schließe das Handle
        return auxDIBImageLoad(Filename);    // Lade das Bitmap und gibt einen Zeiger zurück
    }
    return NULL;                    // Wenn das Laden fehl schlug, gebe NULL zurück
}

Dies ist der Codeabschnitt, welcher das Bitmap lädt (indem der obige Code aufgerufen wird) und konvertiert es in eine Textur. Status wird dazu benutzt, um zu verfolgen, ob oder ob nicht die Textur geladen und erzeugt wurde.
 
int LoadGLTextures()                        // Lade Bitmaps und konvertiere in Texturen
{
    int Status=FALSE;                    // Status Indikator

    AUX_RGBImageRec *TextureImage[1];            // erzeuge Speicherplatz für die Textur

    memset(TextureImage,0,sizeof(void *)*1);        // Setze den Zeiger auf NULL

Unser Textur-Lade-Code wird unser Partikel-Bitmap laden und es in eine linear-gefilterte Textur konvertieren.
 
    if (TextureImage[0]=LoadBMP("Data/Particle.bmp"))    // Lade Partikel Textur
    {
        Status=TRUE;                    // Setze Status auf TRUE
        glGenTextures(1, &texture[0]);            // erzeuge eine Textur

        glBindTexture(GL_TEXTURE_2D, texture[0]);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
        glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
    }

    if (TextureImage[0])                    // Wenn Textur existiert
    {
        if (TextureImage[0]->data)            // Wenn Textur-Image existiert
        {
            free(TextureImage[0]->data);        // gebe Textur-Image Speicher frei
        }
        free(TextureImage[0]);                // gebe die Image-Struktur frei
    }
    return Status;                        // gebe Status zurück
}

Die einzige Änderung, die ich am Resize Code gemacht habe, ist eine tiefere Betrachtungs-Distanz. Anstatt 100.0f Einheiten können wir nun die Partikel 200.0f Einheiten in den Screen hinein sehen.
 
GLvoid ReSizeGLScene(GLsizei width, GLsizei height)        // verändert die Größe und initialisiert das GL-Fenster
{
    if (height==0)                        // Verhindere eine Division durch 0, indem
    {
        height=1;                    // die Höhe auf 1 gesetzt wird
    }

    glViewport(0, 0, width, height);            // Resette die aktuelle Ansicht  (Viewport)

    glMatrixMode(GL_PROJECTION);                // wähle die Projektions-Matrix aus
    glLoadIdentity();                    // Resette die Projektions-Matrix

    // berechne das Seitenverhältnis des Fensters
    gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,200.0f);     ( MODIFIZIERT )

    glMatrixMode(GL_MODELVIEW);                // Wähle die Modelview Matrix aus
    glLoadIdentity();                    // Resette die Modelview Matrix
}

Wenn Sie den Code aus Lektion 1 verwenden, ersetzen Sie ihn mit dem folgenden Code. Ich habe Code zum Laden unserer Textur hinzugefügt und Blending für unsere Partikel gesetzt.
 
int InitGL(GLvoid)                                // Der ganze Setup Kram für OpenGL kommt hier rein
{
    if (!LoadGLTextures())                            // springe zur Textur Lade Routine
    {
        return FALSE;                            // wenn Textur nicht geladen wurde, gebe FALSE zurück
    }

Wir aktivieren Smooth Shading, löschen unseren Hintergrund auf schwarz, deaktivieren Depth Testing, aktivieren Blending und Textur Mapping. Nach der Aktivierung von Textur Mapping wähle wir unsere Partikel Textur aus.
 
    glShadeModel(GL_SMOOTH);                        // aktiviere Smooth Shading
    glClearColor(0.0f,0.0f,0.0f,0.0f);                    // schwarzer Hintergrund
    glClearDepth(1.0f);                            // Depth Buffer Setup
    glDisable(GL_DEPTH_TEST);                        // aktiviert Depth Test
    glEnable(GL_BLEND);                            // aktiviere Blending
    glBlendFunc(GL_SRC_ALPHA,GL_ONE);                    // Art des auszuführenden Blendings
    glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);            // wirklich nette Perspektiven Berechnungen
    glHint(GL_POINT_SMOOTH_HINT,GL_NICEST);                    // wirklich nettes PointSmoothing
    glEnable(GL_TEXTURE_2D);                        // aktiviere Textur Mapping
    glBindTexture(GL_TEXTURE_2D,texture[0]);                // wähle unsere Textur

Der folgende Code initialisiert jeden einzelnen Partikel. Wir fangen damit an, jeden Partikel zu aktivieren. Wenn ein Partikel nicht aktiv ist, wird er nicht auf dem Screen erscheinen, egal wieviel Leben er noch hat.

Nachdem wir die Partikel aktiviert haben, geben wir ihnen Leben. Ich bezweifel, dass die Weise, auf denen ich den Partikel Leben gegeben habe und sie verblassen lasse, der beste Weg ist, aber nochmals, es funktioniert gut! Volles Leben ist gleich 1.0f. Dies gibt dem Partikel auch die volle Helligkeit.
 
    for (loop=0;loop<MAX_PARTICLES;loop++)                    // intialisiere alle Texturen
    {
        particle[loop].active=true;                    // aktiviere alle Partikel
        particle[loop].life=1.0f;                    // gebe allen Partikeln volles Leben

Wir setzen wie schnell die Partikel verblassen, indem wir fade einen zufälligen Wert setzen. Die Variable life wird um fade reduziert, jedes Mal wenn der Partikel gezeichnet wird. Der Wert den wir zuweisen, wird ein zufälliger Wert zwischen 0 und 99 sein. Wir dividieren ihn dann durch 1000, so dass wir einen sehr kleinen Fließkommawert erhalten. Wir addieren dann noch .003 zum Ergebniss, so dass das Geschwindigkeit des Verblassens niemals 0 ist.
 
        particle[loop].fade=float(rand()%100)/1000.0f+0.003f;        // zufällige Geschwindigkeit des Verblassens

Nun, da unser Partikel aktiv ist und wir ihm Leben gegeben haben, wird es Zeit ihm Farbe zu geben. Für den anfänglichen Effekt wollen wir, dass jeder Partikel eine andere Farbe hat. Ich weise jedem Partikel eine der 12 Farben zu, die wir anfangs in unserer Farbtabelle erzeugt haben. Die Mathematik ist simpel. Wir nehmen unsere Schleifenvariable und multiplizieren sie mit der Anzahl der Farben in unserer Farbtabelle dividiert durch die maximale Anzahl der Partikel (MAX_PARTICLES). Das verhindert, dass unser resultierender Farbwert größer als unsere maximale Anzahl an Farben (12) ist.

Ein paar kurze Beispiele: 900*(12/900)=12. 1000*(12/1000)=12, etc.
 
        particle[loop].r=colors[loop*(12/MAX_PARTICLES)][0];        // wähle rote Regenbogen Farbe
        particle[loop].g=colors[loop*(12/MAX_PARTICLES)][1];        // wähle rote Regenbogen Farbe
        particle[loop].b=colors[loop*(12/MAX_PARTICLES)][2];        // wähle rote Regenbogen Farbe

Nun setzen wir die Richtung, in die sich jeder Partikel bewegt, zusammen mit der Geschwindigkeit. Wir multiplizieren die Ergebnisse mit 10.0f um eine spektakuläre Explosion zum Anfang des Programms zu erzeugen.

Wir erhalten entweder einen positivien oder negativen Zufallswert. Dieser Wert wir verwendet um den Partikel in eine zufällige Richtung mit einer zufälligen Geschwindigkeit zu bewegen.
 
        particle[loop].xi=float((rand()%50)-26.0f)*10.0f;        // zufällige Geschwindigkeit auf der X Achse
        particle[loop].yi=float((rand()%50)-25.0f)*10.0f;        // zufällige Geschwindigkeit auf der Y Achse
        particle[loop].zi=float((rand()%50)-25.0f)*10.0f;        // zufällige Geschwindigkeit auf der Z Achse

Zu letzt setzen wir die Menge an Gravitation die auf jeden Partikel angewandet werden soll. Anders als die reguläre Gravitation, die die Dinge nur nach unten zieht, zieht unsere Gravitation nach oben, unten, links, rechts, vorne oder hinten. Anfangs wollen wir mittel starke Gravitation haben, die nach unten zieht. Um das zu erreichen, setzen wir xg auf 0.0f. Kein nach links oder rechts ziehen auf der X-Ebene. Wir setzen yg auf -0.8f. Das erzeugt ein mittel starkes Ziehen nach unten. Wenn der Wert positiv war, würde er nach oben ziehen. Wir wollen nicht, dass die Partikel nach vorne oder hinter abgelenkt werden, weshalb wir zg auf 0.0f setzen.
 
        particle[loop].xg=0.0f;                        // Setze horizontale Ablenkung auf null
        particle[loop].yg=-0.8f;                    // Setze verticale Ablenkung nach unten
        particle[loop].zg=0.0f;                        // Setze Ablenkung auf der Z Achse auf null
    }
    return TRUE;                                // Initialisierung verlief OK
}

Nun zum spaßigen Teil. Der nächste Codeabschnitt ist der, wo wir die Partikel zeichnen, auf Gravitation überprüfen, etc. Es ist wichtig, dass Sie verstehen, was vor sich geht, lesen Sie also sehr vorsichtig :)

Wir resetten die Modelview Matrix nur einmal. Wir positionieren die Partikel mittels dem glVertex3f() Befehl anstatt einer Translation zu verwenden, auf diese Weise verändern wir die Modelview Matrix nicht, während wir unsere Partikel zeichnen.
 
int DrawGLScene(GLvoid)                                // Hier kommt der ganze Zeichnen-Kram hin
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);            // Lösche den Bildschirm und den Depth-Buffer
    glLoadIdentity();                            // Resette die aktuelle Modelview Matrix

Wir fangen mit der Erzeugung einer Schleife an. Diese Schleife aktualisiert jeden unserer Partikel.
 
    for (loop=0;loop<MAX_PARTICLES;loop++)                    // iteriere durch alle Partikel
    {

Das erste was wir überprüfen müssen, ist, ob der Partikel aktiv ist. Wenn er nicht aktiv ist, wird er nicht aktualisiert. In diesem Programm sind alle aktiv, die ganze Zeit. Aber in Ihren eigenen Programmen möchten Sie vielleicht bestimmte Artikel deaktivieren.
 
        if (particle[loop].active)                    // wenn der Partikel aktiv ist
        {

Die nächsten drei Variablen x, y und z sind temporäre Variablen, die wir verwenden, um unsere Partikel x, y und z Position zu speichern. Beachten Sie, dass wir zoom zur z Position addieren, so dass unsere Szene in den Screen hinein bewegt wird, basierend auf dem Wert, der in zoom gespeichert ist. particle[loop].x enthält unsere X-Position für den Partikel den wir zeichnen. particle[loop].y enthält unsere Y-Position unseres Partikels und particle[loop].z enthält die Z-Position.
 
            float x=particle[loop].x;                // ermittle unsere Partikel X Position
            float y=particle[loop].y;                // ermittle unsere Partikel Y Position
            float z=particle[loop].z+zoom;                // Partikel Z Position + Zoom

Nun, da wir die Partikel Position haben, können wir den Partikel färben. particle[loop].r enthält die Rot Intensität unseres Partikels, particle[loop].g enthält die Grün Intensität und particle[loop].b enthält die Blau Intensität. Beachten Sie, dass ich das Leben des Partikels als Alpha-Wert verwende. Wenn der Partikel stirbt, wird er mehr und mehr transparent, bis er eventuell nicht mehr existiert. Das ist der Grund warum das Partikel-Leben niemals mehr als 1.0f sein sollte. Wenn die Partikel länger brennen sollen, versuchen Sie die Ausblend-Geschwindigkeit zu reduzieren, so dass der Partikel nicht so schnell verblasst.
 
            // zeichne den Partikel mit unseren RGB Werten, der Partikel verblasst basierend auf dem Wert seines Lebens
            glColor4f(particle[loop].r,particle[loop].g,particle[loop].b,particle[loop].life);

Wir haben die Partikel Position und die Farbe ist gesetzt. Alles was wir nun machen müssen, ist, unsere Partikel zu zeichnen. Anstatt einer texturierten Quad zu zeichnen, habe ich mich dafür entschieden, einen texturierten Triangle Strip zu verwenden, um die Programmgeschwindigkeit zu erhöhen. Die meisten 3D Karten können Dreiecke viel schneller zeichnen als sie Quads zeichnen können. Einige 3D Karten konvertieren den Quad in zwei Dreiecke für Sie, aber einige machen das nicht. Deshalb machen wir die Arbeit selber. Wir fangen damit an, OpenGL mitzuteilen, dass wir einen Triangle Strip zeichnen wollen.
 
            glBegin(GL_TRIANGLE_STRIP);                // erzeuge Quad aus einem Triangle Strip


Direkt aus dem Red Book zitiert: Ein Triangle Strip zeichnet eine Folge von Dreiecken (dreiseitige Polygone) unter Verwendung der Vertices V0, V1, V2, dann V2, V1, V3 (beachten Sie die Reihenfolge), dann V2, V3, V4, und so weiter. Die Reihenfolge stellt sicher, dass die Dreiecke alle mit der selben Orientierung gezeichnet werden, so dass der Strip korrekt den Teil einer Oberfläche formen kann. Die Erhaltung der Reihenfolge ist für einige Operationen wichtig, wie zum Beispiel beim Culling. Es müssen mindestens 3 Punkte sein, damit was gezeichnet wird.

So wird das erste Dreieck mit den Vertices 0, 1 und 2 gezeichnet. Wenn Sie sich das Bild ansehen, sehen Sie, dass die Vertex Punkte 0, 1 und 2 tatsächlich das erste Dreieck bilden (oben rechts, oben links, unten rechts). Das zweite Dreieck wird mit den Vertices 2, 1 und 3 gezeichnet. Erneut, wenn Sie sich das Bild anschauen, die Vertices 2, 1 und 3 bilden das zweite Dreieck (unten rechts, oben link, oben links). Beachten Sie, dass beide Dreiecke mit der selben Windung gezeichnet werden (gegen den Uhrzeigersinn). Ich habe schon ein paar Webseiten gesehen, die behaupten, dass jedes zweites Dreieck sich in die andere Richtung windet. Das ist nicht der Fall. OpenGL ordnet die Vertices neu an, um sicherzustellen, dass alle Dreiecke die selbe Windung haben!

Es gibt zwei gute Gründe, um Triangle Strips zu verwenden. Erstens, nachdem die ersten drei Vertices für das Anfangs-Dreieck spezifziert wurden, müssen Sie nur jeweils einen weiteren Punkt spezifizieren, für jedes weitere Dreieck. Dieser Punkt wird mit den zwei vorherigen Vertices kombiniert, um ein Dreieck zu erzeugen. Zweitens, bei der Verminderung der Datenmenge, die zur Erzeugung eines Dreiecks benötigt werden, wird Ihr Programm schneller laufen und die Menge des Codes oder der Daten, die zum zeichnen eines Objektes benötigt werden, werden drastisch reduziert.

Anmerkung: Die Anzahl der Dreiecke, die Sie auf dem Screen sehen, ist die Anzahl der Vertices, die Sie spezifiziert haben minus 2. Im folgenden Code haben wir 4 Vertices und wir sehen 2 Dreiecke.
 
                glTexCoord2d(1,1); glVertex3f(x+0.5f,y+0.5f,z); // oben rechts
                glTexCoord2d(0,1); glVertex3f(x-0.5f,y+0.5f,z); // oben links
                glTexCoord2d(1,0); glVertex3f(x+0.5f,y-0.5f,z); // unten rechts
                glTexCoord2d(0,0); glVertex3f(x-0.5f,y-0.5f,z); // unten links

Zu letzt teilen wir OpenGL mit, dass wir fertig mit dem Zeichnen unseres Triangle Strips sind.
 
            glEnd();                        // fertig mit dem Erzeugen des Triangle Strip

Nun können wir den Partikel bewegen. Die folgende Mathematik mag seltsam aussehen, aber nochmal, sie ist einfach. Als erstes nehmen wir die aktuelle X-Position. Dann addieren wir den X-Bewegungs-Wert zum Partikel dividieren durch slowdown mal 1000. Wenn unser Partikel also im Zentrum des Screens auf der X-Achse (0) wäre, unsere Bewegung-Variable (xi) für die X-AChse +10 (was uns nach rechts bewegt) und slowdown gleich 1, würden wir uns um 10/(1*1000) oder 0.01f bewegen. Wenn wir slowdown auf 2 erhöhen, würden wir uns nur um 0.005f bewegen. Hoffentlich hilft Ihnen das beim Verständnis, wie slowdown arbeitet.

Das ist auch der Grund, warum der Startwerte mit 10.0f multipliziert wird, was die Pixel viel schneller bewegt und so eine Explosion erzeugt.

Wir benutzen die selbe Formel für die Y und Z Achse, um den Partikel über den Screen zu bewegen.
 
            particle[loop].x+=particle[loop].xi/(slowdown*1000);    // Bewegung auf der X Achse um mit X Geschwindigkeit
            particle[loop].y+=particle[loop].yi/(slowdown*1000);    // Bewegung auf der Y Achse um mit Y Geschwindigkeit
            particle[loop].z+=particle[loop].zi/(slowdown*1000);    // Bewegung auf der Z Achse um mit Z Geschwindigkeit

Nachdem wir berechnet haben, wo die Partikel als nächstes hinbewegt werden soll, müssen wir die Gravitation oder den Widerstand anwenden. In der ersten folgenden Zeile machen wir das, indem wir unseren Widerstand (xg) zur Geschwindigkeit addieren, mit der wir uns bewegen (xi).

Sagen wir, wir bewegen uns mit einer Geschwindigkeit von 10 und unser Widerstand ist 1. Jedes Mal wenn unser Partikel gezeichnet werden würde, würde der Widerstand darauf angewendet werden. Wenn er nun das zweite Mal gezeichnet wird, würde der Widerstand zum tragen kommen und unsere Bewegungsgeschwindigkeit würde von 10 auf 9 fallen. Das lässt den Partikel etwas langsamer werden. Wenn der Partikel das dritte Mal gezeichnet wird, kommt der Widerstand erneut zum tragen und unsere Bewegungsgeschwindigkeit würde auf 8 fallen. Wenn der Partikel länger als 10 Mal gezeichnet wird, wird er eventuell in die entgegengesetze Richtung bewegen, da die Bewegungsgeschwindigkeit zum negativen Wert wird.

Auf die selbe Weise wird der Widerstand auf die Y und Z Bewegungsgeschwindigkeit angewandt, wie es bei der x Bewegungsgeschwindigkeit getan wird.
 
            particle[loop].xi+=particle[loop].xg;            // beachte die Ablenkung auf der X-Achse
            particle[loop].yi+=particle[loop].yg;            // beachte die Ablenkung auf der Y-Achse
            particle[loop].zi+=particle[loop].zg;            // beachte die Ablenkung auf der Z-Achse

Die nächste Zeile nimmt etwas Leben vom Partikel weg. Wenn wir das nicht machen würden, würde der Partikel niemals verbrennen. Wir nehmen das aktuelle Leben des Partikels und subtrahieren den Fade-Wert für diesen Partikel. Jeder Partikel hat einen anderen Fade-Wert, so dass alle mit einer anderen Geschwindigkeit ausbrennen.
 
            particle[loop].life-=particle[loop].fade;        // Reduziert das Partikel Leben um 'Fade'

Nun überprüfen wir, ob der Partikel immer noch am Leben ist, nachdem ihm etwas Leben genommen wurde.
 
            if (particle[loop].life// wenn der Partikel ausgebrannt ist
            {

Wenn der Partikel tod (ausgebrannt) ist verjüngen wir ihn wieder. Wir machen das, indem wir ihm einen neues volles Leben und eine neuen Fade-Geschwindigkeit geben.
 
                particle[loop].life=1.0f;                // gebe ihm ein neues Leben
                particle[loop].fade=float(rand()%100)/1000.0f+0.003f;    // zufälliger Fade Wert

Wir resetten ebenfalls die Partikel Position auf das Zentrum des Screens. Wir machen das, indem wir die x, y und Z Positionen des Partikels auf 0 resetten.
 
                particle[loop].x=0.0f;                    // zentriere auf der X-Achse
                particle[loop].y=0.0f;                    // zentriere auf der Y-Achse
                particle[loop].z=0.0f;                    // zentriere auf der Z-Achse

Nachdem der Partikel auf das Zentrum des Screens resettet wurde, geben wir ihm eine neue Bewegungsgeschwindigkeit / Richtung. Beachten Sie, dass ich die maximale und minimale Geschwindigkeit, mit der sich der Partikel bewegen kann, auf einen zufälligen Wert von 50 auf 60 erhöht habe, aber diesmal multiplizieren wir die Bewegungsgeschwindigkeit nicht mit 10. Wir wollen diesmal keine Explosion haben, wir wollen Partikel haben, die sich langsamer bewegen.

Beachten Sie auch, dass ich xspeed zu der X-Achsen Bewegungsgeschwindigkeit addiere und yspeed zu der Y-Achse Bewegungsgeschwindigkeit. Das gibt uns die Kontrolle über die Richtung, in die der Partikel sich später im Programm bewegt.
 
                particle[loop].xi=xspeed+float((rand()%60)-32.0f);    // X Achsen Geschwindigkeit und Richtung
                particle[loop].yi=yspeed+float((rand()%60)-30.0f);    // Y Achsen Geschwindigkeit und Richtung
                particle[loop].zi=float((rand()%60)-30.0f);        // Z Achsen Geschwindigkeit und Richtung

Als letztes weisen wir dem Partikel eine neue Farbe zu. Die Variable col enthält eine Zahl zwischen 0 und 11 (12 Farben). Wir benutzen diese Variable, um die Rot, Grün und Blau Intensitäten in unserer Farbtabelle nachzuschlagen, die wir am Anfang erzeugt haben. Die folgende erste Zeile setzt die Rot-Intensität (r) auf den Rot-Wert, der in colors[col][0] gespeichert ist. Wenn col also 0 wäre, würde die Rot-Intensität gleich 1.0f sein. Die Grün und Blau-Werte werden auf die selbe Weise eingelesen.

Wenn Sie immer noch nicht verstanden haben, warum ich den Wert 1.0f für die Rot-Internsität erhalte, wenn col gleich 0 ist, dann exkläre ich es nochmal etwas genauer. Schauen Sie sich noch mal den Anfang des Programms an. Suchen Sie nach der Zeile: static GLfloat colors[12][3]. Beachten Sie, dass es drei Gruppen zu je 3 Zahlen gibt. Die erste der drei Zahlen ist die Rot-Intensität. Der zweite Wert ist die Grün-Intensität und der drittte Wert ist die Blau-Intensität. Die folgenden [0], [1] und [2] repräsentieren den 1sten, 2ten und 3ten Wert, die ich gerade erwähnt habe. Wenn col gleich 0 ist, wollen wir uns die erste Gruppe anschauen. 11 ist die letzte Gruppe (12te Farbe).
 
                particle[loop].r=colors[col][0];            // wähle Rot aus der Farbtabelle
                particle[loop].g=colors[col][1];            // wähle Grün aus der Farbtabelle
                particle[loop].b=colors[col][2];            // wähle Blau aus der Farbtabelle
            }

Die folgende Zeile kontrolliert wieviel Gravitation nach oben wirkt. Indem die 8 auf dem Zahlenblock gedrückt wird, inkrementieren wir die yg (y Gravitation) Variable. Das erzeugt eine Ablenkung nach oben. Dieser Code ist hier in dem Programm plaziert, da es einfacher ist, die Gravitation dank der Schleife auf alle Partikel wirken zu lassen. Wenn der Code außerhalb der Schleife wäre, müssten wir eine weitere erzeugen, um die selbe Aufgabe zu machen, deshalb machen wir das hier.
 
            // Wenn Zahlenblock 8 und Y Gravitation kleiner als 1.5 erhöhe Ablenkung nach oben
            if (keys[VK_NUMPAD8] && (particle[loop].yg


Diese Zeile hat genau den entgegengesetzten Effekt. Indem die 2 auf dem Nummernblock gedrückt wird, vermindern wir yg, was eine stärkere Ablenkung nach unten zu folge hat.



// Wenn Zahlenblock 2 und Y Gravitation größer als -1.5 erhöhe Ablenkung nach unten if (keys[VK_NUMPAD2] && (particle[loop].yg>-1.5f)) particle[loop].yg-=0.01f; 

Nun modifizieren wir die Ablenkung nach rechts. Wenn die 6 auf dem Zahlenblock gedrückt wurde, erhöhen wir die Ablenkung nach rechts.
 
            // Wenn Zahlenblock 6 und X Gravitation kleiner als 1.5 erhöhe Ablenkung nach rechts
            if (keys[VK_NUMPAD6] && (particle[loop].xg


Zu letzt, wenn die 4 auf dem Zahlenblock gedrückt wurde, wird unser Partikel mehr nach links abgelenkt. Mit diesen Taste erreichen wir ein paar wirklich coole ERgebnisse. Zum Beispiel können Sie einen Strom von Partikeln direkt in die Luft schießen. Indem Sie etwas Gravitation nach unten einfügen, können Sie diesen Partikelstrom in eine Wasserfontaine verwandeln!



// Wenn Zahlenblock 4 und X Gravitation größer als -1.5 erhöhe Ablenkung nach links if (keys[VK_NUMPAD4] && (particle[loop].xg>-1.5f)) particle[loop].xg-=0.01f; 

Ich habe dieses Codestück nur zum Spass eingefügt. Mein Bruder meinte, die Explosion wäre ein cooler Effekt :) Indem der Tabulator gedrückt wird, werden alle Partikel im Zentrum des Screens resettet. Die Bewegungsgeschwindigkeit wird erneut mit 10 multipliziert, was eine große Explosion der Partikel erzeugt. Nachdem die Partikel verblasst sind, wird der original Effekt wieder auftreten.
 
            if (keys[VK_TAB])                        // Tabulator verursacht Explosion
            {
                particle[loop].x=0.0f;                    // Zentriere auf der X Achse
                particle[loop].y=0.0f;                    // Zentriere auf der Y Achse
                particle[loop].z=0.0f;                    // Zentriere auf der Z Achse
                particle[loop].xi=float((rand()%50)-26.0f)*10.0f;    // zufällige Geschwindigkeit auf der X Achse
                particle[loop].yi=float((rand()%50)-25.0f)*10.0f;    // zufällige Geschwindigkeit auf der Y Achse
                particle[loop].zi=float((rand()%50)-25.0f)*10.0f;    // zufällige Geschwindigkeit auf der Z Achse
            }
        }
    }
    return TRUE;                                    // alles verlief OK
}

Der Code in KillGLWindow(), CreateGLWindow() und WndProc() hat sich nicht geändert, weshalb wir gleich zu WinMain() springen. Ich werde den gesamten Codeabschnitt nochmal niederschreiben, um es einfacher zu machen, dem Code zu folgen.
 
int WINAPI WinMain(    HINSTANCE    hInstance,            // Instanz
            HINSTANCE    hPrevInstance,            // vorherige Instanz
            LPSTR        lpCmdLine,            // Kommandozeilen Parameter
            int        nCmdShow)            // Fenster Anzeige Status
{
    MSG    msg;                            // Windows Nachrichten Struktur
    BOOL    done=FALSE;                        // Bool Variable um die Schleife zu beenden

    // Frage den Benutzer, in welchen Modus er starten will
    if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
    {
        fullscreen=FALSE;                    // Fenster-Modus
    }

    // erzeuge unser OpenGL Fenster
    if (!CreateGLWindow("NeHe's Particle Tutorial",640,480,16,fullscreen))
    {
        return 0;                        // Beende, wenn Fenster nicht erzeugt wurde
    }

Das ist unsere erste Änderung an WinMain(). Ich habe etwas Code hinzugefügt, um zu überprüfen, ob der Benutzer sich entschieden hat, im Fullscreen-Modus oder Fenster-Modus zu starten. Wenn er sich für den Fullscreen-Modus entschieden hat, ändere ich die Variable slowdown auf 1.0f anstatt von 2.0f. Sie können dieses Codestück auslassen wenn Sie wollen. Ich habe den Code eingefügt, um den das ganze im Fullscreen-Modus auf meiner 3dfx etwas schneller zu machen (läuft VIEL langsamer als im Fenstermodus, aus irgendwelchen Gründen).
 
    if (fullscreen)                            // sind wir im Fullscreen Modus ( HINZUGEFÜGT )
    {
        slowdown=1.0f;                        // mache die Partikel schneller ( HINZUGEFÜGT )
    }

    while(!done)                            // Schleife die so lange läuft, bis done=TRUE
    {
        if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))        // Wartet eine Nachricht?

        {
            if (msg.message==WM_QUIT)            // Haben wir eine Nachricht zum beenden erhalten?
            {
                done=TRUE;                // Wenn ja done=TRUE
            }
            else                        // Wenn nicht, bearbeite die Fenster-Nachrichten
            {
                TranslateMessage(&msg);            // Übersetze die Nachricht 
                DispatchMessage(&msg);            // bearbeite die Nachricht
            }
        }
        else                            // Wenn da keine Nachrichten sind
        {
            if ((active && !DrawGLScene()) || keys[VK_ESCAPE])    // aktualisiere View nur, wenn aktiv
            {
                done=TRUE;                // ESC oder DrawGLScene Signalisiert, dass Beendet werden soll
            }
            else                        // Es ist noch nicht Zeit zum beenden, zeichne Screen neu
            {
                SwapBuffers(hDC);            // Swap Buffers (Double Buffering)

Ich war etwas schludrig mit dem nächsten Codestück. Normalerweise fasse ich nicht alles in einer Zeile zusammen, aber es lässt den Code sauberer aussehen :)

Die folgeden Zeile überprüft, ob die + Taste auf dem Zahlenblock gedrückt wurde. Wenn dem so ist und slowdown größer als 1.0f ist, dekrementieren wir slowdown um 0.01f. Das lässt den Partikel schneller werden. Erinnern Sie sich daran, was ich im obigen Code über slowdown erzählt habe und wie es die Geschwindigkeit beeinflusst, mit der der Partikel sich fortbewegt.
 
                if (keys[VK_ADD] && (slowdown>1.0f)) slowdown-=0.01f;        // mache Partikel schneller

Diese Zeile überprüft, ob die - Taste auf dem Zahlenblock gedrückt wurde. Wenn dem so ist und slowdown kleiner als 4.0f ist, erhöhen wir den Wert von slowdow. Das lässt den Partikel langsamer werden. Ich habe ein Limit von 4.0f gesetzt, da ich sie nicht langsamer bewegen lassen wollte. Sie können die Minimum und Maximum Geschwindigkeiten ändern, wann Sie wollen :)
 
                if (keys[VK_SUBTRACT] && (slowdown// verlangsame Partikel

Die folgende Zeile überprüft, ob Bild hoch gedrückt wurde. Wenn dem so ist, wird die Variable zoom erhöht. Das bringt lässt die Partikel dazu sich näher zu uns hin zu bewegen.
 
                if (keys[VK_PRIOR]) zoom+=0.1f;        // Zoom In

Diese Zeile hat den gegenteiligen Effekt. Indem Bild runter gedrückt wird, wird zoom vermindert und die Szene bewegt sich weiter in den Screen hinein. Das erlaubt uns, mehr vom Screen zu sehen, macht die Partikel aber kleiner.
 
                if (keys[VK_NEXT]) zoom-=0.1f;        // Zoom Out

Der nächste Codeabschnitt überprüft, ob die Enter-Taste gedrückt wurde. Wenn sie es wurde und 'gehalten' wird, lassen wir es den Computer wissen, indem wir rp auf true setzen. Dann wechseln wir den Regenbogenmodus. Wenn rainbow gleich true war, wird es false. Wenn es false war, wird es true. Die letzte Zeile überprüft, ob die Enter-Taste wieder losgelassen wurde. Wenn sie es wurde, wird rp auf false gesetzt, was dem Computer mitteilt, dass sie nicht länger gedrückt wird.
 
                if (keys[VK_RETURN] && !rp)        // Enter Taste gedrückt
                {
                    rp=true;            // Setze Flag das uns mitteilt, dass sie gedrückt wird
                    rainbow=!rainbow;        // wechsel Regenbeogenmodus an / aus
                }
                if (!keys[VK_RETURN]) rp=false;        // wenn Enter losgelassen wird, lösche Flag

Der folgende Code ist etwas verwirrend. Die erste Zeile überprüft, ob die Leertaste gedrückt wurde und nicht festgehalten wird. Sie überprüft ebenso, ob der Regenbogenmodus an ist, und wenn dem so ist, wird überprüft, ob die Variable delay größer als 25 ist. delay ist ein Zähler, den ich zur Erzeugung des Regenbogen-Effeks verwende. Wenn Sie die Farbe in jedem Frame ändern würden, würden die Partikel alle eine andere Farbe erhalten. Indem eine Verzögerung (delay) erzeugt wird, erhält eine Gruppe von Partikeln eine Farbe, bevor die Farbe auf etwas anderes gewechselt wird.

Wenn die Leertaste gedrückt wurde oder raindbow an ist und delay größer als 25, dann wird die Farbe geändert!
 
                if ((keys[' '] && !sp) || (rainbow && (delay>25)))    // Leertaste oder Regenbogenmodus
                {

Die folgende Zeile wurde hinzugefügt, damit der Regenbogenmodus ausgeschaltet werden kann, wenn die Leertaste gedrückt wurde. Wenn wir den Regenbogenmodus nicht ausschalten würden, würden die Farben sich ständig wechseln, bis die Enter-Taste erneut gedrückt werden würde. Es macht Sinn, dass der Benutzer die Leertaste drückt, anstatt Enter, wenn er selbst durch die Farben gehen will.
 
                    if (keys[' ']) rainbow=false;    // wenn Leertaste gedrückt wurde, deaktiviere Regenbogenmodus

Wenn Leertaste gedrückt wurde oder der Regenbogenmodus angeschaltet und delay größer 25 ist, lassen wir den Computer wissen, dass die Leertaste gedrückt wurde, indem wir sp auf true setzen. Dann setzen wir delay zurück auf 0, so dass wir erneut bis 25 zählen können. Zu letzt erhöhen wir die Variable col, so dass die Farbe zur nächsten Farbe in der Farbtabelle wechselt.
 
                    sp=true;            // Setze Flag, das uns mitteilt das Leertaste gedrückt wurde
                    delay=0;            // Resette Delay
                    col++;                // ändere die Partikelfarbe

Wenn color größer als 11 ist, resetten wir zurück auf null. Wenn wir col nicht zurück auf null setzen, würde unser Programm versuchen die 13te Farbe zu finden. Wir haben aber nur 12 Farben! Der Versuch Informationen über eine Farbe zu ermitteln, die nicht existiert, würde das Programm crashen.
 
                    if (col>11) col=0;        // wenn Color zu groß ist, resette
                }

Zu letzt, wenn Leertaste nicht länger gedrückt wird, lassen wir es den Computer wissen, indem wir die Variable sp auf false setzen.
 
                if (!keys[' '])    sp=false;        // wenn Leertaste losgelassen wurde, lösche das Flag

Nun zur Kontrolle der Partikel. Erinnern Sie sich das wir anfangs 2 Variablen am Anfang unseres Programms erzeugt haben? Eine hieß xspeed und eine yspeed. Erinnern Sie sich auch daran, dass, nachdem der Partikel ausgebrannt ist, wir ihm eine neue Geschwindigkeit geben und die neue Geschwindigkeit entweder zu xspeed oder yspeed addieren. Dammit können wir die Richtung der Partikel beeinflussen, in die sich die Partikel bewegen, wenn sie das erste mal erzeugt werden.

Zum Beispiel. Sagen wir, unser Partikel hat eine Bewegungsgeschwindigkeit von 5 auf der X-Achse und 0 auf der Y-Achse. Wenn wir xspeed solange vermindern, bis es -10 wäre, würden wir uns mit einer Geschwindigkeit von -10 (xspeed) + 5 (ursprüngliche Bewegungsgeschwindigkeit) bewegen. So würden wir uns statt mit 10 nach rechts, eher mit -5 nach links bewegen. Verstanden?

Wie dem auch sei. Die folgende Zeile überprüft, ob die Pfeil nach oben Taste gedrückt wurde. Wenn sie es wurde, wird yspeed erhöht. Das lässt unsere Partikel nach oben wandern. Die Partikel werden mit einer maximalen Geschwindigkeit von 200 nach oben wandern. Alles was schneller ist, sieht nicht gut aus.
 
                // Wenn Pfeil hoch und Y Geschwindigkeit kleiner als 200 ist, inkrementiere Aufwärts Geschwindigkeit
                if (keys[VK_UP] && (yspeed


Diese Zeile überprüft, ob die Pfeil nach unten Taste gedrückt wurde. Wenn sie es ist, wird yspeed dekrementiert. Das lässt die Partikel nach unten bewegen. Erneut wird eine maximale Geschwindigkeit nach unten von 200 erzwungen.



// wenn Pfeil runter und Y Geschwindigkeit größer als -200 ist, dann inkrementiere Geschwindigkeit nach unten if (keys[VK_DOWN] && (yspeed>-200)) yspeed-=1.0f; 

Nun überprüfen wir, ob die rechte Pfeiltaste gedrückt wurde. Wenn sie es ist, wird xspeed inkrementiert. Das lässt die Partikel nach rechts driften. Eine maximale Geschwindigkeit von 200 wird erzwungen.
 
                // wenn rechter Pfeil und X Geschwindigkeit kleiner als 200, dann inkrementiere Geschwindikeit nach rechts
                if (keys[VK_RIGHT] && (xspeed


Zu letzt überprüfen wir, ob die linke Pfeiltaste gedrückt wurde. Wenn sie es ist... Sie wissen es bereits... wird xspeed dekrementiert und die Partikel beginnen sich nach links zu bewegen. Maximale Geschwindigkeit von 200 wird erzwungen.



// wenn linker Pfeil und X Geschwindigkeit größer als -200, dann inkrementiere Geschwindikeit nach links if (keys[VK_LEFT] && (xspeed>-200)) xspeed-=1.0f; 

Als letztes müssen wir die Variable delay inkrementieren. Wie ich bereits oben erwähnt habe, wird delay dazu verwendet, um zu kontrollieren, wie schnell die Farben wechseln, wenn man sich im Regenbogenmodus befindet.
 
                delay++;            // Inkrementiere Regenbogen-Modus-Farben-Zyklus-Verzögerungs-Zähler

Wie in den vorherigen Tutorials, stellen wir sicher, dass der Titel des Fensters korrekt ist.
 
                if (keys[VK_F1])        // Wurde F1 gedrückt?
                {
                    keys[VK_F1]=FALSE;    // Wenn ja, setze Taste auf FALSE
                    KillGLWindow();        // Kill unser aktuelles Fenster
                    fullscreen=!fullscreen;    // Wechsel zwischen Fullscreen und Fester-Modus
                    // Erzeuge unser OpenGL Fenster erneut
                    if (!CreateGLWindow("NeHe's Particle Tutorial",640,480,16,fullscreen))
                    {
                        return 0;    // Beenden, wenn das Fenster nicht erzeugt wurde
                    }
                }
            }
        }
    }
    // Shutdown
    KillGLWindow();                        // Kill das Fenster
    return (msg.wParam);                    // Beende das Programm
}

In dieser Lektion habe ich so detailiert wie möglich versucht, alle nötigen Schritte für ein einfaches aber eindrucksvolles Partikelsystem zu beschreiben. Dieses Partikelsystem kann in Ihren eigenen Spielen verwendet werden, um Effekte wie Feuer, Wasser, Schnee, Explosionen, fallende Sterne und mehr zu erzeugen. Der Code kann einfach modifziert werden, um mehr Parameter und neue Effekte (Feuerwerk zum Beispiel) zu händeln.

Dank an Richard Nutman, der vorgeschlagen hat, die Partikel mittels glVertex3f() zu positionieren, anstatt die Modelview Matrix zu resetten und dann jedes einzelne Partikel erneut mit glTranslatef() zu positionieren. Beide Methode sind effektiv, aber diese Methode reduziert die Menge der Arbeit, die der Computer leisten muss, bevor jeder Partikel gezeichnet wird, weshalb das Programm dann sogar schneller läuft.

Dank an Antoine Valentim, der vorgeschlagen hat Triangle Strips zu benutzen, um das Programm zu beschleunigen und einen neuen Befehl in diesem Tutorial vorzustellen. Das Feedback zu diesem Tutorial war großartig, ich weiß das zu schätzen!

Ich hoffe Sie haben dieses Tutorial genossen. Wenn Sie Verständnisprobleme hatten oder einen Fehler in diesem Tutorial gefunden haben, lassen Sie es mich bitte wissen. Ich möchte die besten verfügbaren Tutorials machen. Ihr Feedback ist also wichtig!

Jeff Molofee (NeHe)

* DOWNLOAD Visual C++ Code für diese Lektion.

* DOWNLOAD Borland C++ Builder 6 Code für diese Lektion. ( Conversion by Christian Kindahl )
* DOWNLOAD C# Code für diese Lektion. ( Conversion by Brian Holley )
* DOWNLOAD Code Warrior 5.3 Code für diese Lektion. ( Conversion by Scott Lupton )
* DOWNLOAD Cygwin Code für diese Lektion. ( Conversion by Stephan Ferraro )
* DOWNLOAD Delphi Code für diese Lektion. ( Conversion by Michal Tucek )
* DOWNLOAD Dev C++ Code für diese Lektion. ( Conversion by Dan )
* DOWNLOAD Euphoria Code für diese Lektion. ( Conversion by Evan Marshall )
* DOWNLOAD Game GLUT Code für diese Lektion. ( Conversion by Milikas Anastasios )
* DOWNLOAD Irix Code für diese Lektion. ( Conversion by Dimitrios Christopoulos )
* DOWNLOAD Java Code für diese Lektion. ( Conversion by Jeff Kirby )
* DOWNLOAD Jedi-SDL Code für diese Lektion. ( Conversion by Dominique Louis )
* DOWNLOAD JoGL Code für diese Lektion. ( Conversion by Irene Kam )
* DOWNLOAD LCC Win32 Code für diese Lektion. ( Conversion by Robert Wishlaw )
* DOWNLOAD Linux Code für diese Lektion. ( Conversion by Ken Rockot )
* DOWNLOAD Linux/GLX Code für diese Lektion. ( Conversion by Mihael Vrbanec )
* DOWNLOAD Linux/SDL Code für diese Lektion. ( Conversion by Ti Leggett )
* DOWNLOAD LWJGL Code für diese Lektion. ( Conversion by Mark Bernard )
* DOWNLOAD Mac OS Code für diese Lektion. ( Conversion by Owen Borstad )
* DOWNLOAD Mac OS X/Cocoa Code für diese Lektion. ( Conversion by Bryan Blackburn )
* DOWNLOAD MASM Code für diese Lektion. ( Conversion by Christophe )
* DOWNLOAD Visual C++ / OpenIL Code für diese Lektion. ( Conversion by Denton Woods )
* DOWNLOAD Pelles C Code für diese Lektion. ( Conversion by Pelle Orinius )
* DOWNLOAD Visual Basic Code für diese Lektion. ( Conversion by Edo )
* DOWNLOAD Visual Studio .NET Code für diese Lektion. ( Conversion by Grant James )

 
Deutsche Übersetzung: Joachim Rohde
Der original Text ist hier zu finden.
Die original OpenGL Tutorials stammen von NeHe's Seite.