「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を組み合わせるテストを始めました。
VJやってみたいから、まずはVJアプリでも作ってみるか...。Web Audio APIとWeb MIDI APIとWebGLを使えばブラウザでいけるかな。
— aadebdeb (@aa_debdeb) September 7, 2019
Web Audio APIでマイクから音声を取得してWebGLでビジュアライズする方法について書いた。https://t.co/jzog3TRCif
— aadebdeb (@aa_debdeb) September 18, 2019
このツイートを見てかはわからないですが、9月の終わりごろにNEORTの@r2inomiさんから声をかけていただきました。今回のInterim Reportはいろいろな団体からのキュレーションという形式になっており、そのうちのNEORT枠ということらしいです。
今回は音に合わせて歪みながらカラフルな色を出す球をメインに、球の数を増やしたり、いろいろなグリッチを掛けてバリエーションを出していく感じのVJをしました。
絵の部分はWebGL、マイクからの音の取得とFFT解析はWeb Audio API、MIDIコントローラーからの入力はWeb MIDI APIを使い、ブラウザ上で動かしています。
ソースコードはGitHubに置いてあります。
https://github.com/aadebdeb/InterimReport4_VJ2
この記事では、今回使用した技術のうちオーディオリアクティブな球および引き延ばしグリッチと色を変換するグリッチについて解説します。
音により歪む球はレイマーチングで出しています。
以下はレイマーチングで使用した距離関数です。音量を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
関数は中心からの距離を引数に取るので歪み具合により出てくる色が変わります。これにより、音で球が内側に歪むとカラフルな色をでるようになっています。
ただ単に位置をずらすだけでなく、絵を引き延ばす感じのグリッチを掛けています。
フラグメントシェーダーは以下のようになっており、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
色を変換するグリッチです。適当に作成した四角形の範囲内であれば元の色を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の感想です。
若干ネガティブな感じになりましたが、初VJ楽しかったです。結局は場数を踏まないと思うようにはいかないのかなと感じました。
今後です。
You can support this creator by paying money.
Commercial use NG