Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Vue で書く 空間コンピューティング、TresJS と WebXR を使ったXR デバイスの...

Vue で書く 空間コンピューティング、TresJS と WebXR を使ったXR デバイスのフロントエンド開発

Vue Fes Japan 2026(2026年10月24日)の発表資料です。

Vue と TresJS で 3D のシーンを書き、WebXR で Meta Quest 3 などの XR デバイスに表示するまでを、3D の基礎から順に解説しました。3D や XR を触ったことがない Vue 開発者向けの内容です。

・なぜ Web で XR を作るのか
・3D の基礎(座標、メッシュ、ライト、カメラ、レンダリングパイプライン)
・TresJS で Three.js をタグとして書く方法
・パススルー、平面検出、手で掴む操作など、現実に重ねるための実装

■ デモ映像
https://youtu.be/olZXcD8S6cU

■ スターターキット
https://github.com/XRaft-labs/vue-webxr-starter

■ 参考記事: WebGL入門 その②(レンダリングパイプライン)
https://qiita.com/hiranuma/items/d5389d7e50b058c64b99

■ 登壇者
平沼 真吾(株式会社GENEROSITY 取締役CTO)
note: https://note.com/mistorun
X: https://x.com/mistorun

GENEROSITYでは、一緒に作る仲間を募集しています。
https://recruit.generosity.co.jp/

Avatar for Shingo Hiranuma

Shingo Hiranuma

October 26, 2026

More Decks by Shingo Hiranuma

Other Decks in Technology

