フラグメントシェーダーだけでタイムラインアニメーションを作る考え方

以前、フラグメントシェーダーのみで、タイムラインのようなアニメーションをさせる方法を、[The Book of Shader](https://thebookofshaders.com/?lan=jp)のGalleryの中から学んだのでまとめました。

ちなみに、私が作ったのはこんな感じです。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="en" dir="ltr">Hello World! <a href="https://t.co/xsbLlMsCox">pic.twitter.com/xsbLlMsCox</a></p>&mdash; null (@nuuullnull_dev) <a href="https://twitter.com/nuuullnull_dev/status/1001323775298715648?ref_src=twsrc%5Etfw">2018年5月29日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

背景で動いている四角や、三角がそれです。
これは、Unityで作りましたがHLSLでもGLSLでも考え方は一緒です。
今回はGLSLで説明します。

# 0~1で動くようなアニメーション関数を作る
まずは矩形や円などが動くアニメーション関数を作ります。

```glsl
float animation1(vec2 _st, float t) {
    float x = t;
    float y = 0.5;
    float d = rect(_st, vec2(x, y));
    return d;
}
```

`t`には0〜1が入る想定です。その数値をうまく使ってアニメーションさせます。
上記のは四角が0〜1に向かって移動するイメージです。

## さらにEaseing関数で味を出す
ですが、ただ時間でアニメーションをさせると、一定の動きしかせず、単調になるので、Easeing関数を使っていい感じにしましょう。

Easeing関数とはざっくり説明すると、数値の変化率を変える関数です。
それを使うことで、だんだん速くとか、だんだんゆっくりにとか変化させることができます。

Easingがよくわからないという人はこちらをチェックです。
[Easing Function 早見表](https://easings.net/ja)

シェーダー内でのEasing関数はこちらが参考になります。
[The Book of Shaders:Gallery - Easing Function](https://thebookofshaders.com/edit.php?log=160909064320)

```glsl
float easeInQuad(float t) {
    return t * t;
}

float easeOutQuad(float t) {
    return -1.0 * t * (t - 2.0);
}

float animation1(vec2 _st, float t) {
    float x = easeOutQuad(t);
    float y = 0.5;
    float d = rect(_st, vec2(x, y));
    return d;
}
```

Easing関数には0〜1の値を渡します。
そのまま`t`の値を渡してあげれば、0〜1の変化率を変えてくれます。
今回はEaseOut（だんだん遅く）をかけてみます。

# トータルの時間を決める

次にアニメーションのトータルの時間を決めます。
`mod()`の第一引数に時間、第二引数にトータルの時間を入れます。

```glsl
uniform vec2 resolution;
uniform float time;

void main () {
    vec2 st = gl_FragCoord.xy / resolution.xy;
    //トータルで10秒のアニメーション
    float t = mod(time, 10.0);
    vec3 color = vec3(0.0);
    gl_FragColor = vec4(color, 1.0);
}
```
`mod()`は余りを返してくれるので 常に0〜10を返してくれるようになります。

# 各アニメーションに時間を配分する
それぞれ作ったアニメーション関数に、時間を渡していきます。
それぞれ時間を渡す際は`linearstep()`を使います。

```glsl
uniform vec2 resolution;
uniform float time;

float animation1(vec2 _st, float time) { ... }
float animation2(vec2 _st, float time) { ... }
float animation3(vec2 _st, float time) { ... }

void main () {
    //トータルで10秒のアニメーション
    vec2 st = gl_FragCoord.xy / resolution.xy;
    float t = mod(time, 10.0);
    vec3 color = vec3(0.0);

    //各アニメーションに時間を設定して渡す
    float a1 = animation1(st, linearstep(0.0, 3.0, t));
    float a2 = animation2(st, linearstep(3.0, 7.0, t));
    float a3 = animation3(st, linearstep(7.0, 10.0, t));
    
    //全てをまとめる
    color = vec3(a1) + vec3(a2) + vec3(a3);

    gl_FragColor = vec4(color, 1.0);
}
```
`linearstep()`の第一引数に開始時間、第二引数に終了時間を渡します。

```glsl
//例えば以下の場合は0〜3を0〜1に変換してくれます
//0より小さい数字は0、3より大きい数字は1が返ってきます。
float a1 = animation1(st, linearstep(0.0, 3.0, t));
```
これで、それぞれのアニメーションをどのタイミングで行うかを調整します。

最後に全てのアニメーションをまとめて完成です。

## さらに
`linearstep()`をアニメーション関数の中で使えば、矩形が入ってきて、出ていくみたいなタイミングも調整できます。

```glsl
float easeInQuad(float t) { ... }
float easeOutQuad(float t) { ... }

float animation1(vec2 _st, float t) {
    float x = easeInQuad(linearstep(0.0, 0.5, t)) * 0.5
              + easeOutQuad(linearstep(0.5, 1.0, t)) * 0.5;
    float y = 0.5;
    float d = rect(_st, vec2(x, y));
    return d;
}
```

こんな感じで、0 ~ 0.5まではEaseIn（だんだん速く）、0.5 ~ 1.0まではEaseOut（だんだん遅く）、みたいなこともできます。

これらをうまく組み合わせるとよりいい感じのアニメーションになります。

## 参考文献
* [The Book of Shaders:Gallery - Sequencing](https://thebookofshaders.com/edit.php?log=160909065147)