【Unity】 頂点シェーダーを使った雨の描画

#はじめに
頂点シェーダーを使って雨を描画させてみました。
<img width = 480 src = "https://qiita-image-store.s3.amazonaws.com/0/44288/7d862aba-4513-77b5-cb33-4deb0a4e063e.gif">

<a href = "https://github.com/rngtm/unity-sample-rain">サンプル</a>


#謝辞
今回の記事を書くにあたって、以下のスライドを参考にさせていただきました。 
**PLAYDEADさんに感謝！**
https://docs.google.com/presentation/d/1_JHp-k4bDc9WhcMlC9hAPSGj_0UaJh5DBpSlLkwI74M/mobilepresent?slide=id.g5dea027fa_0375
<img width = 380 src = "https://qiita-image-store.s3.amazonaws.com/0/44288/a1809f95-bcc1-0151-9ec8-b3ae794aeb4a.png">

#0. 雨を描画するまでの簡単な流れ

三角形を大量に出す -> 乱数を使って三角形を高速に揺らす -> **雨っぽく見える**

#1. 三角形を大量に生成するスクリプト
以下のスクリプトを適当なGameObjectにアタッチしてください。

```ProcedualRandomMesh.cs
using UnityEngine;
using System.Collections.Generic;

[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshFilter))]
public class ProcedualRandomMesh : MonoBehaviour
{
    // 三角形の個数
    private int triangleCount = 1500;

    // 三角形の大きさ
    private float triangleScale = 0.002f;

    // メッシュの大きさ
    private Vector3 meshScale = new Vector3(4f, 4f, 4f);

    private void Start()
    {
        this.Initialize();
    }

    /// <summary>
    /// 初期化 
    /// </summary>
    public void Initialize()
    {
        var vertices = new List<Vector3>();
        var triangles = new List<int>();
        int pos = 0;
        for (int i = 0; i < this.triangleCount; i++)
        {
            var v1 = Vector3.Scale(new Vector3(Random.value, Random.value, Random.value) - Vector3.one * 0.5f, this.meshScale);
            var v2 = v1 + new Vector3(Random.value - 0.5f, 0f, Random.value - 0.5f) * triangleScale;
            var v3 = v1 + new Vector3(Random.value - 0.5f, 0f, Random.value - 0.5f) * triangleScale;

            vertices.Add(v1);
            vertices.Add(v2);
            vertices.Add(v3);

            triangles.Add(pos + 0);
            triangles.Add(pos + 1);
            triangles.Add(pos + 2);
            pos += 3;
        }

        //メッシュ生成
        var mesh = new Mesh();
        mesh.vertices = vertices.ToArray();
        mesh.triangles = triangles.ToArray();

        var meshFilter = this.GetComponent<MeshFilter>();
        meshFilter.mesh = mesh;
    }

}
```
決められた領域からランダムな座標をとって三角形を大量に生成。
最後にそれら全ての三角形を1つのメッシュにまとめています。


#2. 雨っぽいアニメーションをするシェーダー
以下のシェーダーを```ProcedualRandomMesh.cs```がアタッチされているGameObjectにアタッチしてください。

```rain.shader
// 参考URL
// https://docs.google.com/presentation/d/1_JHp-k4bDc9WhcMlC9hAPSGj_0UaJh5DBpSlLkwI74M/mobilepresent?slide=id.g5dea027fa_0375
Shader "Custom/Rain" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _Color ("Color", Color) = (0,0,0,1.0) // 雨の色
        _Speed ("Speed", Float) = 6.0 
        _Scale ("Scale", Float) = 4.0
    }
    SubShader {
        Tags {
            "Queue" = "Transparent"
            "RenderType" = "Transparent"
        }
        CGPROGRAM
        #pragma surface surf Lambert alpha
        #pragma vertex vert

        sampler2D _MainTex;

        // [0;1]のランダムな値を返す関数
        float nhash11(float n){
            return frac(sin(n) * 43758.5453);
        }

        // 値域[a;b] を 値域[0;1]へ変換する関数
        float remap(float t, float a, float b){
            return clamp((t-a)/(b-a), 0, 1);
        }

        fixed4 _Color;
        half _Speed;
        half _Scale;

        // 頂点シェーダー関数
        void vert(inout appdata_full v) {
            fixed rnd = nhash11(fmod(v.vertex.z, 512.0));
            float timer = _Time.w * _Speed * remap(0.7, 1.0, rnd);
            v.vertex.y -= fmod(-v.vertex.y + timer, _Scale) + v.vertex.y - _Scale * 0.5;
        }

        struct Input {
            float2 uv_MainTex;
        };

        void surf(Input IN, inout SurfaceOutput o) {
            half4 c = tex2D(_MainTex, IN.uv_MainTex);
            o.Albedo = c.rgb * _Color.rgb;
            o.Alpha  = _Color.a;
        }
        ENDCG
    }
}
```

雨のアニメーションは頂点シェーダー関数の中で行っています。

```glsl
fixed rnd = nhash11(fmod(v.vertex.z, 512.0));
float timer = _Time.w * _Speed * remap(0.7, 1.0, rnd);
v.vertex.y -= fmod(-v.vertex.y + timer, _Scale) + v.vertex.y - _Scale * 0.5;
```

#3. 動かす
上記を実行すると以下のように描画されます。

雨の落ち方に偏りがあってちょっと残念な感じ。
![rain_noRandom.gif](https://qiita-image-store.s3.amazonaws.com/0/44288/4be57239-b38b-c952-ea22-6f2668dbec92.gif)

#4. 雨を乱数で揺らす
雨を乱数で揺らしてみます。
```ProcedualRandomMesh.cs``` へ以下のコードを追加

```csharp
private float randomness = 0.1f;
void Update()
{
    this.transform.position = new Vector3(Random.value, 0f, Random.value) * randomness;
}
```

#### 結果
![rain_random.gif](https://qiita-image-store.s3.amazonaws.com/0/44288/e33958a3-3900-7418-efa8-c61c21ebf302.gif)
いい感じにバラついてくれました。

#5. 完成
雨の色のアルファ値を下げて完成！
![rain_small_alpha.gif](https://qiita-image-store.s3.amazonaws.com/0/44288/e628068f-7dcf-91a9-4336-58ded3a0c54b.gif)

雨の色を白くしても面白いかもしれません。
![rain_small_white.gif](https://qiita-image-store.s3.amazonaws.com/0/44288/9e762c3f-634d-9a7a-defd-a170ce998a5a.gif)