Transcript

  1. 00 導入 自己紹介 note X note.com/mistorun @mistorun 2006年 2008年 Shingo

    Hiranuma 平沼 真吾 株式会社GENEROSITY 取締役CTO / 共同創業者 魔法の学校 主宰 大学院でXR(AR・MR・VR)や近未来UIの研究に従事。 株式会社 東芝に入社。 携帯電話のソフトウェア開発に携わる。 2012年 スマホアプリ・ウェブサイト開発の分野で起業。 ノマドワーカーとして海外を転々としながら、自社サービス開発や スタートアップ数社の立ち上げ、Startup Weekendのイベント運営、 起業家コミュニティの支援などに携わる。 2015年〜 日本に帰国後、株式会社GENEROSITY(ジェネロシティ)の創業時に参画。 現在 XRやDXを用いて、体験をアップデートする研究・開発をおこなっている。 XRとの関わりは、大学院での研究から数えて20年。 今もヘッドセットやXRグラス向けのコンテンツを、自ら手を動かして作り続けている。 個人活動では、興味から楽しく学ぶがコンセプトの「魔法の学校」を主宰し、 子ども向け教材の制作や授業の開催をしている。 4 / 62
  2. 00 導入 今日の流れ 3DとXRは、知らない方もいる前提でお話しします。 01 なぜWebで作るのか XRとは何か、統一されていない作り方と、Webの役割 02 3Dの基礎 座標・メッシュ・ライト・カメラ・パイプライン、現実との重ね方

    03 Vueで書く TresJSで、タグとv-forとrefのまま 04 現実に重ねる パススルーで部屋の映像に箱を重ね、視線と手で掴んで、実物の机の上に置く 05 まとめ 今日の振り返りと、これから 5 / 62
  3. 01 なぜWebで作るのか・言葉の整理 XRは、VR・AR・MRをまとめた呼び名 違いは、現実とCGをどのくらいの割合で混ぜるかです。 現実が多い CGが多い 机 幅120cm AR 拡張現実

    MR 複合現実 VR 仮想現実 例: スマホのAR、地図アプリの案内 例: 今日のデモ(Quest 3のパススルー) 例: Vision Proで動かした今日のデモ 現実に、文字や印を足す CGが机に乗り、手で掴める。現実と関わる 現実は見えず、全部CG XR WebXRでは モードは2つだけ。immersive-ar(AR・MR)と immersive-vr(VR)。MRという名前のモードはありません。この講演では、AR・MRをまとめて「AR」と呼びます。 7 / 62
  4. 01 なぜWebで作るのか スマートグラスは、1年で100機種を超えた いろいろな会社が、いろいろな形のグラスを作っています。 2025年に発表された製品 約110 1月〜10月30日・形式別 カメラグラス 無線ディスプレイグラス VR/ARゴーグル

    有線ディスプレイグラス 独立型グラス オーディオグラス その他 4 9 11 14 13 33 24 Ray-Ban Meta AIグラス XREAL Aura Snap Specs XRグラス Meta VR Glasses XRグラス 2026年1〜3月(前年同期比) +83% 市場全体 +136% +210% −17% 画面付きARグラス 画面なしグラス VR XRグラス(2027年春) 出典: kotauchisunsun「スマートグラスの動向と考察 2025年版」/ スマートグラスサミット2026 Summer 基調講演(MONOist 2026年8月12日) 8 / 62
  5. 01 なぜWebで作るのか AIグラスとXRグラスは、CGを出す場所が違う 同じように頭を右へ向けても、CGの見え方はこう違います。 AIグラス 視界の決まった場所に出る 頭を動かすと、一緒に付いてくる 操作 音声・タッチ・ボタン XRグラス

    空間の決まった場所に置く(6DoF) 頭を動かしても、机の上に残る 操作 ハンドジェスチャ・コントローラ・トラックパッド 正面を見る 右を向く 12:30 翻訳中… 12:30 翻訳中… 頭の向きに追従 どこを向いても、CGは常に 視界の同じ位置に表示される 通知・翻訳・時計など 「見るだけ」の情報向き 空間に固定(6DoF) 頭を動かしても、CGは 現実の同じ場所に留まる 頭の位置3つ(前後・左右・上下)と 向き3つ(3軸の回転)、 合わせて6つの動きを追う 写真: Clay Banks / Unsplash 9 / 62
  6. 01 なぜWebで作るのか XRデバイスは多種多様で、作り方も統一されていない デバイスをまたいで共通しているのは、Web標準の WebXR Device API です。 … Meta

    Quest 3 Meta VR Glasses Samsung Galaxy XR デバイス Apple Vision Pro ヘッドセット グラスは2027年春 発売予定 ネイティブ SwiftUI + RealityKit Unity + Meta XR SDK Web … ヘッドセット・Android XR XREAL Aura グラス・Android XR Snap Specs グラス・Snap OS ほかにも Jetpack XR SDK / Unity など Jetpack XR SDK / Unity など Lens Studio … Web標準: WebXR Device API 同じコードをURLで配布できる。使える機能は端末ごとに違う 画像: Apple・Meta・Samsung・XREAL・Snap の公式ニュースルームおよび製品ページより 10 / 62
  7. 01 なぜWebで作るのか スマホも、端末もOSも多様で、作り方は統一されていなかった そのときOSをまたいで共通していたのは、Web標準の HTML・CSS・JavaScript でした。 デバイス ネイティブ iPhone iOS(2007〜)

    Android 各社の端末(2008〜) Windows Phone BlackBerry 2019年にサポート終了 2022年にサービス終了 Objective-C + Cocoa Touch Java + Android SDK C# + Silverlight Java + BlackBerry SDK 困っていたこと OSごとに作り直す・言語を覚え直す・ストアの審査を待つ・入れて、更新してもらう Web Web標準: HTML・CSS・JavaScript どのスマホにもブラウザがあり、同じコードをURLで配布できる(のちにPWAも) 11 / 62
  8. 01 なぜWebで作るのか 今のスマホでも、ARはOSごとに書き分ける ただし、WebXR Device API が無いiPhoneでも、WebでARを動かす手段はあります。 デバイス iPhone iOS

    Android 各社の端末 ネイティブ Swift + SwiftUI・ARKit / RealityKit Kotlin + Jetpack Compose・ARCore OSの簡易AR AR Quick Look (USDZ) Scene Viewer (ARCore・glTF) WebXR API Web AR (APIなし) リンクから開き、モデルを置いて眺める リンクから開き、モデルを置いて眺める Safari は WebXR Device API に非対応 Chrome: WebXR Device API(immersive-ar) カメラ映像 + IMU・SLAM で位置を推定し、Three.js で重ねる 2010年代後半から(AR.js・8th Wall など)。WebXR Device API は使わず、iPhone でも Android でも動く 8th Wall 平面の検知(SLAM) MediaPipe 顔・手の検知 Three.js / TresJS 描画 OSの簡易ARは手軽だが、操作や見た目のカスタマイズはほぼできない / 8th Wall は2026年にエンジンがオープンソース化(SLAMは別ライセンス) 12 / 62
  9. 01 なぜWebで作るのか Webで作ると、何がうれしいか 品質と性能はネイティブが上。Webで作るメリットは、配布のしやすさと作りやすさです。 01 02 03 ストアの審査も、インストールも不要です。 展示やイベントの場で、 すぐに使ってもらえます。

    Vite、TypeScript、Vue DevTools。 ヘッドセットが無くても、PCのブラウザの エミュレーターで確認できます。 今日のデモは、Quest 3ではパススルー、 Vision ProではVRとして、 同じコードで動いています。 URLを開けば動く Webの知識とツールで作れる 1つのコードで複数の端末へ 逆に、ブラウザからは使えない機能(視線の向き、OS標準の画面部品など)や、最高の描画品質が必要なら、ネイティブアプリを選ぶ理由があります。 13 / 62
  10. 01 なぜWebで作るのか 2026年10月時点で、WebXRのARが使えるデバイス 実機で確認したのは、Quest 3・Vision Pro・XREAL Aura の3台です。 デバイス VR

    現実に重ねる 確認方法 Meta Quest 3 動く 動く カメラ映像に重ねる(パススルー) 実機で確認 Apple Vision Pro 動く Safariが未対応 実機で確認 XREAL Aura 未確認 動く 現実が直接見え、その中にCGを置く 平面検知・ハンドジェスチャも動く 実機で確認 Samsung Galaxy XR 対応(未検証) 対応(未検証) 実機なし ヘッドセット ヘッドセット グラス・Android XR ヘッドセット・Android XR immersive-vr immersive-ar isSessionSupported('immersive-ar') が false 「動く」は、実機で確認したものだけに付けています。 14 / 62
  11. 02 3Dの基礎 3Dのプログラムがやることは、4つだけです この章では、さきほどの映像をこの4つに分解します。 Y X Z STEP 1 物を置く

    座標 / メッシュ (ジオメトリ・マテリアル) テクスチャ STEP 2 光を当てる ライト STEP 3 カメラを決める カメラ / 画角 near・far STEP 4 絵にする レンダリング パイプライン XRでは、この4つのあとに「現実に重ねる」が加わります。章の最後で説明します。 16 / 62
  12. 02 3Dの基礎・STEP 1 物を置く 物を置く場所: ワールド座標 物を置く空間ぜんぶをシーンと呼びます(Webで言うとDOMツリー)。Three.jsのシーンは、Yが上で、1が1メートルです。 よくある事故1:モデルが横倒しで入る Y上 上の向きを

    変換せずに読む Z が上 (0.8, 0.6, 0.4) 原点 Z 手前 1m CSSと違って、Yは上に増える。地面は X と Z の面 Y が上 Zが上のソフトで、立たせて作る 右 X Three.jsでは寝てしまう よくある事故2:100倍の大きさで出る 180 1を1メートル として読む 1が1センチのソフトで、高さ180 人 180メートルの巨人になる 対処: 書き出すときに、上をY・単位をメートルにそろえる Zが上: Blender・Unreal Engine など / 1が1センチ: Unreal Engine・Cinema 4D など 17 / 62
  13. 02 3Dの基礎・STEP 1 物を置く 物ごとの座標: ローカル座標 物はそれぞれ、自分の原点と向きを持っています。親を動かすと、子も一緒に動きます。 机の上にコップを置く 机から見て (0.2,

    0.75, 0) コップも一緒に動く 動かす前 (0.2, 0.75, 0) のまま テンプレートの入れ子が、そのまま親子になる 机だけ動かす Y X ワールドの原点 机の原点 元の原点 机の原点 変わったのは、机の position だけ Webで言うと .parent(机) .child(コップ) .parent { transform: none } .child の値はそのまま .parent(机) .child(コップ) <TresGroup :position="tablePos"> <!-- 机 --> <TresMesh>…</TresMesh> <!-- 天板と脚 --> <TresMesh :position="[0.2, 0.75, 0]"> <!-- コップ --> … </TresMesh> </TresGroup> 子の position は、親から見た位置(ローカル座標) ローカル → ワールドに直すのが、後で出てくる「モデル変換」 手で掴んで動かすときは、手のローカル座標で物との相対位置を覚えておく .parent { transform: translateX(150px) } 18 / 62
  14. 02 3Dの基礎・STEP 1 物を置く 画面に出る物は「メッシュ」 メッシュは、形(ジオメトリ)と見た目(マテリアル)の2つでできています。 + ジオメトリ 三角形の集まり 箱:

    角8つ、面6つ。その面を三角形12枚で作る デモのぬいぐるみ: 三角形8,621枚 コードでは作れないので、3Dソフトで作り、 ファイルにして受け取る = マテリアル 色・つや・質感 ▼ そのファイルの形式 glTF / GLB: 3Dモデルの標準形式 メッシュ 画面に出る物 3DモデルにおけるJPEGのようなイメージ。 形・マテリアル・テクスチャ・アニメーションを 1つのファイル(.glb)にまとめて受け渡せる Webで言うと ジオメトリ ≒ 要素の形 マテリアル ≒ CSS 19 / 62
  15. 02 3Dの基礎・STEP 1 物を置く 表面に貼る画像: テクスチャ 頂点ごとに「画像のどこか」を決めておきます。この画像上の座標を、UVと呼びます。 この頂点の UV は

    (0.5, 0) C ジオメトリ 頂点ごとに UV を持つ Webで言うと background-image に近い。 違いは、平面ではなく形に巻き付けること 1 0.5 A B C 使わない + 1 V テクスチャ 面ごとに切り開いて並べた画像 U = B C A A B 0 貼った結果 A・B・C が、それぞれの面に UVは3Dソフトが作る モデルを作るときに一緒に入る。 デモのぬいぐるみの色も、fur_color.jpg 1枚を UVマップで貼って表現している 20 / 62
  16. 02 3Dの基礎・STEP 2 光を当てる 光を当てる: ライト ライトが無いと、色を指定してもメッシュは真っ黒です。 ライトなし 真っ黒 Webにない

    環境光 全体を均一に明るく(陰が出ない) CSSの色はそのまま画面の色。3Dの色は光との掛け算です。 平行光源 太陽のように一方向から 点光源 電球のように1点から四方へ デモで置いているライトは、この2つだけ <TresAmbientLight :intensity="0.3" /> <TresDirectionalLight :position="[5, 8, 5]" :intensity="1.0" /> 21 / 62
  17. 02 3Dの基礎・STEP 3 カメラを決める どこから見るか: カメラ カメラから広がる四角錐(視錐台)の中だけが、絵になります。 画角: 四角錐の広がり near:

    これより手前は描かない far: これより奥は描かない Webで言うと ビューポートに一番近い。 ただし、カメラは位置と向きを持っています。 XRでは、カメラ = 自分の頭 画角はヘッドセットが決める。 near と far は自分で決められる。 デモは near 0.1m / far 1000m ※ ここで扱うのは、遠くの物ほど小さく見える「透視投影」のカメラです。 距離で大きさが変わらない「平行投影」のカメラもありますが、今回は扱いません。 前方クリッピング面 = near、後方クリッピング面 = far 図の出典: Qiita「WebGL入門 その②」 22 / 62
  18. 02 3Dの基礎・STEP 4 絵にする 絵になるまで(1): 座標を変換する 物ごとの座標 → 部屋の座標 →

    カメラから見た座標、と置き直します。 頂点 › モデル変換 › ビュー変換 モデル変換: 箱ごとの座標を、部屋(ワールド)に置く › 射影変換 › クリッピング › ビューポート変換 › ラスタライズ › シェーディング › Zテスト・合成 ビュー・射影変換: カメラから見て、スクリーンに写す 図の出典: Qiita「WebGL入門 その②」 23 / 62
  19. 02 3Dの基礎・STEP 4 絵にする 絵になるまで(2): 画素にして、色を決める 三角形を画素に分けて、画素ごとに色を決め、奥の物を隠します。 頂点 › モデル変換

    › ビュー変換 › 射影変換 › クリッピング › ビューポート変換 › ラスタライズ › シェーディング › Zテスト・合成 Zテスト 画素ごとに奥行きを比べ、 奥の物を隠す クリッピング → ビューポート変換 四角錐の外を捨て、中を画面の大きさに合わせる ラスタライズ → シェーディング 画素に分けて、光とテクスチャから色を決める アルファブレンド 半透明の色を、後ろの色と混ぜる 図の出典: Qiita「WebGL入門 その②」 24 / 62
  20. 02 3Dの基礎・XRでは XRになると、何が変わるか CGをどこに重ねるかは、デバイスの形で3通りあります。 スマホ ヘッドセット 今日のデモ 方式 ビデオシースルー(画面越し) 見え方

    画面に、カメラ映像とCGを重ねる 描く枚数 1枚 入力 画面をタップ 例 AndroidのChrome 方式 ビデオシースルー 見え方 両目の画面に、カメラ映像とCGを重ねる 描く枚数 左右2枚、1秒に72〜120回 左目 入力 例 (機種と設定による) 右目 手・コントローラ Meta Quest 3、Galaxy XR ビデオシースルー: 背景を透明にした絵を、カメラ映像と重ねる( alpha-blend ) グラス 方式 光学シースルー 見え方 現実はレンズ越しに直接見え、CGは光と して足す 描く枚数 左右2枚 入力 手・本体のボタンなど、端末による 例 XREAL Aura、Snap Specs 光学シースルー: 光を足すので黒は透明に( additive )。暗いUIは見えにくい 25 / 62
  21. 02 3Dの基礎・XRでは 現実に重ねるとき、崩れやすい3つ どれか1つでも崩れると、CGは「貼り付けた絵」に見えます。 遅れ 重なっている OK 机に乗り、影があり、頭を動かしても止 まっている Webで言うと

    幾何学的整合性 位置・大きさ 光学的整合性 明るさ・影 時間的整合性 遅れ 本編: 座標 / 平面検出 本編: ライト 本編:1秒に72〜90回の描き直し 机から浮く・めり込む。大きさが違う。 手前の物に隠れない 部屋の照明と、明るさや影の向きが合 わない 頭を動かすと、CGが遅れて付いてくる・ 揺れる 幾何学的 = レイアウトのずれ / 光学的 = ダークモードで画像だけ白く浮く/ 時間的 = スクロールに遅れて付いてくる追従ヘッダー 26 / 62
  22. 02 3Dの基礎・まとめ Webの言葉に置き換えると Webにないのは、ライト・奥行き・画素ごとの色の計算の3つです。 3Dの言葉 シーン メッシュ ジオメトリ 頂点の位置の集まり マテリアル

    色・質感・光の返し方 テクスチャ カメラ ライト Zテスト / シェーダー Webで言うと DOMツリー 要素 次の章で出てくるタグ div 要素の形と大きさ div 要素の見た目(CSS) <TresCanvas> の中身 <TresMesh> width: 160px height: 80px border-radius: 16px <TresBoxGeometry :args="[1, 1, 1]"> background: green opacity box-shadow <TresMeshStandardMaterial color="…"> background-image ビューポート(位置と向きつき) Webにない Webにない z-index は手動、3Dは画素ごとに自動 マテリアルの map <TresPerspectiveCamera> <TresAmbientLight> <TresDirectionalLight> ふだんは書かない 27 / 62
  23. 03 Vueで書く ゴール: 実物の机の上に、箱を1つ置く Quest 3 のブラウザで見た景色です。ここまでをVueで書きます。 CGの箱 3章 Vueで書く

    画面に箱を描く TresJSで、ウェブブラウザに緑の箱を描画する 4章 現実に重ねる ↓ 部屋に重ねる・掴む WebXRで、背景を透明にする 部屋は本物(パススルー) 写真: Clay Banks / Unsplash 29 / 62
  24. 03 Vueで書く 緑の箱を画面に描画するまでの手順は7つ 2章の4つのステップを、コードを書く単位に分けると7つの手順になります。 STEP 1 物を置く 1 2 STEP

    2 3 4 STEP 3 5 6 形を作る 見た目を決める シーンに置く 光を当てる カメラを決める <TresBoxGeometry> <TresMeshStandard​ Material> <TresMesh :position> <TresDirectional​Light> <TresPerspective​Camera> ジオメトリ マテリアル メッシュ + 位置 ライト STEP 4 カメラ・画角 XR 7 絵にする レンダラ・描画ループ <TresCanvas> 現実に重ねる WebXR・パススルー ARButton 30 / 62
  25. 03 Vueで書く・WebGLで書くと WebGLで書くと、箱1つを描画するのに122行が必要 ※ 行数は書き方によって変わります 光の当たった箱を1つ、WebGLだけで書いて動かしました。レンダリングパイプラインを、ほぼ全部自分で書きます。 webgl-cube.js(全122行、うち空行7行・省略なし) const canvas =

    document.querySelector('canvas') canvas.width = innerWidth canvas.height = innerHeight const gl = canvas.getContext('webgl') // 頂点シェーダー:座標を変換する const vsSource = ` attribute vec3 aPosition; attribute vec3 aNormal; uniform mat4 uModel; uniform mat4 uView; uniform mat4 uProjection; varying vec3 vNormal; void main() { vNormal = mat3(uModel) * aNormal; gl_Position = uProjection * uView * uModel * vec4(aPosition, 1.0); }` // フラグメントシェーダー:画素の色を決める(光の計算) const fsSource = ` precision mediump float; uniform vec3 uColor; uniform vec3 uLightDir; uniform float uAmbient; varying vec3 vNormal; void main() { float diffuse = max(dot(normalize(vNormal), normalize(uLightDir)), 0.0); vec3 color = uColor * (uAmbient + diffuse); gl_FragColor = vec4(color, 1.0); }` function compile(type, source) { const shader = gl.createShader(type) gl.shaderSource(shader, source) gl.compileShader(shader) if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { throw new Error(gl.getShaderInfoLog(shader)) } return shader } const program = gl.createProgram() gl.attachShader(program, compile(gl.VERTEX_SHADER, vsSource)) gl.attachShader(program, compile(gl.FRAGMENT_SHADER, fsSource)) gl.linkProgram(program) if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { throw new Error(gl.getProgramInfoLog(program)) } gl.useProgram(program) // 立方体:6面 × 4頂点。面ごとに法線が違うので頂点を共有できない const s = 0.1 const positions = new Float32Array([ -s, -s, s, s, -s, s, s, s, s, -s, s, s, // 前 -s, -s, -s, -s, s, -s, s, s, -s, s, -s, -s, // 後 -s, s, -s, -s, s, s, s, s, s, s, s, -s, // 上 -s, -s, -s, s, -s, -s, s, -s, s, -s, -s, s, // 下 s, -s, -s, s, s, -s, s, s, s, s, -s, s, // 右 -s, -s, -s, -s, -s, s, -s, s, s, -s, s, -s, // 左 ]) const normals = new Float32Array([ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, ]) const indices = new Uint16Array([ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15, 16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23, ]) function attribute(name, data) { const buffer = gl.createBuffer() gl.bindBuffer(gl.ARRAY_BUFFER, buffer) gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW) const location = gl.getAttribLocation(program, name) gl.enableVertexAttribArray(location) gl.vertexAttribPointer(location, 3, gl.FLOAT, false, 0, 0) } attribute('aPosition', positions) attribute('aNormal', normals) const indexBuffer = gl.createBuffer() gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer) gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW) // 行列:カメラの遠近も、位置も自分で計算する function perspective(fovy, aspect, near, far) { const f = 1 / Math.tan(fovy / 2) const nf = 1 / (near - far) return new Float32Array([ f / aspect, 0, 0, 0, 0, f, 0, 0, 0, 0, (far + near) * nf, -1, 0, 0, 2 * far * near * nf, 0, ]) } function translation(x, y, z) { return new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1]) } const uModel = gl.getUniformLocation(program, 'uModel') const uView = gl.getUniformLocation(program, 'uView') const uProjection = gl.getUniformLocation(program, 'uProjection') gl.uniform3fv(gl.getUniformLocation(program, 'uColor'), [0.26, 0.72, 0.51]) gl.uniform3fv(gl.getUniformLocation(program, 'uLightDir'), [5, 8, 5]) gl.uniform1f(gl.getUniformLocation(program, 'uAmbient'), 0.3) gl.uniformMatrix4fv(uView, false, translation(0, -1.2, 0)) gl.uniformMatrix4fv(uProjection, false, perspective(75 * Math.PI / 180, canvas.width / canvas.height, 0.1, 100)) gl.enable(gl.DEPTH_TEST) // 描画ループ:画面を消して、描く function render() { gl.viewport(0, 0, canvas.width, canvas.height) gl.clearColor(0, 0, 0, 1) gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) gl.uniformMatrix4fv(uModel, false, translation(0, 1.2, -1.4)) gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0) requestAnimationFrame(render) } requestAnimationFrame(render) 色分け=パイプラインの段階 36行 頂点を用意する 33行 座標を変換する 15行 色を決める・ライト 17行 シェーダーをGPUへ 14行 canvasの用意・描く 画素に分ける所(ラスタライズ)だけGPUが自動 このコードで描画できるもの localhost:5173 光の当たった緑の箱が1つ浮かぶだけ 31 / 62
  26. 03 Vueで書く・Three.js だから、みんなThree.jsを使う 2010年からあるライブラリで、今もWebの3Dの標準です。WebGLの煩わしい記述や手順を、まとめて引き受けます。 Three.js シーン・メッシュ・ライト・カメラが最初からある シェーダーと行列の計算、GLBの読み込み、WebXRも入っている ブラウザからGPUへ命令する口 WebGL シェーダー・頂点バッファ・行列を自分で書く

    GPU WebXR Device API 頭と手の位置・向き、入力を渡す口 デバイスごとの違いを吸収する 三角形を画素にして、色を塗る計算をする 122行 → 15行 光の当たった箱を1つ描くコード(実測) WebGL → Three.js それでも自分で書くもの シーン・カメラ・レンダラの組み立て 描画ループ・画面サイズの追従・後片づけ 物を増やすたびに add 、消すたびに remove 32 / 62
  27. 03 Vueで書く・プリミティブ プリミティブ: 箱や球は、数行で作れる WebGLでは箱の頂点24個を手で一つずつ書きました(36行)。Three.jsなら、プリミティブを使うことでサイズとマテリアルを設定 するだけで描画できます。 箱 BoxGeometry 例えば 緑の箱を

    描く場合 複雑な形状は 球 SphereGeometry 円柱 CylinderGeometry 平面 PlaneGeometry ドーナツ TorusGeometry const geometry = new THREE.BoxGeometry(0.2, 0.2, 0.2) // 形:幅・高さ・奥行き(メートル) const material = new THREE.MeshStandardMaterial({ color: '#42b883' }) // 見た目:緑 const box = new THREE.Mesh(geometry, material) // 形 + 見た目 = メッシュ 自動車や建造物、ぬいぐるみ、キャラクターなどは頂点が何千個もあり、プリミティブを組み合わせても作れない。 そこで、GLBファイルを読み込む(この章の後半で説明します) 33 / 62
  28. 03 Vueで書く・Three.jsで書くと Three.jsなら15行 WebGLより短く書けます。しかし、箱の作成に関するコードは5行だけで、残りは描画の準備です。 箱を1つ出す(Three.js) // ① 組み立て:シーン・カメラ・レンダラを作り、canvasをDOMに挿す const scene

    = new THREE.Scene() const camera = new THREE.PerspectiveCamera(75, w / h, 0.1, 100) const renderer = new THREE.WebGLRenderer({ antialias: true }) renderer.setSize(w, h) el.appendChild(renderer.domElement) // ② ライトを作って、シーンに足す scene.add(new THREE.AmbientLight(0xffffff, 0.6)) const light = new THREE.DirectionalLight(0xffffff, 1) light.position.set(5, 8, 5) scene.add(light) // ③ 箱を作って、置く(形 + 見た目 = メッシュ) const box = new THREE.Mesh( new THREE.BoxGeometry(0.2, 0.2, 0.2), new THREE.MeshStandardMaterial({ color: '#42b883' })) box.position.set(0, 1.2, -1.4) scene.add(box) // ④ 描画ループ:毎フレーム、カメラから見た絵を描く renderer.setAnimationLoop(() => renderer.render(scene, camera)) 箱: 5行 ライト: 4行 カメラ: 1行 準備・描画: 5行 Three.jsを使っても、自分で書いて管理する必要があるもの 1. 画面サイズが変わったら、合わせ直す addEventListener('resize', () => { camera.aspect = innerWidth / innerHeight camera.updateProjectionMatrix() renderer.setSize(innerWidth, innerHeight) }) 2. 物を増やす・消すたびに、シーンを手で更新 scene.add(box) // 増やす scene.remove(box) // 消す box.geometry.dispose() // 片づけ(忘れるとメモリが残る) 3. データと画面のずれを、自分で防ぐ 配列が変わるたびに、どの箱を足して、どれを消すかを自分で判断する 34 / 62
  29. 03 Vueで書く・TresJS TresJS: VueのコンポーネントでThree.jsを書く "Declarative ThreeJS using Vue Components"。Reactで3Dを書くときの定番、React Three

    Fiber のVue版です。 作者 登場 現在 名前 ライセンス 周辺 Alvaro Saburido 氏と、コミュニティ 2022年11月(v1.0 は同月末) v5.9.3(2026年10月)。v5 は2025年9月 Tres はスペイン語の「3」。Three.js の Three MIT @tresjs/cientos (便利部品)・@tresjs/rapier (物理エンジン)・@tresjs/post-processing・Nuxtモジ ュール 36 / 62
  30. 03 Vueで書く・TresJS TresJSの考え方 3つの考え方で作られています。 POINT 1 宣言的に書く シーンを、手順ではなくテンプレートで書く。DOMを書くのと同じ感覚 POINT 2

    ラッパーを作らない VueのCustom Rendererで、Three.jsのクラスをそのままタグにする。Three.jsが更新されても、すぐ使える POINT 3 Vueに3Dのエコシステムを Reactには、Three.jsをJSXのタグで書ける React Three Fiber がある。<mesh> と書くと箱が出る。Vueには、それに当たるものが無かった 37 / 62
  31. 03 Vueで書く TresJSとは: Three.jsを、Vueのタグで書く VueのCustom Rendererです。DOMの代わりに、Three.jsのオブジェクトを作ります。 いつものVue <div> → document.createElement('div')

    TresJS Three.jsのクラスを、Vueのタグにする 描画ループ・増減・後片づけ・画面サイズの追従を引き受ける TresJS Three.js シーン・メッシュ・ライト・カメラが最初からある Three.jsで自分で書いていたこと TresJSでは シーン・カメラ・レンダラの組み立て <TresCanvas> で囲むだけ 物を増やす・消す( add / remove ) v-for / v-if で書く 値を変えたら、画面に反映 propsを変えるだけ 消すときの後片づけ コンポーネントと一緒に自動 重力・衝突(落ちる・ぶつかる) 物理エンジン Rapier も <Physics> タグで 描画ループ、配列とシーンの同期は自分で書く ブラウザからGPUへ命令する口 WebGL シェーダー・頂点バッファ・行列を自分で書く GPU WebXR Device API 頭と手の位置・向き、入力を渡す口 デバイスごとの違いを吸収する 三角形を画素にして、色を塗る計算をする <TresMesh> → new Mesh() 38 / 62
  32. 03 Vueで書く Three.jsのクラスが、そのままタグになる Three.jsのドキュメントで名前を探して、頭に Tres を付けるだけです。 Three.js JavaScript const mesh

    = new Mesh( new BoxGeometry(1, 1, 1), new MeshStandardMaterial({ color: '#42b883' }), ) mesh.position.x = 1 scene.add(mesh) クラス名 → Tres + クラス名 コンストラクタの引数 → :args プロパティ → 属性。position-x で中の値まで TresJS Vueのテンプレート <TresMesh :position-x="1"> <TresBoxGeometry :args="[1, 1, 1]" /> <TresMeshStandardMaterial color="#42b883" /> </TresMesh> セットアップ:vite.config.ts に足す1行 isCustomElement: (tag) => tag === 'primitive' || (tag.startsWith('Tres') && tag !== 'TresCanvas'), 39 / 62
  33. 03 Vueで書く TresJSで書くと、こうなる 手順1〜6を、Vueのタグを入れ子にするだけで書けます。new THREE.◦◦ のようなThree.jsのコードは1行もありません。 App.vue のテンプレート <TresCanvas window-size>

    <!-- 6 絵にする --> <TresPerspectiveCamera :position="[0, 1.6, 0]" /> <!-- 5 カメラ --> <TresAmbientLight :intensity="0.6" /> <!-- 4 光 --> <TresDirectionalLight :position="[5, 8, 5]" /> <TresMesh :position="[0, 1.2, -1.4]"> <!-- 3 置く --> <TresBoxGeometry :args="[0.2, 0.2, 0.2]" /> <!-- 1 形 --> <TresMeshStandardMaterial color="#42b883" /> <!-- 2 見た目 --> </TresMesh> </TresCanvas> 描画ループ・画面サイズの追従・後片づけは <TresCanvas> が持つ 準備は three と @tresjs/core を入れて、vite.config に1行 同じ箱を書くと WebGL Three.js TresJS 122行 15行 9行 TresJSは、Three.jsの組み立てと描画ループを <TresCanvas> が引 き受けるぶん短い 40 / 62
  34. 03 Vueで書く シーンは v-for で書く 配列に push すれば、空間に箱が増えます。 script setup

    template const cubes = ref<CubeData[]>([]) <TresMesh v-for="cube in cubes" :key="cube.id" :position="cube.position"> <TresBoxGeometry :args="[0.08, 0.08, 0.08]" /> <TresMeshStandardMaterial color="#00ffff" /> </TresMesh> function placeCube(position: Vector3) { cubes.value.push({ id: crypto.randomUUID(), position, }) } 3D専用の状態管理は不要。Piniaに置いてもよい TresJSが自動で反映する Vueのデータ cubes = ref([...]) { id: 'A', position } { id: 'B', position } { id: 'C', position } 増やす cubes.push(D) scene.add(Mesh D) 消す cubes.splice(1, 1) scene.remove(Mesh B) 片づけ 消したメッシュ geometry / material.dispose() Three.jsのシーン Scene Mesh A Geometry + Material Mesh B Geometry + Material Mesh C … 41 / 62
  35. 03 Vueで書く・つまずきやすい点 TresJSでは、座標の中身を書き換えても、箱は動かない Vueは「値が別のものに入れ替わったか」で変化に気づきます。中身だけ書き換えても気づきません。 そのまま × 動かない cube.position.set(0, 1, 0)

    同じ Vector3 の 中身だけ変わる → Vueは 変化に気づかない → TresJSに 新しい値が渡らない ◦ 動く cube.position = new Vector3(0, 1, 0) 新しい Vector3 に 入れ替わる → Vueが 変化に気づく → TresJSが 新しい位置を渡す どちらを正にするか 基本はVueのデータを正にする。ただし、重力で落ちる物やアニメーションのように毎フレーム動くものは、Three.js側で直接動かし、必要なとき だけVueのデータに書き戻す(今日のデモの物理エンジンがこの形) 42 / 62
  36. 03 Vueで書く・GLB 複雑な形は、ファイルで受け取る: GLB 3Dにも、JPEGのような標準形式があります。glTF と、それを1ファイルにまとめた GLB です。 1つのファイルに、全部入っている model.glb

    Blenderなどから書き出す → 形 ジオメトリ デモのぬいぐるみは三角形8,621枚・頂点7,826個。 プリミティブでは作れない 色・質感 画像 マテリアル テクスチャ Webで言うと 骨 ボーン 動き アニメーション 画像を自分で描かず、JPEGを <img> で読むのと同じ。3Dでは GLBを useGLTF で読む 43 / 62
  37. 03 Vueで書く 3Dモデルは、composable で読み込む GLBは、cientos の useGLTF で読み、<primitive> に渡すだけです。 GLBを読む(@tresjs/cientos)

    const { state: gltf } = useGLTF(modelPath) watch(gltf, (loaded) => { if (!loaded) return mixer = new AnimationMixer(loaded.scene) }) // template <primitive v-if="gltf" :object="gltf.scene" /> useGLTF は読み込みを待たずにすぐ返る 読み終わったら state に中身が入る。最初は null アニメーションの準備は、watch で中身が入ってから行う 表示は <primitive> に渡すだけ デモで読み込んだGLB(毛の生えたぬいぐるみ)を、Quest 3 のパススルー越しに見たところ 44 / 62
  38. 03 Vueで書く 重力や衝突は、物理エンジンのタグを入れ子にするだけ 物理エンジン Rapier を @tresjs/rapier で使います。 重力・衝突:物理エンジン Rapier(@tresjs/rapier)

    <Suspense> <Physics :gravity="[0, -9.81, 0]"> <RigidBody v-for="cube in cubes" :key="cube.id" type="dynamic"> <!-- 当たり判定は、メッシュの形から自動で作られる --> <TresMesh>…</TresMesh> </RigidBody> </Physics> </Suspense> 注意 2026年10月時点の制約と暫定対策 理由 TresJSの毎フレームの処理(useLoop)はrequestAnimationFrameで 動くので、ヘッドセットの中では止まってしまう。 @tresjs/rapier の物理演算も一緒に止まる 対処 暫定対策として、自前の描画ループで物理演算を進め、 その位置を箱に写す。毎フレームの処理もuseLoopではなく、ここに書く (最後に紹介するスターターキットではuseXRFrame()に書く) <Physics> は <Suspense> で囲む。 物理エンジンの読み込みを待つため、無いと箱が作られない 当たり判定は、中のメッシュの形から自動で作られる 45 / 62
  39. 04 現実に重ねる XRモードは、ボタンと数行のコードを足すだけ 3章で書いたシーンはそのまま。XRを有効にして、入口のボタンを置くだけです。 App.vue(要点を抜粋) function onReady(ctx: TresContext) { const

    renderer = ctx.renderer.instance as WebGLRenderer renderer.xr.enabled = true const arBtn = ARButton.createButton(renderer, { optionalFeatures: [ 'local-floor', 'hand-tracking', 'plane-detection', ], }) document.body.appendChild(arBtn) } renderer.setAnimationLoop(renderLoop) // template <TresCanvas window-size :alpha="true" @ready="onReady"> 押す START AR ヘッドセットのブラウザで開く ヘッドセットの中に入る Three.jsのおかげ WebXRとのやり取り、頭の動きに合わせたカメラ、 左右の目の2枚の描画は renderer.xr が自動で行う(ARButton も付属)。 WebGLだけなら自分で書く必要がある。 TresJSのXR用パッケージ( @tresjs/xr )は未公開なので、 ここだけ three.js を直接使う optionalFeatures は「デバイス側にその機能が対応していれば 使用される」。対応していない端末でも、 セッションはエラーになることなく開始される。 47 / 62
  40. 04 現実に重ねる パソコンと Quest 3 をつなぐシステム構成 PCで開発しているウェブページを Quest 3 のブラウザで開くには、USB・Wi-Fi・Webサーバーの3つの方法があります。

    ローカル開発用 A USB でつないで見る 開発時はこれがおすすめ。adb reverse でポートを転送し、 Quest 3 で localhost を開く。HTTPのままでパススルーが動く B LAN で見る Wi-Fiの範囲内を、ケーブルなしで歩き回りたいときに。 PCとQuest 3を同じWi-Fiにつなぎ、PCのIPアドレスを開く adb reverse tcp:5173 pnpm dev 開発PC Vite の開発サーバー USB-C ケーブル Quest 3 のブラウザ 同じWi-Fi Quest 3 のブラウザ Wi-Fiが無くてもよい http://localhost:5173 pnpm dev 開発PC Vite の開発サーバー 社内・自宅のLAN https://192.168.x.x:5173 ウェブ公開用 C サーバーに置いて見る ビルドしたファイルをWebサーバーに置き、URLを開く。 誰でも、どこからでも見られる 注意 URLを知っていれば世界中の誰でも見れるため、 IPアドレス制限などで、ページにアクセスできる人を絞ると良い pnpm build 開発PC ビルドしたファイル アップロード インターネット Webサーバー Netlify・Vercel など Quest 3 のブラウザ https://example.com 48 / 62
  41. 04 現実に重ねる WebXR は HTTPS でないと動かない WebXR は安全な接続(HTTPS か localhost

    )でしか使えません。http://192.168.x.x では、ARボタンが押せません。USBでつ ないで localhost で開けば、HTTPのままで動きます。 A USB でつないで見るときにポートをリバース転送する B LAN で見るときに HTTPS にする B LAN で見るときに HTTPS にする USB で localhost に転送 無料 Cloudflare Tunnel 無料・登録不要 ngrok 無料プランあり adb reverse tcp:5173 tcp:5173 → http://localhost:5173 cloudflared tunnel --url http://localhost:5173 → https://xxxx.trycloudflare.com ngrok http 5173 → https://xxxx.ngrok-free.app Quest 3 から見ると localhost なので、HTTPのままでパスス ルーまで動く。証明書もWi-Fiも不要(デモの開発で使用) URLを Quest 3 に送る(ADB) PCの開発サーバーに、HTTPSのURLを一時的に付ける。 Quest 3 はWi-Fiが別でも開ける adb shell am start -a android.intent.action.VIEW -d "http://localhost:5173" com.oculus.browser 同じ仕組み。無料プランはアカウント登録が必要で、最初に 確認画面が出る URLを Quest 3 で手入力しなくても、PCから 直接ブラウザで開ける 注意 トンネルのURLで開くときは、Viteの server.allowedHosts に '.trycloudflare.com' などを足す。LANで mkcert の自己署名証明書を使う方法もあるが、Quest 3 側で警告が出る 49 / 62
  42. 04 現実に重ねる パススルーの正体は、透明な背景 ARかVRかは、environmentBlendMode で見分けます。 セッションが始まったとき const xr = renderer.xr

    // Three.jsのXR機能(WebXRManager) xr.addEventListener('sessionstart', () => { const session = xr.getSession() const blendMode = session.environmentBlendMode ?? 'opaque' // XRSession.mode というプロパティは無い }) if (blendMode !== 'opaque') { renderer.setClearAlpha(0) // 背景を透明に } else { renderer.setClearAlpha(1) // VR:背景は自分で塗る } // 指定した機能が許可されたかは enabledFeatures で確認 const enabled = session.enabledFeatures alpha-blend のとき、描いた絵の背景は透明 + 描いた絵(背景は透明) = カメラ映像 見える景色 opaque VR。部屋も含めて全部CGで描く alpha-blend ビデオシースルー(Quest 3 のパススルー) additive 光学シースルー(光を足す。黒は透明に) 50 / 62
  43. 04 現実に重ねる 箱を出すだけでは、空中に浮いてしまう 3章で <TresMesh :position="[0, 1.2, -1.4]"> と書いた座標は、ただの数字です。実物の机がどこにあるかは知りません。2章 の「幾何学的整合性」が崩れた状態です。

    × 座標を決めて置いただけ ◦ 平面検出で机を見つけて置く 空中に浮いている → 机の位置を知らない 背景が透明になっても、箱は決めた座標にあるだけ。机とは関係なく、空中に浮く 検出した机の天面 WebXRの平面検出で、現実の床や机の位置と形を取得。その上に置く 51 / 62
  44. 04 現実に重ねる 平面検出で、実物の机に置く WebXRの平面検出で机の形を取得し、物理エンジンの当たり判定にします。箱が机の上に乗り、幾何学的整合性がとれます。 机の上で離す 机の外で離す 机の天面に 厚さ10cmの当たり判定 床にも当たり判定(平面検出の床) 床で止まる

    useDetectedPlanes.ts(要点) // ※1 許可されていないと undefined const planes = frame.detectedPlanes for (const plane of planes) { if (plane.orientation !== 'horizontal') continue const pose = frame.getPose(plane.planeSpace, refSpace) // plane.polygon を厚さ10cmに押し出し、 // 固定の当たり判定にする } ※1 平面検出は、App.vue の ARButton.createButton() (Three.js)に渡す optionalFeatures (WebXR のセッション設定)に 'plane-detection' を指定し、ユーザーが許可したときだけ使える。許可済みで 平面がまだ無いときは、空のSet 前提 Questの「スペースの設定」で部屋をスキャンし、机を机として登録しておく。 部屋のスキャンデータの登録が無いと平面は1枚も検出結果として取得できない 52 / 62
  45. 04 現実に重ねる 視線で狙って、手で掴む Quest 3 で見えている景色です。視線の先をレティクル(照準の輪)で示して選び、ピンチで掴みます。 どちらも WebXR や Three.js

    には無い機能なので、自前で実装しています。 1 視線で狙う 2 手で掴む レティクル(0.5m先) レイから25cm以内=選択 手で掴んで、持ち上げる WebXRは視線を返さないので、頭の正面で代用。 掴んだ瞬間の手と物のずれを記録し、 人は頭より目線を少し下げて物を見るので、 毎フレーム手の位置と向きに合わせて物を動かす。 8°下げた先にレティクルを出す 手をひねると物も回る 掴んでいる間は重力を止め、離すと重力を有効に戻すことで、手を離すと机に落ちるようにできる 写真: Clay Banks / Unsplash 53 / 62
  46. 04 現実に重ねる コントローラと素手は、同じイベントで利用可能 WebXR Device API のおかげで、押した・離したの処理を、入力の種類ごとに分けずに書けます。 コントローラ セッションに直接付ける session.addEventListener('selectstart',

    onSelectStart) session.addEventListener('selectend', onSelectEnd) function onSelectStart(e: XRInputSourceEvent) { const grip = e.frame.getPose(e.inputSource.gripSpace!, refSpace) // トリガーでも、指のピンチでも、ここを通る } コントローラ トリガーを引く 素手(ハンドトラッキング) 親指と人さし指でつまむ 素手 押す・離す selectstart / selectend 握っている位置 指先 gripSpace 握っている位置で代用 関節が取れる なぜセッションに付けるか Vision Proの「視線 + ピンチ」は、つまんでいる間だけ現れて、離すと消える入力 (transient-pointer)。Three.jsのコントローラは、決まった番号の入力にしか紐づか ないため、途中で現れたこの入力のイベントを受け取れないことがある。セッション に付ければ、入力の種類や現れるタイミングに関係なく、すべてのイベントを受け取 れる Quest 3で素手を使うには、本体の「設定 → 動きのトラッキング → ハンドトラッキングとボディトラッキ ング」をオン。 コードでは optionalFeatures に 'hand-tracking' Vision Proはコントローラが無く、視線とハンドジェスチャ(ピンチ)のみ 54 / 62
  47. 04 現実に重ねる 冒頭の緑の箱が、XRグラスで動くまで この手順を踏むくらいの作業量で、冒頭にお見せした緑の箱をXRグラスで表示できるようになります。 STEP 1 XRモードを有効に STEP 2 HTTPS

    で開く STEP 3 背景を透明に STEP 4 平面検出で机に置く STEP 5 視線と手で掴む この5つは、 スターターキットに入っています Quest 3 をお持ちなら、git clone して START AR を押すだけで試せます。 examples/green-box を編集すれば、 自分の作品に差し替えられます。 github.com/XRaft-labs/ vue-webxr-starter 55 / 62
  48. 05 まとめ Vueの書き方のまま、現実に重なる3Dが作れる 3Dの基礎を押さえれば、あとはTresJSとWebXRが橋渡しをしてくれます。 POINT 1 1章 なぜWebで作るのか POINT 2

    2章 3Dの基礎 1つのコードが複数の端末で動き、URLを開くだけで使える Webに無いのはライトと奥行き。XRでは現実との重なりも加わる 複数の端末向けの開発と配布が簡単 3Dの言葉は、Webに置き換えて理解できる POINT 3 3章 Vueで書く POINT 4 4章 現実に重ねる v-for や composable も、いつもどおり使える ジェスチャもコントローラも同じイベント。左右の目の描画も自動 TresJSで、Three.jsのクラスがタグになる XR対応の大部分は、ライブラリに任せられる 57 / 62
  49. 05 まとめ デザイナーが作った表現を、そのままWebへ C4Dで作った動きや質感を、エンジニアが組み直さずにWebGLへつなぎ込むツールを作っています。 C4D デザイナー・モデラー・ アニメーターが、 モデルや動きを作る 書き出し GLB・アニメーション

    → パーティクルの 独自形式 ノード編集 → ノードプログラミングで 変形・連結・ Web向けに最適化 ビューアー Webと同じ → レンダラーで、 見え方を確認 Web・WebXR → ブラウザや XRグラスで、 そのまま動く GENEROSITYで開発中のツール デザイナーが表現したい動きや質を、ギリギリまで高められる 社内で開発中のツール(2026年10月時点) 59 / 62
  50. APPENDIX 配布資料 開発中につまずきそうな7つのポイント 事前に知っておけば、最初の動作確認で詰まりにくくなります。 事象 XR用の @tresjs/xr が入らない エミュレーターで XRWebGLBinding

    エラー 座標を変えても動かない XRに入ると、毎フレームの処理が止まる (物が空中で止まる) 物が目の高さで止まる 実機からPCにつながらない Vision Pro で START AR が押せない 原因 TresJS本体は公開済み。 XR用の追加パッケージだけ未公開(2026年10月時点) three の新しい版と合わない(調査時点) Vueは参照しか見ていない TresJSの useLoop はヘッドセットの中で止まり、 @tresjs/rapier の物理も一緒に止まる local-floor でも原点が床とは限らない Wi-Fi: 社内Wi-Fiが端末間通信を遮断している USB: USBデバッグが未許可、 またはケーブルの抜き差しでポート転送が切れた Safariが immersive-ar 未対応。 現時点(2026年10月)では、WebのARは難しい 対処 three.js の ARButton と renderer.xr を使う 新しい版で試し、落ちたら three@~0.172.0 新しい Vector3 を代入する スターターでは useXRFrame() に書く 床の高さを実測で決める Wi-Fi: Cloudflare Tunnel / ngrok USB: ヘッドセット内で許可し、adb reverse を張り直す VRモードで動かす 62 / 62