[Noise 入門 #57] Audio-Reactive Domain Warping — 音で歪む空間
はじめに
今回は、#56で取得した音声データ(u_audio)を用いて、単にノイズを動かすのではなく、「空間のねじれ(Domain Warping)」そのものを音圧でコントロールし、サイケデリックで破壊的なVFXへと昇華させます。
昨日組んだベースに対して、以下の要素を足す(または調整する)ことで、表現の次元が一段階上がります。
前回の記事:
[Noise 入門 #56] Audio Analysis & FFT — 音響解析の基礎とノイズへの接続
Noise 入門シリーズ第56回。ここから音とノイズが融合する第6集へ突入。ブラウザ上で音を「数式」に変換するFFTの仕組みと、解析したデータをThree.js経由でGLSLに渡し、Audio-Reactiveな世界を作るための土台を作ります。
humanxai.infoYoutube:
Where Frequency Becomes Elevation | Audio-Reactive 3D Terrain (Three.js & GLSL)
In this installment of the Noise Intro series (#57), we explore the convergence of sound and geometry.Originally intended to cover 2D Audio-Reactive Domain...
youtube.com1. 音の「帯域」で役割を分ける(低音 vs 高音)
前回の #56 では、全体の音量(Volume)を単一の u_audio という Uniform 変数として Shader に渡す基礎を構築しました。しかし、単一のパラメータでノイズ全体を動かしてしまうと、画面全体がただ「大きくなったり小さくなったり」するだけの、単調でチープなオーディオビジュアライザーになってしまいます。
音と視覚を高いレベルで同期(シンクロ)させるための極意は、「音の帯域(周波数)ごとに、Shader 内での役割(パラメータ)を明確に分割する」ことです。
なぜ帯域を分割するのか?
楽曲には「キック(低音)」「ボーカル(中音)」「ハイハット(高音)」など、全く異なるリズムと波形が混在しています。これを FFT(高速フーリエ変換)データから切り出し、それぞれ別の Uniform 変数として渡すことで、Shader 内の数式に「グルーヴ」を生み出すことができます。
今回は最も視覚効果の高い「低音(Bass)」と「高音(Treble)」の2極に絞り、Domain Warping の数式にどう組み込むかを解剖します。
低音(Bass):空間の「うねり」を支配する
低音(キックドラムやベースライン)は、楽曲の骨格であり「エネルギー」そのものです。この重くて強いエネルギーは、ノイズのディテール(細かさ)ではなく、空間の根本的な歪み(Domain Warping の Amplitude)に割り当てます。
Domain Warping の基本概念は、座標 に対してノイズでオフセットをかける という構造でした。このオフセットの強烈さに Bass を掛け合わせます。
// Bass成分(0.0 ~ 1.0)
uniform float u_bass;
// ベースとなる座標
vec2 p = uv * 3.0;
// Bassの音圧で、空間のねじれ具合(Amplitude)をブーストする
// 音が鳴っていない時は 1.0 倍、キックが鳴ると最大 4.0 倍に歪む
float warpAmplitude = 1.0 + (u_bass * 3.0);
// オフセット用のベクトル(q)を計算し、Amplitudeを掛ける
vec2 q = vec2(
fbm( p + vec2(0.0, 0.0) ),
fbm( p + vec2(5.2, 1.3) )
) * warpAmplitude;
【視覚効果】 ドン!とキックが鳴るたびに、画面全体の空間がグニャリと大きくねじ曲がり、重力場が歪んだようなダイナミックな衝撃波が生まれます。
高音(Treble):フラクタルの「亀裂」を刻む
一方、高音(ハイハットのチキチキ音やシンセサイザーの高音域)は、非常にテンポが速く、鋭い波形を持っています。これを空間全体の大きな歪みに割り当てると、画面が細かくブレてしまい、見ていると酔ってしまいます。
高音は、FBM(Fractal Brownian Motion)内部の「細かさ・鋭さ(Lacunarity や Frequency)」、あるいはノイズ関数の高いオクターブ層のブレンド率に割り当てます。
// Treble成分(0.0 ~ 1.0)
uniform float u_treble;
// Trebleの音圧で、ノイズの内部周波数(細かさ)をブーストする
// チキチキ鳴るたびに、ノイズのディテールが「鋭く」なる
float detailFrequency = 1.0 + (u_treble * 5.0);
// 先ほど計算した Bass による大きな歪み(q)に、Treble の細かい震えを足し込む
vec2 r = vec2(
fbm( p + q + vec2(1.7, 9.2) * detailFrequency ),
fbm( p + q + vec2(8.3, 2.8) * detailFrequency )
);
// 最終的なノイズの出力
float n = fbm( p + r );
【視覚効果】 ベース音で空間が大きくうねりながら、ハイハットが鳴る瞬間にだけ、ノイズの表面に「バチバチッ」とした微細なフラクタル状の亀裂や、稲妻のようなディテールが走ります。
帯域分割がもたらす「魔法」
このように「Bass = 低周波(大きな構造)」「Treble = 高周波(微細なディテール)」というように、音の物理学とノイズの数学(周波数)を一致させることで、単なるランダムな動きではなく、「音がその形を作っている」という圧倒的な説得力が生まれます。
2. 「残響(減衰)」を作る(Easing / Lerp)
FFTで取得した生の音声データ(振幅)をそのままShaderのUniform変数に流し込むと、視覚表現に致命的な問題が発生します。それは「動きがカクカクして、デジタル特有の安っぽいフリッカー(明滅)になってしまう」という問題です。
なぜ動きがチープになるのか?
デジタルの音声解析は非常に正確です。例えばキックドラムが鳴った瞬間、値は 1.0 に跳ね上がりますが、その数フレーム後にはすぐに 0.1 や 0.0 に急降下します。 これをDomain Warpingの歪み強度に直結させると、空間が一瞬だけ歪んで瞬時に元の形に戻るため、まるでストロボのフラッシュを浴びているような「痛い」映像になり、物質としての「重さ」や「余韻」が全く感じられなくなります。
これを解決するのが、「値の減衰(Lerp / Easing)」です。
JavaScript側でのアプローチ(Lerpによる残響の付与)
最もコントロールしやすいのは、Three.jsの requestAnimationFrame(毎フレームの更新処理)の中で、生のFFTデータを「目標値」とし、現在の値をそこへゆっくり近づける(線形補間する)方法です。
// JS側の変数を準備
let currentBass = 0.0;
const easeAmount = 0.1; // 追従スピード(0.0 ~ 1.0。小さいほど残響が長い)
function animate() {
// 1. FFTから生の低音データを取得(例: 0.0 ~ 1.0)
const targetBass = getRawBassFromFFT();
// 2. 現在の値を目標値に向かって滑らかに近づける(Lerp)
currentBass += (targetBass - currentBass) * easeAmount;
// 3. 減衰した滑らかな値をShaderに渡す
material.uniforms.u_bass.value = currentBass;
requestAnimationFrame(animate);
}
このたった1行の currentBass += … が入るだけで、魔法がかかります。 キックが鳴った瞬間に値は急上昇しますが、音が消えた後も currentBass はすぐにはゼロにならず、ゆっくりと減衰していきます。これにより、空間が「ドン!」と歪んだあと、スライムやゴムのように「ぶるんと震えながらゆっくり元の形に戻る」という、極めて物理的で生物的な質量感を獲得します。
Shader側でのアプローチ(Smoothstep / Pow によるカーブの成形)
JS側で滑らかにした値をShader内で受け取った後、さらにGLSLの数学関数を使って「反応のカーブ」を整えると、よりプロフェッショナルな仕上がりになります。
uniform float u_bass; // JSから渡された滑らかな値
void main() {
// 1. ノイズや環境音による微小な反応をカットする(しきい値処理)
// u_bass が 0.2 以下の時は 0.0 にし、0.2 ~ 0.8 の間を滑らかに補間
float activeBass = smoothstep(0.2, 0.8, u_bass);
// 2. 反応をより「鋭利」にする
// pow関数でカーブを曲げることで、小さな音には鈍感に、大きな音には爆発的に反応させる
float punchyBass = pow(activeBass, 2.0);
// この punchyBass を使って空間を歪ませる
float warpStrength = 1.0 + (punchyBass * 5.0);
// ...以降、ノイズ計算へ
}
物理法則を偽装する
音(波)が物質にぶつかった時、物質は一瞬で変形しますが、元に戻るには「慣性」と「摩擦」によって時間がかかります。 生の音声データをそのまま使うのは「情報」を描画しているだけであり、LerpやSmoothstepを用いて残響をデザインすることは「物理法則」を描画していることと同義です。
これが、オーディオ・リアクティブなノイズ表現を「単なるビジュアライザー」から「生きた現象(Procedural Art)」へと昇華させる重要な分水嶺となります。
3. ドメインワーピングの多重化(Recursive Warping × Audio)
Domain Warping の真骨頂は、一度空間を歪めるだけでなく、「歪んだ空間を、さらに別のノイズで歪める」という再帰的(Recursive)な構造にあります。数式で表すと noise( p + noise(p + noise(p)) ) のように、マトリョーシカのように関数を入れ子にしていく手法です。
この多重構造の「それぞれの階層」に対して、異なる音の帯域(Bass / Treble)を注入することで、単なる波形ビジュアライザーを超えた、立体的でサイケデリックな表現を生み出します。
先ほど提示した GLSL コードの各ステップが、視覚的にどのような役割を果たしているのかを解剖していきましょう。
実装コードの解剖と視覚的意味
Step 1 & 2: 基礎のうねりと Bass による「破壊」
// 1. ベースとなる動き(時間)
vec2 p = uv * 3.0;
// 2. 音圧(Bass)による空間の「オフセット(ねじれ)」
vec2 q = vec2(
fbm( p + vec2(0.0, 0.0) + u_time * 0.1 ),
fbm( p + vec2(5.2, 1.3) - u_time * 0.1 )
);
float warpStrength = 1.0 + (u_audioBass * 3.0);
ここではまず、時間(u_time)によってゆっくりと漂うベースのベクトル場 q を計算します。 重要なのは warpStrength です。キックドラムが鳴り u_audioBass が跳ね上がると、この係数が 1.0 から 4.0 近くまで膨張します。これは、「普段は穏やかに波打っている空間が、重低音の衝撃波によって力強く引き裂かれる」状態を数式で定義しています。
Step 3: 歪んだ空間に Treble で「亀裂」を入れる(ここが多重化)
// 3. 高音(Treble)による「細かな亀裂」
vec2 r = vec2(
fbm( p + q * warpStrength + vec2(1.7, 9.2) + (u_audioTreble * 2.0) ),
fbm( p + q * warpStrength + vec2(8.3, 2.8) + (u_audioTreble * 2.0) )
);
ここが魔法の核心です。次の階層のノイズ r を計算する際、元の座標 p だけでなく、先ほど Bass で大きく引き裂いた q * warpStrength を足し込んでいます。つまり、「すでに大きく歪んでいる地盤の上で、さらにノイズを計算する」ことになります。
さらに、その計算座標に対して u_audioTreble * 2.0 をオフセットとして足しています。これにより、ハイハットのような鋭い高音が鳴るたびに、大きくうねる空間の表面に「チカチカッ」とした微小なフラクタル状の亀裂(ノイズの位相ズレ)が走ります。
Step 4 & 5: 色彩と音圧の同期
// 4. 最終的なノイズ値
float n = fbm( p + r );
// 5. 色付け
vec3 colorBase = vec3(0.1, 0.3, 0.6); // 青系
vec3 colorHighlight = vec3(0.9, 0.2, 0.1); // 赤系
vec3 col = mix(colorBase, colorHighlight, n * u_audioBass);
最後に、最終的なノイズ値 n を用いて色を塗りますが、ここでも u_audioBass を掛け合わせています。 n だけで色を混ぜると、単に「地形の高いところが赤、低いところが青」になるだけです。しかし n * u_audioBass とすることで、「普段は全体が青っぽく沈んでいるが、重低音が鳴った瞬間にだけ、ノイズの稜線(エッジ)が赤熱して発光する」というドラマチックなライティング効果を得ることができます。
数式が「生きた現象」に変わる瞬間
この多重ワーピングのアプローチは、音を単なる「大きさのパラメータ」として扱うのではなく、「次元の異なる物理的な力(大きなうねり・表面の振動・発光エネルギー)」に分解してShaderに流し込む作業です。
コードをコンパイルし、お気に入りのトラックを流してみてください。 キックの重圧で世界が歪み、ハイハットの刻みで空間が震え、ベースラインの唸りに合わせて極彩色の炎が立ち上るはずです。数学と音が完全にリンクした、あなただけの「Audio-Reactive Universe」の完成です。
3Dへの進化:周波数が標高に変わる場所 (Where Frequency Becomes Elevation)
当初は2D平面上のドメインワーピングを解説する予定でしたが、実装の過程で「音のエネルギー」をよりダイナミックに視覚化するため、3D地形(Vertex Displacement)へと昇華させました。数式による「空間のねじれ」が、音楽という命を吹き込まれることで、激しく隆起するデジタル山脈へと姿を変えています。
主なアップデートポイント
- Vertex Shaderによる頂点隆起: FFTデータの低音域(Bass)を直接頂点のZ軸座標に変換し、音圧に合わせて山脈が脈打つ立体表現を実装しました。
- 空間のねじ切り (Vortex Warp): 回転行列(Matrix)を導入し、キックドラムの衝撃に合わせて空間全体をブラックホールのようにねじ曲げる処理を追加しました。
- RGB色収差 (Chromatic Aberration): 音のピークに合わせてRGBのサンプリング位置をズラすことで、レンズが歪むようなグリッチ・エフェクトを付与しています。
Where Frequency Becomes Elevation | Audio-Reactive 3D Terrain (Three.js & GLSL)
In this installment of the Noise Intro series (#57), we explore the convergence of sound and geometry.Originally intended to cover 2D Audio-Reactive Domain...
youtube.com
COMM_LOG: noise-intro-57-audio-reactive-domain-warping