8GBメモリのMacBook Neoでもド派手にグリグリ動く。Claude Codeで自作ビジュアライザーに3D Gaussian Splatを組み込んだら目まぐるしくも不思議なミュージックビデオができた(CloseBox)

テクノロジー AI
松尾公也

テクノエッジ編集部 シニアエディター / コミュニティストラテジスト @mazzo

特集

以前、M2 MacBook Air 24GB 上の独自エージェンティックAI「mazzaim2air 」に「歌詞同期ビジュアライザー」を生やした話を書きました。今回はその発展形です。


前回のビジュアライザーは、WAV/MP3 を読み込んで、WebAudio の AnalyserNode で帯域を分けて、歌詞の内容からシーンを自動分類して、2D Canvas 上でパーティクルと波形を踊らせる、というやつ。

あれはあれで気に入っていて、Xに上げた動画もそれなりに反応があったのですが、もっと面白い、見たことのないようなものにしたいという欲求がありました。波形だけって、よくあるパターンじゃないですか。

そのきっかけを作ってくれたのが、清水亮さんです。4月15日のデイリーAIニュースで「これすごいよ」と紹介していたのがSparkJSという技術。紹介してから数時間後には本人がsparkjs-skillSparkJS という API リファレンスを Claude Code 用スキル形式に整理したものを公開してくれていて、実例として示されていた使い方が面白かったので、「じゃあ自分のビジュアライザーで試してみよう」となったわけです。

Gaussian Splat とポイントクラウドの話

本題に入る前に、Gaussian Splatting という技術が何なのかを、雑に説明しておきます(Claudeが)。

昔から 3D の世界を表現する方法として「ポイントクラウド」というものがあります。点群、です。空間の中に無数の点を打って、それぞれに座標と色を持たせる。LiDARで部屋をスキャンするとポイントクラウドが出てくる、というのはもうおなじみの光景です。ただし、点は点なので、隙間から向こう側が透けて見えたり、近づくとスカスカだったりします。

これを「点ではなく、ちっちゃな楕円体(ガウシアン)で置いてしまえ」としたのが 3D Gaussian Splatting です。2023年の SIGGRAPH で鮮烈に登場して以来、3D再構成の世界を一気に塗り替えた手法で、要するに、

  • 空間の中に何百万個の「ぼやけた楕円の絵の具スタンプ」を配置する

  • それぞれに位置・向き・大きさ・色・透明度・視点による色変化(球面調和関数)を持たせる

  • それをリアルタイムに GPU で重ね合わせて描く

という乱暴だけど驚くほどフォトリアルな手法です。NeRF みたいに推論にGPUをゴリゴリ使う必要もなく、データさえあればiPhoneやAndroidのブラウザでもヌルヌル動く。最近 iPhone でスキャンした部屋が3D空間として AirDrop できたりするのはこの系統の技術のおかげです。

ポイントクラウドが「点描」だとしたら、Gaussian Splat は「水彩画」です。点が輪郭を持たずにふわっと広がるから、間が自然に補間される。

SparkJS とは何か

で、その 3D Gaussian Splat を「普通のウェブ開発者がブラウザで扱えるようにする」のが SparkJS です。World Labs が作っているオープンソースライブラリで、Three.js の上に乗って動きます。

なぜこれが重要なのか。Gaussian Splat は論文と研究コードの段階ではすごく性能が出るけど、実用アプリケーションに組み込もうとすると、CUDA必須だったり、独自ビューワが必要だったり、ファイル形式が乱立していたり、とにかく「研究者のおもちゃ」止まりになりやすかった。

SparkJS はそこを一撃で解決しています。

  • .ply, .spz, .splat, .ksplat, .sog, .rad と主要フォーマットを全部サポート

  • Three.js のシーングラフにそのまま SplatMesh として追加できる

  • 手続き的に splat を生成したり(PackedSplats)、SDF(符号付き距離関数)で splat を削ったり照らしたりできる

  • Level-of-Detail ストリーミング、VR、Dyno という独自シェーダーグラフシステム

