「VJ for Interim Report edition 4」の技術解説とVJの感想

2019.11.7

2019年11月7日にCIRCUS Tokyoで行われたInterim Report edition4 で初VJをしました。この記事ではVJに使用した技術の解説とVJの感想について書いていきます。

経緯

そもそもの経緯ですが、9月の中頃ぐらいから新しいチャレンジとしてVJをやってみたいなと思い、Web Audio APIやWeb MIDI APIとWebGLを組み合わせるテストを始めました。

このツイートを見てかはわからないですが、9月の終わりごろにNEORTの@r2inomiさんから声をかけていただきました。今回のInterim Reportはいろいろな団体からのキュレーションという形式になっており、そのうちのNEORT枠ということらしいです。

技術解説

今回は音に合わせて歪みながらカラフルな色を出す球をメインに、球の数を増やしたり、いろいろなグリッチを掛けてバリエーションを出していく感じのVJをしました。

絵の部分はWebGL、マイクからの音の取得とFFT解析はWeb Audio API、MIDIコントローラーからの入力はWeb MIDI APIを使い、ブラウザ上で動かしています。

ソースコードはGitHubに置いてあります。

https://github.com/aadebdeb/InterimReport4_VJ2

この記事では、今回使用した技術のうちオーディオリアクティブな球および引き延ばしグリッチと色を変換するグリッチについて解説します。

オーディオリアクティブに歪む球

bn2n2tk3p9f80jer8700.png

音により歪む球はレイマーチングで出しています。

以下はレイマーチングで使用した距離関数です。音量をuniformで渡しており、大きくなるほど歪みが大きくなるようにしています。

float estimateDistance(vec3 p) {
  // 音量に応じてサイン波を足すことで位置を歪ませる
  for (int i = 0; i < 16; i++) {
    p.xy *= rotate(random(float(i)) + u_elapsedSecs);
    p.xz *= rotate(random(float(i) + 0.43) + u_elapsedSecs);
    // u_audioLevelに音量が、u_fftLevelsにFFT解析した結果が入っており、
    // 音量とFFTの値が大きいほど歪みが大きくなる
    p.y += u_dispIntensity * u_audioLevel * u_fftLevels[i] * 0.5 * sin(2.0 * p.x + 5.0 * u_elapsedSecs);
  }

  // 球の距離関数
  return length(p) - SPHERE_RADIUS;
}

球の色を求める箇所は以下のようになっています。Deferred Renderingを使用しているので、ライティングに必要な要素をGBuffer構造体に詰めています。

vec3 palette(float t, vec3 a, vec3 b, vec3 c, vec3 d) {
  return a + b * cos(6.28318530718 * (t * c + d));
}

GBuffer getGBuffer(vec3 worldPosition, vec3 worldNormal) {
  vec3 p = worldPosition;
  GBuffer gBuffer;
  gBuffer.diffuse = vec3(0.01);
  gBuffer.specular = vec3(0.3);
  gBuffer.specularIntensity = 0.2;
  gBuffer.worldPosition = worldPosition;
  gBuffer.worldNormal = worldNormal;
  // 位置がSPHERE_RADIUSより内側の場合はエミッションを追加
  // 色は中心からの距離で決定する
  gBuffer.emission = mix(
    palette(20.0 * length(p) + u_elapsedSecs, vec3(0.2), vec3(0.8), vec3(1.0, 2.0, 1.5), vec3(1.0, 0.1, 0.02)),
    vec3(0.00),
    smoothstep(0.98 * SPHERE_RADIUS, 0.99 * SPHERE_RADIUS, length(p))
  );
  return gBuffer;
}

特徴的なカラフルな色を出しているのがgBuffer.emissionの箇所です。位置が球よりも外側だとエミッションの値は0ですが、内側だとpalette関数で色を出すようにしています。palette関数は中心からの距離を引数に取るので歪み具合により出てくる色が変わります。これにより、音で球が内側に歪むとカラフルな色をでるようになっています。

グリッチ (引き延ばし)

bn2ogtc3p9f80jer8710.png

ただ単に位置をずらすだけでなく、絵を引き延ばす感じのグリッチを掛けています。

フラグメントシェーダーは以下のようになっており、X方向とY方向に引き延ばしのエフェクトを掛けており、引き延ばしの量と大きさはuniformで外側から渡せるようになっています。

#version 300 es

precision highp float;

in vec2 v_uv;

out vec4 o_color;

uniform sampler2D u_srcTexture;
uniform float u_elapsedSecs;
uniform float u_shiftXIntensity;
uniform float u_shiftYIntensity;
uniform float u_shiftXRate;
uniform float u_shiftYRate;

float random(float x) {
  return fract(sin(x * 12.9898) * 43758.5453);
}

float srandom(float x) {
  return 2.0 * random(x) - 1.0;
}

