partikler
Grafikk
Aleksander Lygren Toppe / Student 2006
Å tegne:>Partikler

Partikkelsystem

Hva
snowscreen

Mitt prosjekt gikk ut på å se på hvordan vi kunne lage et partikkelsystem i OpenGL. Prosjektet hadde en del faser, hvor forskning på mest effektive måte å representere et partikkelsystem i kode samt mest effektiv måte å representere en partikkel grafisk på skjermen har hatt stor fokus.

Jeg vil nå ta deg med på en del av planlegging og resoneringsprosessen da begrepet partikkelsystem og partikkel har flere betydninger. Jeg spurte meg selv hva som kan regnes som en partikkel i en 3D-scene og kom frem til at begrepet partikkel kunne egentlig tilegnes de fleste bevegelige objekter. Jeg hadde før jeg begynte å undersøke på prosjektet en tanke på at partikkel var et punkt uten noe særlig form for størrelse av typen som for eksempel et sandkorn, men etter litt mer tanker på sitasjoner og systemer i det virkelige liv kom jeg frem til at det fult ut er mulig å tilordne partikkellogikk på en sverm bier, en flokk med fugler, kanskje til og med skalert opp til en slagmark hvor hver kriger vil være en 'partikkel'. Når vi går over til så kompliserte tilfeller som sistnevnte så blir det mer riktig å kalle det et dynamisk intelligent system med agenter, og ble egentlig bare nevnt for å vise et mer ekstremt ytterpunkt av å behandle flere objekter i et system. Vi kan om mulig dele opp i to vanlige logiske måter å implementere et partikkelsystem, den første hvor en partikkel er 'dum' og blir bare påvirket av eksterne krefter, og den andre hvor vi kan for eksempel legge inn en flocking logikk (tenk fugleformasjon) i 'partikkelen' for å skape interessante effekter. Et partikkelsystem sammen med billboarding passer bra inn på å gjengi fenomen som ikke er solide, som for eksempel røyk, ild, eksplosjonseffekter, snø, regn, kondensstriper og skyer.

Hva er inne i en partikkel og et partikkelsystem

Med dette i bakhodet satt jeg med ned for å finne ut dekkende sett med attributter en partikkel i mitt system skulle ha. Jeg valgte å legge en del av de mer generelle attributtene ut i selve partikkelsystem klassen for å minske lagringen. En partikkel trenger kunne plasseres entydig i et tredimensjonalt rom, med andre ord må vi ha en posisjonsattributt, videre vil det være naturlig å tenke seg at denne partikkelen også vil ha en fart i en retning så vi trenger også å lagre hastigheten til partikkelen. Det er også vanlig å la en partikkel leve en bestemt tid, så vi kan enten registrere tidspunktet partikkelen ble opprettet eller registrere hvor lenge den har levd. Sistnevnte krever at vi oppdaterer alderen for hver enkelt partikkel hver gang vi går igjennom partikkelsystemet på hver frame. Førstnevnte krever at vi har en teller som sier hvor lenge partikkelsystemet vårt har levd og vi kan da bruke denne tiden som opprettelsesverdi på partikkelen. Forskjellen i ren kode kan sees her med venstre boks med kode for alt.1 og alt.2 til høyre Hva som velges blir en smakssak og kommer litt an på hvordan enn vil bruke partikkelalder. Videre kan det være aktuelt å lagre fargen vi vil tinte partikkelen vår med. Når det gjelder attributter vi vil lagre på selve partikkelsystemet så har vi alle kreftene som gjør at gjenstander beveger seg, så det vil være naturlig å ha en tyngdekraft på systemet. I systemet mitt blir denne lineær. Da jeg har tidligere snakket om regn og snø vil det også være naturlig å ha med en vindkraft på systemet. I denne sammenheng vil det også kunne være aktuelt å implementere krefter som luftmotstand og turbulens. Merk alle disse kreftene virker helhetlig på hele partikkelsystemet.

Kontrollere utslipp

Vi kan tenke oss følgende scenario. Vi har en sigar som gløder og avgir røyk. Så komme det et vindpust og her vil vi ha behov for å øke antall røykpartikler som blir sluppet. Partikkelsystemet må da ha faktorer som antall partikler som blir sluppet pr intervall og hvor ofte dette intervallet skal være. Når vi har en livstidsfaktor på denne røyken vil vi kunne regne ut antall partikler som maksimalt vil være tilstede i systemet med gitte faktorer. Dette gjør at jeg kan preallokere antall partikler jeg vil anta være aktive på systemet før kjøring og da slippe å allokere minne til partiklene under første kjøring. Sammen med disse to faktorene vil det være aktuelt å ha en attributt som sier maksimalt antall aktive partikler i partikkelsystemet vårt. Dette gjør at vi kan sørge for at et partikkelsystem ikke vil bruke mer enn det minne vi gir den, samt å gjøre at vi kan få bedre ytelse da vi kan finne smertepunktet med antall artikler vi kan ha på skjermen på en gang før ytelsen blir påvirket alt for mye i negativ forstand og så bruke denne på maksantall. Enda et scenario som dette kan være aktuelt i kan være et spill hvor vi har et objekt som passerer raskt forbi en røykkilde, for en realistisk oppdatering av partikkelsystemet kan vi da oppdatere vindretningen på systemet i tillegg som vi midlertidig øker utslippsintervallet.

Flyten i partikkelsystemet

Vi opprettet et objekt av typen ParticleSystem. Vi kan så bruke set-metodene for å sette maksantall, utslippsfaktorene, og de forskjellige kreftene som virker på systemet. Når vi så kaller init() vil det bli pregenerert et visst antall partikler i den frie lista basert på utslippsfaktor og livstid. Neste steg blir faktisk å kjøre en oppdatering på partikkelsystemet og dette gjøres via update(...) som tar inn en verdi som er tiden fra forrige iterasjon til nåværende av kjøreløkka. Det er i update() mye av magien skjer i form av at vi vil sjekke om det er partikler som har nådd sin maksimale levetid og så flytte de over i den inaktive/frie lista, samt oppdatere alle kreftene som virker på partikkelen. Når rutinearbeidet er gjort vil vi så se om vi kan slippe ut nye partikler i systemet vårt. Vi må først sjekke at intervallet i fra forrige utslipp er likt eller større intervallattributten. Deretter må sjekke om vi har ledige partikkelstrukturer å benytte oss av i den frie lista og hvis ikke allokere en ny partikkel, og deretter sette i gang det antall partikler vi har satt for hvert intervall. Dette er da gitt at vi ikke har nådd maksantallet. Når vi så er ferdig å oppdatere alle partiklene og generere nye er vi kommet til det punktet som vi alle har ventet på, nemlig å tegne partiklene opp på skjermen. Vi må først sette parametere som går på OpenGL sin POINT_SPRITE_ARB, dette er faktorer som 'attenuation', og maksstørrelse på partikkel/sprite. Så kan vi tilordne teksturen til disse point spritene før vi sier til OpenGL at vi skal tegne GL_POINT_SPRITE_ARB og så gå over aktivepartikkeler-lista vår og tegne de ut på skjermen.

Under følger et eksempel på hvordan man kan sette opp partikkelsystemet til å lage røyk.

		pSys[0] = new ParticleSystem();

		pSys[0]->setTexture( ".\\smoke.bmp" );
		pSys[0]->setParticleCount (1500 );
		pSys[0]->setReleaseNumber( 5 );
		pSys[0]->setReleaseInterval( 0.01f );
		pSys[0]->setParticleLife( 2.5f );
		pSys[0]->setFade(0.8f);
		pSys[0]->setParticleSize( 30.0f );
		pSys[0]->setInitialPosition( vector3f( 0.0f, -2.0f, 0.0f) );
		pSys[0]->setBoxCorner( vector3f( 1.0f, -2.0f, 1.0f));

		pSys[0]->setInitialVelocity( vector3f( 0.0f, 1.0f, 0.0f) );
		pSys[0]->setGravity( vector3f( 0.0f, 1.0f, 0.0f) );
		pSys[0]->setWind( vector3f( 0.6f, 0.0f, 0.0f) );
		pSys[0]->setWindState(true);

Datastrukturer

Nå som vi har avklart hva en partikkel skal inneholde av data og hva partikkelsystemet vårt skal inneholde av krefter, og lignende vil det være aktuelt å se på hvordan vi mest effektivt kan representere partikler i koden vår. Da minneallokering til en partikkel-struct kan være en dyr affære i forhold til tid er dette noe vi ønsker bare å gjøre en gang. Dette vil si at når en partikkel i systemet vårt er markert som død (liv/alder er på et bestemt tall) så ønsker vi å gjenbruke den og ikke frigjøre minnet allokert til den for deretter å allokere minnet til en ny partikkel når vi trenger en ny.

** Arrayliste
Her oppretter vi et arrayliste av en partikkelstruct eller partikkelklasse. Vi kan så gå igjennom denne listen for hver frame og sjekke for hver partikkel om denne partikkelen er aktiv, trengs å resettes og andre operasjoner på en partikkel. Problemet med denne løsningen er at om vi f.eks. har 100 partikler, og vi slipper ut 5 partikler hvert sekund så vil denne løsningen fortsatt i sin enkleste form trenge å loope igjennom alle partiklene selv om de er markert som inaktive før vi kan gå videre i programmet. Dette er et tenkt 'worst case' scenario, men logikken blir gyldig i større systemer der kanskje halvparten av partiklene er inaktive i systemet. En løsning på dette kan være å ha en pekerarrayer med aktive og inaktive/frie partikler og så flytte pekeren over til første ledige plass i arrayet, men neste beskrevet løsning vil fungere mer effektivt.