要するに「Three.js を書ける人なら誰でも Gaussian Splat をいじれる世界」を提供しているわけで、2010年代の WebGL がやったことに近いもの。研究室から街角へ、という橋渡しです。

そして、このライブラリを使うとき Claude Code に毎回ドキュメント URL を食わせるのが面倒。それを解決するためにと清水さんが作ってくれたのが sparkjs-skill です。~/.claude/skills/sparkjs/ に置いておけば、Claude Code が SparkJS の話題を察知したときに勝手に API リファレンスを参照してくれる。これがないと、途中でAPI迷子になってしまいます(今回も経験済み)。

既存ビジュアライザーに 3D モードを生やす

さて本題。Claude Code を起動して、sparkjs-skill を入れて、こう頼みました。

「既存の2Dビジュアライザーと共存する形で、SparkJS を使った3Dビジュアライザーを足したい」

最初のラウンドで出てきた設計は筋が良かった。要点は以下:

  • 音声グラフを 2D と 3D で共有する(createMediaElementSource は <audio> 1個につき1回しか呼べないので)

  • window.mazzaiViz というフックを 2D 側に生やして、analyser や現在の歌詞インデックスを 3D 側に渡す

  • ツールバーに「3D」ボタンを追加、押すとキャンバスが切り替わる

  • 3D 側は PackedSplats で手続き的にスプラット雲を作り、音量で回転・スケール・色を動かす

ここまでは良かった。動かしてみると、たしかに 3D 雲が回る。「おお、Gaussian Splat が自分の曲に合わせて踊ってる!」と一瞬テンションが上がって、次の瞬間、冷静になります。

地味すぎる。

地味問題を潰していく

Bass 値で 1.0~1.25 倍にスケール、rotation.y = t * 0.12、という穏当な設定にしていたのが敗因でした。落ち着いた知的な動きを目指してはいけない、ビジュアライザーはパーティーです。

Claude Code に「もっと効かせて」と言ったら、こうなりました:

  • 帯域値をスムージングして envelope 化、ガタつきを除去

  • ベースで カメラ半径が縮む(曲の低音で雲に突っ込む感じ)

  • ボーカルで FOV がパンプ

  • 雲の scale が X/Y/Z で異方性になり、ベースで横に広がり縦に潰れる

  • 回転が3軸になり、それぞれ違う周波数で揺れる

これで一気に「酔うレベル」になりました。でもまだ何か足りない。

最初、Claude Code は textSplats という SparkJS の便利関数で歌詞を 3D 空間に浮かせていたのですが、日本語フォントが解決できずに try/catch で握りつぶされていました。要するに何も出ていなかった。

これは DOM オーバーレイに逃がす判断を下しました。後述する動画書き出しのときに問題になるのですが、とりあえず DOM で表示。

楽器ごとに独立した色が欲しい

ここで一歩踏み込むアイデアが出ました。「スプラット雲を1つではなく、帯域ごとに3つ用意したらどうだ」と。

  • Bass用クラウド:内側の扁平ディスク、温色(赤~橙)

  • Vocal用クラウド:中間の球殻、緑~シアン

  • Treble用クラウド:外殻のキラキラ、マゼンタ~紫

それぞれが独立に recolor と scale を持ち、対応する帯域で光って膨らむ。これが効きました。画面の中に「音が見えてる」感覚が強烈に出るんです。ベースドラムがドンと来たら内側の赤い円盤がフラッシュ、ボーカルの伸びる音で緑のシェルが広がり、ハイハットのシャリシャリで外殻の紫が震える。

これはもう、音楽を3Dで観ているのと言って差し支えない状態でした。

でも色が固定だと寂しい

ここまで作って曲を最後まで通したら、色構成に飽きてきます。「赤・緑・紫」の役割分担は強いけど、それが全曲通して同じだと単調。

というわけで次の指示。「色割り当ては固定せず、曲の進行で変化させたい」

