学園アイドルマスターの制服模様をShaderで作った

# はじめに
https://x.com/ayaha__401/status/1807361514153640436

学園アイドルマスターの制服模様をShaderToyで作りました。誰がなんと言おうとファンアートです。

https://twitter.com/gkmas_official/status/1806598415712993744

公式が制服の柄を公開しており、これを見た瞬間に『Shaderで作れそうだな』と感じたので作成しました。
制作時間は8時間ほどです。

# ソースコード
https://www.shadertoy.com/view/XXdXWf

# 解説＆反省点

解説の前提は
* ShaderToyを描いたことがある
* SDFなどの知識がある程度ある
です。

反省点については
* 文字数を少なくできた可能性がある
を観点としています。処理の速さなどはあまり考えて無いです。

## mapメソッドについて
## 四角の模様
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/4a2b5567-efeb-dd22-0b72-755d7a7dbb6f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/5943af31-9799-41c3-b6d4-9fb5c05dd91a.png)
四角模様はこの二つです。ソースコードで`quad-1`と`quad-2`とコメントしてある部分が該当箇所です。

```GLSL
// a:幅 x:方向
float squareWave(float a, float x)
{
    return step(a, fract(x + a));
}
```
このメソッドを使って四角を作ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/07163fea-03d7-a8f1-b27c-68e4c5693193.png)

メソッドをグラフにするとこうなります。
任意の幅(0~1)の矩形波を作り、これで縞模様を作ります。
縦線か横線しか作れませんがグリッド線を最終的に作り合成するので結果的に四角に見えます。


https://www.shadertoy.com/view/43VXRz

このメソッドだけを使ったサンプルです。
アニメーションするので縞模様がどのように作られているかわかりやすいと思います。

```GLSL
// 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つの処理で作成し、月は別で追加処理、その後回転させる
という処理を作ればもっと文字数を少なくできたと思っています。

## 星
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/4b7a2615-e54a-1109-dd96-30dd932e1500.png)

星の模様はこれです。ソースコードで`star`とコメントがしてある部分が該当箇所です。

```GLSL
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つ作る結果となってしまいました。

## 月
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/595133cb-87b9-e45c-f362-a277e221d06c.png)
月の模様はこれです。ソースコードでmoonとコメントがしてある部分が該当箇所です。

```GLSL
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
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/17d83e5a-b7c0-a620-906e-59b2d09f8b0b.png)
Hの模様はこれです。ソースコードでhとコメントがしてある部分が該当箇所です。
そもそもこれがHを表してるのかはわかりませんが。まぁ多分初星のHでしょう。たぶん

```GLSL
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`を使うのは正解だったと思ってます。

## 色塗り
```GLSL
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);
```
最初に色を定義します。これは学マスの公式画像にスポイト機能で色を調べて数値にしました。
```GLSL
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.);
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/d6a59bf9-77b1-6a73-0c53-c418527b5f0c.png)
`uvf`はuvをn倍した小数点以下の部分が入ってます。グラフのように0~1しか入ってません。
この方法はグリッド模様を使うときに効果的です。グリッドはマス目ごとに0~1のuvが欲しいからです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/2befd1da-1b7f-387a-a360-908f38fe5316.png)
`uvd`はuvをn倍した整数部が行ってます。グラフのようになります。
この方法もグリッド模様を作るときに効果的です。整数部分だけがわかることで現在のマス目が何番目のマス目なのか把握することができます。

```GLSL
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`を使用して塗分けます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/4acf54d6-7697-18ab-7b19-8f6c2b3f7baa.png)
1に関してはXとYのマス目番号の合計が偶数のマス目だけ塗ってます。
赤と白の合計が偶数なら`b2`の色になります

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/ec9554d9-618c-c045-7ade-a7f414f4f80b.png)
2に関してはXとYのマス目をかけて1になったら`b1`の色で塗ってます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/ec9554d9-618c-c045-7ade-a7f414f4f80b.png)
3に関してはXとYのマス目の両方が0なら`b3`で塗ります。

4に関しては`d`で線をどこに引くのか入ってるので都合を合わせるために白黒を反転させて塗ります。

**反省点**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/608797/fffcb81e-54ca-0745-d439-94560a9d7c98.png)
`mod`を使ってこれを一つの塊として扱って塗ればもっと文字数を削減できたと思いました。

## その他
どのマスにどの模様を書くのかを最悪な分岐で処理してます。
```GLSL
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文字使ってるんで…