The positions and
velocities of the particles are stored in four floating-point textures,
which are grouped inside two GLTexturePingPong objects (one for the two
position textures and the other for the two velocity textures). The
system has N = W * H, particles, where W and H are the width and
height of these textures. This means that the texel (i, j) contains the
position (velocity) of the (i * W + j) particle:
GLTextureParameters floatTexParams = new GLTextureParameters();
floatTexParams.format = GLTexture.FLOAT4;
partPosTex = new GLTexturePingPong(new GLTexture(this, SYSTEM_SIZE, floatTexParams),
new GLTexture(this, SYSTEM_SIZE, floatTexParams));
partVelTex = new GLTexturePingPong(new GLTexture(this, SYSTEM_SIZE, floatTexParams),
new GLTexture(this, SYSTEM_SIZE, floatTexParams));
The variable
SYSTEM_SIZE is
the number of particles specified by the user, however the final number
of particles is approximated by power-of-two integer N which is closes
to
SYSTEM_SIZE.
The initial position of the particles is set to random values inside
the
(0, width)x(0, height)
box, while the velocities are set to zero:
partPosTex.getReadTex().setRandom(0, width, 0, height, 0, 0, 0, 0);
partPosTex.getWriteTex().setRandom(0, width, 0, height, 0, 0, 0, 0);
partVelTex.getReadTex().setZero();
partVelTex.getWriteTex().setZero();
The movement of the particles is calculated by the
movePartFilter filter, which takes
as parameters the width and height of the screen, current mouse
position and displacement vector with respect to the position in the
previous frame:
movePartFilterParams.setVec21(width, height);
movePartFilterParams.setVec22(mouseX, mouseY);
movePartFilterParams.setVec23(mouseX - pmouseX, mouseY - pmouseY);
GLTexture[] inputTex = { partPosTex.getReadTex(), partVelTex.getReadTex() };
GLTexture[] outputTex = { partPosTex.getWriteTex(), partVelTex.getWriteTex() };
movePartFilter.apply(inputTex, outputTex, movePartFilterParams);
partPosTex.swap();
partVelTex.swap();
The values stored in the position texture are then used to render the
particles at their correct positions with the
renderPartFilter. This filter takes
two textures as input: the position texture, and a texture to paint the
particles:
inputTex[0] = partPosTex.getReadTex();
inputTex[1] = bubbleTex;
renderPartFilter.apply(inputTex, canvasTex);
The method to draw each particle on the correct position involves a
technique called
displacement
mapping, which consists in altering the vertex coordinates using
values read from a texture in the vertex shader. The filter pushes 4 *
N vertices (a quad for each particle) through the GPU pipeline, each
quad being centered at (0, 0). This is configured in the xml of te
render filter by indicating a texture grid as follows:
<grid mode="compiled">
<resolution nx="w0" ny="h0" mode="quads"></resolution>
<point>
<coord x="-0.5" y="+0.5"></coord>
<texcoord s="s" t="t"></texcoord>
<texcoord s="0.0" t="1.0"></texcoord>
</point>
<point>
<coord x="+0.5" y="+0.5"></coord>
<texcoord s="s" t="t"></texcoord>
<texcoord s="1.0" t="1.0"></texcoord>
</point>
<point>
<coord x="+0.5" y="-0.5"></coord>
<texcoord s="s" t="t"></texcoord>
<texcoord s="1.0" t="0.0"></texcoord>
</point>
<point>
<coord x="-0.5" y="-0.5"></coord>
<texcoord s="s" t="t"></texcoord>
<texcoord s="0.0" t="0.0"></texcoord>
</point>
</grid>
The resolution of the grid is set to
(w0,
h0), which represents the resolution of the first input texture,
in this case the position texture. Each vertex in this grid has texture
coordinates (s, t) in the first texture unit, and these coordinates are
used to read the position texture in the vertex stage of the shader and
displace the vertex positions to the correct particle location. The
second texture coordinate is used to draw the particle texture.