これはVRChat Advent Calendar 2019の21日目の記事です。
昨日は落雷さんのAmplify Shader Editorを用いてメッシュを雪で覆うシェーダーを作るでした。冬も来ましたしいろんな冬なワールドで使えそうですね。
まずはこちらの動画をご覧にください。
ShaderFesのポスターがかっこよかったので動くポスターを書きました。 #ShaderFes #VRChat pic.twitter.com/MNJCvUTXz5
— fotfla (@fotfla) 2019年12月15日
このポスターはもともとVRChat上で行われたShaderFes2019にてるらさんが制作したポスターをShaderのみを使って再現し、かつ動きを付けたものです。
もともとはこういったものです。
シェーダーフェス2019の記念品として作成した動くポスター。
— るら/VRC (@Lu_Ra_999) November 13, 2019
シェーダーに関する意匠を取り入れてシックな仕上がりになり、インテリアとしても良い感じです。お家や集会所などで是非貼って下さい!
ShaderFes2019は12/14オープン予定です。#ShaderFes #VRChathttps://t.co/J4kmTeiAHH pic.twitter.com/DQovVLZ7ds
物理世界のポスターというのは紙に印刷されていて基本動くものではありません(最近はデジタルサイネージという形でディスプレイに映像を映しているので必ずしもそうではないですが)。そして最近VRChatでもポスターをたくさん見るようになりましたが当たり前ですが動きません。でも動いて欲しくないですか? なのでShaderでポスターを書いていきます。(それこそポスターに限らず3Dモデルの服やなにかのテクスチャをプロシージャルに生成することができる)
##Shaderで絵を書く
例えば、Shaderで2D絵を書くといえばこういったものがあります。
今回もここでも紹介されてるディスタンスフィールドで書いていきます。
ディスタンスフィールド
今回使うディスタンスフィールドはある点からその図形までの最小の距離を関数にしたものです。なので図形の線上は0外側は0より大きく、内側は0未満となります。ここで基本的なディスタンスフィールドを紹介します
Circle
円です。rが半径です。
float sdCircle(float2 p, float r){
return length(p) - r;
}
Box
基本的な長方形です。sが長方形のサイズです。
float sdBox(float2 p, float2 s){
float2 d = abs(p) - s;
return lenghth(max(d,0.0)) + min(max(d.x,.dy),0.0);
}
Line
直線です。a,bを結ぶ直線になります。
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 );
}
Triangle
二等辺三角形です。qの値がfloat2(辺の長さ,高さ)のようになっています。
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);
}
これらの関数を組み合わせると一通りの図形ができます。
これら距離関数は内側が0未満で外側が0より大きく、これらが区別できればいいのでstepやsmoothstepを使えば図形を描画することができます。
例えば円を描画するときには
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というUnity上で使われてるものと少し違いますが少しの変更で同様に扱えます。
いくつかの描画テクニック
これらの図形だけでは複雑な図形はかけないのでいくつかよく使ったものを紹介します。
min
// 2つの図形を結合します。
float d = min(a,b);
max
// a,bの図形の共通部分を取る
float d1 = max(a,b);
// aの図形からbの図形をくり抜く
float d2 max(a,-b);
またaの図形の枠線を作りたいときは
// aが距離関数, wが線の太さ
float d = abs(a) - w;
これらや、他にも回転や繰り返しなど、最初に紹介したシェーダーお絵かきなどで紹介されてるテクニックを使って絵を描いていきます。
##書いた絵を動かす
次に絵を絵を動かすことを考えます。基本的にはループアニメーションを考えます。基本的に0~1でループさせることを考えるといろいろなことが楽になると思います。ループの長さは時間のスピードを変えることで調整します。
時間を繰り返すには次の関数を使って表現することができます。
float t = _Time.y; // Unity内の時間
float t1 = frac(t); // 0~1を繰り返す
float t2 = fmod(t,10.0); // 0~1を繰り返す
アニメーションカーブ
アニメションの仕方は時間に対してカーブを書くことで動かし方を自由にできます。
アニメーションの種類の例
// アニメーションの速度が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の関数で四角形をアニメーションさせたものです。
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 で他の関数と合うように調整
このように動きをつけることでかっこよく見えます。
アニメーションカーブ pic.twitter.com/886ewx0jRD
— fotfla (@fotfla) December 20, 2019
アニメーションの遷移
アニメーションの遷移はclamp(x,a,b)
関数を使うとできます。clamp関数はある数値xをa以下はすべてa、b以上をすべてbにする関数です。これらを例えば下記のように使うことでアニメーションの遷移が実現できます。
// 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秒ループする動画です。
アニメーションの遷移 pic.twitter.com/0hDB9BpuAE
— fotfla (@fotfla) December 20, 2019
アニメーションの遷移の場合動く秒数で値を割って0~1を取るように調整しておくと上記のアニメーションカーブと合わせて使いやすくなると思います。
以上で、今回紹介したものを基本的に使って最初に紹介した動画は構成されています。
図形の組み合わせなどでいろいろな絵を描いたり、映像をつくれると思うのでぜひShaderでお絵かきをしてVRChatで動くポスターなど作ってみてください。