-
visionOS上のイマーシブなWebサイト環境の詳細
JavaScriptの新しいImmersive APIを活用して、Webサイトの訪問者にApple Vision Proの仮想環境を体験してもらいましょう。インラインのモデル要素からのイマーシブ環境への切り替えのリクエスト、ビデオドッキングなどの機能による魅力的なイマーシブ体験の構築、現実世界そのままのリッチな体験を実現するためにパフォーマンスを最適化する方法を解説します。これらすべては、Webサイト上でわずか数行のコードを実行するだけで実現できます。
関連する章
- 0:00 - Introduction
- 1:46 - Meet the immersive API
- 4:16 - Preview environments inline
- 7:01 - Go immersive
- 12:04 - Optimize the experience
- 17:17 - Image controls
- 18:09 - Next steps
リソース
- Download - Immersive model add-on for Blender
- WebKit.org - Theater Ticket Sales immersive website environment demo for Apple Vision Pro
- WebKit.org - Escape Game immersive website demo for Apple Vision Pro
- GitHub: Spatial Backdrop explainer
- WebKit.org – Report issues to the WebKit open-source project
- Submit feedback
関連ビデオ
WWDC26
WWDC25
-
このビデオを検索
こんにちは、私はJeanです。 visionOS Safariチームのエンジニアです。 このセッションでは、イマーシブ環境を使って Webサイトをより魅力的にする方法を ご紹介します。 観客にストーリーを伝える まったく新しい方法であり、 これまでにない形で 顧客を惹きつけることができます。 このチケット販売Webサイトをご覧ください。 訪問者が席を選ぶと、 選択内容のインラインプレビューが 表示されます。 さらに、劇場に 入ることもできます。 イマーシブ環境として、 選んだ席に文字どおり立つことができます。 これがすべて、Safari上のWebページから 実現できるのです! 今日はその体験の 作り方をご紹介します! でも、それだけではありません… こちらをご覧ください。 これはvisionOS向け脱出ゲームアプリの マーケティングWebサイトです。 訪問者をゲームの脱出部屋の ひとつへと誘います。 薄暗い部屋に、謎めいた映像が流れる テレビ画面があります。 映像が再生されると、訪問者はこの扉の 奥に何があるのかを探るよう促されます。 アプリをダウンロードして 自分で確かめてみるよう誘導します。 この数分間で、最初の Webサイト環境を構築するために 必要なことをすべて 学ぶことができます。 まず、Web向けイマーシブAPIの 概要を説明します。 次に、ページ内で環境を インラインでプレビューする方法をご紹介します。 その後、イマーシブにする方法、 つまりインラインプレビューから 劇場に入る方法と、 脱出部屋への入り方を説明します。 最後に、映像・アニメーション・ シャドウキャストを使って 体験を最適化しながら、 優れたランタイムパフォーマンスを 維持する方法をご案内します。
では、このイマーシブAPIを 紹介しましょう。
すべては HTML model 要素から始まります。 これにより、Webサイト上に 3Dモデルを表示できます。 使用するには、3Dアセットリソースを 指定する必要があります。 ここでは、ティーポットを表す USDZファイルを使用します。 そして環境マップも指定できます。 360度画像であり、 シーン周囲の光の情報を 取り込んでいます。 環境マップを使えば、 劇的な反射効果を加えられます。 このティーポットのような光沢のある物体に 照明を当てることもできます。 3Dモデルと環境マップのアセットは、 さまざまなツールで 作成できます。 このセッションではBlenderを使いますが、 USDZファイルを書き出せる ツールであれば何でも使えます。
このHTML要素についてさらに 詳しく知りたい場合は、 「Get started with the HTML Model Element」 というセッションをご覧ください。 要素自体と、すべてのプラットフォームでの 動作について深く掘り下げています。 では、イマーシブな部分に入っていきましょう JavaScript Fullscreen API に 慣れている方であれば、 すぐに馴染めるはずです。 慣れていなくても、 すぐに分かるようになります。 イマーシブAPIは、 すでに広く使われている Fullscreen APIのパターンに従っています。 requestFullscreen JavaScript APIを通じて 映像をフルスクリーンにするのと 同じように、 requestImmersive JavaScript APIを通じて model要素をイマーシブにします。 requestImmersive JavaScript API を使います。 同様に、 イマーシブプレゼンテーションを終了したり、 機能が利用可能かどうかを検出したり、 現在イマーシブ要素があるかどうかを 確認したり、 変更を監視したり、 エラーイベントを取得したり、 CSSの擬似クラスを使ってスタイルシートで 直接レイアウトをカスタマイズすることもできます。 しかし、Fullscreen APIとは異なり、 Fullscreen APIはWebページのコンテンツを 指定した要素に置き換えますが、 イマーシブAPIはmodel要素を ブラウザの境界を超えて 運び出しながら、 ページを表示し続けます。 両方のAPIを同時に有効にできるので、 フルスクリーンのビデオプレーヤーを表示しながら 仮想環境に没入することが できます。 このAPIの素晴らしいところは、 model要素への1回のリクエスト呼び出しだけで 非常にシンプルに使えること、 あるいはその全機能を使い、 他のAPIと組み合わせて 素晴らしい体験を作れることです。 先ほどご紹介したような イマーシブなWebサイトを 今から深く掘り下げていきます。 まず、会場チケット販売Webサイトから。 環境のインラインプレビューを 提供しています。
インラインプレビューは、訪問者に 環境を最初のステップとして 紹介する優れた方法です。 今から魅力的なプレビューの 作り方をご紹介します。 こちらが私のWebサイトです。 シミュレータでローカル実行しています。 訪問者が座席を選択すると、 このモデルが表示されますが、 まだ空の状態にしてあります。 ここにインラインプレビューを 追加したいと思います! この空のHTML divに、 model要素を追加します。 劇場モデルのUSDZファイルと ライティングマップを指定します。
インラインプレビューには、劇場モデルが 外側から全体表示されています。 これは、デフォルトでモデルが 要素の境界に収まるようスケールされるためです。 この場合、あまり面白みのある 表示ではありませんね。 内側から劇場を 見せたいところです! このデフォルトのフィッティング動作を なくすには、 エンティティトランスフォームを カスタマイズする必要があります。 これはモデルの位置・回転・ スケールを制御するものです。 コードでは、model要素を取得した後、 読み込みが完了して準備ができるまで 待機し、 単位行列を作成します。 そしてモデルのエンティティトランスフォームに 設定します。 これにより、モデルへの すべての変換が実質的に除去されます。 現在、モデルの床はレイヤーの 中心に位置しています。 これは、劇場モデルの原点が 床にあるためです。 しかしここでの目的は、環境を 人間の目の高さで表示することです。 そのため、モデルを 1メートル下に移動します。 これは、普通の椅子に座った ルームメイトの目の高さを計測した値です。
これは良くなりましたね! しかし今は、 ステージからの眺めが表示されています。 実は、なかなか素晴らしい眺めですが、 プレビューの視点を 実際の座席選択に合わせたいと思います。 それを実現するために、 各座席を劇場内の位置に マッピングするJSONファイルを作成しました。 各エントリには、モデルの原点から 座席の底部への 平行移動が含まれており、 座席がステージに向いている 向きを表す角度も含まれています。 値はWebで使われる 右手系Y-up座標系で表されています。 これはWebで使われる慣例です。 Webサイトのコードに戻ると、 正しいトランスフォームを構築するための 新しい関数を作成しました。 現在は、モデルを目の高さで プレビューするための平行移動が含まれています。 現在選択されている座席を パラメータとして追加できます。 対応する座席の回転を適用し、 座席の視点に合わせた 平行移動を行います。 こうして、座席を選択すると、 その座席の位置からの 視点がモデルに表示されます。 このインラインプレビューは、macOSやiOSなどの 他のプラットフォームでも動作します! すでにとても素晴らしい 体験だと思います。
しかし、本当の魔法はここから始まります。 チケット購入者が 劇場の中に入れるようになる瞬間です! ここで、空間プラットフォームの フルポテンシャルが解放されます。 visionOSのような空間プラットフォームで、 イマーシブにしましょう!
まず、イマーシブAPIが 利用可能かどうかを確認します。 このプロパティは、現在のブラウザが イマーシブプレゼンテーションに対応しているかを示します。 これを確認することで、 イマーシブプレビューボタンを安心して表示でき、 その機能が実際にサポートされている 場所にのみ表示されます。 次に、モデルへの イマーシブトランジションをリクエストします。 これは、ユーザーの操作に 応じて行われる必要があります。 この場合は、 イマーシブボタンのタップです。 ひとつ重要な点として、インラインモデルと イマーシブモデルでは 参照フレームが異なります。 原点とスケールが それぞれ異なります。 インライン表示の場合、先ほど示したように、 model要素の原点は インラインレイヤーの中心にあり、 スケールはCSSの 慣例に従っています。 イマーシブの場合、原点は 人の足元、床の上にあり、 スケールは現実世界に 忠実です。 また、イマーシブ環境は Safariのウィンドウの後ろから 開くことを 覚えておいてください。 そのため、モデルのメインフォーカスを 見えるように配置し、 ウィンドウを移動せずに済むよう 心がけてください。 私のユースケースでは、 ビルドトランスフォーム関数に戻り、 2つ目のパラメータを追加しました。 これにより、モデルが イマーシブで表示されているかどうか、 つまりページ内のインライン表示と 区別できます。 イマーシブの場合、わずかな回転を加えることで、 ここでのメインフォーカスであるステージが Safariのウィンドウの後ろに 隠れないようにします。 また、目の高さへの 平行移動は、 ページ内でインライン表示する場合にのみ 行われるようにします。 エンティティトランスフォームはモデルの イマーシブ状態に依存するため、 モデルがイマーシブを切り替えるたびに 更新する必要があります。 model要素のイマーシブ変更イベントを 監視することが、 これを実現する 正しい方法です。 ここでは、現在のドキュメントの イマーシブ状態を確認し、 適切なフラグでエンティティトランスフォームを 再計算します。 そしてここで、ページレイアウトも 現在の状態を反映するよう更新します。 現在の状態を反映させます。 私の場合、イマーシブに切り替えると、 モデルインターフェイスを調整し、 終了ボタンを表示します。 イマーシブ体験には、 明確な終了の手がかりを 提示することが重要です。 しかし、Apple Vision Proを使う訪問者は、 いつでもDigital Crownを使って イマーシブ環境を終了できることも覚えておいてください。 そのため、UIがイマーシブ状態に依存する場合は、 イマーシブ状態の変化を 監視し、 それに応じてレイアウトを 更新することが重要です。 こうして、私はチケット購入者を 劇場の内部へ 転送する体験を構築しました。 選んだ座席にそのまま座った状態で。 周りを見渡したり、 ステージの眺めを確認したり、 バルコニーから身を乗り出したり… おお!かなり高いですね。 下に落ちたくはないですね。 安全柵につかまってください…… え?! ここの安全柵は どうなっているんですか? うっ…すみません、話が逸れました。 この体験は没入感がありすぎます。 では、話を変えましょう。 少し謎めいた内容に 踏み込んでみます。 visionOS向け脱出ゲームアプリを 作ったとしましょう。 このゲームのために、素晴らしい環境を 作り込むのに何時間も費やしてきました。 イマーシブAPIを使えば、 その同じ環境モデルを使って 忘れられない マーケティング体験を作れます。 Webサイトから直接、Safari上で。 作り方をお見せしましょう。 コードがこれほど少なくて済むのに驚きました。 ここでは、model要素を使えば インラインプレビューの作成は 簡単にできますが、 サプライズはそのままにしておきたいと思います。 HTMLコードにmodel要素を追加する際、 単純にdisplayをnoneに設定します。 これにより、インラインレイヤーが ページから非表示になりますが、 モデルをイマーシブとしてリクエストすることは できます。 インラインプレビューをこのように非表示にすることには 実用的なメリットがあります。 アセットはイマーシブリクエストが 実際に発生するまで ダウンロードも デコードもされません。 重い環境モデルの場合、 訪問者が実際に環境に入らなければ、 帯域幅とメモリを 大幅に節約できます。 次にすることは、ボタンのクリックイベントで 脱出部屋モデルを イマーシブとしてリクエストするだけです。 モデルがインラインで事前読み込みされていないため、 イマーシブリクエストには少し時間がかかる場合があります。 特に重くて複雑な アセットの場合はなおさらです。 後ほど、アセットを最適化することで この読み込み時間を短縮する方法を ご紹介します。 しかし今は、 少なくとも訪問者に 何かが起きているというフィードバックを 興味深いローディングアニメーションで 伝えます。
コードでは、イマーシブリクエストの前に 表示しており、 完了したら非表示にします。 環境に入るまでの 手順はこれだけです! とても簡単ですね!
では、この脱出部屋を visionOS上で 生き生きとさせる機能について お話しします。 シンプルな体験を大きく引き上げる 細かいポイントです。 まず映像から始めます。 Webサイトのインラインで 映像を再生する代わりに、 映像ドッキング機能によって 映像を高め、 環境の内部に 直接配置できます。 テレビ画面、プロジェクター、 または看板など、 ストーリーに最も合う 表面に設置できます。 さらに、映像からの光を 拡散させたり、 反射させたりする マテリアルを追加できます。 これにより、映像がシーンの 一部のように感じられます。 この体験を作るには、 USDZファイルにカスタムの RealityKitアノテーションを追加する必要があります。 これらはまだ標準ではありませんが、 私はBlenderの大ファンなので、 環境を作成しながら これらのコンポーネントを直接追加できる 小さなプラグインを作りました。 ここでは、テレビ画面をタグ付けして、 シーンの映像ドッキング 領域に設定しています。 また、この同じBlender拡張機能を使って、 映像の光がマテリアルに 溢れる効果のベイクを容易にしています。 これらの新しいプロパティでアセットを エクスポートしたら、 次にすることは、映像への フルスクリーントランジションをリクエストするだけです。 ここでは、demoButtonのクリックで リクエストしています。 これにより、映像が自動的に 環境内の適切な場所に転送され、 Safariのウィンドウが 非表示になります! 映像が環境に 完全に統合されています。 テレビからの光が 床や壁に広がり、 空間のリアリティを 劇的に高めています。 次に、驚きの展開として、 この謎めいたドアを スライドして開かせる方法をご紹介します。
このドアを開くアニメーションを Blenderで作成しました。 モデルをエクスポートする 直前に作ったものです。 あとは、適切なタイミングで 再生するだけです。 この数行で、映像のendedイベントを 監視し、 フルスクリーン映像を終了して、 映像のドッキングを解除し Webサイトを戻します。 最後に、モデルのアニメーションを再生します。 さあ、部屋が変わり、 ドアがスライドして開き、 謎はさらに深まります。 モデルアニメーションはとても強力です。 アニメーションのタイムライン全体を 作成することもできます。 モデルのcurrentTimeプロパティを使って タイムラインをナビゲートし、 環境を複数の段階で 変化させることができます。 詳しく知りたい方には、 「What's new for the spatial web」 というセッションをお勧めします。 こうした可能性について より深く掘り下げています。 では、もうひとつ 楽しいことをご紹介します。 Safariのウィンドウが環境に 影を落とす仕組みをご紹介します。 私はこのディテールを とても大切にしています。 この影によって、人々は空間内での ウィンドウの 位置関係を 理解しやすくなり、 環境の一部として 感じられるようになります。 これを有効にするにも RealityKitアノテーションが必要です。 同じBlender拡張機能を使って、 影を受けるべきメッシュに Scene Understandingコンポーネントで タグを付けます。 これ専用に最適化された ローポリメッシュを作成しました。 複雑なメッシュで影を計算すると 多くのリソースを消費するためです。 では、パフォーマンスについて 簡単にまとめてセッションを締めくくります。 環境モデルは、シンプルな オブジェクトモデルよりも 重くてレンダリングが 複雑になりがちです。 アセットを最適化するために できることをご紹介します。 よりスムーズなレンダリングと 高速なダウンロードを実現します。 まず、アセットの頂点数を削減します。 脱出部屋では、原点に立った ビューワーから 見えないメッシュを エクスポートしないようにしました。 こうすることで、 頂点数を大幅に削減でき、 誰も気づきません。 次に、エンティティ数を削減します。 私の場合、デスクをすべての 装飾品と統合して、 個別のエンティティが 多くなりすぎないようにしました。 適切な場合はローポリメッシュを使用します。 Scene Understandingコンポーネントで 使用したものがその例です。 低コストで シャドウキャストを実現できます。 シェーダーはできるだけ シンプルに保ちます。 脱出部屋では、すべての照明を マテリアルにベイクしました。 つまり、光と影を テクスチャに描き込みました。 これにより、アンリットマテリアルにでき、 実行時の重いシェーディング計算を 省略できます。 最後に、usdcrushツールを使って USDZのテクスチャを圧縮します。 Macのコマンドラインツールとして 利用でき、 モデルのサイズを 大幅に削減できます。 これは直接、 読み込み時間の短縮につながり、 接続速度が遅い人にも メリットがあります。 WWDCセッションの 「Optimize your custom environments for visionOS」 もご覧ください。3Dアセットの最適化について より深く掘り下げています。 さて、ここまでお伝えしました。 これで、自分だけの 体験を作る準備が整いました。 顧客を自分の環境へと 転送できます。 model要素と数回の API呼び出しだけで、 訪問者がWebサイトと やり取りする方法を再定義できます。
ここではまだ表面をなぞっただけです。 組み合わせられる 他のAPIはたくさんあります。 Webサイトにイマーシブな 次元をもたらすために。 そのひとつが画像コントロールAPIです。 ここでimage要素にcontrolsアトリビュートを 追加するだけで、 ブラウザがネイティブコントロールを 提供します。 プラットフォーム固有の機能に合わせた 適切なユーザーインターフェイスを提供します。 visionOSでは、このパノラマを フルスクリーンにすることができ、 空間の中で 自分を包み込むように表示されます。 これは空間写真にも対応しています。 Apple Vision ProやiPhoneから 直接撮影することができます。 model要素やイマーシブAPIと同様に、 画像コントロールでも、 コードのちょっとした変更が 大きな効果をもたらします。 限界はクリエイティビティだけです。 次にやってみることをお勧めします。
webkit.orgのオンラインデモを Apple Vision Proで試してみてください。 機能の影響を理解するには、 自分で体験するのが 一番の方法です。 最初のWebサイト環境を 作ってみましょう。 このセッションには、さらに役立つリソースを 添付しています。 API仕様などを ぜひご覧ください! そして最後に、自分だけの体験を 作りながら、 bugs.webkit.orgに機能リクエストや バグレポートを提出してください。 また、このセッションもご覧ください。 「Design immersive environments for visionOS apps and the spatial web」 優れた体験を作る ハイレベルの原則を探ります。 フォトリアルな優れた 環境の作り方についてです。 皆さんがどんなイマーシブ体験を 作るか、楽しみにしています。 このセッションに参加いただき、 ありがとうございました。 楽しんでいただけたなら幸いです! 素晴らしいWWDCを!
-
-
1:51 - Basic model element
<model src="teapot.usdz"> </model> -
2:06 - Model element with environment map
<model src="teapot.usdz" environmentmap="kitchen.hdr"> </model> -
4:40 - Adding the environment model on the page for inline preview
<div class="seat-preview"> <model id="theater" src="theater-model.usdz" environmentmap="theater-lighting.hdr"> </model> </div> -
5:14 - Reset the model entity transform
const theater = document.getElementById("theater"); async function updateModelTransform() { // Make sure the model is loaded await theater.ready; // Create a transform matrix const identity = new DOMMatrix(); // Apply the transform matrix to the model theater.entityTransform = identity; } updateModelTransform(); -
5:42 - Translate the model down
const theater = document.getElementById("theater"); async function updateModelTransform() { // Make sure the model is loaded await theater.ready; // Create a transform matrix const transform = new DOMMatrix(); // Translate model down, for eye level preview transform.translateSelf( 0, // x -1.0, // y 0 // z ); // Apply the transform matrix to the model theater.entityTransform = transform; } updateModelTransform(); -
6:40 - Build the seat transform
function buildTransform(seat) { const transform = new DOMMatrix(); const { x, y, z, ry } = seat; // Rotate and translate the model to match // the seat's origin and orientation transform.rotateSelf(0, -ry, 0); transform.translateSelf(-x, -y, -z); // Translate the model down, for eye level preview transform.translateSelf(0, -1.0, 0); return transform; } -
7:16 - Detect feature availability
if (document.immersiveEnabled) { immersiveButton.hidden = false; } -
7:34 - Request the immersive transition on the model
immersiveButton.addEventListener("click", async () => { await model.requestImmersive(); }); -
8:24 - Build immersive transform
function buildTransform(seat, immersive) { const transform = new DOMMatrix(); // [...] Seat transform logic if (immersive) { // Rotate to the left transform.rotateSelf( 0, // x 45, // y 0 // z ); } else { // [...] Eye level translation } return transform; } -
9:01 - Update the entity transform and the layout on immersive state updates
theater.addEventListener("immersivechange", () => { const isImmersive = !!document.immersiveElement; const transform = buildTransform(isImmersive, currentSeat); theater.entityTransform = transform; document.body.classList.toggle("immersive", isImmersive); }); -
10:53 - Hide the inline preview
<model id="escapeRoom" src="escape-room.usdz" environmentmap="room-lighting.hdr" style="display: none"> </model> -
11:25 - Request an immersive transition on the escape room model
const enterButton = document.getElementById("enterButton"); const escapeRoom = document.getElementById("escapeRoom"); enterButton.addEventListener("click", () => { await escapeRoom.requestImmersive(); }); -
11:52 - Handle the request result and show a loading animation
enterButton.addEventListener("click", async () => { showLoadingAnimation(); try { await escapeRoom.requestImmersive(); } catch (error) { console.log(error); } finally { hideLoadingAnimation(); } }); -
13:16 - Dock the video in the environment with the fullscreen API
const trailerVideo = document.getElementById("trailerVideo"); const demoButton = document.getElementById("demoButton"); demoButton.addEventListener("click", async () => { await trailerVideo.requestFullscreen(); }); -
14:01 - Play the model animation
const trailerVideo = document.getElementById("trailerVideo"); const escapeRoom = document.getElementById("escapeRoom"); trailerVideo.addEventListener("ended", async () => { await document.exitFullscreen(); escapeRoom.play(); }); -
16:38 - Compress your USDZ with usdcrush
usdcrush model.usdz -o optimized.usdz
-
-
- 0:00 - Introduction
The immersive API in visionOS Safari is previewed through two example websites — a theater ticket sales experience and an escape-room marketing site — that transport visitors into virtual environments using just a few lines of code.
- 1:46 - Meet the immersive API
Get a high-level overview of how the HTML `
` element pairs with the new JavaScript `requestImmersive()` API and a `:immersive` CSS pseudo-class. Unlike the Fullscreen API, the immersive API opens an environment around your existing webpage rather than replacing its content. - 4:16 - Preview environments inline
Build the inline portion of the ticket sales site: load a theater model into the page, let visitors pick a seat by applying a `DOMMatrix` transform to the `
` element, and prepare the same model for an immersive transition. - 7:01 - Go immersive
Transition from the inline preview into a full immersive environment. Covers the difference between inline and immersive coordinate systems, listening to `immersivechange` events, dismissing the environment, and skipping the inline preview entirely for the escape-room marketing site.
- 12:04 - Optimize the experience
Polish your environment with RealityKit annotations authored in Reality Composer Pro or via a Blender plugin. Dock playing video into a TV inside the scene, trigger model animations from JavaScript, cast Safari's window shadow with the Scene Understanding component, and reduce vertex/entity counts to keep assets fast to load and render.
- 17:17 - Image controls
Add a single `controls` attribute to an `
` element to give visitors an immersive viewing affordance for spatial photos — a small markup change that pairs naturally with model-based environments.
- 18:09 - Next steps
Try the immersive demos on webkit.org with an Apple Vision Pro, file feedback at bugs.webkit.org, and watch "Design immersive environments for visionOS apps and the spatial web" for the design principles behind great photorealistic environments.