I made this shader, which can do up to 8 interacting meta-balls per instance.
Screeny:
//--------------------------------
// 2D Metaballs
//--------------------------------
// By Diggsey
//--------------------------------
bool metaBall1 = false ;
float4 metaBallPos1;
float metaBallRadius1;
float metaBallInvRadiusSq1;
bool metaBall2 = false ;
float4 metaBallPos2;
float metaBallRadius2;
float metaBallInvRadiusSq2;
bool metaBall3 = false ;
float4 metaBallPos3;
float metaBallRadius3;
float metaBallInvRadiusSq3;
bool metaBall4 = false ;
float4 metaBallPos4;
float metaBallRadius4;
float metaBallInvRadiusSq4;
bool metaBall5 = false ;
float4 metaBallPos5;
float metaBallRadius5;
float metaBallInvRadiusSq5;
bool metaBall6 = false ;
float4 metaBallPos6;
float metaBallRadius6;
float metaBallInvRadiusSq6;
bool metaBall7 = false ;
float4 metaBallPos7;
float metaBallRadius7;
float metaBallInvRadiusSq7;
bool metaBall8 = false ;
float4 metaBallPos8;
float metaBallRadius8;
float metaBallInvRadiusSq8;
float lowerLimit = 0.48f;
float upperLimit = 0.52f;
float invLimitDifference = 25.0f;
float4 diffuse = {1.0f, 0.0f, 0.0f, 0.0f};
float screenWidth = 1024.0f;
float screenHeight = 768.0f;
// Textures
texture BaseTX < string Name=""; >;
sampler2D Base = sampler_state
{
texture = <BaseTX>;
};
// Structs
struct VSInput
{
float4 Pos:POSITION;
float2 UV:TEXCOORD;
float3 Normal : NORMAL;
};
struct VSOutput
{
float4 VPos:POSITION;
float2 UV:TEXCOORD0;
float2 Pos:TEXCOORD1;
};
struct PSInput
{
float2 UV:TEXCOORD0;
float2 Pos:TEXCOORD1;
};
VSOutput VS(VSInput IN)
{
VSOutput OUT;
OUT.VPos = IN.Pos;
OUT.Pos = float2 ((IN.Pos.x + 1.0f) * screenWidth * 0.5f, (1.0f - IN.Pos.y) * screenHeight * 0.5f);
OUT.UV = IN.UV;
return OUT;
}
float4 PS8(PSInput IN) : COLOR
{
float4 OUT;
float weight = 0.0;
float2 offset;
float distsq;
if (metaBall1)
{
offset = IN.Pos - metaBallPos1.xy;
distsq = 1.0 - ( dot (offset,offset) * metaBallInvRadiusSq1 );
if (distsq > 0.0)
weight += distsq*distsq;
}
if (metaBall2)
{
offset = IN.Pos - metaBallPos2.xy;
distsq = 1.0 - ( dot (offset,offset) * metaBallInvRadiusSq2 );
if (distsq > 0.0)
weight += distsq*distsq;
}
if (metaBall3)
{
offset = IN.Pos - metaBallPos3.xy;
distsq = 1.0 - ( dot (offset,offset) * metaBallInvRadiusSq3 );
if (distsq > 0.0)
weight += distsq*distsq;
}
if (metaBall4)
{
offset = IN.Pos - metaBallPos4.xy;
distsq = 1.0 - ( dot (offset,offset) * metaBallInvRadiusSq4 );
if (distsq > 0.0)
weight += distsq*distsq;
}
if (metaBall5)
{
offset = IN.Pos - metaBallPos5.xy;
distsq = 1.0 - ( dot (offset,offset) * metaBallInvRadiusSq5 );
if (distsq > 0.0)
weight += distsq*distsq;
}
if (metaBall6)
{
offset = IN.Pos - metaBallPos6.xy;
distsq = 1.0 - ( dot (offset,offset) * metaBallInvRadiusSq6 );
if (distsq > 0.0)
weight += distsq*distsq;
}
if (metaBall7)
{
offset = IN.Pos - metaBallPos7.xy;
distsq = 1.0 - ( dot (offset,offset) * metaBallInvRadiusSq7 );
if (distsq > 0.0)
weight += distsq*distsq;
}
if (metaBall8)
{
offset = IN.Pos - metaBallPos8.xy;
distsq = 1.0 - ( dot (offset,offset) * metaBallInvRadiusSq8 );
if (distsq > 0.0)
weight += distsq*distsq;
}
weight = (weight-lowerLimit)*invLimitDifference;
OUT = float4 (diffuse.xyz, weight);
return OUT;
}
float4 PS6(PSInput IN) : COLOR
{
float4 OUT;
float weight = 0.0;
float2 offset;
float distsq;
if (metaBall1)
{
offset = IN.Pos - metaBallPos1.xy;
distsq = 1.0 - ( dot (offset,offset) * metaBallInvRadiusSq1 );
if (distsq > 0.0)
weight += distsq*distsq;
}
if (metaBall2)
{
offset = IN.Pos - metaBallPos2.xy;
distsq = 1.0 - ( dot (offset,offset) * metaBallInvRadiusSq2 );
if (distsq > 0.0)
weight += distsq*distsq;
}
if (metaBall3)
{
offset = IN.Pos - metaBallPos3.xy;
distsq = 1.0 - ( dot (offset,offset) * metaBallInvRadiusSq3 );
if (distsq > 0.0)
weight += distsq*distsq;
}
if (metaBall4)
{
offset = IN.Pos - metaBallPos4.xy;
distsq = 1.0 - ( dot (offset,offset) * metaBallInvRadiusSq4 );
if (distsq > 0.0)
weight += distsq*distsq;
}
if (metaBall5)
{
offset = IN.Pos - metaBallPos5.xy;
distsq = 1.0 - ( dot (offset,offset) * metaBallInvRadiusSq5 );
if (distsq > 0.0)
weight += distsq*distsq;
}
if (metaBall6)
{
offset = IN.Pos - metaBallPos6.xy;
distsq = 1.0 - ( dot (offset,offset) * metaBallInvRadiusSq6 );
if (distsq > 0.0)
weight += distsq*distsq;
}
weight = (weight-lowerLimit)*invLimitDifference;
OUT = float4 (diffuse.xyz, weight);
return OUT;
}
float4 PS4(PSInput IN) : COLOR
{
float4 OUT;
float weight = 0.0;
float2 offset;
float distsq;
if (metaBall1)
{
offset = IN.Pos - metaBallPos1.xy;
distsq = 1.0 - ( dot (offset,offset) * metaBallInvRadiusSq1 );
if (distsq > 0.0)
weight += distsq*distsq;
}
if (metaBall2)
{
offset = IN.Pos - metaBallPos2.xy;
distsq = 1.0 - ( dot (offset,offset) * metaBallInvRadiusSq2 );
if (distsq > 0.0)
weight += distsq*distsq;
}
if (metaBall3)
{
offset = IN.Pos - metaBallPos3.xy;
distsq = 1.0 - ( dot (offset,offset) * metaBallInvRadiusSq3 );
if (distsq > 0.0)
weight += distsq*distsq;
}
if (metaBall4)
{
offset = IN.Pos - metaBallPos4.xy;
distsq = 1.0 - ( dot (offset,offset) * metaBallInvRadiusSq4 );
if (distsq > 0.0)
weight += distsq*distsq;
}
weight = (weight-lowerLimit)*invLimitDifference;
OUT = float4 (diffuse.xyz, weight);
return OUT;
}
float4 PS2(PSInput IN) : COLOR
{
float4 OUT;
float weight = 0.0;
float2 offset;
float distsq;
if (metaBall1)
{
offset = IN.Pos - metaBallPos1.xy;
distsq = 1.0 - ( dot (offset,offset) * metaBallInvRadiusSq1 );
if (distsq > 0.0)
weight += distsq*distsq;
}
if (metaBall2)
{
offset = IN.Pos - metaBallPos2.xy;
distsq = 1.0 - ( dot (offset,offset) * metaBallInvRadiusSq2 );
if (distsq > 0.0)
weight += distsq*distsq;
}
weight = (weight-lowerLimit)*invLimitDifference;
OUT = float4 (diffuse.xyz, weight);
return OUT;
}
technique MetaBalls8
{
pass p1
{
vertexShader = compile vs_2_0 VS();
pixelShader = compile ps_2_0 PS8();
}
}
technique MetaBalls6
{
pass p1
{
vertexShader = compile vs_2_0 VS();
pixelShader = compile ps_2_0 PS6();
}
}
technique MetaBalls4
{
pass p1
{
vertexShader = compile vs_2_0 VS();
pixelShader = compile ps_2_0 PS4();
}
}
technique MetaBalls2
{
pass p1
{
vertexShader = compile vs_2_0 VS();
pixelShader = compile ps_2_0 PS2();
}
}
It comes with these DBPro functions to make using it REALLY easy:
function InitMetaBalls(effectId, objNum, maxBalls)
load effect "metaballs.fx",effectId,0
make object plain objNum,2,2
set object transparency objNum,1
set object effect objNum,effectId
set effect constant float effectId,"screenWidth",screen width()
set effect constant float effectId,"screenHeight",screen height()
if maxBalls <= 2
set effect technique effectId,"MetaBalls2"
else
if maxBalls <= 4
set effect technique effectId,"MetaBalls4"
else
if maxBalls <= 6
set effect technique effectId,"MetaBalls6"
else
set effect technique effectId,"MetaBalls8"
endif
endif
endif
endfunction
function SetMetaBallsDiffuse(effectId, color)
null = make vector4(MetaBallVector)
set vector4 MetaBallVector,((color >> 16) && 0xFF) / 255.0,((color >> 8) && 0xFF) / 255.0,(color && 0xFF) / 255.0,1.0
set effect constant vector effectId,"diffuse",MetaBallVector
null = delete vector4(MetaBallVector)
endfunction
function SetMetaBallsLimits(effectId,lower as float, upper as float)
set effect constant float effectId,"lowerLimit",lower
set effect constant float effectId,"upperLimit",upper
set effect constant float effectId,"invLimitDifference",1.0/(upper-lower)
endfunction
function MakeMetaBall(effectId,id,x as float,y as float,radius as float)
null = make vector4(MetaBallVector)
set vector4 MetaBallVector,x,y,0,0
set effect constant boolean effectId,"metaBall" + str$(id),1
set effect constant vector effectId,"metaBallPos" + str$(id),MetaBallVector
set effect constant float effectId,"metaBallRadius" + str$(id),radius
set effect constant float effectId,"metaBallInvRadiusSq" + str$(id),1.0/(radius*radius)
null = delete vector4(MetaBallVector)
endfunction
function SetMetaBall(effectId,id,x as float,y as float,radius as float)
null = make vector4(MetaBallVector)
set vector4 MetaBallVector,x,y,0,0
set effect constant vector effectId,"metaBallPos" + str$(id),MetaBallVector
set effect constant float effectId,"metaBallRadius" + str$(id),radius
null = delete vector4(MetaBallVector)
endfunction
function PositionMetaBall(effectId,id,x as float,y as float)
null = make vector4(MetaBallVector)
set vector4 MetaBallVector,x,y,0,0
set effect constant vector effectId,"metaBallPos" + str$(id),MetaBallVector
null = delete vector4(MetaBallVector)
endfunction
function ScaleMetaBall(effectId,id,radius as float)
set effect constant float effectId,"metaBallRadius" + str$(id),radius
set effect constant float effectId,"metaBallInvRadiusSq" + str$(id),1.0/(radius*radius)
endfunction
function DeleteMetaBall(effectId,id)
set effect constant boolean effectId,"metaBall" + str$(id),0
endfunction
Just call 'InitMetaBalls', and then use 'MakeMetaBall' and 'DeleteMetaBall' just like you would any other DBPro object. The ids can range from 1 to the number you used for 'maxBalls' in 'InitMetaBalls', as long as it is no more than 8.
Here is some sample media-less code to show you quite how simple it is to use:
sync on
sync rate 0
#constant MetaBallVector 1
InitMetaBalls(1,1,4)
SetMetaBallsDiffuse(1,0xFF55FF00)
SetMetaBallsLimits(1,0.49,0.51)
MakeMetaBall(1,1,200,200,100)
MakeMetaBall(1,2,250,250,110)
MakeMetaBall(1,3,300,300,90)
MakeMetaBall(1,4,350,350,80)
color backdrop 0
do
if mouseclick() = 0
PositionMetaBall(1,1,mousex(),mousey())
else
if mouseclick() = 1
PositionMetaBall(1,2,mousex(),mousey())
else
if mouseclick() = 2
PositionMetaBall(1,3,mousex(),mousey())
else
if mouseclick() = 4
PositionMetaBall(1,4,mousex(),mousey())
endif
endif
endif
endif
text 0,0,str$(screen fps())
sync
loop
function InitMetaBalls(effectId, objNum, maxBalls)
load effect "metaballs.fx",effectId,0
make object plain objNum,2,2
set object transparency objNum,1
set object effect objNum,effectId
set effect constant float effectId,"screenWidth",screen width()
set effect constant float effectId,"screenHeight",screen height()
if maxBalls <= 2
set effect technique effectId,"MetaBalls2"
else
if maxBalls <= 4
set effect technique effectId,"MetaBalls4"
else
if maxBalls <= 6
set effect technique effectId,"MetaBalls6"
else
set effect technique effectId,"MetaBalls8"
endif
endif
endif
endfunction
function SetMetaBallsDiffuse(effectId, color)
null = make vector4(MetaBallVector)
set vector4 MetaBallVector,((color >> 16) && 0xFF) / 255.0,((color >> 8) && 0xFF) / 255.0,(color && 0xFF) / 255.0,1.0
set effect constant vector effectId,"diffuse",MetaBallVector
null = delete vector4(MetaBallVector)
endfunction
function SetMetaBallsLimits(effectId,lower as float, upper as float)
set effect constant float effectId,"lowerLimit",lower
set effect constant float effectId,"upperLimit",upper
set effect constant float effectId,"invLimitDifference",1.0/(upper-lower)
endfunction
function MakeMetaBall(effectId,id,x as float,y as float,radius as float)
null = make vector4(MetaBallVector)
set vector4 MetaBallVector,x,y,0,0
set effect constant boolean effectId,"metaBall" + str$(id),1
set effect constant vector effectId,"metaBallPos" + str$(id),MetaBallVector
set effect constant float effectId,"metaBallRadius" + str$(id),radius
set effect constant float effectId,"metaBallInvRadiusSq" + str$(id),1.0/(radius*radius)
null = delete vector4(MetaBallVector)
endfunction
function SetMetaBall(effectId,id,x as float,y as float,radius as float)
null = make vector4(MetaBallVector)
set vector4 MetaBallVector,x,y,0,0
set effect constant vector effectId,"metaBallPos" + str$(id),MetaBallVector
set effect constant float effectId,"metaBallRadius" + str$(id),radius
null = delete vector4(MetaBallVector)
endfunction
function PositionMetaBall(effectId,id,x as float,y as float)
null = make vector4(MetaBallVector)
set vector4 MetaBallVector,x,y,0,0
set effect constant vector effectId,"metaBallPos" + str$(id),MetaBallVector
null = delete vector4(MetaBallVector)
endfunction
function ScaleMetaBall(effectId,id,radius as float)
set effect constant float effectId,"metaBallRadius" + str$(id),radius
set effect constant float effectId,"metaBallInvRadiusSq" + str$(id),1.0/(radius*radius)
endfunction
function DeleteMetaBall(effectId,id)
set effect constant boolean effectId,"metaBall" + str$(id),0
endfunction
Enjoy
(And I welcome any mods
)
Tell me what FPS you get and your specs for the standard 4 meta-balls in the example.
I have attached the shader file, project file and example source code so that you can try it out easily.
edit:
There is also a lava lamp demo a few posts down: