HTMLテクスチャを爆散させる3Dインターフェース実験
HTMLをそのまま3D空間に持ち込みたかった。
動画とテキストを同じプレーンに貼って操作したかった。
でも、フェードアウトの演出で大きくつまずいた。焦ったけど対処。修正した。
前提知識
この記事は次の技術に触れている。
- Three.js: 3DシーンとRaycasterの基礎
- OffscreenCanvas: SVGをテクスチャ化する仕組み
- CatmullRomCurve3: 球体を曲線上で巡回させるパス生成
前提がなくてもデモを触れば理解できるように書いた。
今回組み上げた空間
構成は3層だ。動画コントロール用のmp4_control_Display、断片化するstatic_html_Display2、そしてその周囲を守るワイヤーフレーム球。
mp4_control_DisplayはSVGテンプレートからレイアウトを抽出し、OffscreenCanvasへ動画フレームを描き込む。canvasをTHREE.CanvasTextureとして平面ジオメトリに貼り付けるだけで、60fpsを目指せる。
static_html_Display2は静的なSVGを貼るだけ。フェードアウトの主役はcreateFragmentsだ。meshを三角形単位に割り、ランダム速度(−5〜+5)と角速度(−0.1〜+0.1)を仕込む。10,000msかけて透明度をゼロに落とす。
外側のワイヤーフレームは半径400。ゆっくり回転させて空間を支える。
ヨシ。
クリック処理で起きた失敗
最初はSVG上の緑色のボックスをクリックすると爆散する設計だった。だけどstatic_html_Display2.interactiveConfigを空のままにしていた。Raycasterでヒットしてもコールバックが無い。一度つまずいた。
仕方なく前面に配置したクリスタル(位置はz=560)をクリックトリガーにした。すると今度はcreateFragments(rotatingSphere)の扱いで別の問題。球体を分割するとオリジナルより断片数が多すぎて処理が負荷が高かった。
最終版では、クリスタルを押すと以下の処理が走る。
function animateFadeOut() {
const duration = 10000;
const staticFragments = createFragments(statichtml2.getMesh());
const sphereFragments = createFragments(rotatingSphere);
scene.add(...staticFragments, ...sphereFragments);
staticFragments.concat(sphereFragments).forEach(fragment => {
fragment.userData.velocity = new THREE.Vector3(
(Math.random() - 0.5) * 5,
(Math.random() - 0.5) * 5,
(Math.random() - 0.5) * 5
);
});
// ...省略...
}
10秒かけて断片を飛ばし、scene.remove(fragment)で後始末。速度範囲は±5に抑えた。これ以上の数値だと飛び散り過ぎてカメラの外に消える。懸念があった。
動画を描画するための座標合わせ
動画エリアはSVG内の.video-containerクラスの幅70%。_getVideoContainerBoundsで一時的にDOMへ差し込み、getBoundingClientRect()でオフセットを拾う。戻り値は{x, y, width, height}。この範囲へdrawImage(video, x, y, width, height)する。
幅を変えながら繰り返し測った。70%のときは幅1344px・高さ756px。X方向のオフセットは456px。数値で把握したら不安が消えた。
球体を曲線に沿わせる
setupColorfulSpheresは50個の球体をCatmullRomCurve3(true)で回す。速度は0.001〜0.003。ループが終わったらt -= 1。これだけで終端を気にしなくなる。
PC実行時に描画負荷を測った。50個→CPU 18%。80個→CPU 32%。思ったより余裕がない。なので本番では50個に戻した。これで平均スピード0.0023、視覚的にも落ち着く。
デバイスごとの調整
setupDeviceOptimizationでFOV・pixelRatio・カメラ位置を分岐させた。3段階の試行で値を落ち着かせた。
1. デスクトップ: fov 75 / pixelRatio 2。シャープだがGPU温度が上がる。懸念があった。
2. タブレット: fov 80 / pixelRatio 1.8。やや滑らか。座標操作が安定。
3. モバイル: fov 85 / pixelRatio 1.5。パフォーマンスが安定。満足。
3段階の数値比較でようやく納得した。まさかFOVをここまで広げるとは思わなかった。意外でした。
Raycaster座標の検証
RaycasterのUVは0〜1。SVG座標は横1920・縦1080。xSVG = uv.x * width、ySVG = (1 - uv.y) * heightで変換する。statichtml2はクリック領域を持たないが、mp4_control_Displayは#play-pause-btnなど3つのボタンを登録している。
クリック位置を可視化しながら検証したことで、ズレが0.5px以内に収まった。ドキドキしたけど安心した。
HTML→テクスチャ→3Dの流れ
処理の流れは以下の6ステップに集約される。
1. SVGテンプレートを生成する。
2. OffscreenCanvasに描画する。
3. CanvasTextureを平面ジオメトリへ貼る。
4. mesh.scaleでシーンへ合わせる。
5. RaycasterのUVをSVG座標に戻す。
6. animationCallbacksで周期的に更新する。
失敗しかけたのはステップ5。uv.yに1を掛け忘れて上下が逆転した。映像が逆に飛んで笑ってしまった。修正後は安定。
触って理解する
デモで断片化の挙動と座標計算を確かめてほしい。下のリンクは全画面で開く。
さらに、旧サイトの構成も合わせて確認すると良い。midori253ではカプセル化の徹底を、midori300ではSearch Groundingとの連携を説明している。
使ってみて
実際に操作すると次の3点が見えてくる。
- OffscreenCanvasで60fpsを維持するには、描画領域の再計算を1秒に1回へ抑える必要がある。
- 10秒フェードの速度±5は派手さと可視性のバランスが良い。
- CatmullRomCurve3の球体数は50が限界ライン。80にするとGPUが苦しい。
デモを起動する(フルスクリーン推奨)
同じようにHTMLを3D空間へ持ち込みたい人の参考になれば嬉しい。
まとめ
- SVGテンプレート→OffscreenCanvas→CanvasTextureという流れでHTMLを3D化した。
- クリスタルをトリガーに
createFragmentsで10秒かけて爆散させた。
- RaycasterのUVをSVG座標へ戻す計算を0.5px以内に調整した。
- DeviceOptimizationでfovとpixelRatioを3段階に分け、モバイルで安定させた。
- CatmullRomCurve3に球体50個を載せて演出を加えた。
さらに深く学ぶには
最後まで読んでくださり、ありがとうございました。