没入型ウェブとは、ブラウザでホストされる仮想世界体験を意味します。このバーチャル リアリティ エクスペリエンス全体が、ブラウザまたは VR 対応ヘッドセットに表示されます。
没入型ウェブとは、ブラウザでホストされる仮想世界体験のことです。ブラウザや、Google の Daydream、Oculus Rift、Samsung Gear VR、HTC Vive、Windows Mixed Reality Headsets などの VR 対応ヘッドセットや、AR 対応モバイル デバイス用に開発された拡張現実(AR)エクスペリエンス全体をカバーしています。
没入型エクスペリエンスの説明に 2 つの用語を使用していますが、これらは、完全な現実から完全に没入型の VR 環境までのスペクトルであり、その間にさまざまなレベルの AR があります。
没入型の体験の例:
- 臨場感のある 360° 動画
- 臨場感のある環境に表示される従来型の 2D(または 3D)動画
- データの可視化
- ホーム ショッピング
- アート
- 誰も思いつかないようなクールなもの
そこにはどうやって行けばよいですか?
没入型ウェブは、初期段階ではありますが、1 年近く利用可能になっています。これは、Chrome 62 以降のオリジン トライアルで利用可能な WebVR 1.1 API を介して行われました。この API は、Firefox と Edge でもサポートされています。また、Safari 用のポリフィルもサポートされています。
それでは次に進みましょう。
オリジン トライアルは 2018 年 7 月 24 日に終了し、仕様は WebXR Device API と新しいオリジン トライアルに置き換えられました。
WebVR 1.1 はどうなったのですか?
WebVR 1.1 から多くのことを学びましたが、デベロッパーが構築するアプリケーションの種類をサポートするには、いくつかの大きな変更が必要であることが明らかになりました。学んだ教訓の全リストは長すぎるためここでは説明しません。主な教訓としては、API がメインの JavaScript スレッドに明示的に関連付けられていること、デベロッパーが明らかに間違った構成を設定できる機会が多すぎること、マジック ウィンドウなどの一般的な用途が意図された機能ではなく副作用であることなどがあります。(マジック ウィンドウは、ヘッドセットなしで没入型コンテンツを表示する手法です。アプリはデバイスの向きセンサーに基づいて単一のビューをレンダリングします)。
新しい設計により、実装が簡素化され、パフォーマンスが大幅に向上します。同時に、AR などのユースケースが登場し、将来的にそれらをサポートできるように API を拡張することが重要になりました。
WebXR Device API は、これらの拡張されたユースケースを念頭に置いて設計および命名されており、より優れた方法を提供します。WebVR の実装者は WebXR Device API への移行を約束しています。
WebXR Device API とは何ですか?
以前の WebVR 仕様と同様に、WebXR Device API は、Google、Microsoft、Mozilla などのコントリビューターが参加する Immersive Web Community Group の成果物です。XR の 'X は、没入型エクスペリエンスのあらゆることを表す一種の代数変数として意図されています。これは、前述のオリジン トライアルで利用できるほか、ポリフィルでも利用できます。
この記事は Chrome 67 ベータ版の期間中に公開された時点では、VR 機能のみが有効になっていました。拡張現実は Chrome 69 で導入されました。詳しくは、ウェブ向けの拡張現実をご覧ください。
この新しい API には、このような記事では説明しきれない部分があります。ここでは、WebXR サンプルを理解するのに十分な情報を提供します。詳しくは、元の説明と没入型ウェブ先行ユーザー ガイドをご覧ください。後者は、オリジン トライアルの進展に伴い拡大していく予定です。問題を報告したり、プル リクエストを送信したりしてください。
この記事では、XR セッションの開始、停止、実行について説明します。また、入力処理の基本についても説明します。
ここでは、AR / VR コンテンツを画面に描画する方法については説明しません。WebXR Device API には、画像レンダリング機能はありません。ご判断にお任せします。描画は WebGL API を使用して行われます。野心があるなら、そうすることもできます。ただし、フレームワークを使用することをおすすめします。没入型ウェブのサンプルでは、デモ専用に作成された Cottontail を使用します。Three.js は 5 月から WebXR をサポートしています。A-Frame については何も聞いていません。
アプリの起動と実行
基本的なプロセスは次のとおりです。
- XR デバイスをリクエストします。
- 利用可能な場合は、XR セッションをリクエストします。ユーザーがスマートフォンをヘッドセットに装着することを希望する場合は、没入型セッションと呼ばれ、ユーザーのジェスチャーで開始する必要があります。
- このセッションを使用して、1 秒あたり 60 の画像フレームを提供するレンダリング ループを実行します。各フレームで適切なコンテンツを画面に描画します。
- ユーザーが終了するまでレンダリング ループを実行します。
- XR セッションを終了します。
コードを含めて、もう少し詳しく見てみましょう。これから説明する内容からアプリを実行することはできません。なお、これはあくまでも概要を説明するためのものです。
XR デバイスをリクエストする
ここでは、標準の機能検出コードがわかります。これは、checkForXR()
などの関数でラップできます。
没入型セッションを使用していない場合は、機能のアドバタイズとユーザー ジェスチャーの取得をスキップして、セッションのリクエストに直接進むことができます。没入型セッションは、ヘッドセットが必要なセッションです。没入型以外のセッションでは、デバイスの画面にコンテンツが表示されるだけです。前者は、バーチャル リアリティや拡張現実について話すときに、ほとんどの人が思い浮かべるものです。後者は「マジック ウィンドウ」と呼ばれることもあります。
if (navigator.xr) {
navigator.xr.requestDevice()
.then(xrDevice => {
// Advertise the AR/VR functionality to get a user gesture.
})
.catch(err => {
if (err.name === 'NotFoundError') {
// No XRDevices available.
console.error('No XR devices available:', err);
} else {
// An error occurred while requesting an XRDevice.
console.error('Requesting XR device failed:', err);
}
})
} else{
console.log("This browser does not support the WebXR API.");
}
XR セッションをリクエストする
デバイスとユーザー ジェスチャーが用意できたので、セッションを取得します。セッションを作成するには、描画するキャンバスが必要です。
xrPresentationContext = htmlCanvasElement.getContext('xrpresent');
let sessionOptions = {
// The immersive option is optional for non-immersive sessions; the value
// defaults to false.
immersive: false,
outputContext: xrPresentationContext
}
xrDevice.requestSession(sessionOptions)
.then(xrSession => {
// Use a WebGL context as a base layer.
xrSession.baseLayer = new XRWebGLLayer(session, gl);
// Start the render loop
})
レンダリング ループを実行する
このステップのコードは少し複雑です。これを解き明かすために、これからたくさんの言葉を投げかけます。最終的なコードを確認したい場合は、先に進んで確認してから、戻って詳細を説明します。推測できないことがかなりあります。
レンダリング ループの基本的なプロセスは次のとおりです。
- アニメーション フレームをリクエストします。
- デバイスの位置をクエリします。
- デバイスの位置に基づいて、デバイスの位置にコンテンツを描画します。
- 入力デバイスに必要な作業を行います。
- ユーザーが終了するまで、1 秒間に 60 回繰り返します。
プレゼンテーション フレームをリクエストする
Web XR のコンテキストでは、「フレーム」という言葉にはいくつかの意味があります。1 つ目は参照フレームで、座標系の原点がどこから計算されるか、デバイスが移動したときにその原点に何が起こるかを定義します。(ユーザーが動いてもビューは同じままですか?それとも現実世界のように移動しますか?)
2 つ目のフレームタイプは、XRFrame
オブジェクトで表されるプレゼンテーション フレームです。このオブジェクトには、AR/VR シーンの 1 つのフレームをデバイスにレンダリングするために必要な情報が含まれます。プレゼンテーション フレームは requestAnimationFrame()
を呼び出して取得されるため、これは少し混乱します。これにより、window.requestAnimationFrame()
との互換性が確保されます。
理解を深めるために、コードをいくつか紹介します。以下のサンプルは、レンダリング ループの開始方法と維持方法を示しています。フレームという単語の二重使用に注目してください。また、requestAnimationFrame()
の再帰呼び出しに注目してください。この関数は 1 秒間に 60 回呼び出されます。
xrSession.requestFrameOfReference('eye-level')
.then(xrFrameOfRef => {
xrSession.requestAnimationFrame(onFrame(time, xrFrame) {
// The time argument is for future use and not implemented at this time.
// Process the frame.
xrFrame.session.requestAnimationFrame(onFrame);
}
});
ポーズ
画面に何かを描画する前に、ディスプレイ デバイスが指している場所を把握し、画面にアクセスする必要があります。一般に、AR/VR で物体の位置と向きをポーズと呼びます。視聴者と入力デバイスの両方にポーズがあります。(入力デバイスについては後で説明します)。ビューアと入力デバイスのポーズは、どちらも 4 行 4 列のマトリックスとして定義され、Float32Array
に列優先で保存されます。現在のアニメーション フレーム オブジェクトで XRFrame.getDevicePose()
を呼び出すと、ビューアのポーズを取得できます。ポーズが返されたかどうかは必ずテストしてください。問題が発生した場合に、画面に描画しないようにします。
let pose = xrFrame.getDevicePose(xrFrameOfRef);
if (pose) {
// Draw something to the screen.
}
視聴回数
ポーズを確認したら、描画を始めましょう。描画するオブジェクトはビュー(XRView
)と呼ばれます。ここでセッション タイプが重要になります。ビューは、XRFrame
オブジェクトから配列として取得されます。没入型以外のセッションの場合、配列には 1 つのビューがあります。没入型セッション中は、アレイは左右の目に 1 つずつ、2 つあります。
for (let view of xrFrame.views) {
// Draw something to the screen.
}
これは、WebXR と他の没入型システムの重要な違いです。1 つのビューを反復処理するのは無意味に思えるかもしれませんが、これにより、さまざまなデバイスで単一のレンダリング パスを使用できます。
レンダリング ループ全体
これらをすべてまとめると、以下のコードになります。入力デバイスのプレースホルダも残しています。これについては後のセクションで説明します。
xrSession.requestFrameOfReference('eye-level')
.then(xrFrameOfRef => {
xrSession.requestAnimationFrame(onFrame(time, xrFrame) {
// The time argument is for future use and not implemented at this time.
let pose = xrFrame.getDevicePose(xrFrameOfRef);
if (pose) {
for (let view of xrFrame.views) {
// Draw something to the screen.
}
}
// Input device code will go here.
frame.session.requestAnimationFrame(onFrame);
}
}
XR セッションを終了する
XR セッションは、XRSession.end()
の呼び出しによる独自のコードによる終了など、さまざまな理由で終了することがあります。ヘッドセットが切断されている、または別のアプリがヘッドセットを制御している場合も、この問題が発生することがあります。そのため、正常に動作しているアプリは終了イベントをモニタリングし、終了イベントが発生したら、セッション オブジェクトとレンダラ オブジェクトを破棄する必要があります。終了した XR セッションは再開できません。
xrDevice.requestSession(sessionOptions)
.then(xrSession => {
// Create a WebGL layer and initialize the render loop.
xrSession.addEventListener('end', onSessionEnd);
});
// Restore the page to normal after immersive access has been released.
function onSessionEnd() {
xrSession = null;
// Ending the session stops executing callbacks passed to the XRSession's
// requestAnimationFrame(). To continue rendering, use the window's
// requestAnimationFrame() function.
window.requestAnimationFrame(onDrawFrame);
}
インタラクションの仕組み
アプリの存続期間と同様に、AR や VR でオブジェクトを操作する方法を紹介します。
WebXR Device API は、ユーザー入力に対して「ポイント&クリック」のアプローチを採用しています。このアプローチでは、すべての入力ソースに、入力デバイスが指している場所を示すポインタ レイと、何かが選択されたことを示すイベントが定義されています。アプリはポインタ レイを描画し、ポインタが指している場所を示します。ユーザーが入力デバイスをクリックすると、select
、selectStart
、selectEnd
のイベントが発生します。アプリは、クリックされた内容を判断し、適切に応答します。
入力デバイスとポインタ レイ
ユーザーにとっては、ポインタ光線はコントローラと指先との間のわずかな線です。ただし、アプリで描画する必要があります。つまり、入力デバイスのポーズを取得し、その位置から AR / VR 空間内のオブジェクトに線を引く必要があります。この処理はおおむね次のようになります。
let inputSources = xrSession.getInputSources();
for (let xrInputSource of inputSources) {
let inputPose = frame.getInputPose(inputSource, xrFrameOfRef);
if (!inputPose) {
continue;
}
if (inputPose.gripMatrix) {
// Render a virtual version of the input device
// at the correct position and orientation.
}
if (inputPose.pointerMatrix) {
// Draw a ray from the gripMatrix to the pointerMatrix.
}
}
これは、Immersive Web Community Group の入力トラッキング サンプルを短縮したバージョンです。フレーム レンダリングと同様に、ポインタ レイとデバイスの描画は任意です。前述のように、このコードはレンダリング ループの一部として実行する必要があります。
仮想空間でアイテムを選択する
AR や VR で物体を指すだけでは、あまり意味がありません。何か有用なことをするには、ユーザーが選択できる必要があります。WebXR Device API には、ユーザー操作に応答するための 3 つのイベント(select
、selectStart
、selectEnd
)が用意されています。想定外の癖があり、入力デバイスがクリックされたことを示すだけです。環境内のどのアイテムがクリックされたかについてはわかりません。イベント ハンドラは XRSession
オブジェクトに追加され、利用可能になり次第追加する必要があります。
xrDevice.requestSession(sessionOptions)
.then(xrSession => {
// Create a WebGL layer and initialize the render loop.
xrSession.addEventListener('selectstart', onSelectStart);
xrSession.addEventListener('selectend', onSelectEnd);
xrSession.addEventListener('select', onSelect);
});
このコードは、入力選択の例に基づいています。詳細なコンテキストが必要な場合は、そちらをご覧ください。
何がクリックされたかを把握するには、ポーズを使用します。(驚きましたか?)詳細は、アプリまたは使用しているフレームワークに固有のものであるため、この記事の範囲外です。Cottontail のアプローチは、入力選択の例で説明しています。
function onSelect(ev) {
let inputPose = ev.frame.getInputPose(ev.inputSource, xrFrameOfRef);
if (!inputPose) {
return;
}
if (inputPose.pointerMatrix) {
// Figure out what was clicked and respond.
}
}
まとめ: 今後の展望
前述のとおり、拡張現実は Chrome 69(2018 年 6 月の Canary)でリリースされる予定です。それでも、これまでにリリースされた機能を試してみることをおすすめします。改善のため、フィードバックのご提供をお願いいたします。ChromeStatus.com の WebXR ヒットテストで進捗状況を確認できます。WebXR アンカーに沿ってポーズ トラッキングを改善することもできます。