Systemy cząstek (ang. particle systems) wykorzystywane są głównie do uzyskiwania różnorodnych efektów atmosferycznych, takich jak deszcz, śnieg lub mgła. Pomijając kwestie fizyczne, systemy cząstek można zbudować w oparciu o punkty, odcinki lub czworokąty pokryte teksturą. Możliwości biblioteki OpenGL w tym zakresie rozszerzano dwukrotnie. W wersji 1.4 dodano obsługę rozszerzonej geometrii punktów, a w wersji 2.0 wprowadzono sprajty punktowe (ang. point sprite).
Rozszerzona geometria punktów
Punkty opisywaliśmy już dwukrotnie. Jednak z punktu widzenia systemu cząstek punkty w bibliotece OpenGL mają jedną zasadniczą wadę. Ich wielkość nie jest w żaden sposób uzależniona od położenia w przestrzeni 3D, a w szczególności od odległości punktu od obserwatora. Tę wadę usunięto w wersji 1.4 biblioteki OpenGL, a wcześniej w rozszerzeniach ARB point parameters, EXT point parameters i SGIS point parameters.
Kontrolę nad rozszerzoną geometrią punktów umożliwiają funkcje z grupy glPointParameter:
void glPointParameterf( GLenum pname, GLfloat param )
void glPointParameterx( GLenum pname, GLfixed param )
void glPointParameterfv( GLenum pname, const GLfloat * params )
void glPointParameterxv( GLenum pname, const GLfixed * params )
gdzie pname określa rodzaj definiowanego parametru i może przyjąć jedną z poniższych wartości:
Wielkość punktu po przekształceniach geometrycznych określa równanie:
gdzie size jest wielkością punktu określoną przy użyciu funkcji glPointSize, d odległością punktu od obserwatora, a clamp wynikiem obcięcia wielkości punktu do przedziału określonego stałymi
GL_POINT_SIZE_MIN i
GL_POINT_SIZE_MAX. Przy wartościach domyślnych współczynników równania wynoszących a = 1, b = 0 i c = 0, wielkość punktu jest równa wielkości określonej funkcją glPointSize.
Jak już napisaliśmy przy włączonym wielopróbkowaniu stała
GL_POINT_FADE_THRESHOLD_SIZE jest używana do zmiany wielkości punktu oraz zmiany składowej alfa koloru punktu. Wielkość punktu określa funkcja width, a współczynnik mnożenia składowej alfa określa funkcja fade:
Sprajty punktowe
Sprajty lub duszki punktowe (ang. point sprite) zostały wprowadzone w wersji 2.0 biblioteki OpenGL w oparciu o rozszerzenia ARB point sprite i NV point sprite. W uproszczeniu technika ta sprowadza się do obsługi tekstury dwuwymiarowej opartej na pojedynczym wierzchołku - punkcie. Pozwala to na znaczne przyspieszenie generowania grafiki, bowiem karta graficzna przetwarza jeden zamiast typowo czterech wierzchołków.
Sprajty punktowe są domyślnie wyłączone. Ich aktywacja wymaga wywołania funkcji glEnable z parametrem
GL_POINT_SPRITE. Włączenie sprajtów punktowych powoduje jednocześnie deaktywację antyaliasingu punktów.
Wraz z techniką sprajtów punktowych wprowadzono środowisko tekstur
GL_POINT_SPRITE, o którym już wspominaliśmy opisując tekstury. Środowisko to dopuszcza jeden parametr pname funkcji z grupy glTexEnv, który oznaczony jest stałą
GL_COORD_REPLACE. Parametr ten określa czy współrzędne tekstur mają być zastępowane współrzędnymi sprajtów punktowych (wartość
GL_TRUE). Domyślnie jest to, podobnie jak i sprajty punktowe, wyłączone (wartość
GL_FALSE).
Ponadto rozszerzono dopuszczalne wartości parametru pname funkcji z grupy glPointParameter o stałą
GL_POINT_SPRITE_COORD_ORIGIN, która określa czy lewy górny róg duszka ma pokrywać się z lewym górnym rogiem tekstury (wartość
GL_UPPER_LEFT) czy też z prawym dolnym rogiem tekstury (wartość
GL_LOWER_LEFT). Domyślnie OpenGL przyjmuje wartość
GL_UPPER_LEFT.
Program przykładowy
Typowy system cząstek zawiera informacje o ilości cząstek, ich położeniu i innych aspektach fizycznych. W naszym przykładowym programie system cząstek jest maksymalnie uproszczony i całkowicie oddzielony od graficznej reprezentacji cząstek. Pojedynczą cząstkę - płatek śniegu - reprezentuje struktura SnowFlake, która zwiera informację o położeniu cząstki (pola x, y i z) oraz pole logiczne active o jej aktywności. Płatki śniegu generowane są losowo a szybkość ich opadania także jest losowo zmieniana. Cała ta warstwa fizyczna systemu cząstek zaimplementowana jest w funkcji timera. Oczywiście funkcja ta zapewnia także stałą szybkość opadania śniegu, ograniczoną jedynie wydajnością karty graficznej.
Program wyświetla śnieg korzystając z czterech metod. Pierwsza stosuje punkty z antyaliasingiem, przy czym wielkość punktu jest prostą (liniową) funkcją wartości jego współrzędnej z. Komputer Autora pozwala na rendering 20.000 punktów z szybkością około 10 ramek na sekundę (patrz rysunek 1). Druga z zastosowanych metod korzysta z rozszerzonej geometrii punktów i wizualnie praktycznie nie różni się od efektów uzyskanych pierwszą metodą (rysunek 1). Kilkukrotny jest za to przyrost prędkości renderingu (około 65 ramek na sekundę), który dodatkowo ogranicza tempo wywołań funkcji timera.
Druga metoda ma jeszcze jedną zaletę - umożliwia wykorzystanie tablic wierzchołków. Nie było to możliwe przy rysowaniu klasycznych punktów, bowiem jak pamiętamy wielkość punktu może być zmieniana tylko przed wywołaniem funkcji glBegin z parametrem
GL_POINTS.
Dwie ostatnie metody stosują teksturę z rysunkiem płatka śniegu (rysunek 3). Rysunek ten, a właściwie zdjęcie, pochodzi ze wspaniałej kolekcji autorstwa Wilsona Bentley’a której niewielki fragment jest dostępny pod adresem http://snowflakebentley.com/. Rysowane 20.000 płatków śniegu utworzonych z pokrytych teksturą czworokątów na komputerze Autora odbywał się z szybkością około 46 ramek na sekundę (rysunek 4). Analogiczny test z użyciem sprajtów punktowych pozwala na zauważalne przyspieszenie. Scena rysowana jest z szybkością około 65 FPS (rysunek 5) i ponownie jest to szybkość ograniczona wywołaniami funkcji timera.
Przy sprajtach punktowych program ponownie korzysta z tablic wierzchołków, co oczywiście jest możliwe także w przypadku tradycyjnych tekstur, ale wymagałoby radykalnego zwiększenia ilości informacji przechowywanych przez system cząstek.
Plik snieg.cpp
#include <GL/glut.h>
#include <GL/glext.h>
#ifndef WIN32
#define GLX_GLXEXT_LEGACY
#include <GL/glx.h>
#define wglGetProcAddress glXGetProcAddressARB
#endif
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "colors.h"
#include "targa.h"
PFNGLWINDOWPOS2IPROC glWindowPos2i = NULL;
PFNGLPOINTPARAMETERFPROC glPointParameterf = NULL;
PFNGLPOINTPARAMETERFVPROC glPointParameterfv = NULL;
enum
{
STD_POINTS,
GEOMETRIC_POINTS,
TEXTURE,
POINT_SPRITE,
FULL_WINDOW,
ASPECT_1_1,
EXIT
};
int aspect = FULL_WINDOW;
#ifdef near
#undef near
#endif
#ifdef far
#undef far
#endif
const GLdouble left = - 2.0;
const GLdouble right = 2.0;
const GLdouble bottom = - 2.0;
const GLdouble top = 2.0;
const GLdouble near = 3.0;
const GLdouble far = 7.0;
int flake_count = 1000;
const int MAX_FLAKE_COUNT = 20000;
struct SnowFlake
{
GLfloat x, y, z;
bool active;
};
SnowFlake SnowFlakes[ MAX_FLAKE_COUNT ];
int snow_mode = STD_POINTS;
GLfloat std_attenation[ 3 ] =
{
1.0, 0.0, 0.0
};
GLfloat quad_attenation[ 3 ] =
{
0.0, 0.0, 0.2
};
GLuint SNOW_FLAKE;
int frames = 0;
long start_time = 0;
char time_string[ 100 ] = "FPS:";
void DrawString( GLint x, GLint y, char * string )
{
glWindowPos2i( x, y );
int len = strlen( string );
for( int i = 0; i < len; i++ )
glutBitmapCharacter( GLUT_BITMAP_9_BY_15, string[ i ] );
}
void DisplayScene()
{
if( !frames++ )
start_time = clock();
glClearColor( DarkBlue[ 0 ], DarkBlue[ 1 ], DarkBlue[ 2 ], 1.0 );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glTranslatef( 0, 0, -( near + far ) / 2 );
glEnable( GL_DEPTH_TEST );
if( snow_mode == STD_POINTS )
{
glEnable( GL_POINT_SMOOTH );
glColor4fv( Snow );
for( int i = 0; i < flake_count; i++ )
{
glPointSize( 3 + SnowFlakes[ i ].z );
glBegin( GL_POINTS );
glVertex3f( SnowFlakes[ i ].x, SnowFlakes[ i ].y, SnowFlakes[ i ].z );
glEnd();
}
glDisable( GL_POINT_SMOOTH );
}
if( snow_mode == GEOMETRIC_POINTS )
{
glPointParameterf( GL_POINT_SIZE_MIN, 1.0 );
glPointParameterf( GL_POINT_SIZE_MAX, 10.0 );
glPointParameterfv( GL_POINT_DISTANCE_ATTENUATION, quad_attenation );
glPointSize( 10.0 );
glColor4fv( Snow );
glEnable( GL_POINT_SMOOTH );
glEnableClientState( GL_VERTEX_ARRAY );
glVertexPointer( 3, GL_FLOAT, sizeof( SnowFlake ), SnowFlakes );
glDrawArrays( GL_POINTS, 0, flake_count );
glDisableClientState( GL_VERTEX_ARRAY );
glPointParameterfv( GL_POINT_DISTANCE_ATTENUATION, std_attenation );
glDisable( GL_POINT_SMOOTH );
}
if( snow_mode == TEXTURE )
{
glEnable( GL_TEXTURE_2D );
glBindTexture( GL_TEXTURE_2D, SNOW_FLAKE );
glEnable( GL_ALPHA_TEST );
glAlphaFunc( GL_GREATER, 0.2 );
glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
glBegin( GL_QUADS );
for( int i = 0; i < flake_count; i++ )
{
glTexCoord2f( 1.0, 1.0 );
glVertex3f( SnowFlakes[ i ].x + 0.04, SnowFlakes[ i ].y + 0.04, SnowFlakes[ i ].z );
glTexCoord2f( 0.0, 1.0 );
glVertex3f( SnowFlakes[ i ].x - 0.04, SnowFlakes[ i ].y + 0.04, SnowFlakes[ i ].z );
glTexCoord2f( 0.0, 0.0 );
glVertex3f( SnowFlakes[ i ].x - 0.04, SnowFlakes[ i ].y - 0.04, SnowFlakes[ i ].z );
glTexCoord2f( 1.0, 0.0 );
glVertex3f( SnowFlakes[ i ].x + 0.04, SnowFlakes[ i ].y - 0.04, SnowFlakes[ i ].z );
}
glEnd();
glDisable( GL_ALPHA_TEST );
glDisable( GL_TEXTURE_2D );
}
if( snow_mode == POINT_SPRITE )
{
glPointParameterf( GL_POINT_SIZE_MIN, 1.0 );
glPointParameterf( GL_POINT_SIZE_MAX, 10.0 );
glPointParameterfv( GL_POINT_DISTANCE_ATTENUATION, quad_attenation );
glPointSize( 15.0 );
glEnable( GL_TEXTURE_2D );
glBindTexture( GL_TEXTURE_2D, SNOW_FLAKE );
glEnable( GL_ALPHA_TEST );
glAlphaFunc( GL_GREATER, 0.2 );
glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
glTexEnvf( GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE );
glEnable( GL_POINT_SPRITE );
glEnableClientState( GL_VERTEX_ARRAY );
glVertexPointer( 3, GL_FLOAT, sizeof( SnowFlake ), SnowFlakes );
glDrawArrays( GL_POINTS, 0, flake_count );
glDisableClientState( GL_VERTEX_ARRAY );
glPointParameterfv( GL_POINT_DISTANCE_ATTENUATION, std_attenation );
glDisable( GL_POINT_SPRITE );
glDisable( GL_ALPHA_TEST );
glDisable( GL_TEXTURE_2D );
}
glDisable( GL_DEPTH_TEST );
glColor3fv( Yellow );
char str[ 100 ];
sprintf( str, "PARTICLES COUNT = %i", flake_count );
DrawString( 2, 2, str );
if( frames == 50 )
{
frames = 0;
sprintf( time_string, "FPS: %i",( int )( 50 * CLOCKS_PER_SEC /( float )( clock() - start_time ) ) );
}
DrawString( 2, 16, time_string );
glFlush();
glutSwapBuffers();
}
void Timer( int value )
{
srand( time( NULL ) );
for( int i = 0; i < flake_count; i++ )
if( SnowFlakes[ i ].active == false )
{
SnowFlakes[ i ].x = 2 *( rand() /( float ) RAND_MAX ) *( right - left ) - 2 * right;
SnowFlakes[ i ].y =( rand() /( float ) RAND_MAX ) *( top - bottom ) + top;
SnowFlakes[ i ].z =( rand() /( float ) RAND_MAX ) *( far - near );
SnowFlakes[ i ].active = true;
}
else
{
SnowFlakes[ i ].y -= 0.005 + rand() /( float ) RAND_MAX / 200;
SnowFlakes[ i ].x += 0.001 - rand() /( float ) RAND_MAX / 200;
SnowFlakes[ i ].z += 0.001 - rand() /( float ) RAND_MAX / 200;
if( SnowFlakes[ i ].y < bottom )
SnowFlakes[ i ].active = false;
}
DisplayScene();
glutTimerFunc( 10, Timer, 0 );
}
void Reshape( int width, int height )
{
glViewport( 0, 0, width, height );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
if( aspect == ASPECT_1_1 )
{
if( width < height && width > 0 )
glFrustum( left, right, bottom * height / width, top * height / width, near, far );
else
if( width >= height && height > 0 )
glFrustum( left * width / height, right * width / height, bottom, top, near, far );
}
else
glFrustum( left, right, bottom, top, near, far );
DisplayScene();
}
void Keyboard( unsigned char key, int x, int y )
{
switch( key )
{
case '+':
if( flake_count < MAX_FLAKE_COUNT )
flake_count += 1000;
break;
case '-':
if( flake_count > 1000 )
flake_count -= 1000;
for( int i = flake_count; i < 1000; i++ )
SnowFlakes[ i ].active = false;
break;
}
DisplayScene();
}
void Menu( int value )
{
switch( value )
{
case STD_POINTS:
case GEOMETRIC_POINTS:
case TEXTURE:
case POINT_SPRITE:
snow_mode = value;
DisplayScene();
break;
case FULL_WINDOW:
aspect = FULL_WINDOW;
Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
break;
case ASPECT_1_1:
aspect = ASPECT_1_1;
Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
break;
case EXIT:
exit( 0 );
}
}
void GenerateTextures()
{
GLsizei width, height;
GLenum format, type;
GLvoid * pixels;
glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
GLboolean error = load_targa( "snowflakes.tga", width, height, format, type, pixels );
if( error == GL_FALSE )
{
printf( "Niepoprawny odczyt pliku snowflakes.tga" );
exit( 0 );
}
unsigned char * rgba_pixels = new unsigned char[ width * height * 4 ];
unsigned char * rgb_pixels =( unsigned char * ) pixels;
for( int i = 0; i < width * height; i++ )
{
rgba_pixels[ 4 * i + 0 ] = rgb_pixels[ 3 * i + 0 ];
rgba_pixels[ 4 * i + 1 ] = rgb_pixels[ 3 * i + 1 ];
rgba_pixels[ 4 * i + 2 ] = rgb_pixels[ 3 * i + 2 ];
if( rgb_pixels[ 3 * i + 0 ] + rgb_pixels[ 3 * i + 1 ] + rgb_pixels[ 3 * i + 2 ] < 10 )
rgba_pixels[ 4 * i + 3 ] = 0;
else
rgba_pixels[ 4 * i + 3 ] = 255;
}
glGenTextures( 1, & SNOW_FLAKE );
glBindTexture( GL_TEXTURE_2D, SNOW_FLAKE );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
gluBuild2DMipmaps( GL_TEXTURE_2D, GL_RGBA, width, height, GL_BGRA, type, rgba_pixels );
delete[]( unsigned char * ) pixels;
delete[] rgba_pixels;
}
void ExtensionSetup()
{
const char * version =( char * ) glGetString( GL_VERSION );
int major = 0, minor = 0;
if( sscanf( version, "%d.%d", & major, & minor ) != 2 )
{
#ifdef WIN32
printf( "Błędny format wersji OpenGL\n" );
#else
printf( "Bledny format wersji OpenGL\n" );
#endif
exit( 0 );
}
if( major > 1 || minor >= 4 )
{
glWindowPos2i =( PFNGLWINDOWPOS2IPROC ) wglGetProcAddress( "glWindowPos2i" );
}
else
if( glutExtensionSupported( "GL_ARB_window_pos" ) )
{
glWindowPos2i =( PFNGLWINDOWPOS2IPROC ) wglGetProcAddress
( "glWindowPos2iARB" );
}
else
{
printf( "Brak rozszerzenia ARB_window_pos!\n" );
exit( 0 );
}
if( major > 1 || minor >= 4 )
{
glPointParameterf =
( PFNGLPOINTPARAMETERFPROC ) wglGetProcAddress( "glPointParameterf" );
glPointParameterfv =
( PFNGLPOINTPARAMETERFVPROC ) wglGetProcAddress( "glPointParameterfv" );
}
else
if( glutExtensionSupported( "GL_ARB_point_parameters" ) )
{
glPointParameterf =
( PFNGLPOINTPARAMETERFPROC ) wglGetProcAddress( "glPointParameterf" );
glPointParameterfv =
( PFNGLPOINTPARAMETERFVPROC ) wglGetProcAddress( "glPointParameterfv" );
}
else
{
printf( "Brak rozszerzenia ARB_point_parameters!\n" );
exit( 0 );
}
if( !( major >= 2 ) && !glutExtensionSupported( "GL_ARB_point_sprite" ) )
{
printf( "Brak rozszerzenia GL_ARB_point_sprite!\n" );
exit( 0 );
}
}
int main( int argc, char * argv[] )
{
glutInit( & argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH );
glutInitWindowSize( 500, 500 );
#ifdef WIN32
glutCreateWindow( "Śnieg" );
#else
glutCreateWindow( "Snieg" );
#endif
glutDisplayFunc( DisplayScene );
glutReshapeFunc( Reshape );
glutKeyboardFunc( Keyboard );
glutCreateMenu( Menu );
int MenuAspect = glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Aspekt obrazu - całe okno", FULL_WINDOW );
#else
glutAddMenuEntry( "Aspekt obrazu - cale okno", FULL_WINDOW );
#endif
glutAddMenuEntry( "Aspekt obrazu 1:1", ASPECT_1_1 );
int MenuSnowMode = glutCreateMenu( Menu );
glutAddMenuEntry( "Punkty", STD_POINTS );
#ifdef WIN32
glutAddMenuEntry( "Punkty z rozszerzoną geometrią", GEOMETRIC_POINTS );
#else
glutAddMenuEntry( "Punkty z rozszerzona geometria", GEOMETRIC_POINTS );
#endif
glutAddMenuEntry( "Tekstura", TEXTURE );
glutAddMenuEntry( "Sprajty punktowe", POINT_SPRITE );
glutCreateMenu( Menu );
#ifdef WIN32
glutAddSubMenu( "Rodzaj śniegu", MenuSnowMode );
glutAddSubMenu( "Aspekt obrazu", MenuAspect );
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddSubMenu( "Rodzaj sniegu", MenuSnowMode );
glutAddSubMenu( "Aspekt obrazu", MenuAspect );
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
GenerateTextures();
ExtensionSetup();
glutTimerFunc( 10, Timer, 0 );
glutMainLoop();
return 0;
}