void main(void) {
  vec2 uv = v_uv;

  // 乱数作成に使用するシード
  // 0.1(=1.0/10.0)秒ごとにシードが変わるようにしている
  float timeStep = 100.0 * random(floor(10.0 * u_elapsedSecs));

  // X方向に対する引き延ばし
  float shiftPosY = floor(10.0 * (v_uv.y * (1.0 + 0.2 * sin(10.0 * v_uv.y + timeStep))) + timeStep);
  if (random(shiftPosY) < u_shiftXRate) { // u_shiftXRateでX方向に対するずらしの割合を決定

    float start = random(1000.0 + shiftPosY); // 引き延ばしの開始地点
    float width = u_shiftXIntensity * random(123.0 + shiftPosY); // 引き延ばしの大きさを決定(u_shiftXIntensityが大きいほど引き延ばしが大きくなる)

    if (random(243.0 + shiftPosY) < 0.5) { 
      if (uv.x >= start) {
        uv.x = max(start, uv.x - width); // +X方向に引き延ばす
      }
    } else {
      if (uv.x <= start) {
        uv.x = min(start, uv.x + width); // -X方向に引き延ばす
      }
    }
  }

  // Y方向に対するずらし(X方向と同じなので説明を省略)
  float shiftPosX = floor(10.0 * (v_uv.x * (1.0 + 0.2 * sin(10.0 * v_uv.x + timeStep))) + timeStep);
  if (random(432.0 + shiftPosX) < u_shiftYRate) {

    float start = random(103.0 + shiftPosX);
    float width = u_shiftYIntensity * random(342.0 + shiftPosX);

    if (random(243.0 + shiftPosX) < 0.5) {
      if (uv.y >= start) {
        uv.y = max(start, uv.y - width);
      }
    } else {
      if (uv.y <= start) {
        uv.y = min(start, uv.y + width);
      }
    }
  }

  // ずらしたUV値でサンプリング
  o_color = vec4(texture(u_srcTexture, fract(uv)).rgb, 1.0);
}

特に見てもらいたいのがuvを変更している以下の箇所です。これによりstartからwidthの大きさだけ引き延ばすことができます。

if (uv.x >= start) {
  uv.x = max(start, uv.x - width); // +X方向に引き延ばす
}

わかりやすいようにglslsandboxにこの部分だけのサンプルを置いておきました。

http://glslsandbox.com/e#58450.0

グリッチ (色)

bn2md7k3p9f80jer86vg.png

色を変換するグリッチです。適当に作成した四角形の範囲内であれば元の色をpalette関数の引数に渡して新たな色を決定しています。

#version 300 es

precision highp float;

in vec2 v_uv;

out vec4 o_color;

uniform sampler2D u_srcTexture;
uniform float u_elapsedSecs;
uniform float u_intensity;

float random(float x) {
  return fract(sin(x * 12.9898) * 43758.5453);
}

vec2 random2(float x) {
  return fract(sin(x * vec2(12.9898, 51.431)) * vec2(43758.5453, 71932.1354));
}

vec3 palette(vec3 t, vec3 a, vec3 b, vec3 c, vec3 d) {
  return a + b * cos(6.28318530718 * (t * c + d));
}

void main(void) {
  float timeStep = 100.0 * random(floor(10.0 * u_elapsedSecs));
  float maxGlitch = 10.0 * u_intensity * random(floor(10.0 * u_elapsedSecs));
  float idx = -1.0;
  for (float i = 1.0; i <= maxGlitch; i += 1.0) {
    vec2 center = random2(i + timeStep);
    vec2 size = 0.02 + 0.5 * random2(i + timeStep + 342.443);
    // 四角形の内側であれば、色変換を使用する
    if (abs(center.x - v_uv.x) < size.x && abs(center.y - v_uv.y) < size.y) {
      idx = i;
      break;
    }
  }

  if (idx != -1.0) {
    o_color = vec4(
    // 現在の色をもとにpalette関数で色変換する
      palette(texture(u_srcTexture, fract(v_uv)).rgb + u_elapsedSecs + random(idx + timeStep + 432.321) * 100.0,
        vec3(0.5), vec3(0.5), vec3(1.0), vec3(0.0, 0.33, 0.67)), 1.0);
  } else {
    o_color = vec4(texture(u_srcTexture, fract(v_uv)).rgb, 1.0);
  }
}

感想

初VJの感想です。

  • 途中で中だるみする
    • 普段は5秒とか10秒でループするような作品しか作ってないの30分をどうやって絵を持たせればいいのかわからなかった
  • グリッチに頼りすぎた
    • 中だるみを避けようとしたら、グリッチをゴリゴリにかけていくVJになった
    • ただグリッチをかければだいたい何でもかっこよくなるので、できれば避けたかった
  • ブラウザVJはそこそこ辛い
    • VJ中にパラメータが見れないので、パラメータがどうなっているかを記憶しておかねばならず辛い
  • VJ中は割と手持ち無沙汰
    • システムの作り方次第だと思うが、今回のVJは割とタイミングよくボタンを押す系だったので手持ち無沙汰感があった

若干ネガティブな感じになりましたが、初VJ楽しかったです。結局は場数を踏まないと思うようにはいかないのかなと感じました。

今後

今後です。

  • ブラウザVJはそこそこ辛いので、UnityとかTouchDesignerを使ってちゃんとシステムを組めるようになりたい
  • VJ中の手持ち無沙汰が辛いので、常に手を動かす感じのVJがしたい、MIDIパッドを叩いたりとかライブコーディングとか

You can support this creator by paying money.

Commercial use NG