HTML

Az élet kódjai

Csináld maga. Senki nem csinálja meg helyetted.

Friss topikok

Részecskerendszerek geometry shaderben

2012.02.02. 22:20 Travis.CG

Az OpenGL 4.0-tól kezdve nem csak csúcspontokként, pixelenként, de primitívenként is futtathatunk programokat. Ez azt jelenti, hogy ha van egy összetett alakzatunk, akkor az azt felépítő háromszögek mindegyikét feldolgozhatjuk shaderrel.

Mit jelent mindez a gyakorlatban? A vertex shader csak egyfajta "ajánlás", hogy miként jelenjen meg az alakzat, a tényleges megvalósítást a geometry shaderre bízhatjuk. Ha ehhez még hozzávesszük azt is, hogy a geometry shaderben új csúcspontokat hozhatunk létre, akkor nem meglepő, ha az ember rögtön azon kezd el gondolkodni, miként használhatja ezt egy demóban.

A koncepció bemutatására egy nagyon egyszerű részecske rendszert fogunk látni, ami halványan egy tüzijátékra emlékeztet. A csúcspontok a CPU-ban kerülnek kiszámításra, majd a geometry shaderben repeszeket adunk hozzá. Mindent pontokként rajzolunk ki.

Először meghatározzuk a csúcspontok helyzetét:

   for(i = 0; i < PARTICLE_NUM * 3; i++){
      pos[i] = drand48() - 0.5;
   }
Véletlenszerűen kiválasztunk néhány koordinátát. Ezután létrehozunk egy vertex array objectet.

   glGenVertexArrays(1, &fire_vao);
   glBindVertexArray(fire_vao);

   glGenBuffers(1, &fire_vbo);
   glBindBuffer(GL_ARRAY_BUFFER, fire_vbo);
   glBufferData(GL_ARRAY_BUFFER, PARTICLE_NUM * 3 * sizeof(GLfloat), pos, GL_STATIC_DRAW);
   glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
   glEnableVertexAttribArray(0);
További attribútumokat is megadhatunk újabb bufferek hozzáadásával. Minden attól függ, mennyire összetett részecskéket akarunk. Most csak a pozíció kerül átadásra.

void PlayFirework(){
   double time;
   GLuint uni;

   time = getTimeInterval();
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   uni = glGetUniformLocation(fire_prg, "time");
   glUniform1f(uni, time);

   glUseProgram(fire_prg);
   glBindVertexArray(fire_vao);
   glDrawArrays(GL_POINTS, 0, PARTICLE_NUM);
}
A megjelenítés abból áll, hogy a csúcspontokat kirajzoljuk a megfelelő shaderrel. Mivel animációt is akarunk, ezért az eltelt időt is elküldjük egy változón keresztül. (time) Végül lássuk a shadereket.

#version 410
layout (location = 0) in vec3 vertex;

void main(void){
   gl_Position = vec4(vertex, 1.0);
}
A vertex shader nem csinál semmi izgalmasat. Megmondjuk neki, hogy a buffer mely attribútumát használja fel, majd tovább is adjuk ezt az információt.

#version 410

layout (points) in;
layout (points, max_vertices = 40) out;

out vec4 color;

uniform float time;

void main(){
  float theta;

  vec4 emitter = gl_in[0].gl_Position;
  float offset = mod(time, 2) / 22.0;

  gl_Position = emitter;
  color = vec4(1.0);
  EmitVertex();
  EndPrimitive();

  for(theta = 0.0; theta < 3.14 * 2.0; theta += 0.2){
     gl_Position.x = emitter.x + offset * sin(theta);
     gl_Position.y = emitter.y + offset * cos(theta);
     color = vec4(1.0 - (offset * 10.0), 0.0, 0.0, 1.0);
     EmitVertex();
     EndPrimitive();
  }
}
A geometry shader végzi a munka oroszlán részét. Megmondjuk, hogy milyen formában fogadjuk a primitíveket. Most pontok vannak beállítva, de természetesen lehetnek háromszöget is, a program felépítésétől függően. Azt is megadjuk, hogy mi a kimeneti primitív típusa. A példában ez is pont. Érdekes, hogy nincs szükség arra, hogy a bemeneti és kimeneti típusok megegyezzenek. Tehát én akár háromszögeket is felépíthetek a bemeneti pontok alapján.

Leírásokban általában nem utalnak rá, de logikus, hogy nem lehet akár mennyi új csúcspontot képezni. Az általam birtokolt kártya megkövetelte, hogy állítsam be ennek maximális számát. Ennek hiányában nem generált hibaüzenetet, de nem is jelenített meg semmit. Nálam maximálisan 256 csúcspontot lehet emittálni. (Eredetileg gömböket akartam rajzolni, de nagyon hamar túlléptem a keretet.) A color kimeneti változót a fragment shader fogja megkapni. A time pedig az idő intervallum, amit szintén a CPU küld át.

Ami a shader szempontjából a legfontosabb, az a gl_in struktúra tömb. A tömb annyi elemet tartalmaz, ahány csúcspontot a primitív. Mivel pontokból kapjuk a csúcspontokat, ezért egyetlen elemet tartalmaz a tömb. A struktúra egyik eleme a pozíció információ (gl_Position)

Az idő alapján kiszámoljuk, mennyire mozdulhatnak el a repeszek a középponttól, erre szolgál az offset. Most jön az első érdekes rész. A gl_Position változónak megadjuk pontosan ugyan azt az értéket, amit megkaptunk. Ez csupán azért kell, hogy lássuk a középpontját a tüzijátéknak. A színe is más a pontnak. Két geometry shader specifikus függvény zárja a szakaszt. Az EmitVertex() jelzi, hogy elkészültünk egy új csúcsponttal, majd az EmitPrimitive(), hogy a primitív is elkészült. Ha háromszögekkel dolgoznánk, akkor háromszor annyi EmitVertex() kellene, mint EmitPrimitive().

Egy ciklusban is csúcspontokat hozunk létre. Egy folyamatosan növekvő kör érintőjén helyezkednek el a csúcsok, és az idővel halványulnak. Két másodpercenként újabb adag részecske keletkezik.

#version 410

layout (location = 0) out vec4 FragColor;
in vec4 color;

void main(void){
   FragColor = color;
}
Végezetül a korábban beállított szín segítségével megjelenítjük a pontokat.

Az új shaderek segítségével egyre több feladatot adhatunk át a GPU-nak. Új alakzatokat hozhatunk létre, és akár egy jó kis demót is írhatunk segítségükkel, mint amilyen a Texas is a keyborderstől. Ez a demó DirectX-es ugyan, de az OpenGL 4 segítségével mi is használhatjuk a technikát.

Szólj hozzá!

Címkék: programozás demoscene opengl

A bejegyzés trackback címe:

https://cybernetic.blog.hu/api/trackback/id/tr164054865

Kommentek:

A hozzászólások a vonatkozó jogszabályok  értelmében felhasználói tartalomnak minősülnek, értük a szolgáltatás technikai  üzemeltetője semmilyen felelősséget nem vállal, azokat nem ellenőrzi. Kifogás esetén forduljon a blog szerkesztőjéhez. Részletek a  Felhasználási feltételekben és az adatvédelmi tájékoztatóban.

Nincsenek hozzászólások.
süti beállítások módosítása