Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

概要

2019.06.15に、WeWorkで開催されたIceberg LIFE Vol.1というイベントでライブコーディングVJをしてきたので、そこで得た知見とかをまとめました。
ちなみにイベント当日の様子↓

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

ポストエフェクトはkaiware007さんが公開してくれているUnityの資料を参考にしました。

当日の作品公開

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

イベントで見せたかったものを整理して公開したのでよかったら見てみてください。
作品イメージ
- LiveCoding VJ result

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

ShadertoyでSoundCloudを利用するには利用したい曲のURLを指定するだけでOK。
また右上のマイクを選択するとマイクからの音を、SoundCloudの音源と同様に扱えます。
VJ当日は、DJしているMacからの音をそのままマイクにつないで使いました。
SoundCloudの設定
その値を使いたい場合はtexture(iChannel0, vec2(0, 0))などとして、通常のテクスチャフェッチと同じように利用できます。

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

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

ちなみに、この値をフェッチして視覚化すると以下のようになりました。
音源の視覚化
- Visualize Audio texture

sin波を描く

音声の波形とかを描画するのに、まずは単純な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);
}

これを実行すると以下のような絵になります。
sin波の描画

ポイントは以下。

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波上

同様に、smoothstep(0.1, 0.05, y)だけを実行すると以下のような結果になります。
sin波下

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

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

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

これを実行すると以下のようになります。
absで描いた絵

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

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

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

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

直交座標系.png 極座標系.png

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

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の音をサンプリングしてマッピングすると以下のように、各スペクトラムを円形に配置することができます。
Audio visualizer
今回のVJで利用したシーンです。

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

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);
}

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

ビジュアライズのポイント
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
- Burn Sound Wave

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

六角形を使った表現

Hexagon space

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

平面によるくり抜き

ちょっとなに言ってるか分かりませんがw
絵を見たほうが早いでしょう。
capture.gif
上の絵は、空間の折りたたみ(繰り返し)によるシリンダーを描きつつ、それを縦横の平面を使ってくり抜いているイメージです。
これも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 );

これをグラフ化すると以下のようになります。
Graph of smoothstep
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さんがライブコーディングしていてまじでかっこいいので、興味がある人はぜひライブコーディングしてレイマーチしましょう!

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

edo_m18
現在はUnity ARエンジニア。 主にARのコンテンツ制作をしています。 最近は機械学習にも興味が出て勉強中です。 Unityに関するブログは別で書いています↓ https://edom18.hateblo.jp/
http://edom18.hateblo.jp/
unity-game-dev-guild
趣味・仕事問わずUnityでゲームを作っている開発者のみで構成されるオンラインコミュニティです。Unityでゲームを開発・運用するにあたって必要なあらゆる知見を共有することを目的とします。
https://unity-game-dev-guild.github.io/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした