はじめに
学園アイドルマスターの制服模様をShaderToyで作りました。誰がなんと言おうとファンアートです。
公式が制服の柄を公開しており、これを見た瞬間に『Shaderで作れそうだな』と感じたので作成しました。
制作時間は8時間ほどです。
ソースコード
解説&反省点
解説の前提は
- ShaderToyを描いたことがある
- SDFなどの知識がある程度ある
です。
反省点については
- 文字数を少なくできた可能性がある
を観点としています。処理の速さなどはあまり考えて無いです。
mapメソッドについて
四角の模様
四角模様はこの二つです。ソースコードでquad-1
とquad-2
とコメントしてある部分が該当箇所です。
// a:幅 x:方向
float squareWave(float a, float x)
{
return step(a, fract(x + a));
}
メソッドをグラフにするとこうなります。
任意の幅(0~1)の矩形波を作り、これで縞模様を作ります。
縦線か横線しか作れませんがグリッド線を最終的に作り合成するので結果的に四角に見えます。
このメソッドだけを使ったサンプルです。
アニメーションするので縞模様がどのように作られているかわかりやすいと思います。
// r:横向きか縦向きかを決める
// f:表示するかを決める
squareWave(.85, mix(p.x, p.y, r) * 7.9) * f;
メソッドを利用し、幅0.85で縞模様を作り、UVを7.9倍(8.0じゃないのはこっちのほうが見た目がよかったから)することで1つのマス目に複数四角があるようにします。
quad-1
とquad-2
の違いは四角の分割数が6か8かの違いだけです。
反省点
上記の通り、分割数が6か8かの違いだけなので1つの処理にまとめられた気がします。
四角が関係してる月模様と、この四角模様(2つ)の合計3要素を全部1つの処理で作成し、月は別で追加処理、その後回転させる
という処理を作ればもっと文字数を少なくできたと思っています。
星
星の模様はこれです。ソースコードでstar
とコメントがしてある部分が該当箇所です。
vec2 p2,p3;
p2 = p3 = p;
p2.x = abs(p2.x); p3.y = abs(p3.y);
p2.x -= .83; p3.y -= .83;
float s0, s1;
s0 = max(sdCircle(p2, 0.6), -sdCircle(p2, 0.57));
s1 = max(sdCircle(p3, 0.6), -sdCircle(p3, 0.57));
d += step(min(s0, s1), .001) * f;
全部で4つの大きめの円中心から離れた位置に作成し星の模様に見えるようにしてます。
abs
を使うことで2回の計算で済むように作り、それを組み合わせることで作成しています。
手順としては、
- 0.6の円aと0.57の円bを
max(a, -b)
でブーリアンの差分合成をして円の線s0を作る。 - s1の分も同じく作る。
-
min(s0, s1)
のブーリアンの加算合成で4つ分の円の線ができる
といった感じです。
反省点
この模様を見たときにサイクロイドを使って描けるのではないのかと思ったのですが、知識がないため作れませんしでした。
あと、abs
をうまく使えばs0とs1と1つにするのではなく、1つ作るだけで4つ分計算できるはずなのですがうまくいかなかったのでX軸とY軸の2つ作る結果となってしまいました。
月
月の模様はこれです。ソースコードでmoonとコメントがしてある部分が該当箇所です。
vec2 p5 = p;
p5 = mix(vec2(p5.x + .5, p5.y), vec2(p5.x, p5.y + .5), r);
float m;
m = min(1.-squareWave(.9, mix(p.y, p.x, r) * 8.), sdCircle(p5, .5));
d += step(max(m, -sdCircle(p5, .47)), .001) * f;
これは単純で、上記の四角模様と円をブーリアン合成することで作っています。
- 円をX方向かY方向にずらす(回転によって変わる)
- 線と円をブーリアン合成
- ちょっと小さい円をブーリアンの差分合成して線にして完成
反省点
線の時に書きましたが、これをmoon
として計算するのではなく、線と同じ種類として一括で計算したほうが文字数を少なくできた気がします。
H
Hの模様はこれです。ソースコードでhとコメントがしてある部分が該当箇所です。
そもそもこれがHを表してるのかはわかりませんが。まぁ多分初星のHでしょう。たぶん
p6 *= rot(mix(0., 1.57079, r));
float l = step(abs(p6.y), .02) * step(abs(p6.x), .25); // horizontal line
l += step(abs(fract(p6.x*2.+.5)), .07) * step(abs(p6.y), .42); // vertical line
l += step(max(sdCircle(p6, 0.5), -sdCircle(p6, 0.47)), .001);
Hについては横線+縦線+円で作ってます。
円に関しては前までと作り方は変わりません
縦横線は左右対称なのでabs
を使い、step
X方向とY方向で使い、乗算することで箱を作成します。なのでSDFは使って無いです。
反省点
この記事を書いてる時にabs
使う必要性あったかなと思い始めました。特に横線に関してはいらない気がします。中心からのXの長さをそのまま書けば作れるので。
逆に縦線は2本あるのでabs
を使うのは正解だったと思ってます。
色塗り
vec3 b0, b1, b2, b3; // Gradually darker
b0 = vec3(.761, .863, .996);
b1 = vec3(.635, .788, .949);
b2 = vec3(.494, .643, .847);
b3 = vec3(.373, .533, .745);
最初に色を定義します。これは学マスの公式画像にスポイト機能で色を調べて数値にしました。
vec2 uvf = abs(mod(uv * 2., 1.)) - .5;
vec2 uvd = floor(uv * 2.);
float d = 1. - map(uvf, uvd - 1.);
vec3 col = vec3(d);
vec2 d2, d20;
d2 = mod(uvd, 2.);
d20 = d2 * .5;
col = fract(d20.x + d20.y) * 2. * b2 * d;
col += step(.1, d2.x * d2.y) * b1 * d;
col += step(d2.x, .1) * step(d2.y, .1) * b3 * d;
col += b0 * (1.-d);
fragColor = vec4(col,1.);
uvf
はuvをn倍した小数点以下の部分が入ってます。グラフのように0~1しか入ってません。
この方法はグリッド模様を使うときに効果的です。グリッドはマス目ごとに0~1のuvが欲しいからです。
uvd
はuvをn倍した整数部が行ってます。グラフのようになります。
この方法もグリッド模様を作るときに効果的です。整数部分だけがわかることで現在のマス目が何番目のマス目なのか把握することができます。
col = fract(d20.x + d20.y) * 2. * b2 * d; // 1
col += step(.1, d2.x * d2.y) * b1 * d; // 2
col += step(d2.x, .1) * step(d2.y, .1) * b3 * d; // 3
col += b0 * (1.-d); //4
作成した何番目のマス目なのか保存してるd20
を使用して塗分けます。
1に関してはXとYのマス目番号の合計が偶数のマス目だけ塗ってます。
赤と白の合計が偶数ならb2
の色になります
2に関してはXとYのマス目をかけて1になったらb1
の色で塗ってます
4に関してはd
で線をどこに引くのか入ってるので都合を合わせるために白黒を反転させて塗ります。
反省点
mod
を使ってこれを一つの塊として扱って塗ればもっと文字数を削減できたと思いました。
その他
どのマスにどの模様を書くのかを最悪な分岐で処理してます。
if((uv2.x == 7. && uv2.y == 0.) || (uv2.x == 2. && uv2.y == 0.) ||
(uv2.x == 5. && uv2.y == 3.) || (uv2.x == 0. && uv2.y == 3.) ||
(uv2.x == 6. && uv2.y == 2.) || (uv2.x == 1. && uv2.y == 2.) ||
(uv2.x == 8. && uv2.y == 1.) || (uv2.x == 3. && uv2.y == 1.))
{
f = 1.;
}
どうにかしたかったのですがわりと模様の位置がばらばらで規則性が見えてこなかったのでこれしか思いつきませんでした。もう少し賢いやり方して文字数を少なくしたいです。約3000文字使ってるんで…