VRChatで動くポスターを作る

これは[VRChat Advent Calendar 2019](https://adventar.org/calendars/3950)の21日目の記事です。

昨日は落雷さんの[Amplify Shader Editorを用いてメッシュを雪で覆うシェーダーを作る](https://qiita.com/rakurai5/items/c154377f13edfa1088ba)でした。冬も来ましたしいろんな冬なワールドで使えそうですね。

まずはこちらの動画をご覧にください。
<blockquote class="twitter-tweet" data-lang="ja" data-theme="dark"><p lang="ja" dir="ltr">ShaderFesのポスターがかっこよかったので動くポスターを書きました。 <a href="https://twitter.com/hashtag/ShaderFes?src=hash&amp;ref_src=twsrc%5Etfw">#ShaderFes</a> <a href="https://twitter.com/hashtag/VRChat?src=hash&amp;ref_src=twsrc%5Etfw">#VRChat</a> <a href="https://t.co/MNJCvUTXz5">pic.twitter.com/MNJCvUTXz5</a></p>&mdash; fotfla (@fotfla) <a href="https://twitter.com/fotfla/status/1206153183774429184?ref_src=twsrc%5Etfw">2019年12月15日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

このポスターはもともとVRChat上で行われたShaderFes2019にてるらさんが制作したポスターをShaderのみを使って再現し、かつ動きを付けたものです。
もともとはこういったものです。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">シェーダーフェス2019の記念品として作成した動くポスター。<br>シェーダーに関する意匠を取り入れてシックな仕上がりになり、インテリアとしても良い感じです。お家や集会所などで是非貼って下さい！<br>ShaderFes2019は12/14オープン予定です。<a href="https://twitter.com/hashtag/ShaderFes?src=hash&amp;ref_src=twsrc%5Etfw">#ShaderFes</a> <a href="https://twitter.com/hashtag/VRChat?src=hash&amp;ref_src=twsrc%5Etfw">#VRChat</a><a href="https://t.co/J4kmTeiAHH">https://t.co/J4kmTeiAHH</a> <a href="https://t.co/DQovVLZ7ds">pic.twitter.com/DQovVLZ7ds</a></p>&mdash; るら／VRC (@Lu_Ra_999) <a href="https://twitter.com/Lu_Ra_999/status/1194458180438245376?ref_src=twsrc%5Etfw">November 13, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


物理世界のポスターというのは紙に印刷されていて基本動くものではありません（最近はデジタルサイネージという形でディスプレイに映像を映しているので必ずしもそうではないですが）。そして最近VRChatでもポスターをたくさん見るようになりましたが当たり前ですが動きません。でも動いて欲しくないですか？　なのでShaderでポスターを書いていきます。（それこそポスターに限らず3Dモデルの服やなにかのテクスチャをプロシージャルに生成することができる）

##Shaderで絵を書く
例えば、Shaderで2D絵を書くといえばこういったものがあります。

[楽しい！Unityシェーダーお絵描き入門！](https://docs.google.com/presentation/d/1NMhx4HWuNZsjNRRlaFOu2ysjo04NgcpFlEhzodE8Rlg/edit#slide=id.g423da70889_217_0
)

今回もここでも紹介されてるディスタンスフィールドで書いていきます。


## ディスタンスフィールド
今回使うディスタンスフィールドはある点からその図形までの最小の距離を関数にしたものです。なので図形の線上は0外側は0より大きく、内側は0未満となります。ここで基本的なディスタンスフィールドを紹介します
### Circle
円です。rが半径です。

```glsl
float sdCircle(float2 p, float r){
    return length(p) - r;
}
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73842/b73f18ab-a113-e61c-0882-42dead3071b0.png" width=256 height=256>

### Box
基本的な長方形です。sが長方形のサイズです。

```glsl
float sdBox(float2 p, float2 s){
    float2 d = abs(p) - s;
    return lenghth(max(d,0.0)) + min(max(d.x,.dy),0.0);
}
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73842/ea21efa2-1b2e-b0d6-8c24-f7427c4754e0.png" width=256 height=256>

### Line
直線です。a,bを結ぶ直線になります。

```glsl
float sdLine(float2 p, float2 a, float2 b){
    float2 pa = p-a, 
    float2 ba = b-a;
    float2 h = saturate(dot(pa,ba)/dot(ba,ba));
    return length( pa - ba*h );
}
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73842/df537190-0acc-b060-41a8-77c65fecebc9.png" width=256 height=256>


### Triangle
二等辺三角形です。qの値がfloat2(辺の長さ,高さ)のようになっています。

```glsl
float sdTriangleIsosceles(float2 p, float2 q )
{
    p.x = abs(p.x);
    float2 a = p - q*saturate(dot(p,q)/dot(q,q));
    float2 b = p - q*vec2(saturate(p.x/q.x), 1.0);
    float s = -sign( q.y );
    float2 d = min( float2( dot(a,a), s*(p.x*q.y-p.y*q.x) ),
                  float2( dot(b,b), s*(p.y-q.y)  ));
    return -sqrt(d.x)*sign(d.y);
}
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73842/a7ce6ce4-8a0b-c2ae-1964-1a9975e3b5b2.png" width=256 height=256>

これらの関数を組み合わせると一通りの図形ができます。

これら距離関数は内側が0未満で外側が0より大きく、これらが区別できればいいのでstepやsmoothstepを使えば図形を描画することができます。
例えば円を描画するときには

```C
float sphere = sdSphere(uv,1.0)
float c = smoothstep(0.001,0.0,sphere); // step(0,sphere)
```
これで円が描画されます。


この他にもShader界では有名なiq氏のサイト(http://www.iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm) にて他の2Dの距離関数が載っています。ここではGLSLというUnitｙ上で使われてるものと少し違いますが少しの変更で同様に扱えます。

## いくつかの描画テクニック
これらの図形だけでは複雑な図形はかけないのでいくつかよく使ったものを紹介します。
#### min
```glsl
// 2つの図形を結合します。
float d = min(a,b);
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73842/7c682b72-e8c9-7046-7811-8ccce70301e8.png" width=256 height=256>

#### max
```glsl
// a,bの図形の共通部分を取る
float d1 = max(a,b);
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73842/98ca536b-fd07-55b7-0722-af4008451a90.png" width=256 height=256>

```glsl
// aの図形からbの図形をくり抜く
float d2 max(a,-b);
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73842/83415589-502a-f246-e417-e399fc4f319e.png" width=256 height=256>

またaの図形の枠線を作りたいときは

```glsl
// aが距離関数, wが線の太さ
float d = abs(a) - w;
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73842/1ad3b1a7-8643-42bf-84e5-fbdd95a5e730.png" width =256 height=256>

これらや、他にも回転や繰り返しなど、最初に紹介したシェーダーお絵かきなどで紹介されてるテクニックを使って絵を描いていきます。

##書いた絵を動かす
次に絵を絵を動かすことを考えます。基本的にはループアニメーションを考えます。基本的に0~1でループさせることを考えるといろいろなことが楽になると思います。ループの長さは時間のスピードを変えることで調整します。
時間を繰り返すには次の関数を使って表現することができます。

```glsl
float t = _Time.y; // Unity内の時間
float t1 = frac(t); // 0~1を繰り返す
float t2 = fmod(t,10.0); // 0~1を繰り返す
```

### アニメーションカーブ
アニメションの仕方は時間に対してカーブを書くことで動かし方を自由にできます。
アニメーションの種類の例

```glsl
// アニメーションの速度がSpeed倍
float t = _Time.y * Speed;
// tが0~1をループする
float t1 = frac(t); // Liner
float t2 = pow(t1, a); // a < 1 イーズイン ,a > 1 イーズアウト
float t3 = smoothstep(0,1,t1); // イーズイン・アウト
float t4 = sin(t) * 0.5 + 0.5 // 0~1を振動
```
でこのように動きます。下の動画は上からt1~t5の関数で四角形をアニメーションさせたものです。

```glsl

float t = _Time.y;
float t1 = frac(t);
float t2 = pow(t1, 0.5);
float t3 = pow(t1, 2.0);
float t4 = smoothstep(0,1,t1);
float t5 = sin(t * UNITY_PI - UNITY_PI * 0.5) * 0.5 + 0.5 // t = 0 で他の関数と合うように調整
```
このように動きをつけることでかっこよく見えます。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">アニメーションカーブ <a href="https://t.co/886ewx0jRD">pic.twitter.com/886ewx0jRD</a></p>&mdash; fotfla (@fotfla) <a href="https://twitter.com/fotfla/status/1208051052383100929?ref_src=twsrc%5Etfw">December 20, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

### アニメーションの遷移
アニメーションの遷移は```clamp(x,a,b)```関数を使うとできます。clamp関数はある数値xをa以下はすべてa、b以上をすべてbにする関数です。これらを例えば下記のように使うことでアニメーションの遷移が実現できます。

```glsl
// tが0~20の間をループする
float t = fmod(_Time.y,20.0);
// tが0~5の間だけ動く
float t1 = clamp(t,0,5);
// tが5~15の間だけ動く
float t2 = clamp(t - 5,0,10);
// tが15~20の間だけ動く
float t3 = clamp(t- 15,0,5);
```
でしたのツイートは5秒かけて一番上が移動、その後10秒かけて真ん中が移動、最後に5秒かけて一番下が移動してこれを20秒ループする動画です。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">アニメーションの遷移 <a href="https://t.co/0hDB9BpuAE">pic.twitter.com/0hDB9BpuAE</a></p>&mdash; fotfla (@fotfla) <a href="https://twitter.com/fotfla/status/1208051251625123841?ref_src=twsrc%5Etfw">December 20, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

アニメーションの遷移の場合動く秒数で値を割って0~1を取るように調整しておくと上記のアニメーションカーブと合わせて使いやすくなると思います。

----
以上で、今回紹介したものを基本的に使って最初に紹介した動画は構成されています。
図形の組み合わせなどでいろいろな絵を描いたり、映像をつくれると思うのでぜひShaderでお絵かきをしてVRChatで動くポスターなど作ってみてください。

--------
#### 紹介したもの
[ShaderFesのワールド](https://www.vrchat.com/home/world/wrld_f5a298f6-83e6-45ee-be58-4fafbbd38139)
[楽しい！Unityシェーダーお絵描き入門！](https://docs.google.com/presentation/d/1NMhx4HWuNZsjNRRlaFOu2ysjo04NgcpFlEhzodE8Rlg/edit#slide=id.g423da70889_217_0
)
[iq氏の2D距離関数一覧](http://www.iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm)