フラグメントシェーダーでっぽくない色の作り方を考える

フラグメントシェーダを使うとどうしてもRGB!白・黒！みたいな色味になりがちなので、
色の操り方について、考えたので、そのまとめです。

#1.RGBではなくHSBを使う

<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="ja" dir="ltr">極座標とHSB変換 - 2 <a href="https://twitter.com/hashtag/glsl?src=hash&amp;ref_src=twsrc%5Etfw">#glsl</a> <a href="https://t.co/oRpdsOi7v3">pic.twitter.com/oRpdsOi7v3</a></p>&mdash; null (@null_tokyo) <a href="https://twitter.com/null_tokyo/status/1082574827372703744?ref_src=twsrc%5Etfw">January 8, 2019</a></blockquote>

`gl_FragColor`に渡す色は `vec4(r, g, b, a)`なのでどうしてもRGBに寄りがちになります。
色をHSBで考えてから、RGBに変換して渡すと、狙った色を出しやすいです。

HSBはHue（色相）、Saturation(彩度)、Brightness(彩度)の略です。
全て、0~1の間で指定をします。色相環はは0が赤で0~1で一周します。

作例は極座標を使って、角度ごとに色相環を変化させた例です。

コードはHSB変換の例です。

```glsl
//  Function from Iñigo Quiles
//  https://www.shadertoy.com/view/MsS3Wc
vec3 hsb2rgb( in vec3 c ){
    vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),
                    6.0)-3.0)-1.0,
                    0.0,
                    1.0 );
    rgb = rgb*rgb*(3.0-2.0*rgb);
    return c.z * mix(vec3(1.0), rgb, c.y);
}

void main() {
  vec3 hsb = vec3(0.0, 0.5, 0.8);
  vec3 color = hsb2rgb(hsb);
  
  gl_FragColor = vec4(color, 1.0);
}
```

コードは以下を参考にしています
https://www.shadertoy.com/view/MsS3Wc


# 2.mix関数を使う
<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="und" dir="ltr"><a href="https://twitter.com/hashtag/glsl?src=hash&amp;ref_src=twsrc%5Etfw">#glsl</a> <a href="https://t.co/3mdRSrhN0c">pic.twitter.com/3mdRSrhN0c</a></p>&mdash; null (@null_tokyo) <a href="https://twitter.com/null_tokyo/status/1088092144978386944?ref_src=twsrc%5Etfw">January 23, 2019</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

こちらの色味を作る際に使用した例です。
色自体は薄い波打った円形を複数描いています。色は元々は白黒（0.0 ~ 1.0）です。

0.0 ~ 1.0の間の数値を渡すと、その数値に合わせてvec3型で色を返してくれます。

```glsl
vec3 customColor(float d) {
  vec3 gray = vec3(0.9254, 0.8657, 0.8914); //グレー
  vec3 c = vec3(0.7210, 0.9118, 0.9083); //シアン
  vec3 m = vec3(0.9180, 0.2236, 0.9688); //マゼンタ
  vec3 y = vec3(1.0000, 0.9961, 0.3530); //イエロー
  vec3 color = mix(gray, y, smoothstep(0.0, 0.8, d));
  color = mix(color, m, smoothstep(0.0, 0.9, d));
  color = mix(color, c, smoothstep(0.0, 1.0, d));
  return color;
}
```

ポイントは `mix()`です。
`mix(x, y, a)`はxからyの間のある点aの値を取得することができます。（線形補間）

線形補間についてはこちら
https://qiita.com/niusounds/items/c4af702b06582590c82e

またaに渡す値を調節するのもポイントです。
smoothstep()で1を返す部分を微妙にずらすことで色の違いを出やすくしています。

```glsl
  //グレーとイエローを混ぜる
  vec3 color = mix(gray, y, smoothstep(0.0, 0.8, d));
  //さらにマゼンタを混ぜる
  color = mix(color, m, smoothstep(0.0, 0.9, d));
　//さらにさらにシアンを混ぜる
  color = mix(color, c, smoothstep(0.0, 1.0, d));
```

この場合、色の混ぜる順番や、0 ~ 1の比率をいじることでいろんなタイプの色を出せるので、自分の好みの色に調整してみてください。


# 3.step関数を使う

`step()`を使って色の出現率を操り、似たような色の組み合わせを引き出す方法です。

<blockquote class="twitter-tweet"><p lang="ro" dir="ltr">Algorithmic Color Spiral - 2 <a href="https://twitter.com/hashtag/glsl?src=hash&amp;ref_src=twsrc%5Etfw">#glsl</a> <a href="https://t.co/OdbZ5NHXcv">pic.twitter.com/OdbZ5NHXcv</a></p>&mdash; null (@null_tokyo) <a href="https://twitter.com/null_tokyo/status/1082819925373120512?ref_src=twsrc%5Etfw">January 9, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

HSBの出る確率を調整してカラーパターンを作ります。

```glsl
vec3 hsb2rgb( in vec3 c ){
    vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),
                    6.0)-3.0)-1.0,
                    0.0,
                    1.0 );
    rgb = rgb*rgb*(3.0-2.0*rgb);
    return c.z * mix(vec3(1.0), rgb, c.y);
}

vec3 customColor(vec2 st, vec2 m) {
  float r = snoise(st + m);
  float h = 1.0 + step(0.5, r) + step(0.7, r) + step(0.8, r);
  float s = 1.0 + step(0.1, r) + step(0.1, r) + step(0.2, r);
  float b = 1.0 + step(0.1, r) + step(0.1, r) + step(0.2, r);

  vec3 color = vec3(fract(h/4.0 + (1.0 + m.y)*0.5), fract(s/4.0 + (1.0 + m.x)*0.5), b/6.0+0.4);
  return hsb2rgb(color);
}
```

コードのポイントはここです。

```glsl
  // rは0.0~1.0
  float h = 1.0 + step(0.5, r) + step(0.7, r) + step(0.8, r); //1.0~4.0の数になる
  float s = 1.0 + step(0.1, r) + step(0.1, r) + step(0.2, r);
  float b = 1.0 + step(0.1, r) + step(0.1, r) + step(0.2, r);
```

各ブロック0.0~1.0の数字を持っています。
step(a,x)閾値（a）以下の場合0、それ以上の場合は1を返します。
上記の式では1にstep()を３回足しているので、最大の数値は4.0になります。
閾値を色々と調整してあげることで、色の出現率を調整することができます。

```
  vec3 color = vec3(h/4.0, s/4.0, b/4.0);
  return hsb2rgb(color);
```

色として使う際は4で割ってあげると0~1の値になります。

さらに調整したい場合はfract()を使って小数点の部分だけとってあげれば調整が可能です。
例えば、マウスのx座標で変更したい際は以下のようにします。

```glsl
  vec3 color = vec3(fract(h/4.0 + mouse.x), s/4.0, b/4.0);
  return hsb2rgb(color);
```

マウスの位置によって色相が変わるけど、彩度・明度は変わらないので、雰囲気の似た別の色が出るようになります。

# おわりに
最近書いたコードの中で、色にまつわるものをまとめてみました。
他にもいい例や、効率のいいやり方などあればぜひ教えてください。