Metaballs are organic-looking balls that deform as they merge or split, as shown in the picture below. It’s a neat little effect, often used to render liquids or gels. This is a tutorial on how to produce 2D metaballs in XNA. As a bonus, the code requires no custom shaders, and therefore works equally well on Windows Phone, Xbox, and PC.
To produce metaballs, you create a function, f(x, y), that becomes smaller as you get further from the metaball. This is your falloff function. It determines how strong the influence of the metaball is at any given point. Next, you sum all the falloff functions from all the metaballs. Any point that has a value greater than some threshold is part of the metaballs.
You can picture this as taking a rubber sheet and placing heavy marbles on it. The marbles will make wells in the sheet. These wells are your falloff function. Then you take this rubber sheet, and lower it carefully into a tray of paint, just enough so that wells are partly submerged. The painted areas would now look like metaballs.
We will implement this technique as follows:
- Make a metaball texture using a falloff function.
- For each metaball, draw the texture to a render target using additive blending.
- Draw the render target to the backbuffer using AlphaTestEffect.
Making the Metaball Texture
To make the metaball texture, we must first choose a falloff function. A good falloff function meets two requirements:
- It goes to zero after a given distance. This allows us to fit the entire non-zero area of the function in our texture.
- Smoothness. Otherwise you may get creases in your metaballs.
I used the following falloff function:
Where r is the distance from the center of the metaball. If we draw this function, with a value of 1 being white and 0 being black, it produces the texture below on the left. We will use this as the as alpha channel of our metaball texture so we can later threshold it using AlphaTestEffect. You can set the colour channels as you see fit. I coloured mine using a red and yellow gradient.
You could simply save the coloured image above, load it into your XNA project, and use it to make metaballs. However, if you’re interested in making your own metaball textures, the code to so is shown below. If you make your own texture, you can colour it as you like (in case you don’t like my impeccable taste).
int length = radius * 2; Color[] colors = new Color[length * length]; for (int y = 0; y < length; y++) { for (int x = 0; x < length; x++) { float distance = Vector2.Distance(Vector2.One, new Vector2(x, y) / radius); float alpha = Falloff(distance); colors[y * length + x] = PickWhateverColorYouLike(); colors[y * length + x].A = (byte)MathHelper.Clamp(alpha * 256f + 0.5f, 0f, 255f); } } Texture2D tex = new Texture2D(GraphicsDevice, length, length); tex.SetData(colors);
You can use different falloff functions to get different effects. Here is a square falloff function:
And the metaballs it produces:
Summing the Metaballs
This step is simple. First, create a bunch of metaballs at different positions and a render target to draw them to. Then draw the metaballs to the render target using additive blending.
// class variable List<Vector2> metaballPositions = new List<Vector2>(); RenderTarget2D metaballTarget; // ... // in Initialize() Random r = new Random(); int width = GraphicsDevice.Viewport.Width; int height = GraphicsDevice.Viewport.Height; for (int i = 0; i < 30; i++) metaballPositions.Add(new Vector2(r.Next(width), r.Next(height))); metaballTarget = new RenderTarget2D(GraphicsDevice, width, height); // ... // in Draw() GraphicsDevice.SetRenderTarget(metaballTarget); GraphicsDevice.Clear(Color.Transparent); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive); foreach (var position in metaballPositions) spriteBatch.Draw(metaballTexture, position, Color.White); spriteBatch.End();
Running this with my coloured metaball texture yields this:
Thresholding the Metaballs
The final step is thresholding. We draw the render target to the backbuffer using AlphaTestEffect to only draw the parts where the alpha value is greater than some threshold.
// class variables AlphaTestEffect alphaTest; // ... // in LoadContent() (or Initialize()) alphaTest = new AlphaTestEffect(GraphicsDevice); var view = GraphicsDevice.Viewport; alphaTest.Projection = Matrix.CreateTranslation(-0.5f, -0.5f, 0) * Matrix.CreateOrthographicOffCenter(0, view.Width, view.Height, 0, 0, 1); alphaTest.ReferenceAlpha = 128; // our threshold // ... // in Draw() GraphicsDevice.SetRenderTarget(null); // draw to the backbuffer GraphicsDevice.Clear(Color.Black); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, null, null, null, alphaTest); spriteBatch.Draw(metaballTarget, Vector2.Zero, Color.White); spriteBatch.End();
Conclusion
That’s all there is to it. In my demo, I added a few extra features to make it look nicer. The metaballs move around and I added a faint glow behind them. I also added some different colour schemes you can cycle through. For the Windows Phone version I reduced the resolution a bit and drew fewer metaballs to maintain good performance. The Windows Phone has a relatively weak fill rate, so drawing fewer pixels helps a lot.
Download the complete code sample here. Or download the Windows Phone 7 Demo, called Lava Lamp.
Pingback: 2d Metaballs with canvas! | Somethinghitme
Hey, just wanted to say, that your post was very usefull to me. Thx!
Pingback: 40+ Fantastic Game Development Tutorials From Across the Web | Gamedevtuts+
Hey thanks for the great article. I have a question though. I’m trying to restrict the metaballs to a rectangle inside the viewport. So instead of detecting when the balls hit the viewport bounds, I’m trying to see when the hit the rectangle bounds but the balls bounce off walls that seemingly arent there. What I mean is, they stay in the lower right part of the rectangle as if I had drawn the rectangle there. I suspect it has something to do with
effect.Projection = Matrix.CreateTranslation(-0.5f, -0.5f, 0) * Matrix.CreateOrthographicOffCenter(0, viewport.Width, viewport.Height, 0, 0, 1);
Can you let me know what I can do to keep the metaballs inside the rectangle? Thanks again!
Hey, nevermind. I got it. I’m not sure what I was doing wrong but I started over and it works now. Thanks again!
Hi,
Thank you very much for the tutorial.
I recently heard about metaballs, and I immediatly loved them.
They are such an easy way to create a very cool effect, and thanks to your tutorial I really understand them perfectly now.
There’s one thing I wanted to note though:
Here in the tutorial you are using (2/3)(1-r)(1-r)
But in the demo you are using (3/2)(1-r)(1-r)
It should be (3/2), otherwise you won;t see anything.
You might want to fix this 😉
Good catch! I’ve updated the article with the correct equation. Thanks!
Pingback: Cool Effects (XNA & MonoGame) ← Int6 Studios
Hey, I’m trying to implement the square falloff equation, in your solution, and I am not quite grasping what I am doing wrong here.
Is it possible for you to provide the Falloff function for the square falloff equation?
Pingback: Directx Toolkit AlphatestEffect | ?????