はじめに

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つ:

  1. セキュリティ(CSP/X-Frame-Options)でブロックされる
  2. DOM+iframe+3D transform の負荷が重すぎて動作しない

外部ページを“そのまま”動かすのは構造的に不可能。


WebGL の texture にスクリーンショットを貼る(最も安定)

「表示だけでいい」なら、現実的にはこれが最強。

手順:

  1. 外部ページを画像化(外部サーバ or Puppeteerなど)
  2. Three.js のテクスチャとして平面に貼る
  3. 更新したい時だけ画像差し替え

メリット:

  • 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 が応答しなくなると、以下の挙動を取る:

  1. GPUプロセスをリセット
  2. 再発するとハードウェアアクセラレーションを一時的に無効化
  3. 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 のコードとは無関係。