はじめに
Three.js で VRChatのようなVR 空間を自由に作れるようになり、誰しもやりたくなるのはVR内にブラウザを表示してWEBを閲覧する事。
CSS3DRendererというレンダラーがあるのに気づき、その実装を試していたところ、ブラウザがハングアップ。
シンプルに iframe でWEBを表示しようとしただけなのですが、「GPU fallback」問題も含め思いのほかハマったので、AIに情報をまとめてもらったので記事にしておきます。
コードをいくら修正しても直らなかったので、原因特定まで物凄く時間を費やしました。(´・ω・)
CSS3DRenderer は何者なのか?
DOM を transform: matrix3d() で 3Dっぽく見せるだけの仕組み
CSS の transform: matrix3d() を使い、
HTML要素(div・imgなど)を Three.js のカメラ行列に合わせて変形しているだけ。
→ つまり ピクセル描画は一切していない。
GPU ではなく CPU(ブラウザのレイアウトエンジン)が処理する
CSS3DRenderer の 3D 風表示は、DOM のレイアウト計算に依存する。
- ブラウザの
"CSSレイアウトエンジン" - DOM の再フロー・再描画
- コンポジット処理
これらが毎フレーム発生するため、 WebGL よりも遥かに重い場合が多い。
WebGL の仲間だと思われているが、まったく違う
見た目は Three.js の “レンダラー” だが、役割は全然違う。
| 仕組み | 実態 | 性質 |
|---|---|---|
| WebGLRenderer | GPU描画 | 超高速 / 重い3D向け / VR向け |
| CSS3DRenderer | DOM描画 | 重い / UIやHTML用途 / 3D演出向け |
両者は別の描画パイプラインで動いているため、 同時使用すると処理が競合しやすい。
3D UI 向けではない。 “軽いHTMLを3Dに配置するだけ” の用途
CSS3DRenderer の想定用途は非常に限定されている。
- 3D空間にラベル(テキスト)を浮かせたい
- 簡単な HTML をボードのように置きたい
- 軽い UI を 3Dっぽく見せたい
本格的な3D UI
VR用UI
iframeを含む複雑UI
は完全に想定外。
DOMは WebGL と違って “毎フレーム更新” に弱いため、
重いUIを3Dに置くのは破綻する。
2. なぜネットのサンプルはしょぼいのか?
CSS3DRenderer のサンプルを検索すると、
どれも 「divが回ってるだけ」「画像を貼った平面が動いてるだけ」
という 物足りないケースが少なくない。
しかしこれは、作者が手を抜いているのでもなく、使い方を知らないわけでもない。
理由はもっと単純で、もっと根深い。
ちょっと複雑なUIを置くだけで一気に重くなる
CSS3DRenderer は DOM を毎フレーム 3D 変形するため、 以下のような UI は一瞬で破綻する:
- ナビゲーションバー
- スクロール領域
- フォーム
- カード大量配置
- モーダル
- SPA のコンポーネント
DOM のレイアウトエンジンは リアルタイム描画に向かない。
iframe を載せるとほぼ崩壊する
iframe は “ブラウザの中にブラウザを開く” 行為。
- 外部CSS
- 外部JS
- レイアウト計算
- 画像読み込み
- 広告
- セキュリティチェック(CSP/X-Frame)
- イベントハンドリング
これを CSS transform で毎フレーム動かす のは無理がある。
→ GPU フリーズ → fallback が起きた。
WebGLとCSS3DRendererの同時使用は難易度が高すぎる
WebGLRenderer(GPU)
CSS3DRenderer(CPU)
両者は まったく違う描画システムで動いており、
毎フレーム同期させるだけでも負荷が大きい。
だからサンプルも簡素になる。
ネットのサンプルが“しょぼい”理由は、技術の限界そのもの
つまり……
重いから“できない”だけであって、“やってない”わけじゃない。
誰も高度なUIや外部ページ表示をサンプルにしないのは、
やると破綻するのを知っているから。
これが CSS3DRenderer の“表と裏”。
3. iframe を CSS3DRenderer に乗せるとどうなるのか?
CSS3DRenderer の中に iframe を置くと、表面上は「ただの HTML 要素」。
しかし内部では “ブラウザの中でブラウザを起動する” という超重量級処理が走る。
その結果どうなるか。
外部ページを iframe で入れようとすると即ブロックされる
ほぼすべての大手サイトは
- CSP(Content Security Policy)
- X-Frame-Options: DENY / SAMEORIGIN
で、他ドメインからの埋め込みを禁止している。
Google
Yahoo
ニュースサイト
SNS
同一ドメインなら iframe は表示される
そこで “test.html” のような同一ドメインの軽いHTMLなら通る。
しかし……
めちゃくちゃ重くなる
iframe 内部は100% DOM 処理。 CSS3DRenderer は毎フレーム DOM に matrix3d を当てて再描画。
その二重構造が重すぎて、fps が急落する。
GPUがフリーズする(重要)
DOM と WebGL を同時に動かすことで処理負荷が跳ね上がり、 GPU が “ハング” 状態に入ることがある。
ブラウザは GPU が壊れたと判断すると……
ブラウザが自動でハードウェアアクセラレーションを OFF にする
Chrome は GPU が不安定になると、自動的に fallback を発動する。
この fallback モードでは:
- WebGL が CPU描画に切り替わる(致命的)
- GPU利用がすべて停止
- WebGL 全体が激重になる
- どのページを開いても重く感じる
あなたが遭遇した「突然 WebGL 全部が激重」はこれ。
WebGL が CPU fallback → すべて激重になる
CPU が WebGL を描画しようとすると:
- 60fps → 3fps などに急落
- VRはほぼ動かなくなる
- 過去に作ったサンプルまで異常に重くなる
- “WebGL が壊れた” という錯覚になる
実際には壊れていない。 GPUが停止していただけ。
原因に気づくまで、非常に苦労する
このシリーズの問題は
- エラーに具体的な原因が出ない
- CSS3DRenderer の情報が少なすぎる
- iframe 使用例がほぼ存在しない
- WebGL fallbackの挙動を説明した記事も少ない
そのため「なぜ重いのか?」が分かりにくい。
4. CSS3DRenderer が重い本質
CSS3DRenderer は「軽そうに見えて、実際は WebGL より重い」。 その理由は Three.js の実装ではなく、ブラウザの根本アーキテクチャの問題にある。
DOM のレイアウト計算が重い
CSS3DRenderer は Three.js のカメラ行列を DOM の transform に置き換えて実行している。
しかし DOM は以下すべてが走る:
- ボックスモデル計算
- レイアウト
- 再フロー
- 再描画
- コンポジット
- アクセシビリティツリー更新
これらはすべて CPU側の処理。 WebGL のように GPU が高速で処理してくれるわけではない。
transform の毎フレーム更新は “CSS が一番苦手な作業”
CSS の transform 自体は軽いと思われがちだが、 毎フレーム DOM ツリー全体を更新するのは地獄。
- DOM ノードが多いほど激重
- 親子構造が深いほど激重
- CSS の順序次第でレイアウトの再計算が増幅
結果として 60fps を維持するのはほぼ不可能。
iframe は “ブラウザの中でブラウザを起動する行為”
iframe の内部は完全に別プロセスの描画パイプライン。
- HTMLパース
- CSS計算
- レイアウト
- 内部スクリプトの実行
- 外部リソースの読み込み
- セキュリティチェック(CSP, X-Frame)
これ全部を CSS3DRenderer が transform で毎フレーム動かす。
重くならない理由がない。
WebGL(GPU)+ CSS3D(CPU)= 競合と破綻のセット
WebGLRenderer → GPUで描画 CSS3DRenderer → CPUでDOM処理
この2つを同時にリアルタイムで動かすと:
- CPU と GPU のフレーム同期が発生
- メインスレッドの奪い合い
- GPU負荷とCPU負荷が相互悪化
- 一方が詰まると一方も詰まる
今回起きた GPU fallback もこの競合が原因。
VRでは絶対破綻
VRモードでは:
- 2眼分の描画
- 72〜90fps維持
- レイテンシ1桁ミリ秒要求
- Head-tracking で常時高速更新
- UIは常に動き続ける
この環境で CSS + DOM の毎フレーム再描画は絶対に無理。
事実、CSS3DRenderer の VR サポートは「ない」し、 WebXR と組み合わせる公式案内も存在しない。
CSS3DRenderer が「軽そうで重い」原因は、 ブラウザの設計そのものに起因した構造的な限界にある。
5. 本当にやりたいこと(VRChat的なWEB表示)を実現するには?
結論として、CSS3DRenderer を使って VR 空間に“本物のWebページ”を貼り付け、操作可能にするというアプローチは、技術仕様的に不可能に近い。
ただし目的別に、現実的な代替手段は存在する。
CSS3DRendererで外部ページは無理
理由は2つ:
- セキュリティ(CSP/X-Frame-Options)でブロックされる
- DOM+iframe+3D transform の負荷が重すぎて動作しない
外部ページを“そのまま”動かすのは構造的に不可能。
WebGL の texture にスクリーンショットを貼る(最も安定)
「表示だけでいい」なら、現実的にはこれが最強。
手順:
- 外部ページを画像化(外部サーバ or Puppeteerなど)
- Three.js のテクスチャとして平面に貼る
- 更新したい時だけ画像差し替え
メリット:
- GPU で高速
- レイアウト負荷なし
- VR でも安定
- 表示品質も高い
デメリット:
- Webページ自体は操作できない
- “見えるだけ” の仕様
操作したいなら独自 UI を Three.js で実装
VRChatがやっている方法に近い。
- UIを自作(ボタン、テキスト、スクロールなど)
- Three.js の Mesh + Raycaster で操作
- Webページのように振る舞うUIを“模倣する”方式
メリット:
- 完全に3D空間向けに最適化できる
- 操作性が最高
- WebGLと完全に統合できる
デメリット:
- HTMLではない
- 自分でUIを作る必要がある
VR空間で“本物のWEBを操作できる”をやりたいなら WebView(Unity側)
これは Web 技術では不可能。 VRChat 風 UI(壁に貼るブラウザ)を実装したいなら Unity+WebView 一択。
有名ソリューション:
- Vuplex WebView
- UniWebView
- Oculus の WebView
- SteamVR系のブラウザパネル
これらは GPUにHTMLをそのままテクスチャとして描画する仕組み を使っているため、HTMLが動く。
Webブラウザ内の Three.js では再現不可能。
現実的にできる/できないライン
| やりたいこと | 現実度 |
|---|---|
| 3D空間に画像としてWEBを貼る | ◎ 余裕で可能 |
| 3D空間にテキストや軽いHTMLを置く | ○ CSS3Dなら可 |
| WebGLとCSS3Dの併用 | △ 軽いHTML限定 |
| 外部サイトを3D空間に表示 | ✕ セキュリティ的に無理 |
| 外部サイトを操作 | ✕ 技術仕様的に無理 |
| VRChatみたいな“壁にWEBブラウザ” | ✕ Webでは不可能 |
| Unityのように3Dブラウザを扱う | ◎ Unity+WebViewなら可能 |
✔ 結論
VR 空間で “WEBページを見たい・操作したい” の理想形は:
- 表示だけ → WebGLテクスチャ方式
- 操作したい → Three.jsで独自UI構築
- 完全にWebブラウザを埋め込みたい → Unity/WebView
CSS3DRenderer がやる領域ではない。
6. ハードウェアアクセラレーション問題
今回のトラブルの核心は、CSS3DRenderer + iframe の組み合わせによって GPU が一時的にフリーズし、Chrome が自動的にハードウェアアクセラレーションをオフにしたこと。
この挙動は Three.js の問題でも、コードの問題でもない。 ブラウザ側の安全装置が発動した結果起こる現象。
CSS3DRenderer+iframe が GPU落ちを引き起こす
DOM の再描画(CSS3DRenderer)と iframe 内部の独立描画パイプラインが重なり、 メインスレッドとGPUスレッドが過負荷に陥る。
典型的な発生条件:
- iframe内で別HTMLを描画
- CSS3Dが毎フレーム transform
- WebGLRenderer で3D描画
- VRモードでフレームレートが要求される
この3つが重なると GPU が “ハング” したと判断される。
Chrome は GPU が怪しくなると自動で fallback
Chrome は GPU が応答しなくなると、以下の挙動を取る:
- GPUプロセスをリセット
- 再発するとハードウェアアクセラレーションを一時的に無効化
- WebGL と動画再生を CPU描画に切り替え
これがまさに“GPU fallback”。
ユーザーから見える症状は:
- Three.js が激重になる
- WebXR やVR描画が 1~5fps になる
- 過去のサンプルすら重く感じる
- ブラウザを再起動しても直らない場合がある
その結果 WebGL が CPU fallback して全体が激重に
GPUがオフだと WebGL は SwiftShader(CPUベースのソフトウェアレンダラー)に切り替わる。
ソフトウェア描画の特徴:
- GPUの100倍以上遅い
- 3D処理をCPUだけで実行
- VRはほぼ動かない
- どんな簡単な3Dもカクつく
「WebGL が壊れた?」 と錯覚するほど遅くなる。
でも壊れたわけではなく、 GPUが使われていないだけ。
理解しづらいので原因特定が辛い
この問題をややこしくするのは:
- エラーが具体的に表示されない
- CSS3DRenderer の情報が少ない
- iframe との組み合わせ例がほぼ存在しない
- Chrome の GPU fallback を知らないユーザーが多い
そのため、 「どこが原因?」 「なぜ突然重い?」 「何を直せばいい?」 と混乱しやすい。
原因は ブラウザの挙動 であり、Three.js のコードとは無関係。
💬 コメント