GLSLを使ってVJしてきたのでそこで使ったこととかまとめ

## 概要

2019.06.15に、[WeWork](https://www.wework.com/ja-JP)で開催された[Iceberg LIFE Vol.1](https://www.facebook.com/events/wework/iceberg-life-vol1/1063223803887027/)というイベントでライブコーディングVJをしてきたので、そこで得た知見とかをまとめました。
ちなみにイベント当日の様子↓
<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">昨日、Iceberg LIFE Vol.1というイベントで人生初のライブコーディングVJをしてきました！　信じられないくらい手が震えた・・ｗ ちょっと分かりづらいですが、奥でコード書いてるのが俺ですｗ <a href="https://t.co/iTzsKYTkdn">https://t.co/iTzsKYTkdn</a> <a href="https://t.co/oHrXcZvFxs">pic.twitter.com/oHrXcZvFxs</a></p>&mdash; edom18@AR / MESON (@edo_m18) <a href="https://twitter.com/edo_m18/status/1140050305544933376?ref_src=twsrc%5Etfw">June 16, 2019</a></blockquote>

コンセプトとして「**プログラミングってかっこいい！**」と思ってもらうことを目標にしました。
イベントに出ていたのはDJの方々で、一般の人もあまりテック系ではなかったので。
なので、ライブコーディングと言いつつ、即興ではなくて準備して臨みました。

ポストエフェクトは[kaiware007さん](https://twitter.com/kaiware007)が公開してくれているUnityの資料を参考にしました。

- [Unity VJ Shader Slide 2018/11/08](https://github.com/kaiware007/UnityVJShaderSlide20181108)

### 当日の作品公開

ちなみにだいぶトリッキーですが、当日はShadertoyのサイトのレイアウトを強制的に変更してそれっぽく見せてますｗ
なのでShadertoyでやれることなんかもまとめておきます。

イベントで見せたかったものを整理して公開したのでよかったら見てみてください。
[![作品イメージ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5616/9349f742-2163-0bb7-4b20-b7696eb9f9f6.jpeg)](https://www.shadertoy.com/view/3lSGzc)
- [LiveCoding VJ result](https://www.shadertoy.com/view/3lSGzc)



## SoundCloudの音声を使ってごにょごにょする

ShadertoyでSoundCloudを利用するには利用したい曲のURLを指定するだけでOK。
また右上のマイクを選択するとマイクからの音を、SoundCloudの音源と同様に扱えます。
VJ当日は、DJしているMacからの音をそのままマイクにつないで使いました。
![SoundCloudの設定](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5616/ee139962-d6e7-3489-cf59-5ca6babb32fd.png)
その値を使いたい場合は`texture(iChannel0, vec2(0, 0))`などとして、通常のテクスチャフェッチと同じように利用できます。

UV値によってどう値が変わるかは調査中。
おそらくですが、グラフ表示されている位置の値がXの値によって取得でき、さらにその強さをYの値で取得する、という感じかなと。

```glsl
float i = pow(texture(iChannel0, vec2(0.0, 0.85)).x, 2.0);
```

ちなみに、この値をフェッチして視覚化すると以下のようになりました。
![音源の視覚化](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5616/e6df24d3-ba80-0943-1ab9-af9c53f7f6b3.png)
- [Visualize Audio texture](https://www.shadertoy.com/view/3l23Ww)


## sin波を描く

音声の波形とかを描画するのに、まずは単純なsin波を描いてみました。
コードは以下。

```glsl:sin波の描画
#define PI  3.141592653589793

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
	
    uv *= 10.0;
    float t = iTime * 1.0;
    float y = uv.y - sin(uv.x + t * PI);
    y = smoothstep(0.1, 0.15, y) + smoothstep(0.1, 0.05, y);
    vec3 col = 1.0 - vec3(y);

    fragColor = vec4(col,1.0);
}
```

これを実行すると以下のような絵になります。
![sin波の描画](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5616/df759fc4-e077-c1f2-8cab-79bcd503769b.png)

ポイントは以下。

```glsl:smoothstepでなめらかに
y = smoothstep(0.1, 0.15, y) + smoothstep(0.1, 0.05, y);
```

`smoothstep`をふたつ合成することで表現しています。`smoothstep(0.1, 0.05, y)`と、第一引数より小さい値を第二引数にすると逆の処理をしてくれます。
（が、ドキュメントを見るとこのあたりは定義されておらず、たまたまWebGLが意図した挙動になっているだけっぽいので注意）

`smoothstep(0.1, 0.15, y)`だけを実行すると以下のような結果になります。
![sin波上](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5616/9484e3bb-8e55-32be-6118-b44f4ec4ffd9.png)

同様に、`smoothstep(0.1, 0.05, y)`だけを実行すると以下のような結果になります。
![sin波下](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5616/044fd679-5d50-8647-f08c-688dc88e0b29.png)

これを足し合わすことで最初の画像のようにsin波が描かれる、というわけですね。

ちなみに、エッジがやや甘くなりますが簡単に表現するなら以下のようにしても似たような絵になります。

```glsl:absを使ってラインを描く
y = smoothstep(0.01, 0.15, abs(y));
```

これを実行すると以下のようになります。
![absで描いた絵](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5616/78a144a1-8a5b-ef05-90c5-e4494a5532c8.png)

まぁ最終的に使わなかったのですが、参考までに載せておきます。



## UV座標を極座標として扱う

極座標は座標の点を$(r, \theta)$で表現する座標系です。
以前に[直交座標から極座標への変換メモ](https://qiita.com/edo_m18/items/5d61f19512fb05f48501)という記事を書いたので、詳しくはそちらをご覧ください。

そのときの図を再掲すると以下のようなイメージです。

![直交座標系.png](https://qiita-image-store.s3.amazonaws.com/0/5616/e42a3ff1-7567-b05f-75dc-1ab4abce84d5.png) ![極座標系.png](https://qiita-image-store.s3.amazonaws.com/0/5616/e646763d-f599-67ce-8ab2-bea95efc8022.png)

そして今回はUVの値をそのまま**極座標**として扱い、その値を利用してテクセルをフェッチする、という内容です。

```glsl:UV値を極座標系として扱う
// -1～1に正規化（画面中央を原点とするため）
vec2 uv = (fragCoord.xy - 0.5 * iResolution.xy) / iResolution.y;

// 極座標なので(r, θ)の値を計算する

// UV値から長さを得る = r
float r = length(uv);

// 単位円での位置を扱うためnormalize
vec2 nuv = normalize(uv);

// atanで角度を取得し、ラジアンで返ってくるのでPI * 2（360度）で割ることで0～1の範囲に。
float th = fract(atan(nuv.y, nuv.x) / PI_TWO);
```

これを利用してテクスチャをフェッチすると以下のような感じになります。

![極座標でテクセルフェッチ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5616/a690aefd-05e6-3153-5ae0-3344b3af1253.png)

ちなみに元画像はこれ↓
![元テクスチャ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5616/a3e6efbe-a28f-5e20-f5dc-e9d08f876317.png)


### 音のサンプリングに利用する

これを応用してマイクやSoundCloudの音をサンプリングしてマッピングすると以下のように、各スペクトラムを円形に配置することができます。
![Audio visualizer](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5616/f1c63f8f-3323-3926-7d4a-a548ff765176.gif)
今回のVJで利用したシーンです。

コードは以下。だいぶ短く書けました。

```glsl:Audio-visualizer
#define S(a, b, t) smoothstep(a, b, t)
#define PI 3.141592653589793
#define PI_TWO 6.283185307179586

void mainImage(out vec4 O, in vec2 U)
{    
    vec2 uv = (U.xy - 0.5 * iResolution.xy) / iResolution.y;
        
    vec2 nuv = normalize(uv);
    float r = length(uv);
    float th = fract(atan(nuv.y, nuv.x) / PI_TWO);
    
    float c = texture(iChannel0, vec2(th, 0.5)).x;
    float y = S(0.1, 0.2, (c - r) - 0.1);
    float m = step(0.2, r);

    O.rgb = (mod(th * 360., 3.) < 1.)
        ? hue2rgb(th) * y * m
        : vec3(0.0);
}
```

ポイントは以下の部分です。

```glsl:ビジュアライズのポイント
float c = texture(iChannel0, vec2(th, 0.5)).x;

// ...中略...

O.rgb = (mod(th * 360., 3.) < 1.)
      ? hue2rgb(th) * y * m
      : vec3(0.0);
```

前述した極座標としてのUV値を利用し、音の強さを角度を利用してサンプリングします。
そして`mod`を利用して一定間隔で色を出力し、それ以外を黒にすることで上のような図を描いています。


## グリッド表現と空間の歪み

VJで作ったもうひとつのシーンがこちら。
![Burn Sound Wave](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5616/de17eaa1-580c-9a66-a46b-e16f5b66186c.gif)
- [Burn Sound Wave](https://www.shadertoy.com/view/WlB3zc)

こちらの絵はレイマーチングで実装しています。
レイマーチングによりグリッドを表現し、かつそのレイを歪ませることによって表現しています。
これについては別に記事を書いたのでそちらをご覧ください。

- [[コードリーディング] Fire Storm Cube](https://qiita.com/edo_m18/items/e221551b0a7505db4664)


## 六角形を使った表現

![Hexagon space](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5616/33541949-b835-d79b-65c1-64e2d5aec8e4.gif)


詳細について別記事で書いているのでそちらをご覧ください。

- [[GLSL] UV値を使って2Dの六角形を描く](https://qiita.com/edo_m18/items/37d8773a5295bc6aba3d)




## 平面によるくり抜き

ちょっとなに言ってるか分かりませんがｗ
絵を見たほうが早いでしょう。
![capture.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5616/7ee968a2-be3a-d08b-0fb5-a995fcf96036.gif)
上の絵は、空間の折りたたみ（繰り返し）によるシリンダーを描きつつ、それを縦横の平面を使ってくり抜いているイメージです。
これもShadertoyにアップしてあります。

- [Pile space](https://www.shadertoy.com/view/tljGRG)

なお、この表現の詳細については別の記事で書いているのでそちらを参考にしてください。

- [[コードリーディング] Cubeの折りたたみと平面によるくり抜きでかっこいい表現](https://qiita.com/edo_m18/items/f1bba8f66fae5164f87d)



## その他Tips

### smoothstepを使ってラインを描く

冒頭で紹介したsin波を描く部分で利用したものを、もう少し掘り下げた解説です。

[GLSLのドキュメント](https://www.khronos.org/registry/OpenCL/sdk/1.0/docs/man/xhtml/smoothstep.html)では以下のように書かれています。

>Returns 0.0 if x less than or equal to edge0 and 1.0 if x greater than or equal to edge1 and performs smooth Hermite interpolation between 0 and 1 when edge0 < x < edge1. This is useful in cases where you would want a threshold function with a smooth transition.

式にすると以下。

>This is equivalent to:

```glsl
gentype t;
t = clamp(((x - edge0) / (edge1 - edge0), 0, 1);
return t * t * (3 - 2 * t );
```

これをグラフ化すると以下のようになります。
![Graph of smoothstep](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/5616/4cc05a83-8fb8-4cfd-6045-dddf0550a31b.png)
[smoothstep | desmos](https://www.desmos.com/calculator/dplmcwehsn)

このなめらかな曲線がアンチエイリアスの役目を果たしてきれいな線が描ける、というわけですね。


### オブジェクトの回転

詳細は[オブジェクトを行列で回転させる | wgld.org](https://wgld.org/d/glsl/g017.html)をご覧ください。

```glsl
#define PI 3.141592653589793

vec3 rotate(vec3 p, float angle, vec3 axis){
    vec3 a = normalize(axis);
    float s = sin(angle);
    float c = cos(angle);
    float r = 1.0 - c;
    mat3 m = mat3(
        a.x * a.x * r + c,
        a.y * a.x * r + a.z * s,
        a.z * a.x * r - a.y * s,
        a.x * a.y * r - a.z * s,
        a.y * a.y * r + c,
        a.z * a.y * r + a.x * s,
        a.x * a.z * r + a.y * s,
        a.y * a.z * r - a.x * s,
        a.z * a.z * r + c
    );
    return m * p;
}
```

これの利用イメージは、引数に渡したレイの位置（`vec3 p`）を、任意軸の回転行列によって回転させます。
戻り値は回転したあとのベクトルとなります。
つまり、レイ自体を回転させることで結果的にオブジェクトが回転したように見える、というわけです。

物理的に考えても、光自体が曲がれば（歪めば）対象のオブジェクトが歪むのと同じ理屈ですね。


### 時間に応じてシーンを切り替える

考え方はシンプルかつ力技です。
以下のように、`iTime`を、1シーンごとの時間で割って（例だと`5.0`秒）、その値をさらに`mod`で繰り返し処理にします。
こうすることで必ず用意したシーン数分、繰り返されることになります。

```glsl:シーン切り替え

float map(vec3 p)
{
    float span = 5.;
    float idx = mod(iTime / span, 3.0);
    
    if (idx < 1.0)
    {
        return sdSphere(p, 1.1);
    }
    else if (idx < 2.0)
    {
        return sdBox(p, vec3(0.5));
    }
    else if (idx < 3.0)
    {
        return sdCylinder(p, vec3(0.5, 1.5, 0.3));
    }
    
    return sdSphere(p, 0.1);
}
```

今回のVJでは、徐々に構築されていくシーンを時間に応じて切り替えていく演出として導入しました。


## まとめ

なんか色々まとめていたら、個別の記事にしたほうがいいくらいのボリュームになってしまったので、なんだかほぼリンク集みたいな感じになってしまいました；
それぞれのシーンは個別に解説しているので、興味があるやつはそちらをご覧ください。

そして最後に、参加した感想は「**とても楽しかった！**」
正直、途中で絵がでなくなったりとうまく行かない部分で頭が真っ白になったり、手が震えてコーディングどころじゃない場面も多々ありましたが、終わってみたらとんでもなくスキルアップにつながりました。

ちょうど先日、KLab主催のシェーダ勉強会で@kaneta1992さんがライブコーディングしていてまじでかっこいいので、興味がある人はぜひライブコーディングしてレイマーチしましょう！

ちなみにそのときのテクニックなどをまとめてくれているので必読です！

- [魔法使いになりたい人のためのシェーダーライブコーディング入門](https://qiita.com/kaneta1992/items/21149c78159bd27e0860)