CSS3DとWebGLを束ねる midori243 の多層HUD調律記
最初のテストでは HTMLを3D空間に配置する技術のボタンが 3D描画のための技術のドラッグを全部奪った。一度つまずいた。
マウス操作を受け付けるかどうかを制御するCSSの設定をむやみに切ると HUD がただの飾りになる。これは課題。
50歳の目でコードを追い、三層の HUD を同時に扱えるように調律した記録です。
前提として押さえたこと
- Three.js 0.170.0: 3D描画レイヤー、Canvasの内容を3D空間に貼り付けるためのテクスチャ、カメラを回転・ズームできるコントロールを制御。
- HTMLを3D空間に配置する描画エンジン: HTML を 3D 空間へ浮かせるサブシーン。マウス操作の切り替えが肝。
- メインスレッドを塞がずに描画できる技術: 1920×1080 の SVG を描き直して 時計/カレンダー/動画 を 1枚のテクスチャに統合。
- マウス位置から3D空間のオブジェクトを検出する仕組み → SVG: テクスチャ座標を 1920×1080 に再換算し、SVG 内のボタン座標と突き合わせる方式。
完成した多層HUD
トップバーは 5 秒で縮み、インタラクション制御を作成する関数で HTMLを3D空間に配置する技術 の操作をオンオフできる。
時計表示 と カレンダー表示 は メインスレッドを塞がずに描画できる技術 を共有しながら 60fps で更新する。
最初に確認すべきは マウス位置から3D空間のオブジェクトを検出する仕組み のヒット座標。ここがズレると全てが空振りになる。
カレンダー表示にインタラクションを追加する関数は 1920×1080 を基準に テクスチャ座標 を掛け戻し、事前に採寸したヒットボックスへ流し込む。
テーマを切り替えるたびに SVG を再生成し、ボタンの矩形を再採寸する理由がやっと腑に落ちた。
Raycaster を全画面で試す
テーマ採寸を自動化した話
カレンダー表示用のクラスは テーマ 配列に 3 パターンを仕込み、切り替え後に インタラクティブ要素を更新する関数 を必ず呼ぶ。
DOM から button の矩形を拾い直し、持ち出し先が無ければデフォルト値 (x=1110, y=588) でフォールバックする周到さがある。
採寸し直すたびに メインスレッドを塞がずに描画できる技術 を描き直し、Canvasの内容を3D空間に貼り付けるためのテクスチャの更新フラグを立てる仕組みも同じだ。
テーマを 3 回回すと インタラクティブ要素 が毎回新しい矩形を保持する。
おかげで マウス位置から3D空間のオブジェクトを検出する仕組み 側は 1920×1080 の テクスチャ座標 を流すだけで済み、余計な補正を抱えずに済む。
ClockDisplay の切替を体感
ボタン2のクリック処理関数は 表示モード を デジタル / アナログ でトグルし、次回の 更新関数 で SVG を差し替える。
メインスレッドを塞がずに描画できる技術 を毎フレーム描き直すので 60fps でも案外滑らか。意外だ。
ボタン1のクリック処理は3Dオブジェクトの縮尺を 0.8 倍にして 200ms で戻す演出付き。
再生/一時停止 に頼らない HUD の応答性が目標になっている。
VideoDisplay の 60fps 制御
動画表示用のクラスは 更新頻度60fps を既定値にし、現在時刻を取得する関数と 前回の更新時刻 で差分を算出。
描画 60fps で CPU を喰うので 更新頻度を30fpsに設定する関数 に落とすテストをしてみた。
fps を 30 にすると 更新間隔 が 33.3ms(約2倍)まで伸び、Canvasの内容を3D空間に貼り付けるためのテクスチャの更新フラグの頻度も半分になった。
ただし 01.mp4 の動きが鈍くなるので 45fps あたりが妥協点だと感じた。
CSS3D と WebGL の衝突を解消
最初は HTMLを3D空間に配置する技術 のボタンが常に最前面で、カメラを回転・ズームできるコントロール が一切効かなかった。
インタラクション制御を作成する関数で HTMLを3D空間に配置する技術 のDOM を再帰的に走査し、マウス操作の設定 をまとめて切り替える仕様は救いだった。
ショートカットで HUD を触れるようにしたかったので、クリックで HTMLを3D空間に配置する技術 を有効化し、ドラッグしたくなったらすぐ OFF に戻せるようにした。
切り替えが 1 ステップで済むのは 50歳の手でもミスが減る。
初期化フローを Promise.all で束ねた理由
初期化関数は 3 本の非同期処理を 複数の非同期処理を同時に実行する仕組み で待ってから アニメーション開始関数 を呼ぶ。
デバイス検出 → 環境マップ → HUD 起動の順でログを出したくて、わざわざラップしている。
ステップ C (HUD 初期化) が失敗すると alert を出してページの再読み込みを促す。
実際に何度も救われたので、この挙動は残しておきたい。
デバイスごとの最適化プリセット
モバイルでは 視野角85度、画面の解像度の比率1.5倍、ズーム速度0.7倍。デスクトップは 視野角75度、画面の解像度の比率2倍、ズーム速度1.0倍。
ブラウザが送る端末情報を正規表現で判定して コントロール設定を適用する処理 を流し込んでいる。
モバイルで タッチ操作の設定 を差し込むのも忘れてはいけないポイント。
前作の midori244 で痛感したので、今回は最初から組み込んだ。
10 個の球体を踊らせる
飾りの球体は 10 個。位相 を 0.005〜0.01 増やし、半径 も 15〜25 をランダムに持たせた。
色相は インデックス/10 + 位相×0.1 を1で割った余り で循環させ、不透明度 も 0.5±0.3 の範囲で揺らしている。
派手さよりも 3D描画レイヤー が生きていることを視覚的に示す役目が大きい。
カメラを回転・ズームできるコントロールのズーム速度を3.0 でも破綻しない程度の速度で回すのがちょうどよかった。
使ってみて
実際の HUD 配置は旧サイトの 3D シーンで体験できます。
旧サイトの HUD を開く
- HTMLを3D空間に配置する技術 の マウス操作の設定 を切り替えられるので、3D描画のための技術 のカメラ操作と HUD 操作を両立できます。
- カレンダー表示 のテーマ切替は 3 パターン。毎回ヒットボックスを採寸し直すのでクリック漏れが起きにくいです。
- 動画表示 の更新頻度は 60fps が基本ですが、CPU が厳しいときは 45fps に落としても実用的でした。
同じラインで試した midori302 の環境マップ調整とも組み合わせやすい構成です。
コード抜粋: CalendarDisplay.attachInteraction
マウス位置から3D空間のオブジェクトを検出する仕組み が拾った テクスチャ座標 を 1920×1080 に換算し、採寸済みの インタラクティブ要素 に投げる部分を抜粋しました。
this._clickHandler = (event) => {
if (!this.camera || !this.domElement || !this.mesh) return;
const rect = this.domElement.getBoundingClientRect();
const mouse = new THREE.Vector2(
((event.clientX - rect.left) / rect.width) * 2 - 1,
-((event.clientY - rect.top) / rect.height) * 2 + 1
);
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, this.camera);
const intersects = raycaster.intersectObject(this.mesh);
if (intersects.length > 0) {
const uv = intersects[0].uv;
const xSVG = uv.x * this.width;
const ySVG = (1 - uv.y) * this.height;
this.interactiveElements.forEach((element) => {
if (element.contains(xSVG, ySVG)) {
element.callback();
}
});
}
};
数値 1920×1080 を固定しているので、OffscreenCanvas の解像度を変えるならここも忘れずに調整が必要です。
いろいろ試した結果
- 60fps → 45fps: 動画表示 の更新頻度を落としても HUD の遅延は最小限。CPU 使用率は約 18% 減。
- マウス操作の設定 切替: HTMLを3D空間に配置する技術 を無効化した状態だと カメラを回転・ズームできるコントロール のパンが快適。切り替えは 200 回連続でも破綻しなかった。
- 球体数: 15 個まで増やすと 01.mp4 再生時に 50fps まで落ちたため 10 個で打ち止めにした。
今回のまとめ
- マウス位置から3D空間のオブジェクトを検出する仕組み → SVG の変換は 1920×1080 基準で固定し、テーマ切替ごとにヒットボックスを再採寸した。
- 時計表示 / カレンダー表示 / 動画表示 を 複数の非同期処理を同時に実行する仕組み で同時初期化し、60fps の描画も維持できた。
- HTMLを3D空間に配置する技術 の マウス操作の設定 をトグルする仕組みを入れたことで、HUD の操作と カメラを回転・ズームできるコントロール の操作を両立できた。
さらに深く学ぶ
最後まで読んでくださり、ありがとうございました。