Show HN: Metaballs
varun.ca
varun.ca
> Well, for 40 bouncing circles, on a 700x500 grid, that would be on the order of 14 million operations. If we want to have a nice smooth 60fps animation, that would be 840 million operations per second. JavaScript engines may be fast nowadays, but not that fast.
The math is super-cool, and efficiency is important for finding isosurfaces in higher dimensions, but those aren't really scary numbers for normal programs. Just tinting the screen at 2880x1800 is ~2 million operations per frame. GPUs can handle it.
A simple way to render is to draw a quad for the metaball, using the metaball kernel function in the fragment shader. Use additive blending while rendering to a texture for the first pass, then render the texture to screen with thresholding for the second pass. The end result is per-pixel sampling of the isosurface.
Admittedly, it's kind of a brute-force solution, but even the integrated GPU on my laptop can render thousands of metaballs like that at HiDPI resolutions.
(Specifically, I use a Gaussian kernel for my metaballs. It requires exp, which is more expensive computationally than a few multiplies. I render 1500 of them at 2880x1671 at 5ms per frame on an Intel Iris Pro [Haswell].)
Though, the work scales with fragment count, so a few large metaballs may be as costly many smaller ones. For large numbers of metaballs, you probably also want to use instancing so you'd need OpenGL ES 3.0 / WebGL 2.0 which are fairly recent.
But 40 metaballs with a simple kernel at 700x500? That's easy for a GPU.
For a canvas with a more limited API, you can still do it if images are GPU accelerated with a composite mode like "lighter". If that's the case, you can do basically the same thing by first rendering the metaball function to an image once, and then drawing that image for each metaball. Doing it via an image introduces extra aliasing artifacts, but might get around the API limitations.
Edit: I suppose you would still want to find a GPU-accelerated threshold function for the step after that.
You can BTW also "cheat" your way to meta-balls: https://stackoverflow.com/questions/17177748/increasing-real...
It does take some memory, but n operations per pixel for each frame for n balls plus the overhead of transforming into actual color was still pretty great. Instead of saying "GPU can do this", I'd rather ask, hey, can we do even better than that?
It is cool to see what we can do. That is one of the things I really like about winkerVSbecks' approach. It's interesting and different. Better for some uses, too, which is always nice to see.
- Assume that R1, R2 are the radii of the discs and A_ORIG is the original area (eg. R1^2 PI)
- Calculate the area A for a given R1, R2
- Multiply R1 and R2 with SQRT(A_ORIG / A)
- Repeat
If this doesn't converge after a few iterations, you can use the Newton method, or even a simple binary search to find he correct radii very quickly. A(k R1, k R2) should be monotonic for k, so solving it numerically for a given value should be trivial.
Article for more detail: https://tympanus.net/codrops/2015/03/10/creative-gooey-effec...
Beautiful article.
The approximation OP does is a good start but still far from being real metaballs.
Amazing showcase.
It's not a perfect UI element - you can't actually see the options without scrolling through it all, but I could imagine something similar being a pretty cool little thing in the right context
It's possible to do this somewhat efficiently beyond two balls with GLSL and lots of uniforms (or a UBO), since metaballs from the graphics perspective are really just distance fields.
If you want more than a few balls, you can do it in two passes: one to produce the distance field, and one to threshold it.
As an added benefit, it's straightforward to generalize these approaches to any two-dimensional continuous function.
The big difference is that it prerenders a gradient for each ball (it uses html5 canvas for that, but doing it with webgl is completely doable, although a bit more work), which is used as a distance field.
Runs at 60fps for me on a Chromebook from 2014. I suspect you're looking at it on macOS, which has had very poor (arguably the poorest of any x86 platform) OpenGL drivers for the last four or five years.
Trigonometry functions are expensive, especially the reverse ones.
If v=0.5, see [1] for how to find out sine/cosine of a maxSpread * v. For angleBetweenCenters + maxSpread * v, see [2] for how to find sine + cosine of a sum of angles.
If you’ll do all that math in the symbolic form (you can use Maple or Mathematica or something similar), you’ll get the equivalent formulae for p1-p4 that won’t use any trigonometry, only simple math and probably a square root.
[1] https://en.wikipedia.org/wiki/List_of_trigonometric_identiti... [2] https://en.wikipedia.org/wiki/List_of_trigonometric_identiti...
https://gist.github.com/Const-me/46f90ce7fc2bc65dc12a41442ed...
I once reviewed an academic paper at a major CS conference that misspelled metaballs as meatballs throughout.
Touch blob joystick shader: https://www.shadertoy.com/view/4lfcRf
About 2 minutes in there's an excellent realtime metaballs implementation that ran smoothly on a 486-66mhz. Metaballs were an extremely popular effect in the early 90's.
http://www.pouet.net/prod.php?which=1023
http://www.pouet.net/prod.php?which=911
How about first on the C64? Here's Booze in 2010:
Liming wrote a book, but it's rare. Some technical discussion towards the end of this page: http://homepages.inf.ed.ac.uk/rbf/CVonline/LOCAL_COPIES/BOWY...
* https://www.gamasutra.com/view/feature/2438/how_to_prototype...
I had to struggle with metaball rendering on canvas back then. It was so slow. Now I guess a pixel shader in webGL can do a better job.
Check this out too: https://asadmemon.com/SPHjs/ source: https://github.com/asadm/SPHjs
http://jamie-wong.com/2014/08/19/metaballs-and-marching-squa...
"Globs: A Primitive Shape for Graceful Blends Between Circles"
I wonder how much would need to be adjusted to provide a scaling factor to the first metaball such that the area was constant (Thus ending up with two equally sized metaballs) or even utilizing the speed of the pull in determining the second balls size.
Or is this university stuff? Or even spare time stuff?
Cool stuff though.
Metaballs are way too expensive.
On a related note, one of the annoying things about metaballs for fluid surfacing is that there's some spooky action at a distance. Two drops of water will reach out towards each other as they come closer together, which makes no physical sense at all.
> SPH ... with 500 000 particles ... about 2.5 fps on my GTX 1070
Still slower than what CNCD & Fairlight demonstrated in 2011 with "Numb Res", at 120fps (stereo 3D) on a geforce 280:
> The demo features up to 500,000 particles running under 3D SPH in realtime on the GPU, with surface tension and viscosity terms; this is in combination with collisions, meshing, high end effects like MLAA and depth of field, and plenty of lighting effects
Works on Unix, Mac, iOS and Android
Code mirror is here - https://github.com/Zygo/xscreensaver
W.P. van Paassen's metaballs C code is here -
https://github.com/Zygo/xscreensaver/blob/master/hacks/metab...
Also, you can set various parameter tweaks for the metaballs from the command line or settings - count, radius etc.
With the SVG/Bézier approach I doubt you could do 3D, true :)
In order to render a surface you have to either use a contouring algorithm like marching cubes to generate a mesh like the above three.js demo, or raytrace or raymarch them. Because metaballs describe a distance function, its really easy to use SDF raymarching and there is a whole category dedicated to metaball shaders on shader toy (https://www.shadertoy.com/results?query=tag%3Dmetaballs).
Interesting.
(Not affiliated with them, I just found https://blog.codepen.io/2017/10/05/regarding-referer-headers...)