Unity LineRendererでシェーダーを用いて点線を描画する

# 概要
以下のように丸を並べた点線をLineRendererで描画します。ついでにアニメーションもつけました。
![dotted-line.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323576/5f1169d9-1538-bcf5-62eb-198dc9cf3975.gif)

プロジェクトのソースはこちら
https://github.com/Arihide/unity-dotted-line

# 解説

## LineRendererをアタッチする
まず、適当なGameObjectにLineRendererをアタッチします。
その後、下のように[Texture Mode]を[Tile]に設定します。
![スクリーンショット_2020-10-12_0_03_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323576/2671eb8f-59fd-5e32-745e-3825ed5d2fd0.png)

## シェーダーを書く
次に、LineRendererに設定する点線のマテリアルのためのシェーダーを書いていきます。

### 1.丸を並べる
まずは下図のように、丸を隙間なく敷き詰めるLineRenderer用のシェーダーを書きます。
![スクリーンショット 2020-10-12 0.24.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323576/4e8e5aa1-c39d-b93a-8b9a-f742ab40a255.png)

どのようなロジックでこれを実現するかというと、
先程のTextureMode:Tileの設定によって、以下のようなUV座標になるので、
![dotted-line.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323576/86b2e3a0-d110-1e86-2020-21149646b5d8.png)
以下のように0...1になるように座標を変更したあと、
![dotted-line2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323576/745a3b58-999e-38fa-837a-990bad676a08.png)
中心:(0.5, 0.5)、半径:0.5 の円を考えて、内側は不透明、外側は透明になるように描画してあげればよいです。
それを踏まえたシェーダーのコードは以下になります。

```glsl
Shader "Custom/DottedLine"
{
    SubShader
    {
        Tags { "RenderType" = "Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                half2 texcoord : TEXCOORD0;
                half4 vertex : POSITION;
                fixed4 color : COLOR0;
            };

            struct v2f
            {
                half2 uv : TEXCOORD0;
                half4 pos : SV_POSITION;
                fixed4 color : COLOR0;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = v.texcoord;
                o.color = v.color;
                return o;
            }

            fixed4 frag (v2f i) : SV_TARGET
            {
                // 1の剰余をとることで、0と1の間を繰り返すようにする
                i.uv.x = i.uv.x % 1;

                fixed4 col = i.color;

                // 円の内側を不透明、外側を透明にする
                half s = length(i.uv - half2(1, 1) * 0.5);
                col.a = saturate((0.5 - s) * 100);

                return col;
            }
            ENDCG
        }
    }
}
```
上記のシェーダーを記述後、マテリアルを作成しLineRendererのマテリアルにアタッチすると、はじめの図のように線に沿って丸が描画されると思います。

ここで、以下のコード部分が若干複雑なので補足します。

```glsl
// 円の内側を不透明、外側を透明にする
half s = length(i.uv - half2(1, 1) * 0.5);
col.a = saturate((0.5 - s) * 100);
```
この関数をプロットすると以下のようになります。（xy平面がそれぞれUV、ｚ軸はアルファ値）
![geogebra-export.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323576/dc9b7e36-ba3b-102e-b954-c82253bddcac.png)

図を見てもらうとわかるように、円柱ではなく円錐台になっています。
このようにすることによって、円の境界線をぼかしてきれいに描画できるようになります。

### 円の隙間を開ける
隙間が詰まりすぎていて気になるので、次は間隔を少し開けてみます。
![スクリーンショット 2020-10-12 1.54.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323576/52f2c54f-a5c5-ba08-fc0d-e592a3efa80d.png)
これは以下のように、_Spaceという変数を用意して、剰余と円の中心をずらせば実現できます。

```glsl
Shader "Custom/DottedLine"
{
    Properties
    {
        _Space ("Space Between Dots", Range(1, 5)) = 1.2
    }
    SubShader
    {
        //...(中略）

            half _Space;

            fixed4 frag (v2f i) : SV_TARGET
            {
                i.uv.x = i.uv.x % _Space;

                fixed4 col = i.color;

                // 円の内側を不透明、外側を透明にする
                half s = length(i.uv - half2(_Space, 1) * 0.5);
                col.a = saturate((0.5 - s) * 100);

                return col;
            }
            ENDCG
        }
    }
}
```
変更後、マテリアルから［Space Between Dots］の値を調節することで隙間があくのを確認できます。

### アニメーションさせる
これで完成でもよいのですが、せっかくなので冒頭のGifのように時間経過によって丸が動くようなアニメーションもつけたいと思います。これはとても簡単で、frag関数冒頭を

```glsl
i.uv.x = (i.uv.x + _Time * 50) % _Space;
```
と置き換えることで実現できます。見ていただけばわかるように時間ごとに座標をずらしています。

# まとめ
以上、LineRendererのシェーダーで点線を描画する方法について説明しました。
シェーダーを工夫すれば、円ではなく楕円などの点線にすることもできると思うので、色々試してみるのも面白いかもしれません。