【Unity】2Dメタボールの作り方を一から紹介してみる

ここ最近、Unityの開発にて、2DメタボールっぽいAssetを使ってます。ただ、仕組みはスルーしていました。

使うにつれて2Dメタボールの仕組みが気になったので、色々ググり、実際に作ってみました！(当然ながら、偉大なる先人が残した情報のお陰様でできました)

RenderTextureの使い方を学ぶ良いキッカケにもなり、個人的に学習満足度が高かったです！

ここでは、自分の備忘録もかねて、Unityの2Dメタボールの作り方を紹介します。
![mg6.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314351/e84d7c08-92e4-3655-cb17-64456dac1505.gif)

追記：2019/6/21
GithubにサンプルコードをUpしました。参考になれば幸いです！
[2DMetaball_Shader(サンプル| Github ](https://github.com/gurutaka/2DMetaball_Shader)
## 参考にした記事
2Dメタボールの作り方は色々あるのですが、個人的に下記の記事が最もわかりやすかったです。
神記事：[【Unity】 簡単に水に近い表現を実現したい (Metaball)](https://techblog.kayac.com/unity_advent_calendar_2018_22)

他の2DメタボールのチュートリアルやAssetなどのソースも見たのですが、上記の記事が直感的でシンプル！最高でした。
参考：[METABALL TUTORIAL](http://patomkin.com/blog/metaball-tutorial/)

今回は上記の記事を参考に、実装までの流れと作り方を紹介していきます。
## 2Dメタボールの式
2Dメタボールの式はこちら。wikiから引用しています。x０，ｙ０はメタボールの中心です。

```math
\sum_{i=0}^{m} \frac{1}{(x-x_0)^2 + (y-y_0)^2} \leq threshold\\
```
wiki:[Metaballs](https://en.wikipedia.org/wiki/Metaballs)

参考にした[記事](https://techblog.kayac.com/unity_advent_calendar_2018_22)に、２つのメタボールが存在するときの等高線グラフがありました。こちらの図をみますと、何となくイメージできると思います。
![スクリーンショット 2019-05-19 20.43.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314351/3f3c261e-4304-c076-87c6-8e7e77d02399.png)

## 2Dメタボールの実装の流れ
1. Shaderで2Dメタボールの式を表現(メタボール１個の式)
2. RenderTextureに描画
3. ShaderでRenderTextureを完成形にもっていく(各メタボールの和)
4. Quadで描画！
5. CullingやLayerの設定して完成！


### STEP1：Shaderで2Dメタボールの式を表現
２Dメタボール１個分の式をShaderで表現します。参考にした記事と大きく変わりません。というか殆ど同じです(爆)

```MetaballParticle.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
     }
}
}
```
３行でまとめると

- メタボールの左辺の式を色`color`で出力
- メタボールの中央は、uv座標の中心(0,5, 0,5)
- `clip(a,b)`は`b`を`a`が下回ると、0になる

<img width="182" alt="スクリーンショット 2019-05-19 20.59.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314351/dbd226ff-231d-662e-6033-7963c5d3940c.png">

そして、CircleのSpriteを作成。
<img width="534" alt="スクリーンショット_2019-05-19_21_02_39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314351/be7dd612-e1aa-3272-cb72-cbd913fa8a05.png">

マテリアルをアタッチするとこんな感じになります！
<img width="325" alt="スクリーンショット 2019-05-19 21.01.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314351/41614bcf-2e1e-6487-45cb-0b6835780fed.png">
### STEP２： RenderTextureに描画
続いて、RenderTextureに描画させます。理由は各々のメタボールの式の和(ここでは色値の和)を計算する際に、必要だからです。

やり方を図で紹介します。

まずProjectのフォルダ内にRenderTextureを作成。
<img width="451" alt="スクリーンショット_2019-05-19_21_04_55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314351/1dc1d3f6-5473-2c1e-055b-4182a8f5835a.png">

ヒエラルキー内にMainCameraとは別のカメラを用意して、TargetTextureにアタッチします。これで準備OK！
<img width="539" alt="Unity_2018_3_8f1_Personal_-_MetaBall_unity_-_Metaball_shader_-_PC__Mac___Linux_Standalone__Personal___Metal_.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314351/79e1d10a-5d8f-ffa1-9d2f-84b42b0fc8d9.png">

### STEP3：3. ShaderでRenderTextureを完成形にもっていく(各メタボールの和)

続いて、RenderTexture内の色情報の和を計算し、閾値を設けます。参考記事のソースを掲載させて頂きます。

```MetaballRenderer.shader
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にぶちこみます。
<img width="529" alt="スクリーンショット_2019-05-19_21_19_08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314351/7afb87e3-6201-e307-2bc0-470424246a7b.png">

すると、こんな感じになるかと思います。
<img width="314" alt="スクリーンショット 2019-05-19 21.20.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314351/b5dd2871-0ba2-f0c7-ec50-32e6f987535d.png">
### STEP5:CullingやLayerの設定して完成！
カメラのCullingMaskを設定やLayerを設定して、メインカメラにはメタボールの最終系のみ写します。

やり方はこちらになります。

まず、メタボールたちのLayerをDefaultから変更(ここではEffectLayer)します。
<img width="527" alt="スクリーンショット_2019-05-19_21_24_54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314351/92af7bf9-cead-128a-6e89-1511856f51e6.png">

その後、メタボールカメラのCullingMaskを上記で設定したLayerに変更。するとRenderTextureでは完成形前のメタボールのみ描画されるので、所望の描画ができるわけです！
<img width="540" alt="スクリーンショット_2019-05-19_21_25_00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314351/6cb55a93-d21a-2499-312c-181ad79c3674.png">

![スクリーンショット 2019-05-19 21.29.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314351/6d75abff-15a4-b780-fba8-3d9cb855b7d0.png)
## 最後に
いかがでしょうか。思ったより簡単にできる！と感じた方も多いかも知れません。

色々な表現に使えるので、興味ある方はぜひお試し頂ければと思います！