** Lenket liste
Vi bruker her to lenkede lister, en for aktive partikler og en for inaktive/frie partikler. Når vi så i programmet vårt oppdager at en partikkel er død kan vi så bare flytte den i fra den aktive lista og over på den frie. En lenket liste kan fjerne og legge til elementer midt i lista. Svakhetene ved en lenket liste som det å få tilgang til et element midt i lista er ikke noe vi blir berørt av i dette systemet. Det er denne løsningen jeg kommer til å benytte meg av i systemet mitt. Når vi så har bestemt oss for å bruke lenket liste blir det partikkelsystemet sitt ansvar å håndtere partikler i fra den aktive lista og over til den frie, dette er dog enkel peker flytting og er ikke videre vanskelig å implementere eller forstå. Hvis vi ser tilbake på hvilke attributter vi har i vår partikkel mer spesifikt posisjon og fart ser vi at vi gjentar en struktur data med 3 floats. Det kan derfor være effektivt å benytte seg av en utility-klasse som representerer disse tre verdiene som en vektor. Vi kan også bruke denne vektoren for å representere hastighet. En slik vektorklasse vil overloade C++ sine vanlige matteoperatører slik at det går å skrive vektor1 + vektor 2, samt ha funksjoner for kryssprodukt og andre vektorutregningen. Faktisk vil det være behov for å ha en forenklet vektorklasse i de fleste OpenGL programmer samt hjelpeklasser for matriser. I programmet mitt her har jeg brukt et hjelpebibliotek skrevet av Kevin Harris, men det er ikke vanskelig å skrive en selv da det strengt tatt bare blir å følge matteboka på regnemåter på en vektor.

Grafisk fremstilling

Da det å bare vise et lite punkt i programmet vårt kan bli litt ensformig i lengden er det flere teknikker å benytte seg av for å visere mer avanserte strukturer. Et steg opp i fra GL_POINTS er å bruke GL_TRIANGLE_STRIP samt GL_QUADS i forbindelse med å gå vekk i fra punkter kommer vi også til det problemet med at en triangel eller en firkant er særdeles tynn på den ene kanten og dette kan se merkelig ut om vi roterer kameraet rundt slik at vi kommer på kanten av en partikkel. Så det kan være praktisk å bare bruke såkalte 2D-partikler som alltid viser flatsiden mot skjermen. Teknikken jeg bruker i partikkelsystemet mitt er noe som baserer seg på dette siste prinsippet og kalles billboarding og sammen med denne benytter jeg meg også av en extention til OpenGL som bidrar med en mye mer effektiv måte å tegne disse billboardene/spritene. For å benytte oss av denne extentionen må vi laste ned og inkludere en header fil i fra OpenGL sin hjemmeside. Denne filen er inkludert i prosjektet. For å utnytte grafikk-kortet maksimalt er det også vanlig å bruke en vertex-shader for å vise partikkelene. Dette er dog litt over mitt hode. Hvis vi har nok ressurser på systemet som skal behandle programmet og partikkelsystemet vårt er det ingenting som tilsier at vi ikke kan ha relativt detaljert grafikk i form av en mesh eller tilsvarende på hver partikkel. Normalt sett må vi derimot forenkle den grafiske fremstillingen av partikkelsystemet vårt. Ta eksempelet med bier, istedenfor å rendre hver enkelt bie ut i fra meshen sin vil det være hensiktsmessig å prerendre bien og så bruke bildet av bien på partikkelen på et billboard under kjøring.

[1] [2]

Kode

Prosjektet (C++/TAO) med alle nødvendige filer: particlesystem.zip
Merk Hvis du kjører .exe-fila så vil spacebar skifte mellom tre ulike partikkeldemoer (snø, røyk, skygge)

Eksempelkoden i https://www.codesampler.com/source/ogl_point_sprites.zip har også vært til stor hjelp.

Referanser
  1. NeHe Lesson: 19Jens SchneiderNeHe productionsnehe.gamedev.net/data/lessons/lesson.asp?lesson=1914-04-2010
  1. Building an Advanced Particle SystemJohn Van Der BurgGamaSutrawww.gamasutra.com/view/feature/3157/building_an_advanced_particle_.php14-04-2010
Vedlikehold

Skrevet av Student Aleksander Lygren Toppe, våren 2006
Tilpassing av layout, B. Stenseth, mai 2006

Å tegne:>Partikler
til toppen