これは気持ちの良い実装になりました。

  • hueBase というグローバルな基準色相を毎フレームじわっとドリフトさせる

  • 3バンドはそれぞれ hueBase + 固定オフセット(0 / 0.42 / 0.78) で HSL を計算

  • 歌詞行が切り替わる瞬間に hueTarget を 0.11~0.18 ジャンプさせて hueBase を追従させる

これで、曲の進行(歌詞の進行)とともにパレット全体が色相環を回っていく。しかも Bass/Vocal/Treble の相対的な色差は保たれるので、楽器ごとの役割分担は見失わない。静的な色ではなく、「カメラを固定した時計の針が回るように色が動く」感覚です。

3Dモードで動画書き出しが効かない

2D版は MediaRecorder で canvas の captureStream を録っていました。3D 切り替え時もボタンは残っているのに、動画化すると 2D の黒キャンバスが映っている。そりゃそうです、3D キャンバスは別の <canvas> だから。

修正は2点。

  1. visualizer3d.js に beginExportResize(w,h) を追加:WebGLRenderer を 1280×720 に固定し、カメラの aspect も同期させる。復帰関数を返しておいて終了時に自動復元

  2. visualizer.js の exportToMp4 が #viz-stage.mode-3d を検出したら 3D キャンバス側を録る

それと、DOMオーバーレイで出している歌詞は canvas.captureStream で拾えない という罠がここで顕在化します。動画に歌詞が映らない。

なので歌詞表示を THREE.Sprite + CanvasTexture に移しました。canvas 2D API でテキストを描いてテクスチャにして、Three.js のスプライトとして 3D シーンに置く。これなら Gaussian Splat と一緒に録画されます。

結果:3Dモードのまま再生→「MP4で書き出し」を押すと、歌詞付きの3D Gaussian Splat ビジュアライザー動画が降ってくる

動作確認のためにサンプル曲「雨のように消えていく」を再生して書き出したら、なんとも言えない良さになりました。赤い核、緑のシェル、紫のキラキラが、歌詞の進行に合わせて色を変えながら回転している。Gaussian Splat のふわっとした輪郭が、2Dのパーティクルにはない湿度を画面に与えている。

実装ログから見えたこと

全部で 500~600 行の追加で、このレベルの 3D ビジュアライザーが組めてしまう。これは SparkJS が良くできているからでもあるし、Claude Code に「意図」を伝えれば勝手に API を引いてきてくれるからでもあります。

なお、今回の実験環境は M2 MacBook Air の 24GB モデル。「24GBあるならそりゃ快適でしょ」という話ではなく、ブラウザ上で Three.js と SparkJS を走らせる分には 8GB でもたぶん変わらないよ、というのが本音ではあります。ただ、Claude Code が並行してモデルを走らせたり、複数ファイルのコンテキストを抱えたりする作業では、やはり余裕があるに越したことはなかった。

そして今回、sparkjs-skill を使った効果は明確でした。スキルなしだと textSplats のシグネチャを毎回確認させられるし、SplatMesh の recolor と opacity の関係も忘れがちです。スキルを刺しておくと、会話の最初の数ターンで「SparkJS 文脈の頭」に切り替わっていて、レビューやリファクタの精度が明らかに違う。

これは、LLM時代の「ライブラリのエコシステム」の新しい形だと思います。ドキュメントは人間が読むためのもの、スキルは LLM が読むためのもの、ソースは機械が実行するためのもの、という三層構造。SparkJS のような「橋渡し型ライブラリ」にこそ、対応するスキルの存在が効いてくる。清水さんがデイリーAIニュースで紹介してその日のうちにスキルまで公開してくれたおかげで、こういう実験がすぐできてしまう。ありがたい話です。

雨の中の涙のように

前回作った2Dビジュアライザーに込めた感傷 — 雨の中の涙のように消えていく表現の道具たちへの追悼 — は、実は 3D化した瞬間、少し違うものに変わりました。

