概要
2019.06.15に、WeWorkで開催されたIceberg LIFE Vol.1というイベントでライブコーディングVJをしてきたので、そこで得た知見とかをまとめました。
ちなみにイベント当日の様子↓
昨日、Iceberg LIFE Vol.1というイベントで人生初のライブコーディングVJをしてきました! 信じられないくらい手が震えた・・w ちょっと分かりづらいですが、奥でコード書いてるのが俺ですw https://t.co/iTzsKYTkdn pic.twitter.com/oHrXcZvFxs
— edom18@AR / MESON (@edo_m18) June 16, 2019
コンセプトとして「プログラミングってかっこいい!」と思ってもらうことを目標にしました。
イベントに出ていたのはDJの方々で、一般の人もあまりテック系ではなかったので。
なので、ライブコーディングと言いつつ、即興ではなくて準備して臨みました。
ポストエフェクトはkaiware007さんが公開してくれているUnityの資料を参考にしました。
当日の作品公開
ちなみにだいぶトリッキーですが、当日はShadertoyのサイトのレイアウトを強制的に変更してそれっぽく見せてますw
なのでShadertoyでやれることなんかもまとめておきます。
イベントで見せたかったものを整理して公開したのでよかったら見てみてください。
SoundCloudの音声を使ってごにょごにょする
ShadertoyでSoundCloudを利用するには利用したい曲のURLを指定するだけでOK。
また右上のマイクを選択するとマイクからの音を、SoundCloudの音源と同様に扱えます。
VJ当日は、DJしているMacからの音をそのままマイクにつないで使いました。
その値を使いたい場合はtexture(iChannel0, vec2(0, 0))
などとして、通常のテクスチャフェッチと同じように利用できます。
UV値によってどう値が変わるかは調査中。
おそらくですが、グラフ表示されている位置の値がXの値によって取得でき、さらにその強さをYの値で取得する、という感じかなと。
float i = pow(texture(iChannel0, vec2(0.0, 0.85)).x, 2.0);
ちなみに、この値をフェッチして視覚化すると以下のようになりました。
sin波を描く
音声の波形とかを描画するのに、まずは単純な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);
}
ポイントは以下。
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)
だけを実行すると以下のような結果になります。
同様に、smoothstep(0.1, 0.05, y)
だけを実行すると以下のような結果になります。
これを足し合わすことで最初の画像のようにsin波が描かれる、というわけですね。
ちなみに、エッジがやや甘くなりますが簡単に表現するなら以下のようにしても似たような絵になります。
y = smoothstep(0.01, 0.15, abs(y));
まぁ最終的に使わなかったのですが、参考までに載せておきます。
UV座標を極座標として扱う
極座標は座標の点を$(r, \theta)$で表現する座標系です。
以前に直交座標から極座標への変換メモという記事を書いたので、詳しくはそちらをご覧ください。
そのときの図を再掲すると以下のようなイメージです。
そして今回は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);
これを利用してテクスチャをフェッチすると以下のような感じになります。
音のサンプリングに利用する
これを応用してマイクやSoundCloudの音をサンプリングしてマッピングすると以下のように、各スペクトラムを円形に配置することができます。
今回のVJで利用したシーンです。
コードは以下。だいぶ短く書けました。
#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);
}
ポイントは以下の部分です。
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
を利用して一定間隔で色を出力し、それ以外を黒にすることで上のような図を描いています。
グリッド表現と空間の歪み
こちらの絵はレイマーチングで実装しています。
レイマーチングによりグリッドを表現し、かつそのレイを歪ませることによって表現しています。
これについては別に記事を書いたのでそちらをご覧ください。
六角形を使った表現
詳細について別記事で書いているのでそちらをご覧ください。
平面によるくり抜き
ちょっとなに言ってるか分かりませんがw
絵を見たほうが早いでしょう。
上の絵は、空間の折りたたみ(繰り返し)によるシリンダーを描きつつ、それを縦横の平面を使ってくり抜いているイメージです。
これもShadertoyにアップしてあります。
なお、この表現の詳細については別の記事で書いているのでそちらを参考にしてください。
その他Tips
smoothstepを使ってラインを描く
冒頭で紹介したsin波を描く部分で利用したものを、もう少し掘り下げた解説です。
GLSLのドキュメントでは以下のように書かれています。
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:
gentype t;
t = clamp(((x - edge0) / (edge1 - edge0), 0, 1);
return t * t * (3 - 2 * t );
これをグラフ化すると以下のようになります。
smoothstep | desmos
このなめらかな曲線がアンチエイリアスの役目を果たしてきれいな線が描ける、というわけですね。
オブジェクトの回転
詳細はオブジェクトを行列で回転させる | wgld.orgをご覧ください。
#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
で繰り返し処理にします。
こうすることで必ず用意したシーン数分、繰り返されることになります。
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さんがライブコーディングしていてまじでかっこいいので、興味がある人はぜひライブコーディングしてレイマーチしましょう!
ちなみにそのときのテクニックなどをまとめてくれているので必読です!