【Unity】uGUIスプライトが砂のように消滅するシェーダーを作ってみる

# まずは作成したものからどうぞ。
![VanishShader.gif](https://qiita-image-store.s3.amazonaws.com/0/148284/6eee63e0-2364-9d7a-0506-c337dcf10016.gif)

# はじめに
よく、RPGなどでモンスターが死亡したときに砂のように消滅するかっこいい演出をみかけますが、その演出をシェーダーで作成してみました。

# 開発環境
* Unity2018.3.0f2
* Windows10
* 実機動作確認
    * Android Galaxy s6 edgeにて確認。
    * ※iOSを持ってないためiOS未検証です。ご了承ください。


# VanishingShader
こちらが今回作成したシェーダーのコードです。
消滅させるシェーダーなのでそのまま`VanishingShader`と名付けました。

```c:VanishingShader.

Shader "Custom/VanishingShader"
{
    Properties
    {
        _MainTex ("Source", 2D) = "white" {}
        _StrengthX("StrengthX",Float) = 0
        _StrengthY("StrengthY",Float) = 0
        _Alpha("Alpha",Range(0,1)) = 1
    }

    SubShader
    {
        Blend SrcAlpha OneMinusSrcAlpha
        ZWrite Off
        Fog { Mode Off }
        Tags { "Queue" = "Transparent" }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag
            #include "UnityCG.cginc"

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _StrengthX;
            float _StrengthY;
            float _Alpha;

            float rand(float2 co)
            {
                return frac(sin(dot(co.xy, float2(12.9898, 78.233))) * 43758.5453);
            }

            fixed4 frag (v2f_img i) : SV_Target
            {
                float r = rand(i.uv);
                float2 uv = i.uv + r * float2(_StrengthX, -_StrengthY);
                fixed4 c = tex2D(_MainTex, uv );
                c.a *= _Alpha;
                return c;
            }
            ENDCG
        }
    }
}
```

# 解説
シェーダーコードについて解説していきます。
まずは`Properties`からですが、これらプロパティたちをのちほど出てくる
コンポーネントからいじることになります。

```c
    Properties
    {
        _MainTex ("Source", 2D) = "white" {}
        _StrengthX("StrengthX",Float) = 0
        _StrengthY("StrengthY",Float) = 0
        _Alpha("Alpha",Range(0,1)) = 1
    }
```

`_Source`:テクスチャを指定します。
`_StrengthX`:0なら何も効果なし。 値が大きくなるほど横方向に砂化します。
`_StrengthY`:0なら何も効果なし。値が大きくなるほど縦方向に砂化します。
`_Alpha`:0なら透明。値が1で不透明です。

### 頂点シェーダー・フラグメントシェーダーの指定
```c
#pragma vertex vert_img
#pragma fragment frag
```
この部分で、頂点シェーダーに`vert_img`を、フラグメントシェーダーに`frag`を指定しています。

`vert_img`はビルドインの頂点シェーダーです。
`UnityCG.cginc`というビルドインの変数やヘルパー関数が定義されたファイル内に定義されています。

```
#include "UnityCG.cginc"
```

のように定義することで使用できるようになります。
今回はすでに定義された頂点シェーダーを使用したので、書いたシェーダーのコード量も減りましたが、
実際の定義を引用すると、このようになっています。

```c
struct appdata_img
{
    float4 vertex : POSITION;
    half2 texcoord : TEXCOORD0;
    UNITY_VERTEX_INPUT_INSTANCE_ID
};

v2f_img vert_img( appdata_img v )
{
    v2f_img o;
    UNITY_INITIALIZE_OUTPUT(v2f_img, o);
    UNITY_SETUP_INSTANCE_ID(v);
    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

    o.pos = UnityObjectToClipPos (v.vertex);
    o.uv = v.texcoord;
    return o;
}
```


そしてスプライトを砂と化すメイン部分がこちらです。

```c
float r = rand(i.uv);
float2 uv = i.uv + r * float2(_StrengthX, -_StrengthY);
fixed4 c = tex2D(_MainTex, uv );
```

UVをランダムに（Strengthが強いほどどんどん）ズラして、
砂みたいになるのを表現しています。

randについてはこちら[ランダムな値を返す関数 on GLSL](https://qiita.com/shimacpyon/items/d15dee44a0b8b3883f76)を参考にしました。

# テクスチャ設定について
Textureの設定の`WrapMode`は`Clamp`にしておきます。
`Repeat`だと、上方に移動した砂がまた下方から表示されてきてしまうので、
見た目としておかしくなります。

# マテリアルを作成する
マテリアルを作成し、シェーダーは`Custom/VanishingShader` を選択します。
![2018-12-23_21h34_21.png](https://qiita-image-store.s3.amazonaws.com/0/148284/1f4ec88a-32e4-8c1b-b4d5-0b11b05b91d1.png)

# コントローラー
アニメーションからマテリアルのシェーダーのパラメータを操作できるようにします。
シェーダーのパラメータをアニメーションで制御できるようにするために、
コントローラーとなるコンポーネントを作成します。

```csharp:VanishingShaderController.cs
using UnityEngine;

[ExecuteInEditMode]
public class VanishingShaderController : MonoBehaviour
{
    [SerializeField] float strengthX = 0f;
    [SerializeField] float strengthY = 0f;
    [SerializeField] float alpha = 1f;
    [SerializeField] Material material;

    void Update()
    {
        UpdateMaterial();
    }

    void UpdateMaterial()
    {
        if (material == null) { return; }

        material.SetFloat("_StrengthX", strengthX);
        material.SetFloat("_StrengthY", strengthY);
        material.SetFloat("_Alpha", alpha);
    }
}
```

インスペクターはこんな感じ。
![2018-12-23_20h17_28.png](https://qiita-image-store.s3.amazonaws.com/0/148284/51af5eb0-0066-3bf7-269b-2b94f426ac9f.png)

# マテリアルをアタッチ

uGUIの`Image`と`VanishingShaderController`の`Material`にマテリアルをアタッチします。
![2018-12-23_21h33_06.png](https://qiita-image-store.s3.amazonaws.com/0/148284/441c8b47-17ef-ff6d-3b19-6b1a026de098.png)

コンポーネントのインスペクターで露出している値をアニメーションのほうで制御してみましょう。

![2018-12-23_20h18_02.png](https://qiita-image-store.s3.amazonaws.com/0/148284/85f270cc-3ffd-4edc-2011-769dd12ebae5.png)

こんな感じで`StrengthY`(もしくはX)や`Alpha`を制御してやれば、調整可能です。

# プロジェクトデータ
今回作成したプロジェクトをGithubに載せました。
どうぞご自由にご利用ください。
https://github.com/gingerbread4/VanishingShader


# まとめ
いかがでしたでしょうか。作成したシェーダーは初歩的でシンプルながら、
スプライトが砂のように消滅する感じが出ていて、
RPGなど、モンスターが死亡する演出などに使えるのではないでしょうか。

# 課題
よく見ると、ちょっと`Image`の上のほうで砂になったピクセルがImageの境界で見切れているのがわかると思います。透明になっていくことで少しごまかしがきいていると思いますが、見た目としてはあまりよろしくないです。

### 参考サイト様
* シェーダーリファレンス
    * https://docs.unity3d.com/ja/current/Manual/SL-Reference.html
* ランダムな値を返す関数 on GLSL
    * https://qiita.com/shimacpyon/items/d15dee44a0b8b3883f76
* ゴブリンの画像
    * http://lightningseraph.tumblr.com/Resources