2Dの平面で流れていく歌詞と波形には「記録」のニュアンスがあったのですが、3D Gaussian Splat の中に文字が浮かび、楽器ごとの色の雲が空間を回る様子は、「記録」というより「再生」に近い。消えたものを悼むというより、消えたものが別の次元で踊り続けている、という感じ。

ここではステム分離を行わず、周波数区分だけで3パートに分けていますが、MacBook Neo版ビジュアライザーではちゃんと4パートのステムにしているので、そちらに実装したらもっと面白くなるかもしれません。ボーカルのピッチ追随もやってるし。

Gaussian Splat が持つ「点ではなく、にじみのある存在」という質感が、たまたまこのモチーフに合っていたのかもしれません。技術というのはしばしば、作る前に想像していた意味の外側に連れていってくれるものです。

自宅に戻って、今度はMacBook Neoにこのプログラムを組み込んでみました。MacBook Airで仕様書を作って、それをNeoのmazzaineoで読み込ませ再実装。同じような間違いを踏みながらも動作まで漕ぎ着けました。こちらの方が、もともとステム分離やLRC歌詞アライン機能を実装しているので、より高度なことができるんですよね。

8GBというメモリだけが不安材料でしたが、何の問題もなく動作しました。

そして、もっとド派手なものにしたかったので、Claude Codeに追加でいろいろと注文。テキストの重なり、色や形状の変化などを改善しました。

Claude Codeによれば、実装したのはこんなものらしい。

ビジュアライザー 3D (SparkJS / 新機能)

基盤

- Three.js 0.180 + SparkJS 2.0 のimportmap統合

- visualizer3d.js ES module — window.mazzaiViz3D API公開

- window.mazzaiViz 共有インターフェース(2D→3D音声解析ブリッジ)

- 3Dトグルボタン、mode-3d CSS切替、録画時の3Dキャンバスソース対応

3帯域 Gaussian Splatクラウド

- 各帯域を3サブクラウドに分割、6種の分布形状(sphere/disk/torus/filament/spiral/nebula)

- サブクラウド間の位相オフセットで合成形状が常に変化

- 非一様スケールウォブル(X/Y/Z独立に異なる周期で伸縮)

SDF彫刻

- 各帯域に3つのSplatEdit SDFオービター(sphere/ellipsoid/box)

- クラウド内を周回し穴・えぐれ・裂け目をリアルタイム生成

- carver半径がオーディオで脈動

派手なカラーリング

- HDRオーバードライブ(recolor値>1.0で白熱発光)

- ACES Filmicトーンマッピング + 動的露出(ビートでフレア)

- 全帯域彩度MAX(hueSat: 1.0)

- 色変化3倍速(driftPerFrame/lyricJumpBase増)

- トレブルシマー強化(18Hz/振幅0.4)

- ドラムonset全体フレア(1.8倍)

- バースト600粒、HDR 2.5倍フラッシュ、パレットhue連動

- スプラット個体のhue/sat/lightランダム変動

歌詞スプライト

- CanvasTexture → THREE.Sprite(DOM不使用 → MP4書き出し対応)

- renderOrder: 9999で常にスプラット手前

- 行ごと色分け(8色hue巡回)、色付きグロー + 白い芯

- ±5°傾き + 時間揺れ

- 横断・Y揺れ・呼吸スケール・三角エンベロープフェード

全パラメータ リアルタイム調整可能

- mazzaiViz3D.params.* でDevToolsから即時変更

このSparkJS、清水さんはシューティングゲームの面白いのを作ってますが、まだまだアイデア次第でできそうです。

※この記事は、Claude Codeでの記録、Claudeでのリライトを経て、筆者による加筆・修正を行なっています。


《松尾公也》

松尾公也

テクノエッジ編集部 シニアエディター / コミュニティストラテジスト @mazzo

特集

BECOME A MEMBER

『テクノエッジ アルファ』会員募集中

最新テック・ガジェット情報コミュニティ『テクノエッジ アルファ』を開設しました。会員専用Discrodサーバ参加権やイベント招待、会員限定コンテンツなど特典多数です。