Chtěli jste už někdy naprogramovat exploze, vodní fontány, planoucí hvězdy a jiné skvělé efekty, nicméně kódování částicových systémů bylo buď příliš těžké nebo jste vůbec nevěděli, jak na to? V této lekci zjistíte, jak vytvořit jednoduchý, ale dobře vypadající částicový systém. Extra přidáme duhové barvy a ovládání klávesnicí. Také se dozvíte, jak pomocí triangle stripu jednoduše vykreslovat velké množství trojúhelníků.
V této lekci vytvoříme téměř komplexní částicový systém. Jakmile jednou pochopíte, jak pracují zvládnete cokoli.
Předem upozorňuji, že dodneška jsem nikdy nic podobného vytvářel. Vždy jsem si myslel, že ty slavné a "komerční" částicové systémy jsou hodně komplexním kusem kódu.
Možná nebudete věřit, když píši, že tento kód je 100% původní. Neměl jsem před sebou žádné technické dokumentace. Onehdy jsem prostě přemýšlel a náhle se mi v hlavě vygenerovala spousta nápadů. Namísto uvažování o částici jako o pixelu přesunujícím se z bodu A do bodu B a dělajícím to či ono jsem každé přiřadil vlastní objekt (strukturu) reagující na prostředí kolem. Zapouzdřuje život, stárnutí, barvu, rychlost, gravitační závislosti a další vlastnosti.
Takže ačkoli program, podle mě, vypadá perfektně a pracuje přesně, jak jsem chtěl, možná není tou správnou cestou k vytváření částicových systémů. Osobně jsem se nestaral, jak dobře pracuje, ale ve svých projektech jsem ho mohl bez problémů používat. Jestliže jste typem lidí, "šťouralů", kteří potřebují poznat správnou cestu, zkuste strávit hodiny prohledáváním internetu. Toto bylo varování.
Použijeme kód z lekce 1. Symbolická konstanta definuje počet vytvářených částic. Rainbow zapíná/vypíná cyklování mezi duhovými barvami. Sp a rp předcházejí opakování kódu při delším stisku mezerníku a enteru.
#include <windows.h>// Hlavičkový soubor pro Windows
#include <stdio.h>// Hlavičkový soubor pro standardní vstup/výstup
#include <gl\gl.h>// Hlavičkový soubor pro OpenGL32 knihovnu
#include <gl\glu.h>// Hlavičkový soubor pro Glu32 knihovnu
#include <gl\glaux.h>// Hlavičkový soubor pro Glaux knihovnu
#define MAX_PARTICLES 1000// Počet vytvářených částic
HDC hDC = NULL;// Privátní GDI Device Context
HGLRC hRC = NULL;// Trvalý Rendering Context
HWND hWnd = NULL;// Obsahuje Handle našeho okna
HINSTANCE hInstance;// Obsahuje instanci aplikace
bool keys[256];// Pole pro ukládání vstupu z klávesnice
bool active = TRUE;// Ponese informaci o tom, zda je okno aktivní
bool fullscreen = TRUE;// Ponese informaci o tom, zda je program ve fullscreenu
bool rainbow = true;// Duhový efekt?
bool sp;// Stisknutý mezerník?
bool rp;// Stisknutý enter?
Následují pomocné proměnné. Slowdown kontroluje rychlost pohybu částic (čím vyšší číslo, tím pomaleji se pohybují). Xspeed a yspeed ovlivňují rychlost na jednotlivých osách. Jsou pouze jedním faktorem implementovaným kvůli ovládání klávesnicí. Zoom používáme pro přesun do/ze scény.
float slowdown=2.0f;// Zpomalení částic
float xspeed;// Základní rychlost na ose x
float yspeed;// Základní rychlost na ose y
float zoom=-40.0f;// Zoom
Loop využíváme především jako proměnnou cyklu, ve kterých inicializujeme a vykreslujeme částice. Col vychází ze slova color a značí barvu. Pomocí časovače delay při zapnutém duhovém módu cyklujeme mezi barvami.
Poslední proměnná je klasická textura. Rozhodl jsem se pro ni, protože vypadají mnohem lépe než jednobarevné body. Také si můžete vytvořit texturu ohně, sněhu, jakéhokoli objektu.
GLuint loop;// Řídící proměnná cyklů
GLuint col;// Vybraná barva
GLuint delay;// Zpoždění pro duhový efekt
GLuint texture[1];// Ukládá texturu
Následuje struktura definující vlastnosti částic. Obsahuje spoustu atributů, takže si je popíšeme. Pokud bude active rovno true částice bude aktivní a false logicky značí neaktivnost. V tomto programu se tato vlastnost nepoužívá, ale někdy jindy by mohla být užitečná. Life a fade definují, jak dlouho a jak jasně bude částice zobrazena. Od života (life) budeme odečítat stárnutí (fade). Na začátku je inicializujeme na random, stejně jako téměř všechny ostatní vlastnosti.
typedef struct// Vytvoří stukturu pro částici
{
bool active;// Aktivní?
float life;// Život
float fade;// Rychlost stárnutí
float r;// Červená složka barvy
float g;// Zelená složka barvy
float b;// Modrá složka barvy
float x;// X Pozice
float y;// Y Pozice
float z;// Z Pozice
float xi;// X směr a rychlost
float yi;// Y směr a rychlost
float zi;// Z směr a rychlost
Následující proměnné určují působení gravitace (každá ve své ose). Kladná xg značí působení doprava, záporná doleva. Směry jsou analogické ke směrům souřadnicových os.
float xg;// X gravitace
float yg;// Y gravitace
float zg;// Z gravitace
} particles;// Struktura částice
Dále deklarujeme pole datového typu particles (naše struktura) o velikosti MAX_PARTICLES a jménu particle.
particles particle[MAX_PARTICLES];// Pole částic
Inicializací pole barev si vytvoříme barevnou paletu. Každá z dvanácti položek obsahuje 3 RGB složky v rozmezí od červené do fialové.
static GLfloat colors[12][3]=// Barevná paleta
{
{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}
};
Do inicializačního kódu jsem oproti kódu z první lekce přidal loading textury, nastavení blendingu a zadání počátečních hodnot částic.
int InitGL(GLvoid)// Všechna nastavení OpenGL
{
if (!LoadGLTextures())// Nahraje textury
{
return FALSE;
}
glShadeModel(GL_SMOOTH);// Povolíme jemné stínování
glClearColor(0.0f,0.0f,0.0f,0.0f);// Černé pozadí
glClearDepth(1.0f);// Nastavení hloubkového bufferu
glDisable(GL_DEPTH_TEST);// Vypne hloubkové testování
glEnable(GL_BLEND);// Zapne blending
glBlendFunc(GL_SRC_ALPHA,GL_ONE);// Typ blendingu
glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);// Perspektiva
glHint(GL_POINT_SMOOTH_HINT,GL_NICEST);// Jemnost bodů
glEnable(GL_TEXTURE_2D);// Zapne mapování textur
glBindTexture(GL_TEXTURE_2D,texture[0]);// Vybere texturu
Inicializujeme jednotlivé částice. Začneme aktivováním. Pamatujte si, že naktivní nezobrazujeme a neaktualizujeme. Potom je oživíme. Nebyl jsem si jistý, zda je zhasínání (zprůhledňování) částic závislé na zkracování života, správnou cestou. Nicméně pracuje skvěle, tak co :-) Maximální život 1.0f dává nejjasnější vykreslení (viz. blending).
for (loop=0;loop<MAX_PARTICLES;loop++)// Inicializace částic
{
particle[loop].active=true;// Aktivace
particle[loop].life=1.0f;// Oživení
Na randomovou hodnotu nastavíme rychlost stárnutí a postupného zhasínání. Každým vykreslením se život (life) zkracuje o stárnutí (fade). Hodnotu 0 až 99 vydělíme 1000 a tím získáme velmi malé číslo. Aby rychlost stárnutí nikdy nebyla nulová, přičteme 0,003.
particle[loop].fade=float(rand()%100)/1000.0f+0.003f;// Rychlost stárnutí
Nastavíme barvu částic na některou z výše vytvořené palety. Matematika je jednoduchá: vezmeme řídící proměnnou cyklu a vynásobíme ji podílem počtu barev s celkovým počtem částic. Například při prvním průchodu bude loop = 0, po dosazení a výpočtu získáme 0*(12/1000)=0. Při posledním průchodu (loop = počet částic -1 = 999) vyjde 999*(12/1000)=11,988. Protože předáváme int, výsledek se ořeže na 11, což je poslední barva v paletě.
particle[loop].r=colors[loop*(12/MAX_PARTICLES)][0];// Červená
particle[loop].g=colors[loop*(12/MAX_PARTICLES)][1];// Zelená
particle[loop].b=colors[loop*(12/MAX_PARTICLES)][2];// Modrá
Inicializujeme směr a rychlost pohybu částic. Výpočet provedeme opět randomem, který pro počáteční efekt exploze násobíme deseti. Dostaneme kladná nebo záporná čísla určující směr a rychlost pohybu v jednotlivých osách.
particle[loop].xi=float((rand()%50)-26.0f)*10.0f;// Rychlost a směr pohybu na ose x
particle[loop].yi=float((rand()%50)-25.0f)*10.0f;// Rychlost a směr pohybu na ose y
particle[loop].zi=float((rand()%50)-25.0f)*10.0f;// Rychlost a směr pohybu na ose z
Nakonec nastavíme gravitační působení. Většinou gravitace strhává věci dolů, ale ta naše bude moci působit všemi směry. Na začátku ovšem klasicky dolů (yg = - 0,8).
particle[loop].xg=0.0f;// Gravitace na ose x
particle[loop].yg=-0.8f;// Gravitace na ose y
particle[loop].zg=0.0f;// Gravitace na ose z
}
return TRUE;
}
V další funkci se pokusíme o vykreslování, zajistíme působení gravitace ap. Matici ModelView resetujeme pouze jednou a to na začátku. Pozici částic tedy nebudeme určovat složitými posuny a rotacemi, ale pouze souřadnicemi předávanými funkci glVertex3f().
int DrawGLScene(GLvoid)// Vykreslování
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// Vymaže obrazovku a hloubkový buffer
glLoadIdentity();// Reset matice
for (loop=0;loop<MAX_PARTICLES;loop++)// Cyklus prochází každou částici
{
První věcí je zkontrolování, zda je částice aktivní, pokud ne nebudeme ji aktualizovat ani vykreslovat. Nicméně v tomto programu budou aktivní všechny.
if (particle[loop].active)// Pokud je částice aktivní
{
Následující tři proměnné x,y,z jsou spíše pomocné k zpřehlednění kódu. Všimněte si, že k pozici na ose z přičítáme zoom, čímž můžeme jednoduše měnit hloubku v obrazovce.
float x=particle[loop].x;// x pozice
float y=particle[loop].y;// y pozice
float z=particle[loop].z+zoom;// z pozice + zoom
Dále obarvíme částici její barvou. Jako alfa kanál (průhlednost) s výhodou využijeme život, který nabývá hodnot od 1.0f (plný) do 0.0f (smrt). Postupným stárnutím se tedy stává průhlednější až vybledne docela.
// Barva částice
glColor4f(particle[loop].r,particle[loop].g,particle[loop].b,particle[loop].life);
Máme pozici i barvu, takže přejdeme k vykreslení. Původně jsem chtěl použít otexturovaný čtverec, ale pak jsem se rozhodl pro otexturovaný "triangle strip". Většina grafických karet renderuje trojúhelníky mnohem rychleji než čtverce, protože se čtyřúhelníky často konvertují na dva trojúhelníky. K vykreslení klasickým způsobem bychom potřebovali 6 různých bodů, použitím triangle stripu stačí pouze čtyři. Nejprve tedy požádáme OpenGL o vykreslení triangle stripu.
glBegin(GL_TRIANGLE_STRIP);// Vytvoří obdélník pomocí triangle stripu
Triangle strip vykresluje sérii trojúhelníků užitím bodů V0, V1, V2, potom V2, v1, V3 (všimněte si pořadí), dále V2, V3, V4 atd. Tímto pořadím se zajistí, že se všechny vykreslí se stejnou orientací (viz. pořadí zadávání vrcholů), která je důležitá u některých operací, např. cullingu. Aby se něco vykreslilo musí být zadány alespoň tři body. Pro použití triangle stripu existují dva dobré důvody. První: po inicializaci prvního trojúhelníku stačí pro každý nový trojúhelník jenom jeden bod, který bude skombinován s body toho minulého. Druhý: odstraněním části kódu program poběží rychleji. Zdrojový kód bude kratší a přehlednější. Počet vykreslených trojúhelníku vykreslených na monitor bude o dva menší než počet zadaných bodů.
glTexCoord2d(1,1); glVertex3f(x+0.5f,y+0.5f,z);// Horní pravý
glTexCoord2d(0,1); glVertex3f(x-0.5f,y+0.5f,z);// Horní levý
glTexCoord2d(1,0); glVertex3f(x+0.5f,y-0.5f,z);// Dolní pravý
glTexCoord2d(0,0); glVertex3f(x-0.5f,y-0.5f,z);// Dolní levý
glEnd();// Ukončí triangle strip
Po vykreslení přichází na řadu aktualizace částice. Matematika může vypadat strašně, ale je krásně jednoduchá. Vezmeme pozici na konkrétní ose a přičteme k ní pohyb na této ose vydělený slowdown krát tisíc. Např. pokud bude částice uprostřed obrazovky (0,0,0), pohyb xi 10 a slowdown 1 přesuneme ji o 10/(1*1000) - do bodu 0.01f na ose x. Pokud bychom inkrementovali slowdown na 2 přesuneme se pouze na 0.005f. Toto je také důvod násobení startovní hodnoty desítkou. Body se po spuštění programu pohybují mnohem rychleji, takže vytvoří dojem exploze.
particle[loop].x+=particle[loop].xi/(slowdown*1000);// Pohyb na ose x
particle[loop].y+=particle[loop].yi/(slowdown*1000);// Pohyb na ose y
particle[loop].z+=particle[loop].zi/(slowdown*1000);// Pohyb na ose z
Po spočítání pohybu aplikujeme gravitační působení. Docílíme toho přičtením "gravitační síly" k rychlosti pohybu. Řekněme, že rychlost pohybu je 10 a gravitace o velikosti 1 působí v opačném směru. Každým překreslením se rychlost pohybu dekrementováním zpomalí. Po deseti překresleních částice změní směr.
particle[loop].xi+=particle[loop].xg;// Gravitační působení na ose x
particle[loop].yi+=particle[loop].yg;// Gravitační působení na ose y
particle[loop].zi+=particle[loop].zg;// Gravitační působení na ose z
Snížíme hodnotu života o stárnutí. Kdybychom toto nedělali nikdy by částice neshořela. Každá má nastavenu jinou rychlost stárnutí, tudíž nezemřou ve stejný časový okamžik.
particle[loop].life-=particle[loop].fade;// Sníží život o stárnutí
V této chvíli musíme otestovat, zda je po zestárnutí stále naživu.
if (particle[loop].life<0.0f)// Pokud zemřela
{
Pokud zemřela "reinkarnujeme" ji nastavením plného života a nové náhodné rychlosti stárnutí.
particle[loop].life=1.0f;// Nový život
particle[loop].fade=float(rand()%100)/1000.0f+0.003f;// Náhodné stárnutí
Resetujeme její pozici na střed obrazovky.
particle[loop].x=0.0f;// Vycentrování doprostřed obrazovky
particle[loop].y=0.0f;// Vycentrování doprostřed obrazovky
particle[loop].z=0.0f;// Vycentrování doprostřed obrazovky
Určíme novou rychlost a vlastně i směr. Všimněte si, že jsem zvětšil maximální a minimální rychlost z 50 na 60 oproti funkci InitGL(), ale tentokrát výsledek nenásobím deseti. Už nechceme žádné exploze, ale pomalejší pohyb. Z důvodu ovládání klávesnicí přičítáme k hodnotě i globální rychlost (xspeed, yspeed).
particle[loop].xi=xspeed+float((rand()%60)-32.0f);// Nová rychlost a směr
particle[loop].yi=yspeed+float((rand()%60)-30.0f);// Nová rychlost a směr
particle[loop].zi=float((rand()%60)-30.0f);// Nová rychlost a směr
Částici přiřadíme také novou barvu. Proměnná col ukládá číslo 0 až 11 (12 barev). Pomocí ní vybíráme červenou, zelenou a modrou intenzitu z palety vytvořené na začátku programu.
particle[loop].r=colors[col][0];// Vybere barvu z palety
particle[loop].g=colors[col][1];// Vybere barvu z palety
particle[loop].b=colors[col][2];// Vybere barvu z palety
}
Následující kód aktualizuje působení gravitace. Stisknutím 8 na klávesnici zvětšíme yg (y gravitaci) a částice bude tažena vzhůru. Tato testování jsou vložena do vykreslování z důvodu zjednodušení. Kdyby bylo umístěno někam jinam museli bychom vytvořit nový cyklus dělající úplně stejnou práci. Podobné postupy poskytují skvělé možnosti. Např. se můžete pokusíte o proud vody větrem vystřikující přímo vzhůru. Přidáním gravitace působící dolů vytvoříte fontánu vody.
// Pokud je stisknuta 8 a y gravitace je menší než 1.5
if (keys[VK_NUMPAD8] && (particle[loop].yg<1.5f)) particle[loop].yg+=0.01f;
// Pokud je stisknuta 2 a y gravitace je menší než -1.5
if (keys[VK_NUMPAD2] && (particle[loop].yg>-1.5f)) particle[loop].yg-=0.01f;
// Pokud je stisknuta 6 a x gravitace je menší než 1.5
if (keys[VK_NUMPAD6] && (particle[loop].xg<1.5f)) particle[loop].xg+=0.01f;
// Pokud je stisknuta 4 a x gravitace je menší než -1.5
if (keys[VK_NUMPAD4] && (particle[loop].xg>-1.5f)) particle[loop].xg-=0.01f;
Pro radost připíšeme malou "vychytávku". Můj bratr si myslel, že úvodní výbuch je skvělý efekt. Stisknutím klávesy TAB se všechny částice resetují do centra obrazovky. Rychlost se vynásobí deseti a tím vytvoří explozi.
if (keys[VK_TAB])// Způsobí výbuch
{
particle[loop].x=0.0f;// Vycentrování na střed obrazovky
particle[loop].y=0.0f;// Vycentrování na střed obrazovky
particle[loop].z=0.0f;// Vycentrování na střed obrazovky
particle[loop].xi=float((rand()%50)-26.0f)*10.0f;// Náhodná rychlost
particle[loop].yi=float((rand()%50)-25.0f)*10.0f;// Náhodná rychlost
particle[loop].zi=float((rand()%50)-25.0f)*10.0f;// Náhodná rychlost
}
}
}
return TRUE;// Všechno OK
}
Funkci WinMain napíši celou, protože je v ní celkem dost změn.
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
BOOL done=FALSE;
if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?", MB_YESNO|MB_ICONQUESTION) == IDNO)
{
fullscreen=FALSE;
}
if (!CreateGLWindow("NeHe's Particle Tutorial",640,480,16,fullscreen))
{
return 0;
}
Toto je první důležitá změna. Při rozhodnutí uživatele použít fullscreen změníme slowdown ze 2.0f na 1.0f. Tato úprava není až tak důležitá - lze ji vypustit. Slouží k urychlení fullscreenu - moje grafická karta pracuje v okně trochu rychleji. Nevím proč.
if (fullscreen)
{
slowdown=1.0f;
}
while(!done)
{
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
{
if (msg.message==WM_QUIT)
{
done=TRUE;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
else
{
if ((active && !DrawGLScene()) || keys[VK_ESCAPE])
{
done=TRUE;
}
else
{
DrawGLScene();
SwapBuffers(hDC);
Ošetříme vstup z klávesnice (+, -, PageUp, PageDown)
if (keys[VK_ADD] && (slowdown>1.0f)) slowdown-=0.01f;// Urychlení částic
if (keys[VK_SUBTRACT] && (slowdown<4.0f)) slowdown+=0.01f;// Zpomalení částic
if (keys[VK_PRIOR])zoom+=0.1f;// Přiblížení pohledu
if (keys[VK_NEXT])zoom-=0.1f;// Oddálení pohledu
V následujících řádcích testujeme stisk enteru, abychom zapnuli cyklování barvami.
if (keys[VK_RETURN] && !rp)// Stisk enteru
{
rp=true;// Nastaví příznak
rainbow = !rainbow;// Zapne/vypne duhový efekt
}
if (!keys[VK_RETURN]) rp=false;// Po uvolnění vypne příznak
Operace při tisku mezerníku mohou být trochu matoucí. Stejně jako při enteru otestujeme, zda je zapnut duhový efekt. Pokud je, podíváme e jestli je hodnota počítadla counter větší než 25. Používá se ke změně barvy celých skupin částic. Pokud by se změnila barva při každém framu všechny částice by se obarvily jinak. Vytvořením zpoždění stihneme obarvit stejnou barvou více částic.
if ((keys[' '] && !sp) || (rainbow && (delay>25)))// Mezerník nebo duhový efekt
{
Pokud je stisknut mezerník vypne se duhový efekt. Kdybychom ho nedeaktivovali, tak by se dokola měnily barvy dokud by nebyl stisknut enter. Dává smysl, že pokud člověk bouchá do mezerníku namísto do enteru, tak chce barvami procházet sám.
if (keys[' '])rainbow=false;// Pokud je stisknut vypne se duhový mód
Pokud je mezerník stisknut nebo je zapnut duhový mód a zpoždění je větší než 25, přiřazením true do sp oznámíme počítači, že byl stisknut. Poté nastavíme delay na nulu, takže se může znovu počítat do 25. Nakonec inkrementujeme barvu na další v paletě.
sp=true;// Oznámí programu, že byl stisknut mezerník
delay=0;// Resetuje zpoždění duhových barev
col++;// Změní barvu částice
Protože máme pouze 12 barev musíme zamezit přetečení pole a následné zhroucení programu.
if (col>11) col=0;// Proti přetečení pole
}
if (!keys[' ']) sp=false;// Uvolnění mezerníku
Definujeme ovládání částic. Na začátku programu jsme deklarovali dvě proměnné rychlosti (xspeed, yspeed). Když částice vyhoří (zemře) přiřadíme jí novou rychlost závisející na těchto proměnných. Můžeme ovlivňovat jejich směr. Řádek dole testuje stisk šipky nahoru. V takovém případě yspeed inkrementujeme. Částice se bude pohybovat nahoru. Max rychlost je omezena na 200, větší už nevypadá dobře. Analogickým principem pracuje i ovládání ostatními šipkami.
if (keys[VK_UP] && (yspeed<200)) yspeed+=1.0f;// Šipka nahoru
if (keys[VK_DOWN] && (yspeed>-200)) yspeed-=1.0f;// Šipka dolů
if (keys[VK_RIGHT] && (xspeed<200)) xspeed+=1.0f;// Šipka doprava
if (keys[VK_LEFT] && (xspeed>-200)) xspeed-=1.0f;// Šipka doleva
Zbývá inkrementovat zpoždění delay, použité pro rychlost změn barev. Ostatní kód znáte z minulých lekcí.
delay++;// Inkrementace zpoždění duhového efektu
if (keys[VK_F1])
{
keys[VK_F1]=FALSE;
KillGLWindow();
fullscreen = !fullscreen;
if (!CreateGLWindow("NeHe's Particle Tutorial",640,480,16,fullscreen))
{
return 0;
}
}
}
}
}
KillGLWindow();
return (msg.wParam);
}
V této lekci jsem se pokoušel o vysvětlení jednoduchého, ale působivého částicového systému. Jeho nejvýhodnější použití spočívá ve vytvoření efektů typu ohně, vody, sněhu, explozí, hvězd a spousty dalších. Jednoduchým modifikováním kódu lze snadno naprogramovat zcela nový efekt.
Děkuji Richardu Nutmanovi za upozornění, že by bylo výhodnější umisťovat částice použitím glVertex3f() namísto resetováním matice a složitými translacemi. Obě metody vypadají stejně, ale jeho verze snižuje zatížení počítače. Program běží rychleji.
napsal: Jeff Molofee - NeHe <nehe (zavináč) connect.ab.ca>
přeložil: Michal Turek - Woq <WOQ (zavináč) seznam.cz>