以前、フラグメントシェーダーのみで、タイムラインのようなアニメーションをさせる方法を、The Book of ShaderのGalleryの中から学んだのでまとめました。
ちなみに、私が作ったのはこんな感じです。
Hello World! pic.twitter.com/xsbLlMsCox
— null (@nuuullnull_dev) 2018年5月29日
背景で動いている四角や、三角がそれです。
これは、Unityで作りましたがHLSLでもGLSLでも考え方は一緒です。
今回はGLSLで説明します。
0~1で動くようなアニメーション関数を作る
まずは矩形や円などが動くアニメーション関数を作ります。
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 早見表
シェーダー内でのEasing関数はこちらが参考になります。
The Book of Shaders:Gallery - Easing Function
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()
の第一引数に時間、第二引数にトータルの時間を入れます。
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()
を使います。
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()
の第一引数に開始時間、第二引数に終了時間を渡します。
//例えば以下の場合は0〜3を0〜1に変換してくれます
//0より小さい数字は0、3より大きい数字は1が返ってきます。
float a1 = animation1(st, linearstep(0.0, 3.0, t));
これで、それぞれのアニメーションをどのタイミングで行うかを調整します。
最後に全てのアニメーションをまとめて完成です。
さらに
linearstep()
をアニメーション関数の中で使えば、矩形が入ってきて、出ていくみたいなタイミングも調整できます。
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(だんだん遅く)、みたいなこともできます。
これらをうまく組み合わせるとよりいい感じのアニメーションになります。