Kirjautuminen

Haku

Tehtävät

Koodit: C: Partikkelien rendaus (OpenGL)

Kirjoittaja: BlueByte

Kirjoitettu: 04.01.2008 – 04.01.2008

Tagit: grafiikka, koodi näytille, vinkki

Esimerkki OpenGL:n GL_ARB_point_parameters extensionin käytöstä partikkelien rendaamisessa SDL:llä.

GL_ARB_point_parameters on opengl:n laajennus, jonka avulla pystytään helposti ja nopeasti piirtämään teksturoituja pisteitä (quadeja). tällä saa paljon nopeammin piirrettyä partikkeleita kuin erikseen suunnattujen quadien (billboarded quad) piirtelyllä.

Tässä luodaan esimerkin vuoksi yksinkertainen "tulisuihku" partikkeleilla. Partikkeliin käytetään erillistä tekstuuria, joka ladataan turhan häsläyksen vähentämiseksi DevIL-kirjastolla. tarvittava tekstuuri löytyy täältä http://www.nullcore.org/~fuzzybyte/a/particle.png

#include <stdio.h>
#include <SDL/SDL.h>

/* DevIL-kirjasto partikkelitekstuurin lataukseen */
#include <IL/il.h>

/* Ladataan tarvittavat OpenGL-kirjastot
   glext.h sisältää tiedot extensioneista */

/* Windows */
#ifdef WIN32
#include <gl/gl.h>
#include <gl/glu.h>
#include "glext.h"

/* Mac OS X */
#elif defined __APPLE__
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#include <OpenGL/glext.h>

/* Linux ja muut */
#else
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glext.h>
#endif

int initSDL();
int initialize();
void renderScene();

/* näytön surface */
SDL_Surface *screen;
/* resoluutio */
int screenWidth = 1024;
int screenHeight = 768;
/* näytön bittisyys */
int screenBPP = 32;
/* fullscreen optio */
int isFullScreen = 0;

/***********************************************************************************/
/************* JÄNNÄT KIINNOSTAVAT JUTUT ON TÄSSÄ **********************************/

/* kaksi funktiopointteria GL_ARB_point_parameters extensionin funktioille
  näitä funktioita käytetään extensionin ohjasteluun
  APIENTRY tarvitaan varmistamaan oikeanlainen määrittely erilaisille kääntäjille */
void (APIENTRY * glPointParameterfARB)(GLenum, GLfloat) = NULL;
void (APIENTRY * glPointParameterfvARB)(GLenum, const GLfloat *) = NULL;

/* tarkemmat kommentoinnit funktioden edessä */
int isExtSupported(const char *extension);
void updateParticles();

/* muuttuja partikkelin textuurille */
GLuint particleTexture;

/* tietue partikkelille*/
typedef struct
{
	GLfloat pos[3]; /* sijainti */
	GLfloat vel[3]; /* nopeus */
	GLfloat acc[3]; /* kiihtyvyys */
	GLfloat color[4]; /* väri, RGBA */
	GLfloat life; /* "elämä" */
} Particle;

/* partikkelien määrä */
const int numParticles = 100000;
/* taulukko partikkelien tietojen säilytykseen */
Particle * particles;

/*---------------------------------------------------------------------------------*/
/***********************************************************************************/

int main()
{
	/* muuttuja ohjelman lopettamiseen */
	int finished = 0;
	/* muuttuja eventtien keräilyyn */
	SDL_Event event;
	/* varataan muistia partikkeleille */
	particles = calloc(numParticles,sizeof(Particle));

	if(initialize())
		return 1; /* error */

	while(!finished)
	{
		updateParticles();
		renderScene();

		while (SDL_PollEvent(&event))
		{
			switch (event.type)
			{
			case SDL_KEYDOWN:
				switch (event.key.keysym.sym)
				{
				case SDLK_ESCAPE:
					finished = 1;
					break;
				default:
					break;
				}
			case SDL_QUIT:
				finished=1;
			default:
				break;
			}
		}
	}

	/* vapautetaan turhaksi jäänyttä muistia */
	free(particles);
	glDeleteTextures(1, &particleTexture);

	SDL_Quit();

	return 0;
}

int initSDL()
{
	/* Liput SDL_SetVideoModeen */
	int videoFlags = 0;
	/* sisältää tietoja näytöstä */
	const SDL_VideoInfo *videoInfo;

	/* alustetaan SDL */
	if ( SDL_Init( SDL_INIT_AUDIO|SDL_INIT_VIDEO ) < 0 )
	{
		fprintf(stderr, "Video initialization failed: %s\n", SDL_GetError());
		return  1;
	}

	/* Hae tiedot näytöstä */
	videoInfo = SDL_GetVideoInfo( );

	if ( !videoInfo )
	{
		fprintf(stderr, "Video query failed: %s\n", SDL_GetError());
		return 1;
	}

	/* asetetaan liput SDL_SetVideoModeen */
	videoFlags  = SDL_OPENGL;          /* OpenGL tuki SDL:ään   */
	videoFlags |= SDL_HWPALETTE;       /* hardware-paletti */
	if(isFullScreen)
		videoFlags |= SDL_FULLSCREEN;  /* tarkista onko fullscreen päällä */

	/* tarkista hardware-tuki */
	if ( videoInfo->hw_available )
		videoFlags |= SDL_HWSURFACE;
	else
		videoFlags |= SDL_SWSURFACE;

	/* tarkista tuki hw-blittaukselle */
	if ( videoInfo->blit_hw )
		videoFlags |= SDL_HWACCEL;

	/* aseta OpenGL double bufferointi päälle */
	SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );

	/* hae SDL surface */
	screen = SDL_SetVideoMode( screenWidth, screenHeight, screenBPP,
	                           videoFlags );

	/* ohjelman otsikko */
	SDL_WM_SetCaption("Particle test","");

	return 0;
}

int initialize()
{
	/* kuvasuhde */
	GLfloat aspectRatio;

	/*tekstuureita varten */
	ILuint texid;
	ILboolean success;

	/******************/
	/* alustetaan SDL */
	if(initSDL())
		return 1; /* error */


	/* Alusta OpenGL */
	/* kuvasuhde */
	aspectRatio = screenWidth / screenHeight;

	/* Aseta taustaväri mustaksi */
	glClearColor(0.0,0.0,0.0,1.0);

	/* blending pitää olla päällä, jotta partikkelit sekoittuvat oikein keskenään */
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); /* GL_ONE_MINUS_SRC_ALPHALLA saadaan pisteet sekoittumaan keskenään */
	glEnable(GL_BLEND);
	/* Anti-aliasing päälle ja vihjaa opengl tekemään parhainta jälkeä */
	glEnable(GL_POINT_SMOOTH);
	glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
	glEnable(GL_MULTISAMPLE_ARB);

	glViewport( 0, 0, screenWidth, screenHeight );
	glMatrixMode( GL_PROJECTION );
	glLoadIdentity( );
	gluPerspective( 45.0f, aspectRatio, 0.1f, 100.0f );
	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity( );

	/*****************************************/
	/* partikkelitekstuurin lataus DevIL:llä */
	glEnable(GL_TEXTURE_2D);

	/* alustus */
	if (ilGetInteger(IL_VERSION_NUM) < IL_VERSION)
	{
		return 1; /* väärä DevIL version */
	}

	ilInit();
	ilGenImages(1, &texid);
	ilBindImage(texid);
	success = ilLoadImage("particle.png");
	if (success)
	{
		success = ilConvertImage(IL_RGBA, IL_UNSIGNED_BYTE);

		if (!success)
		{
			return 1;    /* Error  */
		}

		/* kaikki ok. generoidaan nimi ja bindataan tekstuuri */
		glGenTextures(1, &particleTexture);
		glBindTexture(GL_TEXTURE_2D, particleTexture);
		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, ilGetInteger(IL_IMAGE_BPP), ilGetInteger(IL_IMAGE_WIDTH),
		             ilGetInteger(IL_IMAGE_HEIGHT), 0, ilGetInteger(IL_IMAGE_FORMAT), GL_UNSIGNED_BYTE,
		             ilGetData());
	}
	else
	{
		return 1; /* Error */
	}

	/* tekstuuri on ladattu, joten siitä ei tarvitse enää säilyttää kopiota muistissa */
	ilDeleteImages(1, &texid);


	/****************************************************************************************/
	/*********************JÄNNÄÄ KIINNOSTAVAA JUTTUU TÄSSÄ MYÖS******************************/
	/* tarkista tuki GL_ARB_point_parameters extensionille */
	if(isExtSupported("GL_ARB_point_parameters"))
	{
		GLfloat maxSize;

		/* haetaan extension funktiopointtereille osoitteet SDL_GL_GetProcAddressilla */
		*(void**)&glPointParameterfARB  = SDL_GL_GetProcAddress("glPointParameterfARB");
		*(void**)&glPointParameterfvARB = SDL_GL_GetProcAddress("glPointParameterfvARB");

		/* haetaan pisteen maksimikoko */
		maxSize = 1.0;
		glGetFloatv( GL_POINT_SIZE_MAX_ARB, &maxSize );

		/* rajoitetaan maksimikoko 50.0:een, joka on tarpeeksi pieni koko (miltein) kaikille näyttikseille.
		minulla esimerkiksi nvidian ajureilla on maksimikoko vain 63. */
		if(maxSize > 50.0)
			maxSize = 50.0;

		/* Kun pisteen koko menee GL_POINT_FADE_THRESHOLD_SIZE_ARB:in alle, niin se häivytetään
 		pois alpha-arvoja muuttamalla sen sijaan, että sen kutistamista jatkettaisiin */
		glPointParameterfARB( GL_POINT_FADE_THRESHOLD_SIZE_ARB, 3.0 );

		/* asetetaan minimi- ja maksimikoot */
		glPointParameterfARB( GL_POINT_SIZE_MIN_ARB, 1.0 );
		glPointParameterfARB( GL_POINT_SIZE_MAX_ARB, maxSize );

	}
	else
	{
		/* ei tukea, printataan virheviesti */
		fprintf(stderr, "No support found for GL_ARB_point_parameters extension!\n");
		return 1;
	}

	/*********************************************************************************/
	/*********************************************************************************/

	return 0;
}

