ここ最近、Unityの開発にて、2DメタボールっぽいAssetを使ってます。ただ、仕組みはスルーしていました。
使うにつれて2Dメタボールの仕組みが気になったので、色々ググり、実際に作ってみました!(当然ながら、偉大なる先人が残した情報のお陰様でできました)
RenderTextureの使い方を学ぶ良いキッカケにもなり、個人的に学習満足度が高かったです!
ここでは、自分の備忘録もかねて、Unityの2Dメタボールの作り方を紹介します。
追記:2019/6/21
GithubにサンプルコードをUpしました。参考になれば幸いです!
2DMetaball_Shader(サンプル| Github
参考にした記事
2Dメタボールの作り方は色々あるのですが、個人的に下記の記事が最もわかりやすかったです。
神記事:【Unity】 簡単に水に近い表現を実現したい (Metaball)
他の2DメタボールのチュートリアルやAssetなどのソースも見たのですが、上記の記事が直感的でシンプル!最高でした。
参考:METABALL TUTORIAL
今回は上記の記事を参考に、実装までの流れと作り方を紹介していきます。
2Dメタボールの式
2Dメタボールの式はこちら。wikiから引用しています。x0,y0はメタボールの中心です。
\sum_{i=0}^{m} \frac{1}{(x-x_0)^2 + (y-y_0)^2} \leq threshold\\
wiki:Metaballs
参考にした記事に、2つのメタボールが存在するときの等高線グラフがありました。こちらの図をみますと、何となくイメージできると思います。
2Dメタボールの実装の流れ
- Shaderで2Dメタボールの式を表現(メタボール1個の式)
- RenderTextureに描画
- ShaderでRenderTextureを完成形にもっていく(各メタボールの和)
- Quadで描画!
- CullingやLayerの設定して完成!
STEP1:Shaderで2Dメタボールの式を表現
2Dメタボール1個分の式をShaderで表現します。参考にした記事と大きく変わりません。というか殆ど同じです(爆)
Shader "Metaball/MetaballParticle" {
Properties
{
// _Color ("Color", Color) = (1,1,1,1)
_Scale ("Scale", Range(0,0.05)) = 0.01
_Cutoff ("Cutoff", Range(0,05)) = 0.01
}
SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
}
Cull Off
Lighting Off
ZWrite Off
Blend One OneMinusSrcAlpha
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata_t
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 texcoord : TEXCOORD0;
};
fixed _Scale;
fixed _Cutoff;
v2f vert(appdata_t IN)
{
v2f OUT;
OUT.vertex = UnityObjectToClipPos(IN.vertex);
OUT.texcoord = IN.texcoord;
// OUT.color = IN.color * _Color;
return OUT;
}
fixed4 frag (v2f i) : SV_Target {
fixed2 uv = i.texcoord - 0.5;
fixed a = 1 / (uv.x * uv.x + uv.y * uv.y);
a *= _Scale;
fixed4 color = a;
clip(color.a - _Cutoff);
return color;
}
ENDCG
}
}
}
3行でまとめると
- メタボールの左辺の式を色
color
で出力 - メタボールの中央は、uv座標の中心(0,5, 0,5)
-
clip(a,b)
はb
をa
が下回ると、0になる

STEP2: RenderTextureに描画
続いて、RenderTextureに描画させます。理由は各々のメタボールの式の和(ここでは色値の和)を計算する際に、必要だからです。
やり方を図で紹介します。
まずProjectのフォルダ内にRenderTextureを作成。
ヒエラルキー内にMainCameraとは別のカメラを用意して、TargetTextureにアタッチします。これで準備OK!
STEP3:3. ShaderでRenderTextureを完成形にもっていく(各メタボールの和)
続いて、RenderTexture内の色情報の和を計算し、閾値を設けます。参考記事のソースを掲載させて頂きます。
Shader "Metaball/MetaballRenderer" {
Properties
{
_MainTex ("MainTex", 2D) = "white" {}
_Color ("Color", Color) = (1,1,1,1)
_Cutoff ("Cutoff", Range(0,1)) = 0.5
_Stroke ("Storke", Range(0,1)) = 0.1
_StrokeColor ("StrokeColor", Color) = (1,1,1,1)
}
SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
// "PreviewType"="Plane"
}
Cull Off//裏も描画する
// Lighting Off
ZWrite Off //transparentで使用する部分
Blend One OneMinusSrcAlpha
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata_t
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
sampler2D _MainTex;
half4 _Color;
fixed _Cutoff;
fixed _Stroke;
half4 _StrokeColor;
v2f vert(appdata_t IN)
{
v2f OUT;
OUT.vertex = UnityObjectToClipPos(IN.vertex);
OUT.texcoord = IN.texcoord;
OUT.color = IN.color * _Color;
return OUT;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 color = tex2D(_MainTex, i.texcoord);
clip(color.a - _Cutoff);
color = color.a < _Stroke ? _StrokeColor : _Color;
return color;
}
ENDCG
}
}
}
clipで最初にマスクし、_Stroke
の閾値で、メタボールの枠線が出たり出なかったりします。
これで大体終わりです。あともう少し!
STEP4:Quadで描画!
STEP2のRenderTextureとSTEP3のshaderをQuadにぶちこみます。
STEP5:CullingやLayerの設定して完成!
カメラのCullingMaskを設定やLayerを設定して、メインカメラにはメタボールの最終系のみ写します。
やり方はこちらになります。
まず、メタボールたちのLayerをDefaultから変更(ここではEffectLayer)します。
その後、メタボールカメラのCullingMaskを上記で設定したLayerに変更。するとRenderTextureでは完成形前のメタボールのみ描画されるので、所望の描画ができるわけです!
最後に
いかがでしょうか。思ったより簡単にできる!と感じた方も多いかも知れません。
色々な表現に使えるので、興味ある方はぜひお試し頂ければと思います!