This sounds like what would traditionally be called metaballs.
How I'd go about it is using shaders with a technique called SDF (Signed Distance Field). Basically it's a mathematical way of determining if a pixel is inside or outside of a shape, and how far. For example if you get an answer of 4, that means 4 pixels away from a shape, 0 means on the surface and -3 would be 3 pixels inside.
That makes it very easy to draw outlines.
The great thing about SDFs is you can combine them and distort them, such as adding the effect you want.
Have a look here for the formulas. They are actually quite small.
https://iquilezles.org/articles/distfunctions2d/ - look at the 2D circle function near the top.
https://iquilezles.org/articles/distfunctions/ - look at the smoothUnion function near the bottom. This page is 3D, but some of the functions like smoothUnion work the same, they deal with distances instead of coordinates.
I made a little shadertoy (great website, by the same guy as the articles I linked above) example.
Go to
shadertoy.com and enter this text into the source box:
// Distance to a circle
float sdCircle( vec2 p, float r )
{
return length(p) - r;
}
// Smoothly combine 2 shapes using their distances.
float opSmoothUnion( float d1, float d2, float k )
{
float h = clamp( 0.5 + 0.5*(d2-d1)/k, 0.0, 1.0 );
return mix( d2, d1, h ) - k*h*(1.0-h);
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Shadertoy passes in pixels instead of UV like normal shaders, so divide by res to get a 0 to 1.0 range.
vec2 uv = fragCoord/iResolution.xy;
// Circle positions and radii.
vec2 c1 = vec2(0.3,0.3);
vec2 c2 = vec2(0.6,0.4);
float r1 = 0.15;
float r2 = 0.15;
// Distances to the circles. The function wants circle relative positions, so do uv - circle pos.
float d1 = sdCircle(uv-c1, r1);
float d2 = sdCircle(uv-c2, r2);
// Combine the two distances to do a smooth union. d is now the distance to either shape, including the smooth part between.
float d = opSmoothUnion(d1,d2,0.15);
// Set the colour to be dark green for the background, white inside of the circles and black around the edge.
vec3 col = vec3(0,0.5,0); // background is default
if(d<-0.01)
col=vec3(1,1,1); // inside of circles
else if(d<=0.0)
col=vec3(0,0,0); // in the edge
// Output to screen
fragColor = vec4(col,1.0);
}
That won't be 100% SFML shader compatible (should be almost right though), but will let you experiment with the look of the technique.