void renderScene()
{
	/* pari muuttujaa fps:n laskemiseen */
	static int T0     = 0;
	static int frames = 0;
	int i;

	/*****************************************************************************************************/
	/*--------------------ALEMPANA SELITYS --------------------------------------------------------------*/
	GLfloat pointScale[] =   { 0.10, 0.0, 1.0/500.0 };
	/*---------------------------------------------------------------------------------------------------*/
	/*****************************************************************************************************/


	int tick = SDL_GetTicks();

	/* Tyhjennetään ruutu */
	glClear(GL_COLOR_BUFFER_BIT);

	glPushMatrix();

	glTranslatef(0.0,-1.5,-4.0);
	glRotatef(20.0,1.0,0.0,0.0);

	/*****************************************************************************************************/
	/*---------------------------------------------------------------------------------------------------*/
	/*Pisteen skaalaus tapahtuu asettamalla sopivat skaalausmääreet GL_POINT_DISTANCE_ATTENUATION_ARB:lle.
	pisteen oikea koko näytöllä voidaan laskea vaikka näin:

				                             1.0
	pisteen oikea koko  = GL_POINT_SIZE * sqrt(  -------------------  ) , jossa
			                             a + b * d + c * d^2

	a on ensimmäinen parametri (vakioskaalaus), b on toinen (lineaarinen skaalaus) ja
	c on kolmas (neliöskaalaus), sekä d on pisteen etäisyys.
	*/

	glPointParameterfvARB( GL_POINT_DISTANCE_ATTENUATION_ARB, pointScale );

	/* Tässä määrätään opengl:ää käyttämään pisteitä tekstuurien koordinaatteina */
	/* ideana on siis se että piirrellään pisteitä, jotka sitten näytönohjain muuttaa quadeiksi, teksturoi
	  ja asettelee ne tavallisten suunnattujen (billboarded) quadien tapaan, joka on paljon nopiampaa erikseen asettelu*/
	glTexEnvf( GL_POINT_SPRITE_ARB, GL_COORD_REPLACE_ARB, GL_TRUE );

	glPointSize( 7.0 );

	/*  pistetään extensioni päälle */
	glEnable( GL_POINT_SPRITE_ARB );

	glBegin( GL_POINTS );

	for(i=0;i<numParticles;i++)
	{
		glColor4fv(particles[i].color);
		glVertex3f(particles[i].pos[0],particles[i].pos[1],particles[i].pos[2]);
	}

	glEnd();

	/* pois käytöstä kun ei tarvita */
	glDisable( GL_POINT_SPRITE_ARB );


	/*---------------------------------------------------------------------------------------------------*/
	/*****************************************************************************************************/

	glPopMatrix();

	/* Draw it to the screen */
	SDL_GL_SwapBuffers( );

	/* Gather frames per second */
	frames++;
	if (tick - T0 >= 1000)
	{
		float seconds = (tick - T0) / 1000.0;
		float fps = frames / seconds;
		printf("%i frames in %f seconds = %f FPS\n", frames, seconds, fps);
		T0 = tick;
		frames = 0;
	}
}

/*/////////////////////////////////////////////////////////////////////////////////////*/
/* isExtSupported funktio tarkistaa tukeeko laitteisto parametrin extensionName nimistä*/
/* extensionia. Palauttaa 1 jos tuki löytyy, muuten 0.                                 */
int isExtSupported(const char *extensionName)
{
	/* Haetaan tiedot extensioista */
	char *extensions = (char *)glGetString(GL_EXTENSIONS);

	/* etsitään haluttua extensionia */
	if (strstr(extensions, extensionName))
		return 1;
	else
		return 0;
}

