Original Post
I'm playing around with a bit of code that renders 2D metaballs. I got it up and running fairly quickly, because the theory and maths behind metaballs is fairly straightforward (at least in 2D it is) ;) My code uses the inverse-square law for working out how much influence a metaball has on any given pixel depending on the distance, scales it by a "mass" given to the metaball (I tend to think of a metaball's area of influence as being akin to a gravity field - I render everything inside an arbitrary fixed "event horizon", in this case 1.0). Here's my first-pass at the code: I have a 2D array of "influence" values, and I iterate through every one of them adding up the influence on that pixel from all of my metaballs, and then I do a second pass, ignoring everything below the threshold value (colouring it black), and generating a good colour for everything above it (in the cut-down code above, it just renders it white). It works, but it's not very efficient. Increasing the number of metaballs slows it down - increasing the screen resolution slows it down a LOT. My question is: What would you do to optimise this? - Is there a clever trick I can do in the first pass to help me avoid doing WINDOW_WIDTH * WINDOW_HEIGHT * NUM_METABALLS bits of calculation? This function gets called every frame, and the movement of the metaballs is fairly arbitrary so I'm not sure what (if anything) I can predict or cache. - Is there a way I can fold both passes into a single pass? Would it help? - Is this as simple as it gets, and my only option from here is to rewrite this in assembly? (I don't really know assembly, but I'd have a stab at learning it if it was the best way to do this) - Is this a suitable job for some kind of shader? (again, I haven't really played with shaders but would be willing to try) Any thoughts would be much appreciated. Cheers.
typedef struct
{
float r, g, b;
} Colour;
typedef struct
{
float x, y;
float mass;
} MetaBall;
MetaBall metaballs[NUM_METABALLS];
float influence[WINDOW_WIDTH][WINDOW_HEIGHT];
Colour pixels[WINDOW_WIDTH][WINDOW_HEIGHT];
void CalculateMetaballs()
{
// Process metaball positions and stuff
for (int ball = 0; ball < NUM_METABALLS; ++ball)
{
// ... Update the metaballs mass and position ...
}
// Reset the influence map
memset(influence, 0, WINDOW_WIDTH * WINDOW_HEIGHT * sizeof(float));
// 1st pass: Recalculate the "gravity" field
for (int x = 0; x < WINDOW_WIDTH; ++x)
{
for (int y = 0; y < WINDOW_HEIGHT; ++y)
{
for (int ball = 0; ball < NUM_METABALLS; ++ball)
{
float xDist = (x - metaballs[ball].x);
float yDist = (y - metaballs[ball].y);
float xDistSq = xDist * xDist;
float yDistSq = yDist * yDist;
// Inverse square law... 1 / (distance^2)
// Here it's being scaled by the metaball's "mass", which affects its size onscreen
float g = metaballs[ball].mass / (xDistSq + yDistSq);
// Add this to the influence map
influence[x][y] += g;
}
}
}
// 2nd pass: Copy to texture
for (int x = 0; x < WINDOW_WIDTH; ++x)
{
for (int y = 0; y < WINDOW_HEIGHT; ++y)
{
float currInfl = influence[x][y];
// Test to see if this pixel falls inside or outside a metaball boundary
if (currInfl >= 1.0f)
{
// Flat shading for the purposes of this example, although my code
// can also do some extra maths which turns the influence value into something
// that looks nicely shaded onscreen
currInfl = 1.0f;
}
else
{
currInfl = 0.0f;
}
// set the pixel colour (greyscale in this case)
pixels[x][y].r = pixels[x][y].g = pixels[x][y].b = currInfl;
}
}
// Bind the pixel buffer to a texture, and render it as a 2D quad
}

