# はじめに
この記事はOUCC Advent Calendar 2020の19日目に登録された記事です。
今回はVertex/Fragment Shaderでテクスチャを用いずにいろいろな図形を描き、模様を作ってみようと思います!
初心者の記事なので何卒ご容赦ください。
###環境
今回Unityのバージョンは2018.4.20f1を使用しています。
###Shaderについて
Unityでよく使われるShaderとしては
- Surface Shader (以下SShader)
- Vertex/Fragment Shader(以下VFShader)
があります。
SShaderはVFShaderをわかりやすくするために作られたもので、複雑なライティング計算を行ってくれます。
VFShaderは知識が必要ですが、SShaderよりも幅広い表現が出来ます。
ただライトの影響を受けないため、初心者や発光するものが作りたい人はSShaderがおすすめです。
#円
まずは円を描いてみましょう。
図形を描写するには距離関数が必要で、距離関数は図形ごとに違います。
円の距離関数は以下の通りです。
float circle(float2 p, float radius){
return distance(float2(0,0),p) -radius;
//distanceは2点間の距離(今回は中心(0,0)との距離)を返す。radiusは半径。
}
fixed4 frag (v2f i) : SV_Target{
float2 st = frac(i.uv) * 2 - 1;//fracは小数部分(例えば2.45なら0.45)を返す。
float2 col = step(0,circle(st, _Size));//0より小さい部分は0,0以上は1
return col;
}
円が書けました。(半径はプロパティで設定しています)
これだけだと味気ないですね・・。
#ハート
次はハートを書きましょう。さらに図形の数を増やしスクロールしてみます。
float heart(float2 p, float size){
p.x = 1.2 * p.x - sign(p.x) * p.y * 0.55;//signは符号が正なら1、負なら0を返す
return distance(0,p) - size;
}
fixed4 frag (v2f i) : SV_Target{
float2 uv = i.uv;
uv.y += _Time.x;//図形を上から下にスクロール。_Time.xは2秒間に1増加する。
int pixel =10;//uvに定数をかけることで図形を繰り返し表示する。この場合10^2個のハートができる。
float2 st = frac(i.uv*pixel) * 2 - 1;
float size = abs(sin(_Time.y +st.y))*_size;//ハートのサイズを正弦関数で変える。
fixed4 col = step(0,heart(st,size));
return (1-col)*_Color;//反転させてプロパティで設定した色をつける。
}
模様らしくなってきましたがまだ物足りないような・・。
##ひし形
次はひし形で模様を作ります。さらに回転行列を用いて図形を回転させてみます。
回転させる前に座標に値を加えるのがポイントです。
(ちなみに回転させた後に座標に定数を加えると、図形の座標自身が回転します)
float rhombus(float2 p, float size){
//ひし形の距離関数
return abs(p.x) + abs(p.y) - size;
}
float2 rotation(float2 p, float theta){
return float2((p.x) * cos(theta) - p.y * sin(theta), p.x * sin(theta) + p.y * cos(theta)); //thetaだけ回転させる関数
}
fixed4 frag (v2f i) : SV_Target{
float2 uv = i.uv;
uv += _Time.x;//今度はx,y座標ともにスクロールさせる。
int pixel =10;
float2 st = frac(i.uv*pixel) * 2 - 1;
st = rotation(st, _Time.z);//引数thetaに時間を代入しひし形を回転させる。
float size = abs(sin(_Time.y -distance(0,uv)))*_Size;//こうすることで波を作る。
fixed4 col =step(0.5,rhombus(st, size ));//0.5より小さいと0、0.5以上は1
return (1-col)*_Color;
}
アートっぽくなってきましたね!
#RGBごとに動かす
今度はRGBごとに別々の図形を作って動かします。
float RGBrhombus(float2 uv, float n, float theta){
uv += _Time.x;
float2 st = frac(uv*n) * 2 - 1;
float size = _Size*abs(sin(_Time.y -distance(0,uv)));
st = rotation(st, theta );
return step(0.5,rhombus(st, size));
}
fixed4 frag (v2f i) : SV_Target{
float pixel =5;
float theta = _Time.z*2;
fixed4 col = fixed4(RGBrhombus(i.uv,pixel, theta), RGBrhombus(i.uv,pixel*2, theta+2), RGBrhombus(i.uv,pixel*4, theta+4), 1);
//R,G,Bのひし形の数ををそれぞれpixel^2個,(pixel*2)^2個,(pixel*4)^2個に設定。さらに角度も少しずつずらす。
return (1-col)*_Color;
}
先ほど行った処理を関数RGBrhombus
にまとめています。
RGBごとにそれぞれ異なる引数を代入することでRGBごとに異なる処理が行うことができるのです。
こんな感じでカラフルな模様が描けました!!
#マスごとに動きを変える
おまけとして、図形ごとに異なる動きをさせる方法を紹介します。
例えば図形がそれぞれ不規則な大きさになるようにするにはどうしたらいいでしょうか。
図形ごとに動きを制御するには、各ピクセルが自分が入っている座標の真ん中の点としてふるまえば実現できます。
そこで整数値を返すfloor
関数を使います。
float rand(float2 uv){
return frac(sin(dot(uv, float2(12.9898, 78.233))) * 43758.5453);//乱数を返す関数
}
float randomSize(float2 uv, float n){
float2 st = (floor(uv * n) + 0.5) / n;//各ピクセルが自分が入っている座標の真ん中の点としてふるまうようになる!
float offs = rand(st) * 5;
return abs(sin(_Time.y * 3 + offs));//0~1にして出力
}
このranodomSize
関数を変数size
にかけることで、図形の大きさが不規則となります。
重要なのはst = (floor(uv * n) + 0.5) / n;
の部分で、この処理を行わないと図形が穴ぼこになります。
また変数stをsin
やdistance
の引数として使うことで、図形の大きさを自由に動かすことができるのです。
最後にRGBrhombus
関数にもう少し手を加えてみましょう!
float RGBrhombus(float2 uv, float n, float theta){
float speed = (floor(st*n)%2)*2-1;//1または-1となります。
uv += _Time.x *speed;//speedをかける
float2 st = frac(uv*n) * 2 - 1;
float size = _Size*abs(sin(_Time.y -distance(0,uv)));
st = rotation(st, theta *speed);//thetaにspeedをかける。
return step(0.5,rhombus(st, size));
}
図形の回転の向き、y軸のスクロール方向がそれぞれ交互に入れ替わっているのがわかります!
#まとめ
今回はShaderでお絵描きということで、いくつか模様を作ってみました。
汎用性が高くて工夫次第でおしゃれな壁や床が簡単に作れそうです。ほんまに使えるのか?
ShaderArtは奥深くて楽しいのでぜひやってみてください!!
#参考文献
距離関数に関して空の缶詰さんのこちらのサイトを参考にさせていただきました。
紹介したほかにもたくさんの図形が紹介されているので、興味のある方はぜひ。