/* updateParticles() päivittää partikkeleiden tietoja, jolloin syntyy yksinkertainen partikkelisuihku */
void updateParticles()
{
	int i;

	for(i=0;i<numParticles;i++)
	{
		particles[i].pos[0] += particles[i].vel[0];
		particles[i].pos[1] += particles[i].vel[1];
		particles[i].pos[2] += particles[i].vel[2];

		particles[i].vel[0] += particles[i].acc[0];
		particles[i].vel[1] += particles[i].acc[1];
		particles[i].vel[2] += particles[i].acc[2];

		if(particles[i].life>0.0)
		{
			particles[i].life -= 0.005;
			particles[i].color[1] -= .02;
			particles[i].color[2] -= 0.05;
			particles[i].color[3] = particles[i].life; /*alpha määräytyy elämän mukaan */
		}

		else
		{
			particles[i].pos[0] = (rand()%2000 - 1000)/10000.0;
			particles[i].pos[1] = (rand()%2000 - 1000)/10000.0;
			particles[i].pos[2] = (rand()%2000 - 1000)/10000.0;

			particles[i].vel[0] = 0.0;
			particles[i].vel[1] = 0.02;
			particles[i].vel[2] = 0.0;

			particles[i].acc[0] = -(rand()%2000-1000)/2000000.0;
			particles[i].acc[1] = 0.0;
			particles[i].acc[2] = -(rand()%10000)/6000000.0;

			particles[i].color[0] = 1.0;
			particles[i].color[1] = 1.0;
			particles[i].color[2] = 1.0;
			particles[i].color[3] = 1.0;

			particles[i].life = 1.0 - rand()%1000/1000.0;
		}
	}

}

Kommentit

Metabolix [05.01.2008 10:46:15]

#

Vinkistä olisi paremmin hyötyä, jos oleelliset osat olisi koottu jonnekin. Tuossa on hirveästi "turhaa" tavaraa, jolloin ne kymmenkunta oleellista riviä on vaikea poimia. Lisäksi et selitä käytännössä lainkaan, miten systeemi toimii. Voisit siis kertoa hieman kustakin rivistä, joka sisältää merkit ARB. Tuollaisenaan vinkistä ei ymmärrä paljonkaan, jos ei ennestään osaa asiaa. (Itse asiassa paljon enemmän selviää parissa minuutissa ensimmäisistä Google-hakutuloksista.) Ideanahan on, että OGL-piste on neliö ja tekstuuri asetetaan peittämään se kokokonaan, mutta kommenttisi eivät todellakaan kerro, miten eri rivit tähän asiaan vaikuttavat.

Partikkelien init-koodi on turha, helpompi olisi alustaa taulukko nollaksi (calloc), jolloin päivitysfunktio arpoisi alussa kaikki sijainnit. Asetuskoodihan on kummassakin sama.

Miksi lähes ANSI C:tä olevassa ohjelmassa on parissa kohti C++-kommentteja ja muuttujamäärittelyjä? Miksi verteksi annetaan komponentti kerrallaan mutta väri fiksusti osoittimen avulla?

BlueByte [05.01.2008 19:33:35]

#

parantelin hieman. pitäisi olla nyt ansin mukainen ja siistimpää koodia (vaikka gcc vieläkin herjaa tuosta sdl_gl_getproccaddressista.. jos joku tietää mikä on oikea tapa tehdä tuo, niin voisiko kertoa). toivottavasti löytyy tärkeät kohdat vähän helpommin.

en nyt tuota kauheasti selittänyt tosiaan kun oletin, että monet osaavat tavallisesti quadeilla piirrellä partikkeleita ja sitten suuntaamalla ne kameraan, nii periaate lienee aika selvä. tuossahan on vaan että näyttis hoitaa ne suuntaus- ja teksturointijutut jne. hömpsyt eikä cpu niinku tavallisesti

tosiaan tuossa on tuota epäoleellista juttua paljon, mutta aika paljon siitä on tarpeellista kuitenkin.

Metabolix [05.01.2008 21:49:43]

#

Kuten GCC sanoo, tyyppimuunnos osoittimesta funktio-osoittimeksi on kielletty. Standardinmukainen purukumi näyttää tältä:

*(void**)&funktio = SDL_GL_GetProcAddress("f");

BlueByte [05.01.2008 22:10:30]

#

noniin nyt menee -Wall -pedantic -ansi liput nii eiköhä toi oo, ok.
kummaku sdl manuaalissa oli esimerkki tuosta, joka oli väärin samallalailla. nojoo.. ymmärrän kyllä miksi, rumaahan tuollane on.

Metabolix [07.01.2008 16:01:17]

#

Ai niin, SDL_GL_DOUBLEBUFFER ei kuulu SDL_SetVideoModen lippuihin ja voi siis periaatteessa aiheuttaa aivan odottamattomia sivuvaikutuksia. Kannattaa siis poistaa sieltä.

Mutta nythän tämä vaikuttaa jo aika kivalta, hienot huomautukset noissa tärkeissä kohdissa. ;)

Kirjoita kommentti

Muista lukea kirjoitusohjeet.
Tietoa sivustosta