HTML

Az élet kódjai

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

Friss topikok

Részecskerendszerek fragment shaderben

2011.12.22. 22:28 Travis.CG

Biopunk című demónkban volt egy érdekes effekt, ami egyfajta sejtosztódást próbált meg utánozni. Maga az effekt nem túl bonyolult, egy metaballs implementáció volt 2D-ben. Röviden arról van szó, hogy a képernyőn részecskék mozognak, de minden pixelnél az összes részecskéktől való távolságot összeadjuk, és ez alapján határozzuk meg az adott pixel színét. Itt sokkal részletesebben lehet olvasni róluk.

Organikusnak tűnő alakzatokat lehet vele készíteni, amelyek "nehezen szakadnak" el egymástól. Ideálisak sejtosztódás szimulálására. A megjelenítés módja pedig óhatatlanul azt vonta maga után, hogy fragment shaderben kell elkészíteni.

Az én implementációm csak annyiban különbözött a hétköznapi metaballtól, hogy bizonyos idő elteltével minden részecskéből egy új született. Az új részecske tulajdonságait a GPU-n kívül akartam beállítani. Itt jött az első probléma: hogyan küldjem el a részecskék tulajdonságait (sebesség, életkor, pozíció) a fragment shadernek? Kis számú részecske esetén változóként beadhatom a shadereknek, ez nem probléma, de én 80 darab sejtet akarta.

A megoldás az volt, hogy textúraként adom át a részecske adatokat. CPU-n meghatározom a tulajdonságokat, majd textúrát készítek belőle és hagyom, hogy a megjelenítés nehézségét a shader elvégezze. Csupán egy uniform változót adok át, a részecskék számát (a textúrán kívül, természetesen).

A textúra minden egyes oszlopa egy részecskének felelt meg, a sorok pedig az egyes tulajdonságoknak. Az egyszerúség kedvéért csak egycsatornás textúrát készítettem.

   glGenTextures(1, &celltexture);
   glBindTexture(GL_TEXTURE_2D, celltexture);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
   glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, MAXCELLNUM, CELLATTRIBNUM, 0, GL_RED, GL_UNSIGNED_BYTE, 0);
   glBindTexture(GL_TEXTURE_2D, 0);

 Ezután készítettem egy pixel buffer objectet (továbbiakban PBO).

   glGenBuffers(1, &cellbuffer);
   glBindBuffer(GL_PIXEL_UNPACK_BUFFER, cellbuffer);
   glBufferData(GL_PIXEL_UNPACK_BUFFER, sizeof(GLubyte) * MAXCELLNUM * CELLATTRIBNUM, 0, GL_DYNAMIC_DRAW);
A kirajzolásnál frissítettem a PBO-t, majd átmásoltam azt a textúrába (glTexSubImage2D függvény segítségével):

   glBindTexture(GL_TEXTURE_2D, celltexture);
   glBindBuffer(GL_PIXEL_UNPACK_BUFFER, cellbuffer);
   updatePixelData(time);
   /* Copy from PBO to texture */
   glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, MAXCELLNUM, CELLATTRIBNUM, GL_RED, GL_UNSIGNED_BYTE, 0);
   glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
Az updatePixelData tartalmazza a PBO frissítését. A részecskékkel kapcsolatos részt most kihagyom és csak az OpenGL kódra összpontosítok:

   ptr = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
   for(i = 0; i < cells.bornedcells; i++){

   /* Particle part skipped */

      ptr[0 * MAXCELLNUM + i] = (65535.0 * cells.cells[i].xpos) / 255.0;
      ptr[1*  MAXCELLNUM + i] = (int)(65535.0 * cells.cells[i].xpos) % 255;
      ptr[2 * MAXCELLNUM + i] = (65535.0 * cells.cells[i].ypos) / 255.0;
      ptr[3 * MAXCELLNUM + i] = (int)(65535.0 * cells.cells[i].ypos) % 255;
   }
   glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
Gyakorlatilag a glMapBuffer és glUnmapBuffer a lényeges. Kapunk egy pointert, amin keresztül új értékekkel tölthetjük fel a PBO-t. Mivel nekem megvolt a részecskék összes tulajdonsága a CPU-ban is, ezért GL_WRITE_ONLY-ként határoztam meg a puffert.

A shader a celldata nevű textúrában kapja meg a részecskéket és a texelFetch utasítással olvasom ki az értékeket.

#version 150
#define MIN_THRESHOLD 0.99f
in vec2 texcoord;
out vec4 gl_FragColor;
uniform sampler2D celldata;
uniform int bornedcellnum;

float equation(float radius, float mx, float my, float x, float y){
   return( radius / sqrt( (x-mx)*(x-mx) + (y-my)*(y-my) ) );
}

void main(void){
   int i;
   vec2 cellsize = textureSize(celldata, 0);
   float sum = 0.0;

   for(i = 0; i < bornedcellnum && sum < MIN_THRESHOLD; i++){
      float mxl = texelFetch(celldata, ivec2(i,0), 0).r;
      float mxh = texelFetch(celldata, ivec2(i,1), 0).r;
      float myl = texelFetch(celldata, ivec2(i,2), 0).r;
      float myh = texelFetch(celldata, ivec2(i,3), 0).r;
      float mx = mxl + mxh/256.0;
      float my = myl + myh/256.0;
      float x = texcoord.x;
      float y = texcoord.y;
      sum += equation(0.003, mx, my, x, y);
   }

   if(sum >= MIN_THRESHOLD){
      float c = sum / 3.0;
      c = max(c, 0.5);
      gl_FragColor = vec4(c,c,c,1.0);
   }
   else{
      gl_FragColor = vec4(0.0, 1.0 - distance(texcoord, vec2(0.5)), 0.0, 1.0);
   }
}
A sejtek pozícióit nem elég 8 biten tárolni, ezért a pozíciókat a textúrában két sorban tárolom, azért van ennyi bűvészkedés a sejtek helyzetének meghatározása körül. Gyakorlatilag ez egy tervezési hiba, ha a textúrák nem 1 byte-on tárolnák az adatokat, a kód egyszerűbb lehetne. Aki fel akarja használni a kódot, annak célszerű kijavítani ezt.

A teljes forrás letölthető innen. Az src könyvtárban található celldivide.c fájl tartalmazza a sejtosztódós részt.

Szólj hozzá!

Címkék: programozás demoscene opengl

A bejegyzés trackback címe:

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

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