WebXR 設定の照明 - Web API

WebXR 設定の照明
===========

WebXR Device API は、シーンのすべてのレンダリング、テクスチャリング、およびライティング（照明）を実行するために他のテクノロジー（つまり、WebGL とそれに基づくフレームワーク）に依存しているため、WebXR 設定またはシーンには、他の WebGL で生成されたディスプレイと同じ一般的な照明の概念が適用されます。

ただし、特に拡張現実（AR）アプリケーションの場合、照明のコードを作成する際に留意すべき問題と詳細があります。 このガイドでは、これらのトピックについて説明します。 そして、この記事では、照明が一般的にどのように機能するかについて簡単に説明しますが、照明のチュートリアルや、適切に照明された 3D シーンを作成する方法のガイドではありません。

フラッシュバック: 3D での照明のシミュレーション
--------------------------

この記事は 3D シーンを照明するための包括的なガイドではありませんが、照明が一般的にどのように機能するかについて簡単に思い出させるのに役立ちます。 基本的に、仮想シーンでの照明のシミュレーションには、シーン内の各オブジェクトと相互作用して反射した後、各光源からの光が目で受け取る量を計算することが含まれます。

### 光の反射

**反射角が入射角にどのように対応するかを示す図。**
![反射角が入射角にどのように対応するかを示す図。](/ja/docs/Web/API/WebXR_Device_API/Lighting/law-of-reflection.svg)

私たちが見るすべてのオブジェクトは、オブジェクトが光を放出または反射する（あるいはその両方）ために見えます。 入ってくる光線（**入射光線**、incident ray）は、**入射角**（angle of incidence）と呼ばれる角度で到達します。 入射角 Θi は、入射光線と表面の法線ベクトルの間の角度です。

粗い表面の場合、光はあらゆる方向に均等に反射されます。 ただし、光沢のある鏡のような表面は、法線ベクトルの反対側にあることを除いて、**反射角**（angle of reflection） Θr が入射角に等しい方向にほとんどの光を反射します。 **反射光線**（reflected ray）は、法線からその角度で出発します。 これが\*\*反射の法則\*\*（law of reflection）です。 これは、シーンのシェーディングに関係する多くの基盤であり、さまざまな種類の光源の振る舞いの観点で役に立ちます。

もちろん、反射光の光線の色は、光が表面と相互作用するために強度や色相が変化する可能性がありますが、角度は常に同じです。

### 光源の構成要素

光源には 3 つの主要な構成要素があります。 各構成要素は本質的に一種の光です。

ビューアーの画面またはヘッドセットに表示されるオブジェクトとそのピクセルの色と明るさに影響を与える可能性のある 3 種類の光があります。

#### 環境光

**環境光**（ambient light）は、定義された光源からではなく、シーン全体に存在する光です。 この光は、シーン内のあらゆる表面にあらゆる方向から同じ強度で到達し、あらゆる方向に均等に反射します。 その結果、環境光によって適用される効果は、シーン全体で普遍的に等しくなります。

**環境照明のみの球。 球の奥行きを示すための陰影がまったくないことに注意してください。**
![環境照明のみを持つ球。 球の奥行きを示すための陰影がまったくないことに注意してください。](/ja/docs/Web/API/WebXR_Device_API/Lighting/sphere-ambient-light-only.jpg)

環境光の効果は、光源の強度にピクセルの位置での表面の反射率を単純に乗算することによって計算されます。 シーン内のあらゆるピクセルの色と強度は、シーン内のどこにあるか、または向きに関係なく、まったく同じように影響を受けます。 環境光は通常、シーン全体に影響を与えますが、影の部分が暗くなりすぎるのを防ぐために存在します。 ただし、シーン内の環境光の量は非常に少なくする必要があります。

光の跳ね返りと散乱はリアルタイムで計算するのにコストがかかる可能性があるため、特に複数の光源が関係している場合は、光散乱の真の効果を実際に計算するのではなく、環境照明を使用してシーン内の他のすべての光源によって引き起こされる散乱光をシミュレートするのが一般的です。 ただし、これを行う場合は、環境光をシーンの照明の実際の効果に一致させるように注意する必要があります。

環境光を使用して、シーンに色合いを適用することもできます。 例えば、プレーヤーが黄色がかった特別な眼鏡を持っているゲームでは、黄色の環境光を追加できます。

#### 拡散光

**拡散光**（diffuse light）は、表面から均一かつ指向性を持って放射または反射する光です。 これは私たちが通常目にする光の大部分です。 拡散光は特定の位置または方向から来て、影を落とします。 その指向性により、拡散光に面しているオブジェクトの面は、他の面よりも明るくなります。

**土星で 5 番目に大きい月テティスは、左下から日光を浴びています。**
![土星で5番目に大きい月テティスは主に太陽に照らされており、土星からの光が反射しています。 これは拡散照明です。](/ja/docs/Web/API/WebXR_Device_API/Lighting/tethys.jpg)

拡散光の強度は入射角（angle of incidence、光が表面に到達する方向を表すベクトルと表面の法線ベクトルまたは表面に垂直なベクトルとの間の角度）に依存するため、オブジェクトによって反射する光の強度または明るさは、光源に対する表面の向きによって異なります。

#### 鏡面反射光

鏡面反射光（specular light）は、宝石、目、光沢のあるカップや皿などの反射オブジェクトのハイライトを構成する光です。 鏡面反射光は、光源が表面に最も直接当たる点の表面に明るいスポットまたは正方形として現れる傾向があります。

**NASA のカッシーニ宇宙船が撮影した写真。 土星の月タイタンの表面にある液体メタンの湖からの光の鏡面反射を示しています。**
![NASA のカッシーニ宇宙船が撮影した写真。 土星の月タイタンの表面にある液体メタンの湖からの光の鏡面反射を示しています。](/ja/docs/Web/API/WebXR_Device_API/Lighting/specularlight-titan.jpg)

すべての光源は、環境光、拡散光、および/または鏡面反射光の組み合わせによって表されます。 WebGL シェーダープログラムは、各光源の色、指向性、明るさ、およびその他の要素を取得し、各ピクセルの最終的な色を計算します。

### 光源の種類

光源には 4 つの基本的な種類があります。 それぞれに、描画されるオブジェクトからの距離と光波の指向性によって光源が特定の特性を帯びる仮想光源が含まれます。 ほとんどの場合、これらの光源タイプの 1 つまたは複数を使用して、現実世界の光源をシミュレートできます。

#### 環境光源

**環境光源**（ambient light source）は、シーン内の環境光のレベルと色を表す光源です。 シーンにはこれらが複数存在する場合がありますが、それぞれが常にすべてのピクセルに均等に影響するため、これらを 1 つにまとめることで、パフォーマンスをわずかに向上させることができます。

環境光源は通常、シーン内のどのオブジェクトにも対応しておらず、現実世界の類似物もありません。

#### 指向性光源

**指向性光源**（directional light source、平行光源）は、特定の方向から来る光源ですが、特定の光源からは来ないため、放出される光線は互いに平行です。 また、光の強さは距離によって変化しません。 これは、指向性光によって投影される影が非常にシャープであり、光と影の間で本質的に瞬時に遷移することを意味します。

**地球と月は両方とも太陽の指向性照明によって半分照らされています。**
![ガリレオ宇宙船が約630万キロメートル離れた場所から撮影した写真で、地球と月の両方が太陽に半分照らされています。](/ja/docs/Web/API/WebXR_Device_API/Lighting/earthandmoon.jpg)

指向性光の最も一般的な例は太陽です。 太陽は実際には単一の（大きな）オブジェクトですが、非常に遠くにあるため、太陽からの光線は基本的に平行です。 太陽光は実際には距離とともに強度が低下しますが、変化率は非常に低く、広大な距離でのみ認識されるため、太陽光の強度変化率は通常、3D シーンのレンダリングには関係ありません。

#### 点光源

**点光源**（point light source）は、特定の場所に配置され、すべての方向に均等に外側に放射する光源です。 電球、ろうそくなどは点光源の例です。 オブジェクトが点光源に近いほど、そのオブジェクトに照射される光は明るくなります。 ポイントライト（point light）の明るさが低下するレートは**減衰**（attenuation）と呼ばれ、WebGL やその他の照明システムの光源の構成可能な機能です。

反射の法則と光線の明るさが距離とともに減少するという事実との間で、点光源から放出されて反射する光は、光源に最も近い点で最も明るくなり、光源から離れるほど暗くなる傾向があります。 表面が平らであっても、光源に最も近い点が中心で、法線から離れるように角度が変化するにつれて光線はますます長くなります。

#### スポット光源

**スポット光源**（spot light source）または**スポットライト**（spotlight）は、特定の位置に配置され、その方向ベクトルの方向に光の円錐を放出する光源です。 テーパリングレートパラメーターは、光の円錐の端で光の明るさがどれだけ速く落ちるかを定義し、ポイントライトと同様に、減衰パラメーターは、光が距離とともにどのようにフェードするかを制御します。

**夜に漆喰の壁を照らすスポットライトの写真。**
![夜に漆喰の壁を照らすスポットライトの写真。](/ja/docs/Web/API/WebXR_Device_API/Lighting/spotlight-on-stucco.jpg)

光の円錐の端では、光は表面にまったく影響を与えなくなります。

#### 照明の計算コスト

シーンを目に見えるようにするには、何らかの照明が含まれている必要があるため、すべてまたはほぼすべてのシーンに少なくとも 1 つの光源があり、非常に多くの光源を持っているかもしれません。 各光源は、表示される各ピクセルの最終的な色と明るさを決定するために必要な計算量を大幅に増やします。 これらの光源の種類のそれぞれに対してシェーディングを実行することは、その前のものよりも計算量が多くなります。 したがって、環境光を適用するのが最も費用がかからず、次に指向性光源、ポイントライト、最後にスポットライトが続きます。

さらに、照明がより正確になるように設計されているほど、計算コストが高くなります。 陰影詳細の増加、体積光（つまり、太陽光のビームや空のスポットライトのビームなど、空中で見ることができる照明）、およびその他の照明効果は、シーンにリアリズムと美しさを追加できますが、シーンが GPU を圧倒しないように注意が必要です。

### 照らされたピクセルの色の計算

一部のグラフィックライブラリーには光源オブジェクトのサポートが含まれており、照明効果を自動的に計算して適用しますが、WebGL には含まれていません。 幸い、照明を独自の頂点シェーダーとフラグメントシェーダーで適用することはそれほど難しくありません。

シーン内のポリゴンごとに、**頂点シェーダー**（vertex shader）のプログラムが頂点の色を決定し、**フラグメントシェーダー**（fragment shader）は、割り当てられたテクスチャー、任意の色合いまたは効果、およびその他の視覚データから適切なテクセルを組み合わせて、ポリゴン内の各ピクセルを生成します。 ピクセルがフレームバッファーに格納される前に、シーンの照明が考慮され、ピクセルに適切に適用されるのはこのときです。

最終的なレンダリングされたシーンの各ピクセルの色は、次のような要素を考慮した複雑な計算を使用して計算されます。

* 画面のピクセルに対応する**テクスチャー要素**（texture element、オブジェクトにマップされたテクスチャー内のピクセル。 **テクセル**（texel）とも呼ばれます）の色。 オブジェクトのジオメトリー、各ポリゴンに対するビューアーの位置と方向などが与えられます。
* ビューアーの位置と距離。
* 表面の材質（マテリアル）と反射率。
* ターゲット位置での表面の凹面または凸面。
* シーン内の各光源の位置、色、指向性、および明るさ。
* シーン内の環境光の色と明るさ。 これは、シーン全体に均等に適用される光であり、光源がないため、影や明るさの変化がありません。
* シーン内の他の表面で反射した光の効果。 反射光の色、指向性、明るさは、光が触れるピクセルの色に影響します。

WebGL で照明を実行する方法の詳細については、WebGL でのライティングの記事を参照してください。

複合現実コンテンツの照明の問題
---------------

シーンの照明中に対処する必要のある通常の問題に加えて、VR または AR のユースケースでは、シェーダーを作成する際の懸念事項が追加されます。 このセクションでは、シーンを構築、レンダリング、および照明するときに考慮すべきいくつかの基本的な複合現実の照明のガイドラインを提供します。 これらのいくつかは他の 3D 設定でも役立ちますが、ほとんどは仮想現実に固有であり、場合によっては拡張現実に固有です。

シーンは、人物またはそのアバターが存在する可能性のある設定を表すことを目的としているため、光源の配置と提示に関して、ある程度の一貫性とリアリズムを実現するように努める必要があります。 明らかに、このガイドラインには例外があります。 例えば、シーンが異世界または異星人の設定を表している場合や、不安な視覚効果を作成することが目標である場合などです。

### 光源配置のリアリズム

可能であれば、仮想光源を実際に存在するオブジェクトに対応させるようにしてください。 頭上の照明が必要な仮想の部屋がある場合は、光源の場所に天井ランプのモデルを用意します。 例外として、設定にベースライン量の照明を追加するだけの環境照明や、指向性光である太陽（つまり、すべての光線が平行で、空のどこかから来てシーン内のどこかで終わる光源）などがあります。

作成しようとしている設定とムードに合わせて、現実的な場所に光源を配置してください。 自然に照らされた現実世界の設定のように感じることを目的としたシーンには、スタジオ照明がありません。 太陽光や、シーン内のオブジェクトや水から反射した光などがありますが、シーン内のオブジェクトや人の顔に向けられたランプはありません。

### 光とプレイヤーの相互作用のリアリズム

光源がシーン内にある場合は、ビューアーのアバターが光源と物理的に交差しないようにする必要があります。 結果は…奇妙なものになるかもしれません。

ビューアーのアバターが物理的な形をとることを意図している場合、ビューアーがそれを決して見ることができない場合でも、光がアバターと正しく相互作用するように、モデルを持っている必要があります。 最低限でも、これはアバターが適切な影を落とす必要があることを意味しますが、アバターが見えるかどうかや、モデルのマテリアル、テクスチャー、その他の属性（特に反射率を含む）などの要因によっては、アバターも光を反射する必要があり、反射した光の着色に影響を与える可能性があります。

### 拡張現実におけるリアリズム

拡張現実は、仮想オブジェクトが独自の光源を持つ物理的な世界内に存在する必要があるため、オブジェクトを照明するのがさらに複雑になります。 そのため、照明を現実世界の光源にできるだけ一致させるようにしてください。 これは、照明推定と呼ばれる手法を使用して行われます。

逆に、それ自体が光源である仮想オブジェクトは、現実世界の設定にその照明を当てることができるコードを作成する準備ができていない限り、使用しないようにする必要があります。 現実世界のオブジェクトに光を当てるのは、基本的に影を付けるのとは逆です。 それは可能ですが、それほど広く実装されていません。

照明推定
----

**照明推定**（lighting estimation）は、拡張現実プラットフォームで使用される手法であり、シーン内の仮想オブジェクトの照明を、ビューアーを取り巻く現実世界の照明に一致させようとします。 これには、さまざまなセンサー（加速度計とコンパスがある場合はそれを含む）、カメラ、および場合によっては他のセンサーから取得される可能性のあるデータの収集が含まれます。 その他のデータは Geolocation API を使用して収集され、このすべてのデータはアルゴリズムと機械学習エンジンに送られ、推定された照明情報が生成されます。

現在、WebXR は照明推定のサポートを提供していません。 ただし、仕様は現在 W3C の支援の下で起草されています。 仕様の GitHub リポジトリに含まれている説明文書で、提案された API のすべてと、照明推定の概念についてかなりの量を学ぶことができます。

本質的に、照明推定は、光源と、シーン内のオブジェクトの形状と方向、およびそれらを構成している材質に関する情報を収集し、現実世界の照明とほぼ一致する仮想光源オブジェクトの作成に使用できるデータを返します。

特に提案された API のコンテキストで、照明推定がどのように機能するかの詳細は、現時点では範囲外です。 API が安定したら、このドキュメントを詳細で更新します。

セキュリティとプライバシーの懸念
----------------

現実世界のデータを使用して仮想オブジェクトに照明を生成および適用するために、このすべてのデータを収集することに関連する潜在的なセキュリティ問題がいくつかあります。

もちろん、多くの AR アプリケーションは、ユーザーがどこにいるかをかなり明確にします。 ユーザーが*ルーブルのツアー*というアプリを実行している場合、ユーザーがフランスのパリにあるルーブル美術館（Musée du Louvre）にいる可能性が非常に高くなります。 ただし、ブラウザーは、ユーザーの同意なしにユーザーの居場所を物理的に特定することを困難にするために、いくつかの手順を実行する必要があります。

### Ambient Light Sensor API

Ambient Light Sensor API (en-US) を使用して光データを収集すると、さまざまな潜在的なプライバシー問題が発生します。

* 照明情報は、ユーザーの周囲やデバイスの使用パターンに関する情報をウェブに漏洩する可能性があります。 このような情報は、ユーザープロファイリングおよび行動分析データを強化するために使用できます。
* 2 つ以上のデバイスが同じサードパーティのスクリプトを使用するコンテンツにアクセスする場合、そのスクリプトを使用して、照明情報と、それが時間の経過とともにどのように変化するかを相互に関連付けて、デバイス間の空間的関係を決定しようとすることができます。 これは、理論的には、例えば、デバイスが大体同じ領域にあることを示すことができます。
### ブラウザーがこれらの問題を軽減する方法

これらのリスクを軽減するために、WebXR Lighting Estimation API 仕様では、ブラウザーは、真の値から多少ずれた照明情報を報告する必要があります。 これを行うには多くの方法があります。

#### 球面調和関数の精度

ブラウザーは、球面調和関数（spherical harmonics）の精度を下げることにより、フィンガープリント（fingerprinting）のリスクを軽減できます。 仮想現実または拡張現実のアプリケーションの場合のように、リアルタイムレンダリングを実行する場合、球面調和関数照明（spherical harmonic lighting）を使用して、非常にリアルな影とシェーディングを生成するプロセスを簡素化および加速します。 これらの機能の精度を変更することにより、ブラウザーはデータの一貫性を低下させ、重要なことに、同じ設定であっても 2 台のコンピューターによって生成されるデータを異なるものにします。

#### 方向の照明からの切り離し

測位（geolocation）を使用して方向と潜在的な位置情報を決定する AR アプリケーションでは、その情報が照明の状態に直接相関しないようにすることは、ブラウザーがフィンガープリント攻撃からユーザーを保護できるもう 1 つの方法です。 ユーザーの位置に近い（または近いと主張する）すべてのデバイスでコンパスの方向と光の指向性が同じではないことを確認するだけで、周囲の照明の状態に基づいてユーザーを見つける能力が取り除かれます。

ブラウザーが非常に明るい指向性光源に関する詳細を提供する場合、その光源はおそらく太陽を表しています。 この明るい光源の指向性を時刻と組み合わせて使用すると、Geolocation API を使用せずにユーザーの地理的位置を特定できます。 AR シーンの座標がコンパス座標と一致しないようにし、太陽の光の角度の精度を下げることにより、この手法を使用して位置を正確に推定できなくなります。

#### 時間的空間的フィルタリング

建物の自動照明システムを使用して、既知のパターンで光をすばやくオン/オフする攻撃について考えてみます。 適切な予防策がなければ、照明推定データを使用してこのパターンを検出し、ユーザーが特定の場所にいると判断する可能性があります。 これはリモートで実行することも、同じ部屋にいるが他の人も同じ部屋にいるかどうかを判断したい攻撃者が実行することもできます。

照明推定を使用してユーザーに関する情報を許可なく取得できる別のシナリオとしては、光センサーがユーザーのディスプレイに十分近く、ディスプレイの内容によって引き起こされる照明の変化を検出する場合、アルゴリズムを使用して、ユーザーが特定の動画を視聴しているかどうかを判断したり、ユーザーが見ている動画の数を特定する可能性さえあります。

Lighting Estimation API 仕様では、すべてのユーザーエージェントが時間的空間的フィルタリング（temporal and spatial filtering）を実行して、ユーザーの位置を特定したりサイドチャネル攻撃（side-channel attacks）を実行したりする目的での有用性を低下させる方法でデータをぼやけさせることを義務付けています。

関連情報
----

* WebXR Lighting Estimation API の説明（英語）
* WebXR Lighting Estimation API Level 1 仕様（英語）
* シェーダーを用いた WebGL での色の指定
* WebGL でのテクスチャの使用
* WebGL でのライティング
* GLSL シェーダー


移動、向き、モーション: WebXR の例 - Web API

移動、向き、モーション: WebXR の例
=====================

この記事では、WebXR チュートリアルシリーズの以前の記事で紹介した情報を利用して、ユーザーが VR ヘッドセット、キーボード、マウスを使用して自由に移動できる回転立方体をアニメーション化する例を作成します。 これは、3D グラフィックスと VR の幾何学がどのように機能するかについての理解を深めるのに役立つだけでなく、XR レンダリング中に使用される関数とデータがどのように連携するかを確実に理解するのに役立ちます。

**この例の実際のスクリーンショット**
![ユーザーが動きまわることができるテクスチャー付き立方体を示す例のスクリーンショット](/ja/docs/Web/API/WebXR_Device_API/Movement_and_motion/xr-sample.png)

この例のコアである、回転するテクスチャー付きで照明付きの立方体は、WebGL チュートリアルシリーズから抜粋したものです。 つまり、シリーズの最後から 2 番目の記事の WebGL でのライティングをカバーします。

この記事と付随するソースコードを読むときは、3D ヘッドセットのディスプレイが半分に分割された単一の画面であることを覚えておくと役に立ちます。 画面の左半分は左目でのみ表示され、右半分は右目でのみ表示されます。 没入型プレゼンテーションのためにシーンをレンダリングするには、各目の視点から 1 回ずつ、シーンを複数回レンダリングする必要があります。

左目をレンダリングする場合、`XRWebGLLayer` (en-US) の `viewport` は、描画を描画面の左半分に制限するように構成されています。 逆に、右目をレンダリングする場合、ビューポートは描画を描画面の右半分に制限するように設定されます。

この例では、XR デバイスを使用して没入型ディスプレイとしてシーンを提示する場合でも、画面上のキャンバスに表示することでこれを示しています。

依存関係
----

この例では、`three.js` などの 3D グラフィックフレームワークに依存しませんが、行列演算には過去に他の例で使用している `glMatrix` ライブラリーを使用します。 この例では、WebXR API の仕様を担当するチームである Immersive Web Working Group によって管理されている WebXR ポリフィルもインポートします。 このポリフィルをインポートすることで、WebXR がまだ実装されていない多くのブラウザーでこの例を機能させることができ、WebXR 仕様のまだ実験的な日々の間に発生する仕様からの一時的な逸脱を滑らかにします。

オプション
-----

この例には、ブラウザーにロードする前に定数の値を調整することで構成できるいくつかのオプションがあります。 コードは次のようになります。

```js
const xRotationDegreesPerSecond = 25;
const yRotationDegreesPerSecond = 15;
const zRotationDegreesPerSecond = 35;
const enableRotation = true;
const allowMouseRotation = true;
const allowKeyboardMotion = true;
const enableForcePolyfill = false;
//const SESSION\_TYPE = "immersive-vr";
const SESSION\_TYPE = "inline";
const MOUSE\_SPEED = 0.003;
```

`xRotationDegreesPerSecond`

X 軸を中心に回転させる 1 秒あたりの度数。

`yRotationDegreesPerSecond`

Y 軸を中心に回転させる 1 秒あたりの度数。

`zRotationDegreesPerSecond`

Z 軸を中心に回転させる 1 秒あたりの度数。

`enableRotation`

立方体の回転を有効にするかどうかを示すブール値。

`allowMouseRotation`

`true` の場合、マウスを使用して視野角をピッチ（上下）したりヨー（左右）したりできます。

`allowKeyboardMotion`

`true` の場合、W、A、S、D キーはビューアーを上、左、下、右に移動し、上下の矢印キーは前後に移動します。 `false` の場合、ビューへの XR デバイスの変更のみを許可します。

`enableForcePolyfill`

このブール値が `true` の場合、ブラウザーが実際に WebXR をサポートしている場合でも、この例では WebXR ポリフィルの使用を試みます。 `false` の場合、ブラウザーが `navigator.xr` を実装していない場合にのみポリフィルを使用します。

`SESSION_TYPE`

作成する XR セッションのタイプ: ドキュメントのコンテキストで提示するインラインセッションの `inline` と、シーンを没入型 VR ヘッドセットに提示する `immersive-vr`。

`MOUSE_SPEED`

ピッチとヨーの制御に使用するマウスからの入力をスケーリングするために使用される乗数。

`MOVE_DISTANCE`

シーン内でビューアーを移動するために使用するキーのいずれかに応答して移動する距離。

**メモ:** この例では、`immersive-vr` モードを使用している場合でも、常に画面にレンダリングされる内容が表示されます。 これにより、2 つのモード間のレンダリングの違いを比較でき、ヘッドセットがない場合でも没入型モードからの出力を確認できます。

セットアップおよびユーティリティ関数
------------------

次に、WebGL および WebXR 固有の情報を格納するために使用されるものから始めて、アプリケーション全体で使用される変数と定数を宣言します。

```js
let polyfill = null;
let xrSession = null;
let xrInputSources = null;
let xrReferenceSpace = null;
let xrButton = null;
let gl = null;
let animationFrameRequestID = 0;
let shaderProgram = null;
let programInfo = null;
let buffers = null;
let texture = null;
let mouseYaw = 0;
let mousePitch = 0;
```

この後に一連の定数が続きます。 これには主に、シーンのレンダリング中に使用されるさまざまなベクトルと行列が含まれます。

```js
const viewerStartPosition = vec3.fromValues(0, 0, -10);
const viewerStartOrientation = vec3.fromValues(0, 0, 1.0);

const cubeOrientation = vec3.create();
const cubeMatrix = mat4.create();
const mouseMatrix = mat4.create();
const inverseOrientation = quat.create();
const RADIANS\_PER\_DEGREE = Math.PI / 180.0;
```

最初の 2 つ（`viewerStartPosition` と `viewerStartOrientation`）は、空間の中心に対してビューアーが配置される場所と、最初に見る方向を示します。 `cubeOrientation` は立方体の現在の方向を格納し、`cubeMatrix` と `mouseMatrix` はシーンのレンダリング中に使用される行列のストレージです。 `inverseOrientation` は、レンダリングされるフレーム内のオブジェクトの参照空間に適用する回転を表すために使用されるクォータニオンです。

`RADIANS_PER_DEGREEE` は、角度をラジアンに変換するために度単位の角度を乗算する値です。

宣言された最後の 4 つの変数は、ユーザーに行列を見せるための出力先の `<div>` 要素を参照するためのストレージです。

### エラーのロギング

`LogGLError()` と呼ばれる関数は、WebGL 関数の実行中に発生したエラーのログ情報を出力するための簡単にカスタマイズされた方法を提供するために実装されています。

```js
function LogGLError(where) {
  let err = gl.getError();
  if (err) {
    console.error(`WebGL error returned by ${where}: ${err}`);
  }
}
```

これは、プログラムのどの部分がエラーを生成したかを示すために使用される文字列 `where` を唯一の入力として受け取ります。 これは、同様のエラーが複数の状況で発生する可能性があるためです。

### 頂点シェーダーとフラグメントシェーダー

頂点シェーダーとフラグメントシェーダーはどちらも、WebGL でのライティングの記事の例で使用されているものとまったく同じです。 ここで使用されている基本的なシェーダーの GLSL (en-US) ソースコードに興味がある場合は、それを参照してください。

頂点シェーダーは、各頂点の初期位置と、ビューアーの現在の位置と方向をシミュレートするためにそれらを変換するために適用する必要のある変換を指定して、各頂点の位置を計算するとだけ言っておきましょう。 フラグメントシェーダーは、テクスチャーで見つかった値から必要に応じて補間し、照明効果を適用して各頂点の色を返します。

WebXR の起動と停止
------------

スクリプトを最初にロードするときに、`load` イベントのハンドラーをインストールして、初期化を実行できるようにします。

```js
window.addEventListener("load", onLoad);

function onLoad() {
  xrButton = document.querySelector("#enter-xr");
  xrButton.addEventListener("click", onXRButtonClick);

  projectionMatrixOut = document.querySelector("#projection-matrix div");
  modelMatrixOut = document.querySelector("#model-view-matrix div");
  cameraMatrixOut = document.querySelector("#camera-matrix div");
  mouseMatrixOut = document.querySelector("#mouse-matrix div");

  if (!navigator.xr || enableForcePolyfill) {
    console.log("Using the polyfill");
    polyfill = new WebXRPolyfill();
  }
  setupXRButton();
}
```

`load` イベントハンドラーは、WebXR のオンとオフを切り替えるボタンへの参照を `xrButton` に取得し、`click` イベントのハンドラーを追加します。 次に、4 つの `<div>` ブロックへの参照を取得します。 このブロックには、シーンの実行中に情報提供の目的で、手がかりとなる行列それぞれの現在の内容が出力されます。

次に、`navigator.xr` が定義されているかどうかを確認します。 そうでない場合、および/または `enableForcePolyfill` 構成定数が `true` に設定されている場合は、`WebXRPolyfill` クラスをインスタンス化して WebXR ポリフィルをインストールします。

### 起動と停止の UI の処理

次に、`setupXRButton()` 関数を呼び出します。 この関数は、`SESSION_TYPE` 定数で指定されたセッションタイプに対する WebXR サポートの有無により、必要に応じて "Enter/Exit WebXR" ボタンを有効または無効にするための構成を処理します。

```js
function setupXRButton() {
  if (navigator.xr.isSessionSupported) {
    navigator.xr.isSessionSupported(SESSION\_TYPE).then((supported) => {
      xrButton.disabled = !supported;
    });
  } else {
    navigator.xr
      .supportsSession(SESSION\_TYPE)
      .then(() => {
        xrButton.disabled = false;
      })
      .catch(() => {
        xrButton.disabled = true;
      });
  }
}
```

ボタンのラベルは、実際に WebXR セッションの開始と停止を処理するコードで調整されます。 以下にそれを示します。

WebXR セッションは、ボタンの `click` イベントのハンドラーによってオンとオフが切り替えられ、ボタンのラベルは、"Enter WebXR" または "Exit WebXR" に適切に設定されます。 これは、`onXRButtonClick()` イベントハンドラーによって行われます。

```js
async function onXRButtonClick(event) {
  if (!xrSession) {
    navigator.xr.requestSession(SESSION\_TYPE).then(sessionStarted);
  } else {
    await xrSession.end();

    if (xrSession) {
      sessionEnded();
    }
  }
}
```

これは、`xrSession` の値を調べて、進行中の WebXR セッションを表す `XRSession` (en-US) オブジェクトがすでにあるかどうかを確認することから始まります。 ない場合は、クリックは WebXR モードを有効にする要求を表しているので、`requestSession()` (en-US) を呼び出して、目的の WebXR セッションタイプの WebXR セッションを要求し、次に `sessionStarted()` を呼び出して、その WebXR セッションでシーンの実行を開始します。

一方、進行中のセッションがすでにある場合は、その `end()` (en-US) メソッドを呼び出してセッションを停止します。

このコードで最後に行うことは、`xrSession` がまだ非 `NULL` かどうかを確認することです。 そうである場合は、`end` (en-US) イベントのハンドラーである `sessionEnded()` を呼び出します。 このコードは必要ないはずですが、少なくとも一部のブラウザーが `end` イベントを正しく発火しないという問題があるようです。 イベントハンドラーを直接実行することにより、この状況で終了プロセスを手動で完了します。

### WebXR セッションの開始

`sessionStarted()` 関数は、イベントハンドラーを設定し、頂点シェーダーとフラグメントシェーダーの GLSL コードをコンパイルしてインストールし、レンダリングループを開始する前に WebGL レイヤーを WebXR セッションにアタッチすることにより、実際のセッションの設定と開始を処理します。 これは、`requestSession()` (en-US) によって返される promise のハンドラーとして呼び出されます。

```js
function sessionStarted(session) {
  let refSpaceType;

  xrSession = session;
  xrButton.innerText = "Exit WebXR";
  xrSession.addEventListener("end", sessionEnded);

  let canvas = document.querySelector("canvas");
  gl = canvas.getContext("webgl", { xrCompatible: true });

  if (allowMouseRotation) {
    canvas.addEventListener("pointermove", handlePointerMove);
    canvas.addEventListener("contextmenu", (event) => {
      event.preventDefault();
    });
  }

  if (allowKeyboardMotion) {
    document.addEventListener("keydown", handleKeyDown);
  }

  shaderProgram = initShaderProgram(gl, vsSource, fsSource);

  programInfo = {
    program: shaderProgram,
    attribLocations: {
      vertexPosition: gl.getAttribLocation(shaderProgram, "aVertexPosition"),
      vertexNormal: gl.getAttribLocation(shaderProgram, "aVertexNormal"),
      textureCoord: gl.getAttribLocation(shaderProgram, "aTextureCoord"),
    },
    uniformLocations: {
      projectionMatrix: gl.getUniformLocation(
        shaderProgram,
        "uProjectionMatrix",
      ),
      modelViewMatrix: gl.getUniformLocation(shaderProgram, "uModelViewMatrix"),
      normalMatrix: gl.getUniformLocation(shaderProgram, "uNormalMatrix"),
      uSampler: gl.getUniformLocation(shaderProgram, "uSampler"),
    },
  };

  buffers = initBuffers(gl);
  texture = loadTexture(
    gl,
    "https://cdn.glitch.com/a9381af1-18a9-495e-ad01-afddfd15d000%2Ffirefox-logo-solid.png?v=1575659351244",
  );

  xrSession.updateRenderState({
    baseLayer: new XRWebGLLayer(xrSession, gl),
  });

  if (SESSION\_TYPE == "immersive-vr") {
    refSpaceType = "local";
  } else {
    refSpaceType = "viewer";
  }

  mat4.fromTranslation(cubeMatrix, viewerStartPosition);

  vec3.copy(cubeOrientation, viewerStartOrientation);

  xrSession.requestReferenceSpace(refSpaceType).then((refSpace) => {
    xrReferenceSpace = refSpace.getOffsetReferenceSpace(
      new XRRigidTransform(viewerStartPosition, cubeOrientation),
    );
    animationFrameRequestID = xrSession.requestAnimationFrame(drawFrame);
  });

  return xrSession;
}
```

新しく作成した `XRSession` (en-US) オブジェクトを `xrSession` に保存した後、ボタンのラベルを "Exit WebXR" に設定して、シーンの開始後の新しい機能を示します。 また、`end` (en-US) イベントのハンドラーをインストールしているため、`XRSession` の終了が通知されます。

次に、HTML にある `<canvas>` への参照とその WebGL レンダリングコンテキストを取得します。 これは、シーンの描画面として使用されます。 `xrCompatible` プロパティは、要素で `getContext()` を呼び出して、キャンバスの WebGL レンダリングコンテキストにアクセスするときに要求されます。 これにより、コンテキストが WebXR レンダリングのソースとして使用できるように構成されます。

次に、`mousemove` と `contextmenu` のイベントハンドラーを追加しますが、`allowMouseRotation` 定数が `true` の場合に限ります。 `mousemove` ハンドラーは、マウスの動きに基づいてビューのピッチとヨーを処理します。 「マウスルック」機能はマウスの右ボタンを押している間のみ機能し、マウスの右クリックでコンテキストメニューがトリガーされるため、`contextmenu` イベントのハンドラーをキャンバスに追加して、ユーザーが最初にマウスのドラッグを開始したときにコンテキストメニューが表示されないようにします。

次に、シェーダープログラムをコンパイルし、その変数への参照を取得し、各位置の配列を格納するバッファーを初期化し、各頂点の位置テーブルへのインデックス、頂点法線、各頂点のテクスチャー座標を格納します。 これはすべて WebGL サンプルコードから直接取得されているため、WebGL でのライティングとその前の記事の WebGL を用いた 3D オブジェクトの作成および WebGL でのテクスチャーの使用を参照してください。 次に、`loadTexture()` 関数を呼び出して、テクスチャーファイルをロードします。

レンダリング構造とデータがロードされたので、`XRSession` を実行する準備を開始します。 `baseLayer` を新しい `XRWebGLLayer` (en-US) に設定して `XRSession.updateRenderState()` (en-US) を呼び出すことにより、セッションを WebGL レイヤーに接続して、レンダリング面として何を使用するかを認識します。

次に、`SESSION_TYPE` 定数の値を調べて、WebXR コンテキストを没入型にするかインラインにするかを確認します。 没入型セッションは `local` 参照空間を使用し、インラインセッションは `viewer` 参照空間を使用します。

`glMatrix` ライブラリーの 4x4 行列用の `fromTranslation()` 関数は、`viewerStartPosition` 定数で指定されたビューアーの開始位置を変換行列 `cubeMatrix` に変換するために使用します。 ビューアーの開始方向である `viewerStartOrientation` 定数は、`cubeOrientation` にコピーして、時間の経過に伴う立方体の回転を追跡するために使用します。

`sessionStarted()` は、セッションの `requestReferenceSpace()` メソッドを呼び出して、オブジェクトを作成する空間を記述する参照空間オブジェクトを取得することで仕上げます。 返された promise が `XRReferenceSpace` オブジェクトに解決されると、その `getOffsetReferenceSpace` メソッドを呼び出して、オブジェクトの座標系を表す参照空間オブジェクトを取得します。 新しい空間の原点は、`viewerStartPosition` で指定された世界座標にあり、その方向は `cubeOrientation` に設定されています。 次に、`requestAnimationFrame()` (en-US) メソッドを呼び出して、フレームを描画する準備ができたことをセッションに通知します。 後でリクエストをキャンセルする必要がある場合に備えて、返されたリクエスト ID を記録します。

最後に、`sessionStarted()` は、ユーザーの WebXR セッションを表す `XRSession` (en-US) を返します。

### セッション終了時

（ユーザーによる終了か、`XRSession.end()` (en-US) の呼び出しで）WebXR セッションが終了すると、`end` (en-US) イベントが送信されます。 これを、`sessionEnded()` という関数を呼び出すように設定しました。

```js
function sessionEnded() {
  xrButton.innerText = "Enter WebXR";

  if (animationFrameRequestID) {
    xrSession.cancelAnimationFrame(animationFrameRequestID);
    animationFrameRequestID = 0;
  }
  xrSession = null;
}
```

プログラムで WebXR セッションを終了したい場合は、`sessionEnded()` を直接呼び出すこともできます。 いずれの場合も、ボタンのラベルを更新して、クリックによってセッションが開始されることを示します。 その後、アニメーションフレームに対する保留中のリクエストがある場合は、`cancelAnimationFrame` (en-US) を呼び出してキャンセルします。

それが完了すると、`xrSession` の値を `NULL` に変更して、セッションが終了したことを示します。

コントロールの実装
---------

それでは、キーボードとマウスのイベントを WebXR シナリオでアバターを制御するために使用できるものに変換するコードを見てみましょう。

### キーボードを使用した移動

空間を移動するための入力を備えた WebXR デバイスがなくても、ユーザーが 3D 世界を移動できるようにするために、`keydown` のハンドラーである `handleKeyDown()` は、押されたキーに基づいてオブジェクトの原点からのオフセットを更新することで応答します。

```js
function handleKeyDown(event) {
  switch (event.key) {
    case "w":
    case "W":
      verticalDistance -= MOVE\_DISTANCE;
      break;
    case "s":
    case "S":
      verticalDistance += MOVE\_DISTANCE;
      break;
    case "a":
    case "A":
      transverseDistance += MOVE\_DISTANCE;
      break;
    case "d":
    case "D":
      transverseDistance -= MOVE\_DISTANCE;
      break;
    case "ArrowUp":
      axialDistance += MOVE\_DISTANCE;
      break;
    case "ArrowDown":
      axialDistance -= MOVE\_DISTANCE;
      break;
    case "r":
    case "R":
      transverseDistance = axialDistance = verticalDistance = 0;
      mouseYaw = mousePitch = 0;
      break;
    default:
      break;
  }
}
```

キーとその効果は次のとおりです。

* `W`
 キーは、ビューアーを `MOVE_DISTANCE` だけ上に移動します。
* `S`
 キーは、ビューアーを `MOVE_DISTANCE` だけ下に移動します。
* `A`
 キーは、ビューアーを `MOVE_DISTANCE` だけ左にスライドさせます。
* `D`
 キーは、ビューアーを `MOVE_DISTANCE` だけ右にスライドさせます。
* 上矢印キー
 `↑`
 は、ビューアーを `MOVE_DISTANCE` だけ前方にスライドさせます。
* 下矢印キー
 `↓`
 は、ビューアーを `MOVE_DISTANCE` だけ後方にスライドさせます。
* `R`
 キーは、入力オフセットをすべて 0 にリセットすることにより、ビューアーを開始位置と方向にリセットします。

これらのオフセットは、次のフレームの描画からレンダラーによって適用されます。

### マウスによるピッチとヨー

また、マウスの右ボタンが押されているかどうかを確認する `mousemove` イベントハンドラーがあり、押されている場合は、その次に定義されている `rotateViewBy()` 関数を呼び出して、新しいピッチ（上下を見る）とヨー（左右を見る）の値を計算して保存します。

```js
function handlePointerMove(event) {
  if (event.buttons & 2) {
    rotateViewBy(event.movementX, event.movementY);
  }
}
```

新しいピッチとヨーの値の計算は、次の `rotateViewBy()` 関数で処理します。

```js
function rotateViewBy(dx, dy) {
  mouseYaw -= dx \* MOUSE\_SPEED;
  mousePitch -= dy \* MOUSE\_SPEED;

  if (mousePitch < -Math.PI \* 0.5) {
    mousePitch = -Math.PI \* 0.5;
  } else if (mousePitch > Math.PI \* 0.5) {
    mousePitch = Math.PI \* 0.5;
  }
}
```

入力としてマウス移動量の `dx` と `dy` が与えられると、新しいヨー値は、`mouseYaw` の現在の値から `dx` と `MOUSE_SPEED` スケーリング定数の積を引くことで計算します。 そして、`MOUSE_SPEED` の値を増やすことで、マウスの応答性を制御できます。

フレームの描画
-------

`XRSession.requestAnimationFrame()` (en-US) のコールバックは、以下に示す `drawFrame()` 関数に実装されています。 その仕事は、ビューアーの参照空間を取得し、最後のフレームからの経過時間を考慮して、アニメーション化されたオブジェクトに適用する必要のある動きの量を計算し、ビューアーの `XRPose` (en-US) によって指定された各ビューをレンダリングすることです。

```js
let lastFrameTime = 0;

function drawFrame(time, frame) {
  let session = frame.session;
  let adjustedRefSpace = xrReferenceSpace;
  let pose = null;

  animationFrameRequestID = session.requestAnimationFrame(drawFrame);
  adjustedRefSpace = applyViewerControls(xrReferenceSpace);
  pose = frame.getViewerPose(adjustedRefSpace);

  if (pose) {
    let glLayer = session.renderState.baseLayer;

    gl.bindFramebuffer(gl.FRAMEBUFFER, glLayer.framebuffer);
    LogGLError("bindFrameBuffer");

    gl.clearColor(0, 0, 0, 1.0);
    gl.clearDepth(1.0); // Clear everything
    gl.clear(gl.COLOR\_BUFFER\_BIT | gl.DEPTH\_BUFFER\_BIT);
    LogGLError("glClear");

    const deltaTime = (time - lastFrameTime) \* 0.001; // Convert to seconds
    lastFrameTime = time;

    for (let view of pose.views) {
      let viewport = glLayer.getViewport(view);
      gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
      LogGLError(`Setting viewport for eye: ${view.eye}`);
      gl.canvas.width = viewport.width \* pose.views.length;
      gl.canvas.height = viewport.height;
      renderScene(gl, view, programInfo, buffers, texture, deltaTime);
    }
  }
}
```

最初に行うことは、`requestAnimationFrame()` (en-US) を呼び出して、次のフレームをレンダリングするために `drawFrame()` を再度呼び出すように要求することです。 次に、オブジェクトの参照空間を `applyViewerControls()` 関数に渡します。 この関数は、キーボードとマウスを使用してユーザーが適用した移動、ピッチ、ヨーを考慮してオブジェクトの位置と方向を変換する、改訂された `XRReferenceSpace` を返します。 いつものように、ビューアーではなく、世界のオブジェクトを、移動し、方向を変更することを忘れないでください。 返された参照空間により、それを簡単に行うことができます。

新しい参照空間が手元にあると、ビューアーの両目の視点を表す `XRViewerPose` (en-US) が得られます。 それが成功した場合は、セッションで使用されている `XRWebGLLayer` (en-US) を取得し、そのフレームバッファーを WebGL フレームバッファーとして使用するようにバインドすることでレンダリングの準備を開始します（WebGL のレンダリングはレイヤーに描画することで、XR デバイスのディスプレイに描画されます）。 XR デバイスにレンダリングするように WebGL が構成されたので、フレームを黒にクリアして、レンダリングを開始する準備が整いました。

最後のフレームがレンダリングされてからの経過時間（秒単位）は、`time` パラメーターで指定された現在の時刻から前のフレームのタイムスタンプ `lastFrameTime` を減算し、0.001 を掛けてミリ秒を秒に変換することで計算します。 次に、現在の時刻を `lastFrameTime` に保存します。

`drawFrame()` 関数は、`XRViewerPose` (en-US) で見つかったすべてのビューを反復処理し、ビューのビューポートを設定し、`renderScene()` を呼び出してフレームをレンダリングすることで終了します。 各ビューのビューポートを設定することにより、各目のビューがそれぞれ WebGL フレームの半分にレンダリングされる典型的なシナリオを処理します。 次に、XR ハードウェアは、各目がその目向けの画像の部分のみを表示するように処理します。

**メモ:** この例では、XR デバイスと画面の両方にフレームを視覚的に表示しています。 画面上のキャンバスがこれを実行できる適切なサイズであることを確認するために、その幅を個々の `XRView` (en-US) の幅にビューの数を掛けたものに等しくなるように設定します。 キャンバスの高さは常にビューポートの高さと同じです。 キャンバスサイズを調整する 2 行のコードは、通常の WebXR レンダリングループでは必要ありません。

### ユーザー入力の適用

何かをレンダリングし始める前に `drawFrame()` によって呼び出される `applyViewerControls()` 関数は、ユーザーがキーを押したり、マウスの右ボタンを押したままマウスをドラッグしたときに `handleKeyDown()` 関数と `handlePointerMove()` 関数によって記録された 3 方向のそれぞれのオフセット、ヨーオフセット、およびピッチオフセットを取得します。 オブジェクトのベース参照空間を入力として受け取り、入力の結果と一致するようにオブジェクトの位置と方向を変更する新しい参照空間を返します。

```js
function applyViewerControls(refSpace) {
  if (
    !mouseYaw &&
    !mousePitch &&
    !axialDistance &&
    !transverseDistance &&
    !verticalDistance
  ) {
    return refSpace;
  }

  quat.identity(inverseOrientation);
  quat.rotateX(inverseOrientation, inverseOrientation, -mousePitch);
  quat.rotateY(inverseOrientation, inverseOrientation, -mouseYaw);

  let newTransform = new XRRigidTransform(
    { x: transverseDistance, y: verticalDistance, z: axialDistance },
    {
      x: inverseOrientation[0],
      y: inverseOrientation[1],
      z: inverseOrientation[2],
      w: inverseOrientation[3],
    },
  );
  mat4.copy(mouseMatrix, newTransform.matrix);

  return refSpace.getOffsetReferenceSpace(newTransform);
}
```

すべての入力オフセットがゼロの場合、元の参照空間を返すだけです。 それ以外の場合は、`mousePitch` と `mouseYaw` の方向の変更から、その方向の逆を指定するクォータニオンを作成します。 これにより、`inverseOrientation` を立方体に適用すると、ビューアーの動きが正しく表示されます。

次に、移動または方向変更されたオブジェクトの新しい `XRReferenceSpace` を作成するために使用する変換を表す新しい `XRRigidTransform` オブジェクトを作成します。 位置は、`x`、`y`、`z` がこれらの各軸に沿って移動したオフセットに対応する新しいベクトルです。 方向は、`inverseOrientation` クォータニオンです。

変換の `matrix` を `mouseMatrix` にコピーします。 これは、後でマウス追跡行列（Mouse tracking matrix）をユーザーに表示するために使用します（したがって、これは通常スキップできる手順です）。 最後に、`XRRigidTransform` をオブジェクトの現在の `XRReferenceSpace` に渡して、この変換を統合し、ユーザーの動きを考慮したユーザーに対する立方体の配置を表す参照空間を取得します。 その新しい参照空間を呼び出し元に返します。

### シーンのレンダリング

`renderScene()` 関数は、ユーザーがその瞬間に見える世界の部分を実際にレンダリングするために呼び出されます。 XR ギアに必要な 3D 効果を確立するために、それぞれの目でわずかに異なる位置を使用し、それぞれの目に対して 1 回ずつ呼び出されます。

このコードのほとんどは、WebGL でのライティングの記事の `drawScene()` 関数から直接取得した典型的な WebGL レンダリングコードであり、この例の WebGL レンダリング部分の詳細についてはそこを参照してください（GitHub でコードを見る）。 しかし、ここでは、この例に固有のコードから始まっているので、その部分について詳しく見ていきます。

```js
const normalMatrix = mat4.create();
const modelViewMatrix = mat4.create();

function renderScene(gl, view, programInfo, buffers, texture, deltaTime) {
  const xRotationForTime =
    xRotationDegreesPerSecond \* RADIANS\_PER\_DEGREE \* deltaTime;
  const yRotationForTime =
    yRotationDegreesPerSecond \* RADIANS\_PER\_DEGREE \* deltaTime;
  const zRotationForTime =
    zRotationDegreesPerSecond \* RADIANS\_PER\_DEGREE \* deltaTime;

  gl.enable(gl.DEPTH\_TEST); // Enable depth testing
  gl.depthFunc(gl.LEQUAL); // Near things obscure far things

  if (enableRotation) {
    mat4.rotate(
      cubeMatrix, // destination matrix
      cubeMatrix, // matrix to rotate
      zRotationForTime, // amount to rotate in radians
      [0, 0, 1],
    ); // axis to rotate around (Z)
    mat4.rotate(
      cubeMatrix, // destination matrix
      cubeMatrix, // matrix to rotate
      yRotationForTime, // amount to rotate in radians
      [0, 1, 0],
    ); // axis to rotate around (Y)
    mat4.rotate(
      cubeMatrix, // destination matrix
      cubeMatrix, // matrix to rotate
      xRotationForTime, // amount to rotate in radians
      [1, 0, 0],
    ); // axis to rotate around (X)
  }

  mat4.multiply(modelViewMatrix, view.transform.inverse.matrix, cubeMatrix);
  mat4.invert(normalMatrix, modelViewMatrix);
  mat4.transpose(normalMatrix, normalMatrix);

  displayMatrix(view.projectionMatrix, 4, projectionMatrixOut);
  displayMatrix(modelViewMatrix, 4, modelMatrixOut);
  displayMatrix(view.transform.matrix, 4, cameraMatrixOut);
  displayMatrix(mouseMatrix, 4, mouseMatrixOut);

  {
    const numComponents = 3;
    const type = gl.FLOAT;
    const normalize = false;
    const stride = 0;
    const offset = 0;
    gl.bindBuffer(gl.ARRAY\_BUFFER, buffers.position);
    gl.vertexAttribPointer(
      programInfo.attribLocations.vertexPosition,
      numComponents,
      type,
      normalize,
      stride,
      offset,
    );
    gl.enableVertexAttribArray(programInfo.attribLocations.vertexPosition);
  }

  {
    const numComponents = 2;
    const type = gl.FLOAT;
    const normalize = false;
    const stride = 0;
    const offset = 0;
    gl.bindBuffer(gl.ARRAY\_BUFFER, buffers.textureCoord);
    gl.vertexAttribPointer(
      programInfo.attribLocations.textureCoord,
      numComponents,
      type,
      normalize,
      stride,
      offset,
    );
    gl.enableVertexAttribArray(programInfo.attribLocations.textureCoord);
  }

  {
    const numComponents = 3;
    const type = gl.FLOAT;
    const normalize = false;
    const stride = 0;
    const offset = 0;
    gl.bindBuffer(gl.ARRAY\_BUFFER, buffers.normal);
    gl.vertexAttribPointer(
      programInfo.attribLocations.vertexNormal,
      numComponents,
      type,
      normalize,
      stride,
      offset,
    );
    gl.enableVertexAttribArray(programInfo.attribLocations.vertexNormal);
  }

  gl.bindBuffer(gl.ELEMENT\_ARRAY\_BUFFER, buffers.indices);
  gl.useProgram(programInfo.program);

  gl.uniformMatrix4fv(
    programInfo.uniformLocations.projectionMatrix,
    false,
    view.projectionMatrix,
  );
  gl.uniformMatrix4fv(
    programInfo.uniformLocations.modelViewMatrix,
    false,
    modelViewMatrix,
  );
  gl.uniformMatrix4fv(
    programInfo.uniformLocations.normalMatrix,
    false,
    normalMatrix,
  );
  gl.activeTexture(gl.TEXTURE0);
  gl.bindTexture(gl.TEXTURE\_2D, texture);

  gl.uniform1i(programInfo.uniformLocations.uSampler, 0);

  {
    const vertexCount = 36;
    const type = gl.UNSIGNED\_SHORT;
    const offset = 0;
    gl.drawElements(gl.TRIANGLES, vertexCount, type, offset);
  }
}
```

`renderScene()` は、前のフレームがレンダリングされてから経過した時間内に、3 つの軸のそれぞれの周りで発生する回転量を計算することから始まります。 これらの値により、アニメーションする立方体の回転を適切な量に調整して、システムの負荷によって発生する可能性のあるフレームレートの変動に関係なく、その移動速度が一定に保たれるようにすることができます。 これらの値は、経過時間を指定して適用する回転のラジアン数として計算して、定数の `xRotationForTime`、`yRotationForTime`、`zRotationForTime` に格納します。

奥行きテストを有効にして構成した後、`enableRotation` 定数の値をチェックして、立方体の回転が有効になっているかどうかを確認します。 そうである場合は、glMatrix を使用して、3 つの軸を中心に `cubeMatrix`（世界空間に対する立方体の現在の方向を表す）を回転させます。 立方体のグローバルな方向が確立されたら、それをビューの変換行列の逆行列で乗算して、最終的なモデルビュー行列を取得します。 この行列は、オブジェクトに適用して、アニメーションの目的でそれを回転させるだけでなく、それを移動したり方向を変えたりして、空間を介したビューアーのモーションをシミュレートすることもします。

次に、モデルビュー行列を取得して反転（逆行列を計算）し、転置（列と行を入れ替える）することで、ビューの正規行列を計算します。

この例で追加されたコードの最後の数行は、ユーザーによる分析のために行列の内容を表示する関数である `displayMatrix()` への 4 つの呼び出しです。 関数の残りの部分は、このコードの派生元である古い WebGL サンプルと同一または本質的に同一です。

### 行列の表示

この例では、説明のために、シーンのレンダリング中に使用している重要な行列の内容を表示しています。 これには、`displayMatrix()` 関数を使用します。 この関数は MathML を使用して行列をレンダリングし、MathML がユーザーのブラウザーでサポートされていない場合は、より配列に近い形式にフォールバックします。

```js
function displayMatrix(mat, rowLength, target) {
  let outHTML = "";

  if (mat && rowLength && rowLength <= mat.length) {
    let numRows = mat.length / rowLength;
    outHTML =
      "<math xmlns='http://www.w3.org/1998/Math/MathML' display='block'>\n<mrow>\n<mo>[</mo>\n<mtable>\n";

    for (let y = 0; y < numRows; y++) {
      outHTML += "<mtr>\n";
      for (let x = 0; x < rowLength; x++) {
        outHTML += `<mtd><mn>${mat[x \* rowLength + y].toFixed(2)}</mn></mtd>\n`;
      }
      outHTML += "</mtr>\n";
    }

    outHTML += "</mtable>\n<mo>]</mo>\n</mrow>\n</math>";
  }

  target.innerHTML = outHTML;
}
```

これにより、`target` で指定された要素の内容が、4x4 行列を含む新しく作成された `<math>` 要素に置き換えられます。 各エントリーは、小数点以下 2 桁まで表示します。

### ほかのすべて

残りのコードは、前の例で見つかったものと同じです。

`initShaderProgram()`

GLSL シェーダープログラムを初期化し、`loadShader()` を呼び出して各シェーダーのプログラムをロードおよびコンパイルしてから、各シェーダーを WebGL コンテキストにアタッチします。 それらがコンパイルされると、プログラムはリンクされ、呼び出し元に返されます。

`loadShader()`

シェーダーオブジェクトを作成し、指定されたソースコードをそのオブジェクトにロードしてから、コードをコンパイルし、コンパイラが成功したことを確認してから、新しくコンパイルされたシェーダーを呼び出し元に返します。 エラーが発生した場合は、代わりに `NULL` を返します。

`initBuffers()`

WebGL に渡すデータを含んだバッファーを初期化します。 これらのバッファーには、頂点位置の配列、頂点法線の配列、立方体の各面のテクスチャー座標、および頂点インデックスの配列（頂点リストのどのエントリーが立方体のそれそれの角を表すかを指定）が含まれます。

`loadTexture()`

指定された URL で画像をロードし、そこから WebGL テクスチャーを作成します。 画像の寸法が両方とも 2 の累乗でない場合（`isPowerOf2()` 関数を参照）、ミップマップを無効とし、ラッピングをエッジに固定します。 これは、ミップマップテクスチャーの最適化されたレンダリングが、WebGL 1 の 2 の累乗の寸法のテクスチャーに対してのみ機能するためです。 WebGL 2 は、ミップマップに任意の寸法のテクスチャーをサポートしています。

`isPowerOf2()`

指定された値が 2 の累乗の場合、`true` を返します。 それ以外の場合は `false` を返します。

### すべてをまとめる

このコードをすべて取得して、上記に含まれていない HTML およびその他の JavaScript コードを追加すると、この例を Glitch で試したときに表示されるものが得られます。 覚えておいてください、歩き回って迷子になったら、`R` キーを押すだけで最初に戻ることができます。

ヒント: XR デバイスをお持ちでない場合は、顔を画面に非常に近づけて、キャンバス内の左目と右目の画像の境界に沿って鼻を中央に配置すると、3D 効果を得ることができるかもしれません。 画面を通して画像に注意深く焦点を合わせ、ゆっくりと前後に動くことで、最終的に 3D 画像に焦点を合わせることができるはずです。 これには練習が必要で、視力の鋭さによっては、文字通り鼻が画面に触れているかもしれません。

この例を出発点として、できることはたくさんあります。 世界にオブジェクトを追加したり、移動コントロールを改善してよりリアルに移動してみてください。 壁、天井、床を追加して、無限に見える宇宙に迷うのではなく、空間に閉じ込めます。 衝突テストやヒットテスト、または立方体の各面のテクスチャーを変更する機能を追加します。

自分で設定すれば、できることに制限はほとんどありません。

関連情報
----

* WebGL を学ぶ（英語、カメラの優れた視覚化と、それが仮想世界とどのように関連しているかを含む）
* WebGL の基本
* OpenGL を学ぶ（英語）


WebXR の形状と参照空間 - Web API

WebXR の形状と参照空間
==============

基本的なレベルでは、拡張現実または仮想現実のコンテキストでの WebXR プレゼンテーションのシーンのレンダリングは WebGL を使用して実行されるため、 2 つの API は同じ設計言語の多くを共有します。ただし、XR ヘッドセットなどの機器を使用して真の三次元でシーンを提示する機能を提供するために、WebXR には理解する必要がある追加の概念があります。

この記事では、 WebXR が WebGL の形状を拡張する方法と、オブジェクトの位置と方向（物理的および仮想的）が空間、特に参照空間を使用して相互にどのように記述されるかを紹介します。

WebXR での空間追跡 (en-US)の記事は、ユーザーの頭の物理的な位置と向きだけでなく、手などの潜在的に身体の他の部分がデジタル世界にマッピングされ、物理オブジェクトと仮想オブジェクトの両方が動き回るときに追跡されるかをカバーするため、ここで提供される情報に基づいて、シーンを適切にレンダリングして合成できるようにします。

### 三次元形状の基礎

ここでは、仮想空間内のオブジェクトの位置、方向、動きを計算するために使用される必要な数学演算に加えて、シーンの人間のビューアーを混合物の中に統合する必要性について説明しますが、形状やシーンの三次元表現を管理するための行列とベクトルは、この記事で達成できることの範囲をはるかに超えています。 ウェブの行列計算で個々の演算について詳しく知ることができます。

単位
--

WebXR で使用される 三次元空間の形状の詳細について説明する前に、三次元の世界に適用される測定単位を理解しておくと役に立ちます。

#### 長さと距離

WebGL はすべての距離と長さを**メートル** (meters) で測定します。 WebXR はこの標準を継承しています。 また、世界は幅 2 メートル、高さ 2 メートル、奥行き 2 メートルの立方体であるという事実も継承しています。 3 つの軸のそれぞれの最小値は -1.0、最大値は 1.0 で、立方体の中心は (0, 0, 0) にあります。

![X、Y、Z 座標軸のそれぞれの最小値が -1、最大値が 1 である WebXR 空間を示す図。](/ja/docs/Web/API/WebXR_Device_API/Geometry/defaultspacedimensions.svg)

この 8 立方メートルの空間は、コードのために宇宙全体を囲んでいます。 描画するすべてのものは、コード内で明示的に、または変換を使用してすべての頂点の座標を調整することにより、この空間に収まるように座標をマッピングする必要があります。 もちろん、最も効率的な方法は、WebGL と同じ座標系を使用するようにオブジェクトとコードを設計することです。

WebGL の座標と長さは、レンダリング時にシーンがレンダリングされているビューポートの大きさに自動的に変換されます。

#### 角度

角度は **ラジアン** (radians) を使用して指定します。 度をラジアンに変換するには、度の値に `π/180` を掛けるだけです。 次のコードスニペットは、 2 つの単純な関数、`degreesToRadians()` と `radiansToDegrees()` を示しています。 これらは、角度を測定するために2つの単位間で相互に変換します。

```js
const RADIANS\_PER\_DEGREE = Math.PI / 180.0;

let degreesToRadians = (deg) => deg \* RADIANS\_PER\_DEGREE;
let radiansToDegrees = (rad) => rad / RADIANS\_PER\_DEGREE;
```

#### 時刻と持続時間

**メモ:** セキュリティ上の理由から、`DOMHighResTimeStamp` は通常、フィンガープリントやタイミングベースの攻撃で使用されないようにするために、クロックに少しの不正確さを導入します。

WebXR のすべての時刻と持続時間は、 `DOMHighResTimeStamp` 型を使用して測定します。 これは、開始時刻を基準にミリ秒単位で時刻を指定する倍精度浮動小数点値です。 値は浮動小数点数であるため、プラットフォームとハードウェアによっては、ミリ秒レベルよりも正確である場合があります。

時刻は主に、シーンの前のアニメーションフレームが描画されてからの経過時間を決定するために使用します。 そのため、時刻は通常、ディスプレイのリフレッシュレートに同調し、パフォーマンスの問題によりフレームレートを制限する必要がある場合は、その一部に同調します。 これは、60 FPS のフレームレートを想定して、時刻は通常 1/60 秒の間隔で進むことを意味します。 計算すると、これは各フレームが 16.6667 ミリ秒間隔で理想的にレンダリングされることを意味することがわかります。

### 行列を使用した形状操作

以下に、三次元シーンのレンダリング時に実行する必要がある3つの主要な変換に行列を使用する方法など、三次元形状に関連する行列数学のガイドを提供します。

* **平行移動** (translation) は、仮想空間を介して点の位置をずらすための行列の使用です。この動きは、オブジェクトの軸のいずれか、またはそれらの任意の組み合わせに沿ったものにすることができます。
* **回転** (rotation) は、オブジェクトの座標系の原点を中心に点を回転させる行列の応用です。
* **拡大縮小** (scaling) とは、行列を使用してオブジェクトの大きさを変更することです。

変換が点に適用されると言うとき、その延長線上で考えると、点の*集まり*に適用できることに注意してください。 オブジェクトは空間内のいくつかの点で構成されるいくつかのポリゴンで表されるため、オブジェクトを構成するすべての点に同じ変換を適用すると、オブジェクト全体に同じ変換が適用されます。 ベクトルは座標値を使用して記述され、ベクトルの方向と大きさを定義するため、変換をベクトルに適用することもできます。

空間の原点について
---------

XR で表現されたシーンは、仮想であれ拡張であれ、 1 から数十フレームの参照情報を合成したものです。位置と方向のデータを WebXR システムと直接交換する必要があるシーン内の各オブジェクトは、シーン内の他のオブジェクトが理解できるように、必要に応じて理解および適応できる方法でその情報を報告できる必要があります。

拡張現実 (AR) では、これは、仮想オブジェクトを現実の世界に挿入する必要があるためです。 仮想オブジェクトを正しく配置するだけでなく、ユーザーの視点が変化しても、仮想オブジェクトが自分でさまよっているように見えないようにします。 仮想現実 (VR) では、ユーザーの動きと仮想ディスプレイの映像が一致するような空間演出をすることで、違和感やズレを防ぐことが重要です。

したがって、それは空間感覚を作り出すことがすべてです。 XR 開発者の観点から見ると、ステージの設計はユーザーにとって最も重要な部分です。 建築家やセットデザイナーのように、あなたは物理的な環境を通して気分や体験を生み出す力を持っています。 その空間をどのように構築するかは、ユーザーがどのように対話して探索できるかに依存し、影響を与えます。

**メモ:** 空間には通常、前景、中距離、背景の要素があります。 適切なバランスは、ユニークな存在感を生み出し、ユーザーを導くことができます。 前景には、直接対話できるオブジェクトとインターフェイスが含まれています。 中距離には、ある程度対話できるオブジェクト、またはより密接に調査して関与するために近づくことができるオブジェクトが含まれます。 一方、背景は通常、少なくともユーザーが中距離または前景の範囲に移動して近づくことができない限り、ほとんどまたは完全に非対話です。

WebXR では、（シーンが起こる座標空間など）**空間** (space) の基本的な概念は、`XRSpace` (en-US) のインスタンスによって表されます。 この空間は、ユーザーの環境内のオブジェクトと（光源やカメラなど）他のエンティティの相対位置と動きを決定するために使用します。

三次元空間内の点は、前述のとおり、 3 つの成分で構成され、それぞれが 3 つの軸の 1 つに沿った点の距離を識別します。

これは空間の**ネイティブの原点** (native origin) であり、ユーザーの環境内の特定の物理的な場所に対応しています。 各空間には、XR 機器の追跡システムによって追跡される独自のネイティブの原点があります。 これは、空間のローカル座標系の原点である**実質的な原点** (effective origin) とは異なる場合があります。

座標系の方向性を次の図に示します。

![WebGL および WebXR で使用される座標系を示す図。](/ja/docs/Web/API/WebXR_Device_API/Geometry/webgl_coordinates.svg)

**原点オフセット** (origin offset) と呼ばれる `XRRigidTransform` を使用して、点を空間自体の実質的な座標系から XR 機器のネイティブ座標系に変換します。 通常、 2 つの原点は空間が最初に確立されるときに位置合わせされるため、原点オフセットは最初は単純に恒等変換です。 ただし、位置合わせの変化は時間とともに蓄積されるため、補正するために原点オフセットが変化する場合があります。

原点に対する空間内の点の位置は、上の図に示す 3 つの空間軸のそれぞれに沿った点の距離を決定することによって決定されます。 空間の原点は、空間の中心にあり、各軸に沿ってゼロの位置にある点 (0, 0, 0) です。 具体的には、初期の開始条件の下で、空間に対する閲覧者の既定の向きを使用します。

* **x 軸** (x-axis) は原点から離れて左から右に水平方向に伸び、 +1.0 の x 座標は世界の右端にあります。 x の負の値は、原点から左に向かって伸び、空間の左端で -1.0 の値に達します。
* **y 軸** (y-axis) は正で、原点から画面の上部に向かって上向きに伸び、世界空間の上部で +1.0 に達します。 0 未満の y の値は原点の下にあり、画面の下部に向かって伸び、世界空間の下部で -1.0 に達します。
* **z 軸** (z-axis) は原点から画面の外側に伸び、その方向でユーザーに最も近い点で +1.0 に達します。 z の負の値は、ユーザーから離れて画面内に伸び、世界で最も離れた点の z は -1.0 になります。

すべてのオブジェクトは、最も単純なレベルでは、三次元空間内の点とオフセット変換によって定義される一連のポリゴンであり、オブジェクトを移動および回転して空間内の目的の点に配置する方法を示します。 オフセット変換が単位行列の場合、オブジェクトは原点に配置されます。

ただし、空間追跡やシーン形状に役立つためには、XR 機器の知覚位置を空間の座標系と相関させる必要があります。 そこで参照スペースの出番です。

参照空間
----

さまざまな XR ハードウェアが利用可能であり、多くの開発者からさまざまなフォーム係数で提供されるため、開発者が使用している追跡技術と直接通信する必要があることを期待することは非現実的であり、拡張性がありません。 代わりに、WebXR 機器 API は、開発者がユーザーエクスペリエンスを計画し、それらのニーズを最もよく表す適切な参照空間を要求するように設計されています。 これは、ユーザーエージェントに対してこれらのニーズに一致する **`XRReferenceSpace`** を要求することによって行われます。

`XRReferenceSpace` オブジェクトは、 1 つの座標系の基準系を別の座標系の基準系に適合させる手段として機能します。 ヘッドセットを装着した後、あなたの周りの仮想世界が、あなたの位置が (0, 0, 0) である座標系を持っていると考えてください。 つまり、あなたはすべての中心にいます。 それは力強さを感じませんか？ ヘッドセットに向かって 真正面が -Z 軸で、+Z が後ろにあります。 X は右が正、左が負です。 Y は下に行くと負、上に行くと正になります。 これは、XRシステムの使用開始時の空間におけるヘッドセットの位置を示し、原点 (0, 0, 0) は基本的には鼻筋に配置されています。 この空間が**世界空間** (world space) です。

次に、左手にある XR コントローラーについて考えます。 動きとその向きを報告する機能がありますが、ヘッドセットの位置や、より重要なことに、その座標系については何も知りません。 ただし、コントローラーにはその位置をアプリに報告する方法が必要です。 したがって、独自の座標系があります。 これは、入力イベントが発生したときにアプリに提供される参照空間です。 この参照空間は、コントローラーの座標をヘッドセットの座標にマップする方法を内部的に認識しているため、WebXR は座標を相互に変換できます。

`XRReferenceSpace` を作成すると、モーションとオリエンテーションの追跡を一定レベル保証し、 `XRViewerPose` (en-US)を取得する機構を提供します。この空間がユーザーのヘッドセット、観察者のヘッドセット、仮想カメラなどのビューアを表す場合、ワールド空間に対する位置と向きを表す行列を取得することができます。

これはすべてブラウザーの処理責任であり、基盤となる各参照空間の能力に関係なく、一貫した振る舞いを提供します。 個々の XR 機器がどれほど強力であったりシンプルであっても、WebXR を使用して記述されたコードは、利用可能なハードウェアの制限内で機能します。

選択する参照空間の種類に関係なく、その型は `XRReferenceSpace` または `XRReferenceSpace` から派生した型です。現在利用可能な参照空間型を以下に示します。

`bounded-floor`

`XRBoundedReferenceSpace` で、 `local` 型と同様ですが、返されたオブジェクトの `boundsGeometry` によって指定された所定の境界の外にユーザーが移動することは想定されていません。

`local`

`XRReferenceSpace` で、セッションが作成された時の閲覧者の位置の近くに原点を持つ追跡空間です。正確な位置はプラットフォームと実装に依存します。ユーザーは開始位置からあまり動くことはないと思われますので、追跡はこの用途に最適化されています。 6 つの自由度 (6DoF) 追跡機能を持つ機器の場合、`local`参照空間は、環境に対して原点を安定に保とうとします。

`local-floor`

`local` 型に似た `XRReferenceSpace` で、開始位置が閲覧者が安全に立てる場所に置かれ、 y 軸の値が床面を 0 とすることを除きます。その床レベルが分からない場合、ユーザーエージェントは床レベルを推定します。推定された床レベルが 0 でない場合、ブラウザーはフィンガープリントを避けるような方法でそれを（おそらく最も近いセンチメートルに）丸めることが期待されています。

`unbounded`

`XRReferenceSpace` で、原点から非常に長い距離を自由に移動することができる追跡空間です。閲覧者は全く追跡されません。追跡はユーザーの現在位置の安定性のために最適化されており、ネイティブの原点はその必要性に応じてドリフトすることができます。

`viewer`

`XRReferenceSpace` で、閲覧者の位置と向きを原点として追跡する追跡空間です。これはユーザーが物理的に移動できる環境で使用され、没入型、インライン型問わず `XRSession` (en-US) の全てのインスタンスで対応していますが、インラインセッションで最も有用です。これは、閲覧者と入力の間の距離を決定するとき、またはオフセット空間を扱うときに特に便利です。それ以外では、通常、他の参照空間型のいずれかがより頻繁に使用されます。

このガイドの残りの部分では、アプリのニーズに適した参照空間を選択する方法について説明します。

参照空間を使用した空間関係の定義
----------------

環境に対するオブジェクトの位置と方向を参照したり、環境自体を制約したりするために一般的に使用される方法がいくつかあります。 そのために、WebXR は、**参照空間** (reference space) と呼ばれる標準空間のセットを定義します。 それぞれの標準空間は、ローカル空間の基準系の座標系を、それが存在する空間の座標系に関連付けるさまざまな手法をに対応しています。

ただし、使用している参照空間の型に関係なく、同じ関数を使用して座標を空間から親空間に変換できます。

参照空間型の選択
--------

まず、使用する参照型を決定するプロセスの最も簡単なステップを説明しましょう。 使用する可能性が最も高い参照空間は、`local`、`local-floor`、`unbounded`、`bounded-floor` です。

#### 床レベルの参照空間

名前に `-floor` が含まれている参照空間型は、対応する非床空間と同じように機能しますが、ビューアーが地面またはその近く（常に上）の安全な場所に自動的に配置されるようにします。 これは、他に床が確立されていない限り、`y` 座標が常に 0 である平面です。 これらの空間型は、部屋の床が平らでない場合や、地上の高さが変化する床の場合、アバターの垂直位置の変更をに対応しないため、実行*できません*。

#### 主な参照空間型

`viewer` 参照空間は、ビューアーの空間内の位置に対応します。 `XRFrame` (en-US) メソッド `getViewerPose()` (en-US) によって返される `XRViewerPose` (en-US) によって使用されます。 それ以外の場合、通常は直接使用されません。 唯一の実質的な例外は、ウェブコンテンツ内で XR シーンをインラインで実行するときに `viewer` 参照空間を使用する可能性が高いことです。

`local` 参照空間は、通常、シングルルームなどの比較的小さなエリアを記述するために使用されます。 没入型セッションモード（`immersive-vr` または `immersive-ar`）を使用している場合は常に使用できるだけでなく、新しいセッションを要求するときに常にオプション機能に含まれます。 したがって、`navigator.xr.requestSession()` (en-US) によって作成されたすべてのセッションは、`local` 参照空間型をに対応します。

複数の部屋が含まれる可能性のある大きな領域を表すには、ビューアーの動きに制約を指定しない `unbounded` 参照空間型を使用できます。 ユーザーが特定の領域に移動できないようにする場合は、自分で処理する必要があります。

`bounded-floor` 参照空間型には、対応する床に制限されない型はありません。 ユーザーの XR ハードウェアが現実世界の空間内を移動することを許可し、それが可能である場合、`bounded-floor` 参照空間を使用すると、通過が許可され安全な領域の境界を明確に定義できるため便利です。 制限付き参照空間の使用の詳細については、制限付き参照空間の使用 (en-US)の記事を参照してください。

参照空間を使用してオブジェクトの位置と方向を記述することにより、WebXR は、基盤となる XR ハードウェアに関係なく、これらの記述に使用するデータの形式を標準化できます。 参照空間の構成は、空間のコンテンツを正しくレンダリングするために必要なビュー行列とオブジェクトポーズを提供します。

### 参照空間の確立

最上位の空間（`XRSession` (en-US) のメソッド `requestReferenceSpace()` を呼び出すことによって取得される空間）は、世界空間全体に使用される座標系を表します。 すべては基本的にこの座標系に関連付けられ、ユーザーの機器の位置と仮想世界の間の関係を表します。

WebXR を使用して、アノテーションによる世界の拡張から 360°動画再生、科学シミュレーション、仮想現実トレーニングシステムなど、想像できるあらゆるものに対応できますが、三次元ビデオゲームを典型的な WebXR アプリケーションの例として取り上げましょう。 ゲーム世界の空間に立っているプレイヤーのアバターのモデルを考えてみましょう。 世界空間を基準にしてアバターを配置するには、世界の参照空間で定義された座標系を使用します。

プレーヤーを新しい位置に移動するには、すべての座標を書き換えるか、移動するたびに手動で変換を適用しますが、参照空間とそれらを相互に作成できるため、より簡単な方法があります。 プレーヤーのアバターの新しい位置と方向を表す `XRRigidTransform` オブジェクトを作成してから、`XRReferenceSpace` メソッド `getOffsetReferenceSpace()` を使用して、新しい位置にアバターの視点を表す新しい参照空間を作成します。 これは、キーボードやマウスなどの非 XR 機器を使用してプレーヤーのアバターを世界中に移動するためのサポートを実装する場合に特に便利です。

新しく作成された参照空間を使用すると、アバターは同じ座標に留まることができますが、新しい位置に配置され（そして世界をその視点から見ることができるように）世界に現れます。 参照空間を使用してプレーヤーの視点を管理する方法の詳細については、の記事を参照してください。

私たちのゲームアバターの例の場合、アバター（または他の動いているクリーチャーや機械）が世界中を滑走する単純なブロブになることはまれです。 それらは通常、追加の形だけでなく、動く脚、歩くときに揺れる腕、回転したり素早く上下する頭、動き回る武器などの内部の動きも持っています。 標準の WebGL 手法を使用してこれらを実現し、位置決め行列または `XRRigidTransform` オブジェクトで実質的な原点に対して正しい位置にシフトします。

### 参照空間に関するデバイスの制限

一部の XR 機器は、API が不足している機能を補うために努力しているにもかかわらず、特定の体験をに対応するように作成できないものもあります。 例えば、GearVR デバイスなどの基本的なヘッドセットを、ユーザーが実質的な動きを追跡して環境を歩き回るのをに対応する必要があるアプリで機能させる方法はありません。

プログレッシブエンハンスメントをに対応し、それによってアプリまたはサイトの可用性を広げるには、必要な機能の量が最も少ない参照空間を選択するか、参照空間の取得の失敗を検出し、あまり強力でない代替手段で再試行するフォールバックメカニズムを提供します。

発生する互換性の問題は、VR のみのヘッドセットで `immersive-ar` モード（拡張現実セッション）をに対応できないのと同じくらい根本的なものであるか、XR セッションを作成しようとしたときに満たすことができない1つ以上の必須オプションの要求が含まれる場合があります。

XR セッションは、`navigator.xr.requestSession()` (en-US) メソッドを使用して作成します。 オプションの引数の 1 つは、`XRSessionInit` (en-US) 辞書に準拠したオブジェクトであり、これを使用して、セッションが対応する必要がある（または理想的には対応すべき）必須またはオプションの機能を指定できます。 現在対応しているオプションは、標準参照空間を識別する文字列のみです。 これらを使用すると、コードを実行する前に、必要な、または優先する参照空間型をに対応できる WebXR セッションにアクセスできることを保証できます。

**メモ:** 現在、`XRSession` (en-US) を作成するときに使用できるオプションは、使用または優先する参照空間のみです。 将来的には、より多くのオプションが利用可能になる可能性があります。

オブジェクトの配置と方向付け
--------------

アプリと WebXR API の間で交換されるすべての空間（位置、方向、および動き）情報は、フレームのレンダリング時に特定の空間に関連して表現されます。 それ以上の位置と方向の管理はユーザーと WebGL の間で行われますが、オブジェクトを 三次元世界に正しく配置するために、参照空間からの原点オフセットを利用します。

アニメーションフレームをレンダリングするときは、WebXR セッションの `XRSession` (en-US) オブジェクトの `requestAnimationFrame()` (en-US) メソッドを呼び出したときに指定されたコールバック関数が呼び出されます。 コールバックは、その引数の1つとして、フレームが発生する時刻を示すタイムスタンプを受け取り、対応するアニメーションフレームのすべてのレンダリングを実行する必要があります。

時刻値を増やしながらコールバックが繰り返し呼び出されると、コールバックは XR ハードウェアを使用して提示される一連のフレームを生成し、それによって 三次元シーンがユーザーに表示されます。

アニメーションプロセスの詳細については、レンダリングと WebXR フレームアニメーションコールバックの記事を参照してください。

仮想空間でオブジェクトを配置、方向付け、移動する方法の例と、コードレベルでの詳細な説明については、移動、向き、モーションの記事を参照してください。

関連情報
----

* WebXR 機器 API
* WebGL: ウェブのための 2D および 3D レンダリング
* ウェブの行列計算


レンダリングと WebXR フレームアニメーションコールバック - Web API

レンダリングと WebXR フレームアニメーションコールバック
===============================

WebXR 環境をセットアップし、進行中の XR 環境セッションを表す `XRSession` (en-US) を作成したら、レンダリングのためにシーンのフレームを XR 機器に提供する必要があります。 この記事では、`XRSession` (en-US) を使用して各フレームを表す `XRFrame` (en-US) オブジェクトを取得し、それを使用して、XR 機器に配信するためのフレームバッファーを準備し、レンダリングループで XR シーンのフレームを機器に駆動するプロセスについて説明します。

仮想環境をレンダリングする前に、`navigator.xr.requestSession()` (en-US) メソッドを使用して `XRSession` (en-US) を作成することにより、WebXR セッションを確立する必要があります。 また、セッションをフレームバッファーに関連付けて、他のセットアップタスクを実行する必要もあります。 これらのセットアップタスクについては、WebXR セッションの起動と停止の記事で説明されています。

レンダラーの準備
--------

XR セッションをセットアップし、WebGL フレームバッファーを接続し、WebGL をシーンをレンダリングするために必要なデータで準備したら、レンダラーをセットアップして実行を開始できます。 これは、描画する参照空間を取得することから始まります。 その原点と方向は、ビューアーの開始位置と視線方向に設定します。 それが手に入ったら、次にシーンをレンダリングするためにフレームバッファーが必要になったときにブラウザーがあなたのレンダリング関数を呼び出すように要求します。 これは、`XRSession` (en-US) メソッドの `requestAnimationFrame()` (en-US) を呼び出すことによって行います。

したがって、レンダラーの手はじめは次のようになります。

```js
let worldRefSpace;

async function runXR(xrSession) {
  worldRefSpace = await xrSession.requestReferenceSpace("local");

  if (worldRefSpace) {
    viewerRefSpace = worldRefSpace.getOffsetReferenceSpace(
      new XRRigidTransform(viewerStartPosition, viewerStartOrientation),
    );
    animationFrameRequestID = xrSession.requestAnimationFrame(myDrawFrame);
  }
}
```

没入型世界の参照空間を取得した後、これは、その位置と方向を表す `XRRigidTransform` を作成し、`XRReferenceSpace` のメソッド `getOffsetReferenceSpace()` を呼び出すことにより、ビューアーの位置と方向を表すオフセット参照空間を作成します。

次に、`XRSession` (en-US) のメソッド `requestAnimationFrame()` (en-US) を呼び出して最初のアニメーションフレームをスケジュールし、フレームをレンダリングするためのコールバック関数 `myDrawFrame()` を提供します。

このコードにはループがないことに注意してください！ 代わりに、フレームレンダリングコード（この場合は `myDrawFrame()` という関数）が、もう一度 `requestAnimationFrame()` を呼び出して別のフレームを描画する時刻をスケジュールします。

リフレッシュレートとフレームレート
-----------------

画面が最後にリフレッシュされてから `XRSession` (en-US) のメソッド `requestAnimationFrame()` (en-US) を呼び出したとすると、ブラウザーは、アプリまたはサイトウィンドウを再描画する準備ができるたびにフレームレンダリングコールバックを呼び出します。 このコンテキストでは、「再描画」とは、画面に表示されるコンテンツが、DOM およびその中の要素が現時点で提示しようとしているものと一致することを保証するプロセスを意味します。

### ハードウェア垂直リフレッシュレート

ブラウザーは、WebXR コンテンツが表示されている `<canvas>` をリフレッシュする準備ができると、フレームレンダリングコールバックを呼び出します。 このコールバックは、指定されたタイムスタンプと、モデルやテクスチャーなどの他の関連データ、およびアプリケーションの状態を使用して、指定された時刻に表示されるように、シーンを WebGL バックバッファーにレンダリングします。 コールバックが戻ると、ブラウザーは最後に画面がリフレッシュされてから変更されたものと共に、そのバックバッファーをディスプレイまたは XR 機器に転送します。

歴史的に、ディスプレイは毎秒 60 回リフレッシュされています。 これは、タイミングを合わせるために、米国では 1 秒あたり 60 回（ヨーロッパでは 50 回）循環する AC 配電網の電流フロー波形を使用した初期のディスプレイによるものです。 次のように、このことはいくつかの異なる名前で示されていますが、それらはすべて同等またはほぼ同じです。

* リフレッシュレート（Refresh rate）
* 垂直リフレッシュレート（Vertical refresh rate）
* 垂直帰線消去レート（Vertical blanking rate、VBL）
* 垂直同期レート（Vertical sync rate）

他にも同様の用語が使用されていますが、それが何と呼ばれるかに関係なく、適用される測定単位はヘルツ（Hz）です。 1 秒あたり 60 回リフレッシュするディスプレイには、60 Hz のリフレッシュレートがあります。 つまり、1 秒間に表示できるフレームの最大数は 60 です。 それを超える 1 秒あたりのフレーム数に関係なく、1 秒の間に 60 フレームしか画面に表示されません。

ただし、すべてのディスプレイが 60 Hz で動作するわけではありません。 最近では、より高性能のディスプレイがはるかに高いリフレッシュレートを使用し始めています。 例えば、120 Hz、つまり 120 フレーム/秒のディスプレイは、ますます一般的になっています。 ブラウザーは常にディスプレイと同じレートでリフレッシュを試みます。 つまり、一部のコンピューターでは、コールバックは 1 秒あたり最大 60 回実行されますが、他のコンピューターでは、フレームレートによって異なり 1 秒あたり 90 または 120 回、あるいはそれ以上呼び出される場合があります。

### 各フレームのレンダリングに利用できる時間

これにより、フレーム間で利用可能なほとんどの時間を使用することが重要になります。 ユーザーの端末が 60 Hz のディスプレイを使用している場合、コールバックは 1 秒あたり最大 60 回呼び出され、それよりも頻繁に呼び出されることはないので、確実にできることをすることが目標です。 これを実現するには、メインスレッド外で可能な限り実行し、フレームレンダリングのコールバックをできるだけ効率的にします。 以下の図は、時間の 60 Hz ブロックへの分割を示しています。 各ブロックは、少なくとも部分的にシーンのレンダリングに使用されています。

![フレーム期間あたりのレンダラー実行時間](/ja/docs/Web/API/WebXR_Device_API/Rendering/frames-and-refresh-rate.svg)

コンピュータのビジー状態が増すにつれて、コールバックをフレームごとに正確に呼び出すことができなくなり、フレームをスキップしなければならない場合があるため、これは重要です。 これを**コマ落ち**（dropping frames）と呼びます。 これは、レンダリングが遅延したため、またはレンダリング自体に使用可能な時間よりも長い時間がかかったために、フレームのレンダリングにかかる時間がフレーム間で使用可能な時間を超えると発生します。

![フレーム期間あたりのレンダラー実行時間](/ja/docs/Web/API/WebXR_Device_API/Rendering/dropped-frames-timing.svg)

上の図では、フレーム 3 がペイントされる予定になるまでフレーム 2 がレンダリングを完了しなかったため、フレーム 3 はコマ落ちしています。 次に描画されるフレームはフレーム 4 になります。 これは、レンダリングコールバックに渡されるタイムスタンプが役立つもう 1 つの理由です。 フレーム番号ではなく時間に基づいてシーンを構成することにより、レンダリングされたフレームが遅れることなく、期待したものと一致することを保証できます。

フレームがコマ落ちすると、影響を受ける表示領域のコンテンツは、フレームループを通過しても変更されません。 そのため、ときどきフレームがコマ落ちすることは通常あまり目立ちませんが、頻繁に発生し始めた場合（特に、非常に短い時間に複数のフレームがコマ落ちした場合）は、不快になり、ディスプレイが使用できなくなる可能性があります。

幸い、フレーム間で使用できる時間を `1/refreshRate` 秒として簡単に計算できます。 つまり、1 をディスプレイのリフレッシュレートで除算します。 結果の値は、フレームがコマ落ちしないようにするために、各フレームをレンダリングするのに使用できる時間です。 例えば、60 Hz のディスプレイでは、1 フレームのレンダリングに 1/60 秒、つまり 0.0166667 秒が使用されます。 また、デバイスのリフレッシュレートが 120 Hz の場合、コマ落ちを避けたい場合、各フレームをレンダリングするのに必要な時間は 0.00883333 秒しかありません。

ただし、ハードウェアが実際には 120 Hz である場合でも、毎秒 60 回リフレッシュするだけで十分であり、通常はそれをターゲットとすることをお勧めします。 60 FPS はすでに、ほとんどの人がアニメーションが単なる高速の一連の静止画像ではないことを簡単に検出できるポイントを超えています。 つまり、判別がつかないときは、ディスプレイが 60 Hz でリフレッシュされていると想定できます。 コードが適切に記述されている限り、すべてが問題なく動作します。

### レンダラーのパフォーマンスの問題

明らかに、フレームごとにシーンをレンダリングする時間はほとんどありません。 それだけでなく、レンダラー自体がその時間よりも長く実行されると、フレームがコマ落ちするだけでなく、その時間が完全に無駄になり、他のコードがそのフレームに対してまったく実行されなくなる可能性があります。

それだけでなく、レンダリングが垂直リフレッシュ境界をまたぐ場合、**ティアリング**効果（tearing effect）が発生する可能性があります。 ティアリングは、前のフレームがまだ画面に描画されている間にディスプレイハードウェアが次のリフレッシュサイクルを開始すると発生します。 その結果、画面の上部に新しいフレームが表示されますが、フレームの下部には、前のフレームと場合によってはその前のフレームの組み合わせが表示される視覚効果となります。

したがって、あなたの使命は、利用可能な時間を超過したり、コマ落ちやメインスレッドの過度の悪用を引き起こしたりしないように、コードを十分にタイトかつ軽量に保つことです。

これらの理由により、レンダラーがかなり小さくて軽量で、ほとんど何もすることがないのでない限り、ブラウザーが他の処理を行う間に次のフレームを計算できるように、できる限りすべてをワーカーにオフロードすることを検討する必要があります。 フレームが実際に呼び出される前に計算とデータを準備するだけで、サイトまたはアプリをより効率的にレンダリングし、メインスレッドのパフォーマンスを向上させ、一般的にユーザーエクスペリエンスを向上させることができます。

幸い、レンダリングのニーズが特に重い場合は、影響をさらに減らし、パフォーマンスを最適化するために使用できるいくつかのトリックがあります。 WebXR パフォーマンスガイド (en-US)を参照して、パフォーマンスをできる限り向上させるための推奨事項とヒントを確認してください。

WebXR フレーム
----------

フレームレンダリングコールバック関数は、2 つのパラメーターを入力として受け取ります。 フレームが対応する時刻と、その時刻のシーンの状態を記述する `XRFrame` (en-US) オブジェクトです。

### 3D の光学

私たちが 2 つの目を持つには理由があります。 2 つの目を持つことで、それぞれが本質的にわずかに異なる角度から世界を見ることができます。 それらは既知の固定距離だけ離れているため、私たちの脳は基本的な幾何学と三角法を実行し、その情報から 3D の実在の本質を理解できます。 また、遠近法（perspective）、大きさの違い、さらには通常、3 番目の次元の詳細を理解するために物事がどのように見えるかについての理解も利用します。 これらの要因は、とりわけ、私たちの奥行き知覚（depth perception）の源です。

グラフィックスをレンダリングするときに 3 次元の幻想を作成するには、これらの要因をできるだけ多くシミュレートする必要があります。 これらをシミュレートするほど、そして正確に行うほど、人間の脳をだまして 3D で画像を知覚させることができます。 XR の利点は、古典的な単眼テクニックを使用して 3D グラフィックス（遠近法、大きさ、シミュレートされた視差）をシミュレートできるだけでなく、アニメーションのフレームごとに、各目につき 1 回ずつシーンを 2 回レンダリングすることで、両眼視（つまり、2 つの目を使用した視覚）をシミュレートできることです。

典型的な人間の瞳孔間距離（pupillary distance、瞳孔の中心間の距離）は、54 〜 74 ミリメートル（0.054 〜 0.074 メートル）です。 したがって、ビューアーの頭の中心が `[0.0, 2.0, 0.0]`（水平方向の空間の中心で地上レベルの約 2 メートル）にある場合、まず `[-0.032, 2.0, 0.0]`（中心から左に 32 mm）からシーンをレンダリングし、次に `[0.032, 2.0, 0.0]`（中心から右に 32 mm）で再びレンダリングする必要があります。 このようにして、ビューアーの目の位置を人間の平均瞳孔距離 64 mm に配置します。

その距離（または XR システムが使用するように構成されている瞳孔間距離）は、網膜歪覚（各網膜の見え方の違い）と視差効果によって脳がオブジェクトまでの距離とオブジェクトの奥行きを計算できるようにするために、私たちの心に十分な違いを見せるのに十分です。 これにより、網膜が 2D 表面にすぎないにもかかわらず、3 次元を知覚できるようになります。

これは下の図に示されています。 下の図では、それぞれの目がビューアーの真正面にあるさいころをどのように認識するかを示しています。 この図では、説明のために一部の点で効果を誇張していますが、概念は同じです。 各目は、境界が目の前の円弧を形成する領域を見ます。 それぞれの目は頭の中心線の片側または反対側にオフセットされ、それぞれの目はほぼ同じ視野を見るので、その結果、それぞれの目は、その前にある世界のわずかに異なる部分を別の角度から見ることができます。

![両眼視のしくみを示す図](/ja/docs/Web/API/WebXR_Device_API/Rendering/binocular-vision.svg)

左目はさいころを中央から少し左に見、右目はさいころを中央から少し右に見ます。 その結果、左目はオブジェクトの左側が少しだけ見え、右側が少し見えません。 逆も同様です。 これらの 2 つの画像は網膜に焦点が合わせられ、結果の信号は視神経を介して後頭葉の後部にある脳の視覚皮質に送信されます。

脳はこれらの信号を左目と右目から受け取り、ビューアーの脳内に世界の単一の統一された 3D 画像を構成し、その画像を見ます。 また、左目と右目で見られるものの違いにより、脳はオブジェクトの奥行きや大きさなどに関する多くの情報を推測できます。 推測された奥行き情報を、遠近法、影、これらの関係の意味の記憶などの他の手がかりと組み合わせることで、私たちの周りの世界について多くを理解することができます。

### フレーム、ポーズ、ビュー、フレームバッファー

シーンのある瞬間の状態を表す `XRFrame` を取得したら、ビューアーを基準にしてシーン内のオブジェクトの位置を決定し、レンダリングできるようにする必要があります。 参照空間に対するビューアーの位置と方向は、`XRFrame` (en-US) のメソッド `getViewerPose()` (en-US) を呼び出して取得した `XRViewerPose` (en-US) で表されます。

`XRFrame` は、あなたの世界内のオブジェクトの位置または方向を直接追跡しません。 代わりに、位置と方向をシーンの座標系に変換する方法を提供し、ビューアーの位置と方向のデータを XR ハードウェアから収集し、あなたが構成した参照空間に変換して、フレームレンダリングコードにタイムスタンプ付きで配信します。 そのタイムスタンプとあなた独自のデータを使用して、シーンのレンダリング方法を決定します。

シーンを 2 回レンダリングした後（フレームバッファーの左半分に 1 回、フレームバッファーの右半分に 1 回）、フレームバッファーは XR ハードウェアに送信され、フレームバッファーの各半分が対応する目に表示されます。 これは、多くの場合（常にではありません）、画像を 1 つの画面に描画し、レンズを使用してその画像の正しい半分を各目に転送します。

3D が WebXR によってどのように表現されるかについて詳しくは、WebXR による 3D の表現をご覧ください。

シーンを描く
------

ブラウザーがシーンの次のフレームをペイントできるようにフレームバッファーを準備するときが来たら、`requestAnimationFrame()` に指定した関数が呼び出されます。 それは、描画するフレームの時刻と、レンダリングする必要のあるフレームのシーンの状態に関する詳細を提供する `XRFrame` (en-US) オブジェクトを入力として受け取ります。

理想的には、このコードを 60 FPS のフレームレートに十分か可能な限りそれに近い速さを維持することが必要です。 この 1 つの関数にはあなたのコードだけではないことを思い出してください。 メインスレッドがフレーム自体の持続時間よりもフレームあたりの時間を長く実行する必要がないことを確認する必要があります。

### 基本的なレンダラー

このバージョンの WebXR レンダリングコールバックでは、比較的単純なプロジェクトに最適な非常に単純なアプローチを使用しています。 この疑似コードは、そのプロセスの概要を示しています。
```

for each view in the pose's views list:
  get the WebXR GL layer's viewport
  set the WebGL viewport to match
  for each object in the scene
    bindProgram()
    bindVertices()
    bindMatrices()
    bindUniforms()
    bindBuffers()
    bindTextures()
    drawMyObject()
```

簡単に言えば、この形式のレンダラーは**ビュー優先順**（view-first order）を使用しています。 すべてのオブジェクトを 1 つのビューに描画してから、同じオブジェクトのセットを他のビューにレンダリングして、XR 機器のディスプレイを構成する 2 つのビューのそれぞれを続けてレンダリングします。 その結果、オブジェクトを描画するために必要なデータの多くは、フレームごとに 2 回 GPU に送信されるため、多くの複製された作業があります。 ただし、これは既存の WebGL コードの移植を簡略化し、多くの場合、この作業を行うのに十分なほど優れているため、最初にこの方法を見ていきます。

そのフレームのシーンを構成する次のオブジェクトに進む前に、各オブジェクトを各目に対して 1 回ずつ、2 回続けてレンダリングする（つまり、**オブジェクト優先順**（object-first order）でレンダリングする）代替アプローチについては、オブジェクト優先順でレンダリングすることによる最適化を参照してください。

#### レンダリングコールバックのサンプル

この基本的なパターンに従う実際のコードを見てみましょう。 上記の例では、この関数に `myDrawFrame()` という名前を付けたので、ここでは引き続きこれを使用します。

```js
let lastFrameTime = 0;

function myDrawFrame(currentFrameTime, frame) {
  const session = frame.session;
  let viewerPose;

  // 時間が来たらペイントされる次のフレームをスケジュールします。

  animationFrameRequestID = session.requestAnimationFrame(myDrawFrame);

  // ビューアーの位置と方向を表す XRViewerPose を取得します。
  // 成功した場合、フレームをレンダリングします。

  viewerPose = frame.getViewerPose(viewerRefSpace);
  if (viewerPose) {
    const glLayer = session.renderState.baseLayer;
    gl.bindFrameBuffer(gl.FRAMEBUFFER, glLayer.framebuffer);

    // まず、色と奥行きのフレームバッファーを消去します。

    gl.clearColor(0, 0, 0, 1.0);
    gl.clearDepth(1.0);
    gl.clear(gl.COLOR\_BUFFER\_BIT | gl.DEPTH\_BUFFER\_BIT);

    // 最後のフレームがレンダリングされてからの経過時間を計算します。
    // この値を使用して、アニメーションが意図したとおりの速度で実行されるようにします。

    const deltaTime = currentFrameTime - lastFrameTime;
    lastFrameTime = currentFrameTime;

    // 次に、セッションのビューごとにシーンレンダリングコードを1回呼び出します。

    for (const view of viewerPose.views) {
      const viewport = glLayer.getViewport(view);
      gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
      myDrawSceneIntoView(view, deltaTime);
    }
  }
}
```

`myDrawFrame()` 関数は、`frame` パラメーターで指定された `XRFrame` (en-US) オブジェクトから `XRSession` (en-US) を取得し、セッションの `requestAnimationFrame()` (en-US) メソッドを呼び出して、次のフレームのレンダリングをすぐにスケジュールします。 これにより、すぐにキューに入ることが保証され、`myDrawFrame()` 関数のこの反復で費やされた残りの時間は、次のフレームを描画するタイミングにカウントされます。

次に、フレームの `getViewerPose()` (en-US) メソッドを使用して、ビューアーのポーズ（その位置と方向）を表す `XRViewerPose` (en-US) オブジェクトを取得し、WebXR セッションのセットアップ中に以前に取得した `viewerRefSpace` からビューアーの参照空間を渡します。

ビューアーのポーズを手にすると、フレームのレンダリングを開始できます。 最初のステップは、WebXR 機器がフレームを描画したいフレームバッファーへのアクセスを取得することです。 これは、セッションの `renderState` (en-US) オブジェクトの `baseLayer` (en-US) プロパティからターゲット WebGL レイヤーを取得してから、その `XRWebGLLayer` (en-US) オブジェクトから `framebuffer` (en-US) を取得することによって行われます。 次に、`gl.bindFrameBuffer()` (en-US) を呼び出して、今後のすべての描画コマンドのターゲットとしてそのフレームバッファーをバインドします。

次のステップは、フレームバッファーを消去することです。 *レンダリングコードがフレームバッファー内のすべてのピクセルを書き込むことが保証されている場合に限り*、理論上はこのステップをスキップできますが、パフォーマンスの全てを最後まで出し切る必要がない限り、とにかくすべてのピクセルに触れていることを確実にするために、描画を開始する前にそれをクリアして、描画を開始するのが一般的に最も安全です。 背景色は、`gl.clearColor()` を使用して完全に不透明な黒に設定します。 奥行きのクリアは、`gl.cleardepth()` を呼び出して 1.0 に設定します。 これにより、ピクセルが属するオブジェクトがどれだけ離れているかに関係なく、すべてのピクセルがクリアされます。 最後に、フレームのピクセルバッファーと奥行きバッファーは、`COLOR_BUFFER_BIT` と `DEPTH_BUFFER_BIT` の両方を設定したビットマスクを渡して `gl.clear()` を呼び出して両方とも消去します。

WebXR はすべてのビューに単一のフレームバッファーを使用し、ビュー上のビューポートはフレームバッファー内の各目の視点を分離するために使用されるため、各目（または他の視点）を個別にクリアするのではなく、単一のフレームバッファーをクリアするだけで済みます。

次に、前のフレームがレンダリングされてからの経過時間は、`currentFrameTime` パラメーターで指定された現在の時刻から、最後のフレームがレンダリングされた保存時刻 `lastFrameTime` を差し引いて計算されます。 結果は、最後のフレームがレンダリングされてから経過したミリ秒数を示す `DOMHighResTimeStamp` 値です。 シーンの描画中にこの値を使用して、コールバックが一貫したフレームレートで起動されると想定するのではなく、実際の経過時間を考慮して適切な距離ですべてを移動できるようにします。 この経過時間は変数 `deltaTime` に保存され、`lastFrameTime` の値はこのフレームの時刻に置き換えられ、次のフレームの差分を計算する準備が整います。

それでは、実際にそれぞれの目に対してシーンをレンダリングする時が来ました。 ビューアーのポーズの `views` (en-US) 配列内のビューを反復処理します。 シーンに対する目のパースペクティブ（perspective）を表すこれらの `XRView` (en-US) オブジェクトのそれぞれについて、描画を現在の目の可視画像を表すフレームバッファーの領域に制限することから始める必要があります。

`XRWebGLLayer` (en-US) のメソッド `getViewport()` (en-US) を呼び出して、現在の目の画像用に予約されているフレームバッファー内の領域に描画を制限するビューポートを取得することにより、目のコンテンツをレンダリングする WebGL を準備することから始めます。 次に、ビューポートの X 原点と Y 原点を、幅と高さとともに `gl.viewport()` (en-US) に渡して、WebGL ビューポートを一致するように設定します。

最後に、メソッド `myDrawSceneIntoView()` を呼び出して、実際に WebGL を使用してシーンをレンダリングします。 これには、描画する目を表す `XRView` (en-US)（透視マッピング（perspective mapping）などを実行するため）と `deltaTime` を渡します。 これにより、シーン描画コードは、時間とともに移動するオブジェクトの位置を決定するときに経過時間を正確に表すことができます。

ビューを反復するループが終了すると、ビューアーにシーンを提示するために必要なすべての画像がレンダリングされ、戻ると、フレームバッファーは GPU を経由して、最終的には XR 機器のディスプレイに到達します。 関数の上部で `requestAnimationFrame()` (en-US) を呼び出してあるので、シーンのアニメーションの次のフレームをレンダリングするときに、コールバックがもう一度呼び出されます。

#### このアプローチの欠点

この関数に費やす時間をできるだけ最小限に抑えることが重要であるため、状態変化の処理に費やす時間が長いほど、実際に描画する時間が短くなります。 このテクニックは少数のオブジェクトに対して非常にうまく機能しますが、各オブジェクトのすべてのデータを 2 回（左目に対して 1 回、右目に対して 1 回）再バインドする必要があるため、状態の調整、バッファーとテクスチャーのアップロードなどに、多くの時間を費やしています。 次のセクションでは、これらの状態の変化を大幅に減らし、特にオブジェクト数が増えるにつれて、はるかに高速なレンダリングアプローチを提供する、変更されたアプローチについて説明します。

### オブジェクト優先順でレンダリングすることによる最適化

単一の WebGL フレームバッファーを使用して、左目と右目の両方のビューを単一のフレームバッファーに含めるという WebXR のアプローチの利点は、処理の順序を再配置することにより、レンダリングパフォーマンスを大幅に向上できることです。 特定のビュー（左目など）のビューポートを設定し、左目で見えるすべてのオブジェクトを 1 つずつレンダリングし、各オブジェクトに行ったらバッファーを再構成する代わりに、各オブジェクトをそれぞれの目に 1 回ずつ、2 回続けてレンダリングします。 したがって、両方の目に対してバッファー、ユニフォームなどを 1 回セットアップするだけで済みます。

結果の疑似コードは次のようになります。
```

for each object in the scene
  bindProgram()
  bindUniforms()
  bindBuffers()
  bindTextures()
  for each view in the pose's views list
    get the XRWebGLLayer's viewport
    set the WebGL viewport to match
    bindVertices()
    bindMatrices()
    drawMyObject()
```

このように変更することにより、プログラム、ユニフォーム、バッファー、テクスチャー、その他の可能性のあるものだけを、シーン内の各オブジェクトに対して 2 回ではなく、フレームごとに 1 回だけバインドします。 これにより、潜在的に非常に大きなマージンでオーバーヘッドが削減されます。

### フレームレートの制限

他のコードを実行するためにより多くの時間を確保しながら、維持しようとするベースラインのフレームレートを確立するために、意図的にフレームレートを制限する必要がある場合は、フレームを意図的に定期的にスキップすることができます。

例えば、フレームレートを 50% 下げるには、1 フレームおきにスキップします。

```js
let tick = 0;

function drawFrame(time, frame) {
  animationFrameRequestID = frame.session.requestAnimationFrame(drawFrame);

  if (!(tick % 2)) {
    /\* シーンを描く \*/
  }
  tick++;
}
```

このバージョンのレンダリングコールバックは、`tick` カウンターを維持します。 `tick` が偶数の値である場合にのみ、フレームがレンダリングされます。 このようにして、ひとつおきのフレームのみをレンダリングします。

同様に、`!(tick % 4)` を使用して、4 フレームごとにレンダリングする等々ができます。

### アニメーションを経過時間に合わせる

レンダリングコールバックは、正当な理由で `time` パラメータを受け取ります。 この `DOMHighResTimeStamp` 値は、フレームのレンダリングがスケジュールされた時刻を示す浮動小数点値です。 コールバックの実行は正確に 1/60 秒間隔で発生しないため — そして実際、ユーザーのディスプレイのフレームレートが異なる場合、他のレートで発生する可能性があるため — コードが実行されているという単純な事実に頼って、最後のフレームから 1/60 秒であると想定することはできません。

そのため、アニメーションが目的の速度で正確にレンダリングされるように、提供されているタイムスタンプを使用する必要があります。 これを行うには、最初に行う必要があるのは、最後のフレームがレンダリングされてから経過した時間を計算することです。

```js
let lastFrameTime = 0;

function drawFrame(time, frame) {
  /\* ... 次のフレームのスケジュール、バッファーの準備など ... \*/

  const deltaTime = (time - lastFrameTime) \* 0.001;
  lastFrameTime = time;

  for (const view of pose.views) {
    /\* 各ビューのレンダリング \*/
  }
}
```

これは、前のフレームのレンダリング時間を含む `lastFrameTime` と呼ばれるグローバル（またはオブジェクトプロパティ）を維持します。 この場合、時間の値はミリ秒単位で格納されるため、0.001 を掛けて時間を秒に変換します。 場合によっては、これにより後で時間を節約できます。 他の状況では、ミリ秒単位の時間が必要なため、何も変更する必要はありません。

経過時間を手に入れれば、レンダリングコードは、すべての移動オブジェクトが経過時間内にどれだけ移動したかを計算する手段を持ちます。 例えば、オブジェクトが回転している場合、次のように回転を適用できます。

```js
const xDeltaRotation =
  xRotationDegreesPerSecond \* RADIANS\_PER\_DEGREE \* deltaTime;
const yDeltaRotation =
  yRotationDegreesPerSecond \* RADIANS\_PER\_DEGREE \* deltaTime;
const zDeltaRotation =
  zRotationDegreesPerSecond \* RADIANS\_PER\_DEGREE \* deltaTime;
```

これは、フレームが最後に描画されてからオブジェクトが 3 つの軸のそれぞれを中心に回転した量を計算します。 これがないと、経過時間に関係なく、シェイプはフレームごとに指定された量だけ回転します。 これにより、多くの場合、かなりのつっかえが発生します。

単に回転するのではなく、移動するオブジェクトに適用される同様の概念では、次のようになります。

```js
const xDistanceMoved = xSpeedPerSecond \* deltaTime;
const yDistanceMoved = ySpeedPerSecond \* deltaTime;
const ZDistanceMoved = zSpeedPerSecond \* deltaTime;
```

`xSpeedPerSecond`、`ySpeedPerSecond`、`zSpeedPerSecond` は、それぞれのオブジェクトの速度の軸の成分を含みます。 つまり、`[xDistanceMoved, yDistanceMoved, zDistanceMoved]` は、オブジェクトの速度を表すベクトルです。

シーンのアニメーションに関連する追加のタスク
----------------------

もちろん、レンダラーを通過するたびに発生する可能性のある他のこともあります。 最も一般的な 2 つは、ユーザー入力の処理 (en-US)と、シーン内のオブジェクトのユーザー制御状態やアニメーションパスなどの既知の要因に基づいて、オブジェクト（またはビューアー）の位置を更新することです。

### ユーザー制御入力の処理

WebXR アプリケーションの使用中にユーザーが入力を提供する方法は 3 つあります。 まず、WebXR は、XR ハードウェア自体に統合されているコントローラーからの入力の直接処理をサポートしています。 これらの入力ソースには、ハンドコントローラー、光学追跡システム、加速度計と磁力計などのデバイス、およびそのような他のデバイスが含まれます。

2 番目のタイプの入力は、XR システムを介して接続されたゲームパッドです。 これは、Gamepad API から継承されたインターフェイスを使用しますが、WebXR を介してそれらを操作します。

3 番目の最後のタイプの入力は、キーボード、マウス、トラックパッド、タッチスクリーン、非 XR ゲームパッドおよびジョイスティックなどの従来の非 XR 入力デバイスです。

XR ハードウェアから直接収集できる方向と位置の情報は、自動的に適用されます。 したがって、自分で処理する必要があるのは他の種類の入力です。

* ポインティングデバイスのターゲットとボタンの押下
* ゲームパッドの入力
* 非 XR 入力デバイスの入力

WebXR を使用してシーンを表示する際にユーザー入力を処理する方法の詳細については、入力と入力ソース (en-US)の記事を参照してください。

### オブジェクトの位置の更新

ほとんどの（すべてではありませんが）シーンには、何らかの形のアニメーションが含まれています。 アニメーションでは、物事が適切に動き、互いに反応します。

例えば、仮想現実や拡張現実のゲームでは、敵の非プレイヤーキャラクター（NPC）がコンピューターに制御され、シーン内を移動する場合があります。 時間の経過とともに世界での位置が変化するだけでなく、各 NPC には相互に関連して移動するボディパーツまたはコンポーネントがある可能性があります。 クリーチャーが歩くと腕と足が揺れ、頭が素早く上下したり回転し、髪が跳ねたり揺れたりし、キャラクターが呼吸すると胴体は拡張収縮します。

さらに、動いている物体や構造物があるかもしれません。 スポーツゲームでは、空中で弧を描くボールがあり、その動きをシミュレートする必要があります。 レーシングゲームでは、車やその他の乗り物があり、車輪を含めてアニメーションする可動部品があります。 シーンに水がある場合、波紋または波がリアルに見えるようにする必要があります。 （一部のタイプのゲームの場合）ドア、壁、床など、構造の一部が動いている場合があります。

モーションのもう 1 つの一般的なソースは、プレイヤー自身です。 コントロールからの入力を解釈した後（XR 所属とそれ以外の両方）、ユーザーの動きをシミュレートするために、それらの変更をシーンに適用する必要があります。 詳細とこれがどのように機能するかの完全な例については、移動、向き、モーションの記事を参照してください。

次のステップ
------

レンダラーを作成したら — または、完成していなくても機能するものがあれば — カメラとそのシーン全体の動きを処理することができます。 これについては、WebXR の視点とビューアーに関する記事で説明しています。

関連情報
----

* WebXR の形状と参照空間
* WebXR での空間追跡 (en-US)
* 視点とビューアー: WebXR でのカメラのシミュレーション
* 移動、向き、モーション: WebXR の例
* WebXR パフォーマンスガイド (en-US)


WebXR の基礎 - Web API

WebXR の基礎
=========

WebXRは、 WebXR 機器 API を中核として、拡張現実と仮想現実（AR および VR）の両方をウェブ上で実現するために必要な機能を提供します。これらの技術を合わせて、**複合現実 (MR)** または**クロス現実 (XR)** と呼びます。複合現実は大規模で複雑なテーマであり、学ぶべきことが多く、また、ユーザーにとって魅力的な体験を生み出すために他の多くの API をまとめる必要があります。

このガイドでは、 WebXR とは何か、どのように動作するかの概要と、ウェブ向けの拡張現実や仮想現実の体験の開発を始めるために必要な予備的な基礎知識を提供します。

WebXR とは何であり、何でないのか
-------------------

WebXR は、ウェブコンテンツやアプリが、 VR ヘッドセットや AR ヘッドセットやメガネなどの複合現実ハードウェアとインターフェイスするために使用する API です。これには、三次元体験をシミュレートするために必要なビューのレンダリングプロセスの管理と、ヘッドセット（またはその他のモーションセンシング機器）の動きを感知して、ユーザーに表示する画像を更新するために必要なデータを提供する機能の両方が含まれます。

WebXR はさらに、携帯型 VR コントローラーや特殊な複合現実ゲームパッドなどの制御機器からの入力を受け入れるための対応も行っています。

WebXR はレンダリング技術ではないので、三次元データを管理したり、ディスプレイにレンダリングしたりする機能は提供しません。これは重要な事実として覚えておいてください。 WebXR はシーンを描画する際に関連するタイミングやスケジューリング、さまざまな視点を管理しますが、モデルをロードして管理する方法や、レンダリングしてテクスチャを貼る方法などはわかりません。その部分は完全にあなた次第です。幸いなことに、 WebGL と WebGL ベースのさまざまなフレームワークとライブラリーが用意されており、これらすべてを簡単に扱うことができます。

### WebXR は WebVR とどう違うのか

WebVR は、仕様作成者がウェブ上で仮想現実 API を作成するための最適なアプローチを決定するために設計された実験的な API と見なされていました。ブラウザー実装者は WebVR のサポートをブラウザーに追加し、ウェブ開発者が実験を行えるようにしました。しかしすぐに、ウェブ上の仮想現実の API を完成させるには、 WebVR を「修正」しようとするよりも、新しい仕様を開始する方がより理にかなっていることが明らかになりました。

それが WebXR の誕生につながりました。根本的な違いは、 WebXR は仮想現実だけでなく、仮想オブジェクトとユーザーの周辺環境を融合させる拡張現実にも対応していることです。

もう一つの大きな違いは、 WebVR がコントローラーへの対応をゲームパッド API に依存していたのに対し、 WebXR はほとんどの複合現実ヘッドセットで使用される高度な入力コントローラー (en-US)の対応を統合していることも大きな違いです。WebXR では、主な選択アクションとスクイーズアクションはイベントを使用して直接対応し、その他の制御は `Gamepad` オブジェクトの WebXR 固有の特別な実装を介して利用することができます。

基本概念
----

XR のコードを開発する方法を学ぶ前に知っておくべき基本的な概念について考えてみましょう。

### 視野角

**視野角** (**FOV**) とは、昔のフィルムカメラから現代のデジタルビデオカメラ、さらにはコンピューターやモバイル機器のカメラに至るまで、あらゆる映像技術に適用される用語です。

![](/ja/docs/Web/API/WebXR_Device_API/Fundamentals/binocular-vision.svg)

#### 視野角とは？

視野とは、周囲を見ることができる範囲のことです。視野の幅は、度またはラジアン単位で指定され、視野の左端から右端までの円弧を定義する角度として測定されます。

人間の目は、約 135° の視野を取ることができます。健康な両目があると仮定すると、視野角は約 200° から 220° になります。なぜ、両眼で見ると視野が広くなるのに、単眼の 2 倍にはならないのでしょうか。それは、両眼の視野が大きく重なり合うからです。この重なりによって、奥行きを感じることができるのですが、その視野は約 115° です。重なり合った部分の外側では、単眼視に戻ります。

この図は、 FOV の概念を示すもので、左目は青い三角形、右目は赤い三角形です。薄茶色の重なり合った部分が両眼視で、奥行きを感じることができる部分です。よく見ると、それぞれの目が微妙に異なる形で物体を見ており、その 2 つが合成されて三次元の形状になっていることがわかります。

一般的に、アプリケーションは水平の視野角のみを定義し管理します。詳しくは、レンダリングと WebXR フレームアニメーションコールバックのの三次元の光学を参照してください。

#### 視野角と複合現実機器

ユーザーの目が仮想世界が完全に自分を取り囲んでいると錯覚するほど広い視野を実現するには、視野角が少なくとも両眼視領域の幅に近づく必要があります。基本的なヘッドセットは通常 90° 前後から始まり、最高のヘッドセットでは一般的に 150° 前後の視野を持ちます。視野角はレンズの大きさとユーザーの目との距離の問題であるため、ユーザーの眼球にレンズを装着せずに視野角を広げるには限界があります。

視野が広ければ、ユーザーの没入感を大幅に向上させることができます。しかし、視野角を広げると、その分、ヘッドセットの重量やコストが増加します。

### 自由度

**自由度** (degrees of freedom) という用語は、ユーザーが仮想世界内でどの程度自由に動けるかを示すものです。これは、 WebXR のハードウェア構成が、どれだけの種類の動きを認識し、仮想シーンに再現することができるかに直接関係しています。

**図: 3 軸の自由度を持つハードウェアで可能な動き（ヨー、ロール、ピッチ）を示した図**
![3 軸の自由度を持つハードウェアで可能な動き（ヨー、ロール、ピッチ）を示した図](/ja/docs/Web/API/WebXR_Device_API/Fundamentals/3-degrees-of-freedom-min.svg)

#### 回転運動の自由度

最初の 3 つの自由度は **回転の自由度** です。回転の自由度は以下の通りです。

* ピッチ (pitch): 見上げる・見下ろす
* ヨー (yaw): 左右を向く
* ロール (roll): 左右に傾ける

これらのケースでは、ユーザーは空間の同じ場所に留まりながら、3つの軸のうちの1つまたは複数を回転させて、見ている方向を変えます。軸の自由度が2つのシステムは、ユーザーが左右や上下を見ることは感知できても、それ以外の動きを報告することはできません。

一般的なベースラインのヘッドセットは、 3 軸の自由度を持ち、 3 軸すべてで回転を認識します。これは、よく **3DoF** という略語で呼ばれます。

#### 平行移動の自由度

前後、左右、上下の 3 つの軸の自由度は、平行移動の自由度です。 6 つの自由度に対応していることを、 **6DoF** と呼びます。

![WebXR の設定で、 3 つの軸のそれぞれを中心に回転する様子を示した図](/ja/docs/Web/API/WebXR_Device_API/Fundamentals/xr-translation-headset.png)

より高度なヘッドセットでは、少なくとも平行運動の検出に最低限対応しているものもありますが、空間を通してより実質的な動きを捉えるには、通常、カメラ（可視光線または赤外線を使用）などの外部センサーが必要です。

### WebXR セッションモード

WebXR は、同じ API を使用して、 AR （拡張現実）セッションと VR （仮想現実）セッションの両方に対応しています。どちらの種類のセッションを作成するかは、セッションを作成するときに指定します。これは、作成したいセッションの種類に応じた適切なセッションモード文字列を指定することで行われます。

#### 仮想現実

VR 環境では、前景のオブジェクトから背景やスカイボックスまで、画像全体がアプリやサイトによってデジタル処理で作成されます。フレーム描画コードは、残像が発生する可能性を避けるために、各フレームで各ビューのすべてのピクセルを再描画する必要があります。プラットフォームによっては、以前にクリアされたフレームを提供する場合もありますが、フレームごとに各ピクセルを 2 回タッチする必要がないように、フレームバッファーを消去しないようにして、パフォーマンスを最適化する場合もあります。

WebXR では、2 つの VR セッションモードが利用可能です。**インライン**と**没入型**です。前者はセッションモード文字列 `inline` で指定され、ウェブブラウザーの文書のコンテキスト内でレンダリングシーンを表示し、表示するために特別な XR ハードウェアは必要ありません。没入型セッションモードは、セッションモード `immersive-vr` で示されます。このセッションモードは、ヘッドセットなどの XR 機器を必要とし、ユーザーのそれぞれの目に表示されるディスプレイを使用して、世界全体をレンダリングシーンで置き換えます。

#### 拡張現実

拡張現実 (AR) では、ユーザーは自分の周りの物理的な現実環境の上に表示されたレンダリングイメージを見ることになります。 AR は常に没入型の体験であり、シーンは（画面上の箱に囲まれているのではなく）ユーザーの周りの世界全体であるため、 AR セッションモードは `immersive-ar` のみです。

AR 機器には、基本的に 2 つの種類があります。

* カメラでユーザーの目の前の世界を撮影し、その画像の上に WebXR コンテンツを描画し、画面に画像を表示する機器です。端末の画面上に 2D で表示する携帯電話や、両眼のカメラでステレオ撮影し、奥行きを確保した上で、両眼の背景を撮影した WebXR シーンを描画するゴーグルなどがこれにあたります。
* 透明なメガネを使い、レンダリング画像を重ね合わせながら世界を見ることができる機器。ユーザーは、デジタル写真の羅列ではなく、現実の世界を直接見ることができます。

どちらの種類の機器も、 VR セッションを表示することが可能であるはずです。 WebXR は、一般に、使用する機器の種類を問いません。レンダリング処理は、各フレームをレンダリングする前に背景やスカイボックスを消去しないことを除いて、VRの場合とほとんど同じです。

WebXR ハードウェアの種類
---------------

最もシンプルな XR の表現は、シーンをユーザーの画面に直接レンダリングするもので、ウェブ文書のコンテキスト内、または全画面モードのいずれかになります。これは、ユーザーが専用の XR 機器を持っていない場合や、電話などの携帯端末で AR や VR のアプリを閲覧している場合によく見られる方法です。

よりシンプルで低価格の XR 機器は、通常、内蔵コンピュータを使用するか、スマートフォンに接続し、基本的にモバイル CPU と GPU を使用してアプリを実行し、画像をレンダリングしてユーザーに表示します。より高性能なソリューションでは、アプリケーションの実行とグラフィック処理をデスクトップコンピューターなどの外部機器に任せて、ケーブルを使ってコンピューターに接続するか、無線ネットワークを使って画像を受信し、ユーザーに表示します。

### ヘッドセット

没入型 VR 体験の多くは、ゴーグルや何らかのヘッドセットを使って行われます。 VR ヘッドセットは頭に装着し、ストラップで頭の後ろを固定し、 1～2 台のディスプレイの画面をレンズで目に映します。それぞれの目に微妙に異なる画像を表示することで、奥行きを感じさせ、三次元を疑似体験することができます。

![標準的な VR ヘッドセットのイメージ図](/ja/docs/Web/API/WebXR_Device_API/Fundamentals/publicdomainq-virtual_reality_headset.svg)

大半のヘッドセットでは、フレームが半分に分割された単一のディスプレイを使用し、その半分がユーザーのそれぞれの目に焦点を合わせています。例えば、ヘッドセットが 2560 x 1440 の画面を使用し、左半分が左目の表示に、右半分が右目の表示に使われる場合、フレームバッファーは次のように使用されます。

![フレームバッファーを両眼の視点で分割する様子を示した図](/ja/docs/Web/API/WebXR_Device_API/Fundamentals/twoviewsoneframebuffer.svg)

最も単純なヘッドセットは、統合されたセンサーを持たず、スクリーンの各半分を対応する目に焦点を合わせます。この代表的な例が Google Cardboard、 Google が最初に作ったヘッドセットの一種で、段ボールやその他の安価な材料を使って安価に作ることができます。これらの機器は、多くの場合、携帯電話をヘッドセットにはめ込み、その画面とオンボードのグラフィックプロセッサーを使用して、 XR シーンをレンダリングして表示することで動作します。

より高度なヘッドセットにはディスプレイが内蔵されており、ゴムやストラップ、またはマジックテープの留め具を使って頭に装着します。 これらのヘッドセットは、統合されたスピーカーとマイク、および/または外部のものを取り付けるためのコネクターを含む場合があります。さらに、これらのヘッドセットは、ヘッドセットが空間内を移動するときに検出するための様々なセンサーを有していてもよい。含まれるセンサーの型と数によって、ユーザーが持つ自由度の数が決定されます。

### ゴーグルとメガネ

XR ゴーグルは、シミュレーションシーンの奥行きを再現するために必要なシーンのビューをレンダリングするために、グラフィック表示面を目の前に置くという点で、ヘッドセットと似ています。

しかし、ゴーグルは現実の世界を通過し、ユーザーの物理的な環境の上にレンダリングされた画像を重ね合わせるという違いがあります。これは、完全なヘッドセットで必要とされるような、世界をデジタルで再現することなく行われます。その代わり、ディスプレイの表面は透明で、何も表示していなければ、通常のメガネをかけているのと基本的に同じです。オブジェクトが描画されると、ゴーグルのレンズ上に描画され、その部分から物理的な環境が見えなくなります。

### CAVEs

**Cave Automated Virtual Environment** (**CAVE**) は没入型VR環境で、壁（場合によっては天井や床も）にシーンが投影または表示されるため、ユーザーはシミュレーションに完全に囲まれ、シーンに没入することができます。ユーザーは三次元メガネを着用し、投影された画像に三次元効果を加えるとともに、システムが前景のオブジェクトを世界にレンダリングするための手段を提供します。

ユーザーの行動は、ユーザーが身につけたり、手に持ったりするモーションセンサーや、最近では赤外線カメラで検知してモニターすることもあります。また、室内のあちこちに設置されたスピーカーからは、臨場感あふれるサウンドが聞こえてきます。

これらは、日常的に利用されるものではなく、実験的なもの、デモンストレーションのためのもの、あるいは大きな組織で利用されるものがほとんどです。欠点としては、 CAVE では壁より近いものをシミュレーションすることができないことです。

健康と安全に関する重要な注意事項
----------------

仮想の三次元の世界を作るという行為は、要するに、目が光を集める仕組みと、集めたデータを脳が解釈する仕組みを利用したものですから、ソフトウェア設計者や開発者は、その結果が正しいかどうか、いつも以上に注意する責任があることを心に留めておくことが大切です。

### VR 酔い

VR 酔いとは、仮想現実を体験した人が、体験中や体験後しばらくの間、不快感や方向感覚の喪失、あるいは深刻な吐き気などを感じる症状のことです。

仮想現実の何が不快感や気持ち悪さを引き起こすのかについては、さまざまな説がありますが、その多くは、脳が考えていることと見ていることの間に微妙な違いがあっても、こうした症状を引き起こすという考え方に重点を置いています。

欠陥やズレ、歪みがあると、目と脳が混乱し、目の痛みや頭痛から、場合によってはめまいや激しい吐き気まで、さまざまな症状を引き起こす可能性があります。また、ヘッドセットの包括的な性質を考えると、発作を誘発する可能性のあるものを表示しないように注意することが重要です。ユーザーは、苦痛を与えるような画像を表示された場合、すぐに目をそらすことができないかもしれません。

### 物理的なリスク

没入型仮想現実のもう一つの潜在的な問題は、ユーザーがヘッドセットを装着したまま部屋の中を移動した場合、物理的な障害物に衝突してしまうことです。安全な環境でない限り、物理的な環境の中で安全だとわかっている空間をシミュレーションするなど、ユーザーの動きを制限する手がかりを提供することが重要です。

ユーザーのヘッドセットが機器につながれている場合、ユーザーがヘッドセットのコードを引っ張ったり引っ張ったりするような動きを促したり誘惑したりしないようにするのがよいでしょう。これは、怪我を引き起こすだけでなく、ユーザーのヘッドセットまたは機器（電話かコンピュータかにかかわらず）に大きな損傷を与える可能性があります。

どのようなユーザーにとっても危険な状況になる可能性があるコンテンツがある場合は、警告メッセージを表示する必要があります。同様に、可能であれば座ったままでいること、完全没入型バーチャルリアリティを体験する場合は、ヘッドセットを装着したまま動き回ることに注意するよう、ユーザーに注意を促すとよいでしょう。後悔するよりも、安全であることが一番です。

フレームワークの役割
----------

三次元グラフィック、特に複合現実には、複雑な数学、データ管理、その他の複雑なタスクが多く含まれるため、ほとんどの場合、シーンのレンダリングに WebGL を直接使用することはないでしょう。その代わりに、WebGL の上に構築されたフレームワークや ライブラリーを使用して、より便利に作業を行うことが多いでしょう。

WebGL API を直接使用するのではなく、フレームワークを使用することの利点は、ライブラリが仮想カメラ機能を実装している傾向があることです。OpenGL （およびその拡張である WebGL）はカメラビューを直接提供しませんが、代わりにカメラビューをシミュレーションするライブラリーを使用すれば、特に仮想世界を自由に移動できるコードを構築するときに、作業がずっとずっと楽になります。

WebGL は、WebXR セッションに三次元の世界をレンダリングするために使用されるので、まず、WebGL の一般的な使用法と三次元グラフィックの基本についてよく理解しておく必要があります。

### 汎用三次元フレームワーク

これらのフレームワークは、汎用的なプログラミングや、ロジックを自分で作りたい場合のゲーム開発に向いています。三次元シーンの作成やアニメーションを、文脈に関係なく行うことができるように設計されています。

* A-Frame （特に WebXR ベースのアプリを作成するために設計されています）
* Babylon.js
* Three.js
### ゲームのツールキット

ゲームのツールキットは、ゲーム開発者向けに設計されており、物理モデル、入力制御システム、アセット管理、三次元サウンド再生など、ゲームに特化した機能が含まれていることが多いです。

* PlayCanvas
次のステップ
------

これらの基本情報が分かったら、複合現実の世界へ次のステップを踏み出す準備が整いました。以下の記事が参考になります。

* WebXR アプリケーションのライフサイクル (en-US)
* WebXR セッションの起動と終了
* 移動、向き、動作: WebXR の例


WebXR セッションの起動と停止 - Web API

WebXR セッションの起動と停止
=================

**安全なコンテキスト用:** この機能は一部またはすべての対応しているブラウザーにおいて、安全なコンテキスト (HTTPS) でのみ利用できます。

すでに 3D グラフィックス全般、特に WebGL に精通していると想定すると、次の大胆なステップで複合現実を実現できます。 現実の世界に加えて、またはその代わりに人工の風景やオブジェクトを表示するという考え方は、それほど複雑ではありません。 拡張現実または仮想現実のシナリオのレンダリングを開始する前に、WebXR セッションを作成してセットアップする必要があります。 また、適切に停止する方法も知っておく必要があります。 この記事では、これらのことを行う方法を学びます。

WebXR API へのアクセス
----------------

アプリによる WebXR API へのアクセスは、`XRSystem` オブジェクトから始まります。 このオブジェクトは、ユーザーの機器で利用可能なハードウェアとドライバーを通じて利用可能な WebXR デバイススイート全体を表します。 `Navigator` のプロパティ `xr` を介してドキュメントで使用できるグローバルな `XRSystem` オブジェクトがあります。 これは、使用可能なハードウェアとドキュメントの環境を考慮して、適切な XR ハードウェアが使用できる場合に `XRSystem` オブジェクトを返すプロパティです。

したがって、`XRSystem` オブジェクトをフェッチする最も単純なコードは次のとおりです。

```js
const xr = navigator.xr;
```

WebXR が利用できない場合、`xr` の値は `null` または `undefined` になります。

### WebXR の可用性

新しい、まだ開発中の API として、WebXR のサポートは特定のデバイスとブラウザーに限定されています。 そして、それらでさえ、デフォルトで有効にならないかもしれません。 ただし、互換性のあるシステムがない場合でも、WebXR を試すことができる選択肢がある場合があります。

#### WebXR ポリフィル

WebXR 仕様を設計しているチームは、WebXR API をサポートしていないブラウザーで WebXR をシミュレートするために使用できる WebXR ポリフィルを公開しています。 ブラウザーが古い WebVR API をサポートしている場合は、それが使用されます。 それ以外の場合、ポリフィルは、Google の Cardboard VR API を使用する実装にフォールバックします。

ポリフィルは仕様とともに維持され、仕様に合わせて最新に保たれます。 さらに、WebXR および WebXR に関連するその他のテクノロジーのサポートと、ポリフィルの変更の実装の経時的なサポートとして、ブラウザーとの互換性を維持するために更新されます。

必ず readme を注意深く読んでください。 ポリフィルには、ターゲットブラウザーに含まれる新しい JavaScript 機能との互換性の程度に応じて、いくつかのバージョンがあります。

#### WebXR API エミュレーター拡張機能

Mozilla WebXR チームは、WebXR API をエミュレートし、HTC Vive、Oculus Go、Oculus Quest、Samsung Gear、Google Cardboard などの互換性のあるさまざまなデバイスをシミュレートする、Firefox と Chrome の両方と互換性のある WebXR API Emulator ブラウザー拡張機能を作成しました。 拡張機能を配置すると、ヘッドセットと任意のハンドコントローラーの位置と向き、およびコントローラーのボタンを制御できる開発者ツールパネルを開くことができます。

##### エミュレーターの使用

実際のヘッドセットを使用するのに比べて少し厄介ですが、これにより、WebXR が通常利用できないデスクトップコンピューターで WebXR のコードを試して開発することができます。 また、コードを実際のデバイスに取り込む前に、いくつかの基本的なテストを実行できます。 ただし、エミュレーターはまだすべての WebXR API を完全にエミュレートしていないため、予期しない問題が発生する可能性があることに注意してください。 ここでも、readme ファイルを注意深く読み、開始する前に制限事項を確認してください。

**重要**: 製品をリリースまたは出荷する前に、常に実際の AR や VR のハードウェアでコードをテストする必要があります。 エミュレート、シミュレーション、またはポリフィルされた環境は、物理デバイスでの実際のテストに代わる適切なものではありません。

##### 拡張機能の取得

以下のサポートされているブラウザー用の WebXR API エミュレーターをダウンロードしてください。

* Google Chrome
* Mozilla Firefox

拡張機能のソースコードは、GitHub で入手できます。

##### エミュレーターの問題とメモ

これは拡張機能に関する完全な記事の場所ではありませんが、言及する価値のある特定の事項がいくつかあります。

拡張機能のバージョン 0.4.0 は 2020 年 3 月 26 日に発表されました。 安定状態に近づいている WebXR AR モジュールによる拡張現実（AR）のサポートが導入されました。 AR のドキュメントは、近日中に MDN で公開されます。

その他の改善には、エミュレーターを更新して `XR` インターフェイスの名前を `XRSystem` に変更し、スクイーズ（グリップ）入力ソースのサポートを導入し、`XRInputSource` のプロパティ `profiles` のサポートを追加します。

### コンテキスト要件

WebXR 互換環境は、安全にロードされたドキュメントから始まります。 ドキュメントは、ローカルドライブ（`http://localhost/...` などの URL を使用するなど）からロードするか、ページのロード時に HTTPS を使用する必要があります。 同様に、JavaScript コードは安全にロードされている必要があります。

ドキュメントが安全にロードされなかった場合は、それほど遠くまで到達できません。 `navigator.xr` プロパティは、ドキュメントが安全にロードされていない場合には存在しません。 これは、互換性のある XR ハードウェアが利用できない場合にも当てはまります。 どちらの場合でも、`xr` プロパティの欠如に備える必要があり、エラーを適切に処理するか、何らかの形式のフォールバックを提供する必要があります。

### WebXR ポリフィルにフォールバック

フォールバックの選択肢の 1 つは、WebXR 標準化プロセスを担当する Immersive Web ワーキンググループによって提供される WebXR ポリフィルです。 ポリフィルは、WebXR をネイティブでサポートしていないブラウザーに WebXR のサポートを提供し、サポートしているブラウザーの実装間の不整合を解消するため、WebXR がネイティブで利用できる場合でも役立つ場合があります。

ここでは、前の `<script>` タグを使用してポリフィルが含まれている、またはロードされていると想定して、オプションでポリフィルをインストールした後に `XRSystem` オブジェクトを返す `getXR()` 関数を定義します。

```js
let webxrPolyfill = null;

function getXR(usePolyfill) {
  let tempXR;

  switch (usePolyfill) {
    case "if-needed":
      tempXR = navigator.xr;
      if (!tempXR) {
        webxrPolyfill = new WebXRPolyfill();
        tempXR = webxrPolyfill;
      }
      break;
    case "yes":
      webxrPolyfill = new WebXRPolyfill();
      tempXR = webxrPolyfill;
      break;
    case "no":
    default:
      tempXR = navigator.xr;
      break;
  }

  return tempXR;
}

const xr = getXR("no"); // ネイティブの XRSystem オブジェクトを取得
const xr = getXR("yes"); // 常にポリフィルから XRSystem を返す
const xr = getXR("if-needed"); // navigator.xr がない場合にのみポリフィルを使用
```

返された `XRSystem` オブジェクトは、MDN で提供されているドキュメントに従って使用できます。 グローバル変数 `webxrPolyfill` は、ポリフィルへの参照を保持するためにのみ使用され、不要になるまでポリフィルを使用できるようにします。 これを `null` に設定すると、依存しているオブジェクトがそれを使用しなくなったときに、ポリフィルをガベージコレクションできることを示します。

もちろん、必要に応じてこれを簡略化できます。 アプリはおそらくポリフィルを使用するかどうかについてあまり行き来しないので、これを必要な特定のケースに単純化できます。

### 権限とセキュリティ

WebXR を中心に展開する多くのセキュリティ対策があります。 まず、ユーザーの世界観を完全に置き換える `immersive-vr` モードを使用するには、`xr-spatial-tracking` 機能ポリシーを設定する必要があります。 それに加えて、ドキュメントは安全で現在フォーカスされている必要があります。 最後に、`click` イベントのハンドラーなどのユーザーイベントハンドラーから `requestSession()` (en-US) を呼び出す必要があります。

安全な WebXR の活動と使用方法の詳細については、WebXR の権限とセキュリティ (en-US)の記事を参照してください。

### 必要なセッションタイプが利用可能であることの確認

新しい WebXR セッションを作成する前に、ユーザーのハードウェアとソフトウェアが使用したいプレゼンテーションモードをサポートしているかどうかを最初に確認するのがしばしば賢明です。 これは、たとえば、没入型プレゼンテーションとインラインプレゼンテーションのどちらを使用するかを決定するためにも使用できます。

特定のモードがサポートされているかどうかを確認するには、`XRSystem` のメソッド `isSessionSupported()` (en-US) を呼び出します。 これは、指定されたタイプのセッションが使用できる場合は `true`、そうでない場合は `false` に解決される promise を返します。

```js
const immersiveOK = await navigator.xr.isSessionSupported("immersive-vr");
if (immersiveOK) {
  // 没入型 VR セッションを作成して使用する
} else {
  // 代わりにインラインセッションを作成するか、
  // インラインが必要な場合は非互換性についてユーザーに伝えます
}
```

セッションの作成と開始
-----------

WebXR セッションは `XRSession` (en-US) オブジェクトによって表されます。 `XRSession` を取得するには、`XRSystem` の `requestSession()` (en-US) メソッドを呼び出します。 このメソッドは、`XRSession` を正常に確立できる場合に `XRSession` で解決する promise を返します。 基本的には、次のようになります。

```js
xr.requestSession("immersive-vr").then((session) => {
  xrSession = session;
  /\* セッションのセットアップを続行します \*/
});
```

このコードスニペットの `requestSession()` に渡されるパラメーター `immersive-vr` に注意してください。 この文字列は、確立する WebXR セッションのタイプを指定します。 この場合は、完全に没入型の仮想現実体験です。 次の 3 つの選択肢があります。

`immersive-vr`

ヘッドセットまたは同様のデバイスを使用した、完全に没入型の仮想現実セッション。 ユーザーの周りの世界をあなたが提示する画像で完全に置き換えます。

`immersive-ar`

ヘッドセットまたは類似の装置を使用して画像が現実世界に追加される拡張現実セッション。 *AR 仕様は流動的であるため、このオプションはまだ広くサポートされていません。*

`inline`

ドキュメントウィンドウのコンテキスト内での XR 画像の画面表示。

機能ポリシーがその使用を禁止したり、ユーザーがヘッドセットの使用許可を拒否したりするなど、何らかの理由でセッションを作成できなかった場合、promise は拒否されます。 したがって、起動して WebXR セッションを返すより完全な関数は次のようになります。

```js
async function createImmersiveSession(xr) {
  try {
    session = await xr.requestSession("immersive-vr");
    return session;
  } catch (error) {
    throw error;
  }
}
```

この関数は、新しい `XRSession` (en-US) を返すか、セッションの作成中にエラーが発生した場合に例外をスローします。

### セッションのカスタマイズ

表示モードに加えて、`requestSession()` (en-US) メソッドは、セッションをカスタマイズするための初期化パラメーターを持つオプションのオブジェクトを取ります。 現在、セッションの構成可能な唯一の側面は、世界の座標系を表すためにどの参照空間を使用する必要があるかです。 必要な参照空間または使用したい参照空間と互換性のあるセッションを取得するために、必須またはオプションの参照空間を指定できます。

たとえば、無制限（`unbounded`）の参照空間が必要な場合は、取得するセッションで無制限の空間を使用できるようにするために、それを必須機能として指定できます。

```js
async function createImmersiveSession(xr) {
  try {
    session = await xr.requestSession("immersive-vr", {
      requiredFeatures: ["unbounded"],
    });
    return session;
  } catch (error) {
    throw error;
  }
}
```

一方、*インライン*セッションが必要で、ローカル（`local`）参照空間を使用する場合は、次のようにします。

```js
async function createInlineSession(xr) {
  try {
    session = await xr.requestSession("inline", {
      optionalFeatures: ["local"],
    });
    return session;
  } catch (error) {
    throw error;
  }
}
```

この `createInlineSession()` 関数は、ローカル参照空間と互換性のあるインラインセッションを作成しようとします。 参照空間を作成する準備ができたら、ローカル空間を試すことができます。 それが失敗した場合は、すべてのデバイスがサポートする必要があるビューアー（`viewer`）参照空間にフォールバックします。

### 新しいセッションを使用するための準備

`requestSession()` (en-US) メソッドが返した promise が正常に解決されると、使用可能な WebXR セッションが手中にあることがわかります。 次に、セッションを使用できるように準備し、アニメーションを開始できます。

セッションの構成を完了するために必要な（または必要になる可能性のある）主なことは、次のとおりです。

* 監視する必要があるイベントのハンドラーを追加します。 ほとんどの場合、これには `end` (en-US) が含まれるため、セッションの終了を検出できます。
* XR 入力コントローラーを使用する場合は、`inputsourceschange` (en-US) イベントを監視して、XR 入力コントローラーの追加または削除、およびさまざまな選択およびスクイーズのアクションイベント (en-US)を検出します。
* `XRSystem` のイベント `devicechange` (en-US) を監視して、利用可能な没入型デバイスのセットが変更されたときに通知を受けることができます。
* ターゲットコンテキストで `HTMLCanvasElement` のメソッド `getContext()` を呼び出して、フレームをレンダリングする予定のキャンバスの WebGL コンテキストを取得します。
* WebGL データとモデルを設定し、シーンをレンダリングする準備をします。
* `XRWebGLLayer` (en-US) を作成し、セッションの `renderState` (en-US) のプロパティ `baseLayer` (en-US) に値を渡して、XR システムのソースとして WebGL コンテキストを設定します。
* 必要に応じて、オブジェクトの初期位置と拡大縮小の計算を実行します。
* フレームレンダリングサイクルを開始します。

基本的な形式では、この最終的なセットアップを行うコードは次のようになります。

```js
async function runSession(session) {
  let worldData;

  session.addEventListener("end", onSessionEnd);

  let canvas = document.querySelector("canvas");
  gl = canvas.getContext("webgl", { xrCompatible: true });

  // WebGL データなどを設定する

  worldData = loadGLPrograms(session, "worlddata.xml");
  if (!worldData) {
    return NULL;
  }

  // WebGL の構成を完了する

  worldData.session.updateRenderState({
    baseLayer: new XRWebGLLayer(worldData.session, gl),
  });

  // シーンのレンダリングを開始します

  referenceSpace = await worldData.session.requestReferenceSpace("unbounded");
  worldData.referenceSpace = referenceSpace.getOffsetReferenceSpace(
    new XRRigidTransform(
      worldData.playerSpawnPosition,
      worldData.playerSpawnOrientation,
    ),
  );
  worldData.animationFrameRequestID =
    worldData.session.requestAnimationFrame(onDrawFrame);

  return worldData;
}
```

この例では、`worldData` という名前のオブジェクトを作成して、その世界とレンダリング環境に関するデータをカプセル化します。 これには、`XRSession` (en-US) 自体、WebGL でシーンをレンダリングするために使用されるすべてのデータ、その世界の参照空間、および `requestAnimationFrame()` (en-US) によって返される ID が含まれます。

最初に、`end` (en-US) イベントのハンドラーが設定されます。 次に、レンダリングするキャンバスを取得し、その WebGL コンテキストへの参照を取得して、`getContext()` を呼び出すときに `xrCompatible` オプションを指定します。

次に、WebGL レンダラーに必要なデータとセットアップが実行されてから、WebGL が独自のフレームバッファーとして WebGL コンテキストのフレームバッファーを使用するように構成されます。 これは、`XRSession` (en-US) のメソッド `updateRenderState()` (en-US) を使用して行われ、レンダリング状態の `baseLayer` (en-US) を、WebGL コンテキストをカプセル化する新しく作成された `XRWebGLLayer` (en-US) に設定します。

### シーンをレンダリングする準備

この時点で、`XRSession` 自体が完全に構成されているため、レンダリングを開始できます。 まず、その世界の座標が記述される参照空間が必要です。 `XRSession` の `requestReferenceSpace()` メソッドを呼び出すことにより、セッションの初期参照空間を取得できます。 `requestReferenceSpace()` を呼び出すときに、必要な参照空間のタイプの名前を指定します。 この場合、`unbounded` です。 ニーズに応じて、`local` または `viewer` を簡単に指定できます。

**メモ:** ニーズに合った適切な参照空間を選択する方法を理解するには、WebXR の幾何学と参照空間の参照空間タイプの選択を参照してください。

`requestReferenceSpace()` によって返される参照空間は、原点 (0, 0, 0) を空間の中心に配置します。 これは素晴らしいことです — プレイヤーの視点が世界の正確な中心から始まる場合は。 しかし、ほとんどの場合、そうではありません。 その場合は、最初の参照空間で `getOffsetReferenceSpace()` を呼び出して、(0, 0, 0) がビューアーの位置に配置されるように座標系をオフセットし、同様に顔を望ましい方向にシフトする*新しい*参照空間を作成します。 `getOffsetReferenceSpace()` への入力値は、デフォルトの世界座標で指定されたプレーヤーの位置と方向をカプセル化する `XRRigidTransform` です。

新しい参照空間が手中にあり、保管するために `worldData` オブジェクトに格納された状態で、セッションの `requestAnimationFrame()` (en-US) メソッドを呼び出して、WebXR セッションのアニメーションの次のフレームをレンダリングするときにコールバックが実行されるようにスケジュールします。 戻り値は、必要に応じて後でリクエストをキャンセルするために使用できる ID であるため、`worldData` にも保存します。

最後に、`worldData` オブジェクトが呼び出し元に返され、メインコードが後で必要なデータを参照できるようになります。 この時点で、セットアッププロセスが完了し、アプリケーションのレンダリング段階に入りました。 レンダリングの詳細については、レンダリングと WebXR フレームアニメーションコールバックを参照してください。

### 運用の詳細について

明らかに、これはほんの一例です。 すべてを保存するために `worldData` オブジェクトは必要ありません。 あなたが好きな方法で維持するために必要な情報を保存できます。 別の情報が必要になったり、別の特定の要件が発生したりして、それはあなたが別の方法で、または別の順序で物事を行う原因となります。

同様に、モデルやその他の情報を読み込んだり、WebGL データ（テクスチャ、頂点バッファー、シェーダーなど）を設定したりするために使用する特定の方法は、ニーズや使用しているフレームワークの状況などによって大きく異なります。

重要なセッション維持イベント
--------------

WebXR セッションの過程で、セッションの状態の変化を示す、またはセッションを適切に動作させ続けるために必要なことを通知するいくつかのイベントのいずれかを受け取る場合があります。

### セッションの可視状態の変化の検出

`XRSession` の可視性の状態が変化すると（セッションが非表示または表示されたとき、またはユーザーが別のコンテキストにフォーカスしたときなど）、セッションは `visibilitychange` (en-US) イベントを受け取ります。

```js
session.onvisibilitychange = (event) => {
  switch (event.session.visibilityState) {
    case "hidden":
      myFrameRate = 10;
      break;
    case "blurred-visible":
      myFrameRate = 30;
      break;
    case "visible":
    default:
      myFrameRate = 60;
      break;
  }
};
```

この例では、可視性の状態に応じて変数 `myFrameRate` を変更します。 おそらく、レンダラーはこの値を使用して、アニメーションループの進行に応じて新しいフレームをレンダリングする頻度を計算します。 したがって、シーンの「ぼかし（blurred）」が多くなるほどレンダリングの頻度は低くなります。

### 参照空間のリセットの検出

時折、ユーザーの世界での位置を追跡しているときに、ネイティブの原点に不連続またはジャンプが発生することがあります。 これが発生する最も一般的なシナリオは、ユーザーが XR デバイスの再キャリブレーションを要求したとき、または XR ハードウェアから受信した追跡データの流れに一時的な障害が発生したときです。 これらの状況により、ネイティブの原点は、ネイティブの原点をユーザーの位置と向きに合わせるために必要な距離と方向の角度で突然ジャンプします。

これが発生すると、`reset` イベントがセッションの `XRReferenceSpace` に送信されます。 イベントの `transform` プロパティは、ネイティブの原点を再調整するために必要な変換を詳述する `XRRigidTransform` です。

**メモ:** `reset` イベントは `XRSession` (en-US) ではなく `XRReferenceSpace` で発生することに注意してください。

`reset` イベントのもう 1 つの一般的な原因は、制限付き参照空間（`XRReferenceSpaceType` が `bounded-floor` である参照空間）が、`XRBoundedReferenceSpace` のプロパティ `boundsGeometry` の変更によって指定されたジオメトリを持っている場合です。

参照空間のリセットのより一般的な原因と、詳細およびサンプルコードについては、`reset` イベントのドキュメントを参照してください。

### WebXR 入力コントロールの使用可能なセットが変更されたときの検出

WebXR は、WebXR システムに固有の入力コントロールのリストを保持しています。 これらのデバイスには、ハンドヘルドコントローラー、モーションセンサーカメラ、モーションセンシティブグローブ、その他のフィードバックデバイスなどが含まれます。 ユーザーが WebXR コントローラーデバイスを接続または切断すると、`inputsourceschange` (en-US) イベントが `XRSession` にディスパッチされます。 これは、デバイスの可用性をユーザーに通知する機会であり、デバイスの入力を監視し始め、構成オプションを提供するか、またはそれを使用するために必要なものを提供します。

WebXR セッションの終了
--------------

ユーザーの VR または AR セッションが終了に近づくと、セッションは終了します。 `XRSession` (en-US) の停止は、ユーザーがボタンをクリックしてセッションを終了したために停止する時であるとセッション自体が判断した場合（ユーザーが XR デバイスをオフにした場合など）、またはアプリケーションが然るべきその他の状況に応じて発生します。

ここでは、WebXR セッションの停止を要求する方法と、要求によるかどうかにかかわらず、セッションが終了したことを検出する方法の両方について説明します。

### セッションの停止

終了時に WebXR セッションを完全に停止するには、セッションの `end()` (en-US) メソッドを呼び出す必要があります。 これは、停止がいつ完了するかを知るために使用できる promise を返します。

```js
async function shutdownXR(session) {
  if (session) {
    await session.end();

    /\* この時点で、WebXR は完全に停止しています \*/
  }
}
```

`shutdownXR()` が呼び出し元に戻ると、WebXR セッションは完全かつ安全に停止しています。

リソースの解放など、セッションの終了時に実行する必要がある作業がある場合は、メインコード本体ではなく、`end` (en-US) イベントハンドラーでその作業を実行する必要があります。 このようにして、停止が自動的にトリガーされたか手動でトリガーされたかに関係なく、クリーンアップを処理します。

### セッションが終了したときの検出

以前に確立したように、`end()` (en-US) メソッドを呼び出したか、ユーザーがヘッドセットをオフにしたか、XR システムで何らかの解決できないエラーが発生したかなど、`XRSession` (en-US) に送信される `end` (en-US) イベントを監視することで、WebXR セッションが終了したことを検出できます。

```js
session.onend = (event) => {
  /\* セッションが停止しました \*/

  freeResources();
};
```

ここでは、セッションが終了し、`end` イベントが受信されると、`freeResources()` 関数が呼び出され、XR の提示を処理するために以前に割り当てたりロードしたりしたリソースが解放されます。 `end` イベントハンドラーで `freeResources()` を呼び出すことにより、ユーザーが停止をトリガーするボタンをクリックしたとき（上記の `shutdownXR()` 関数を呼び出すことなど）と、エラーまたは何らかの理由でセッションが自動的に終了したときの両方で `freeResources()` を呼び出します。

関連情報
----

* WebXR Device API
* WebXR の基本
* WebXR での空間追跡 (en-US)
* 視点とビューアー: WebXR でのカメラのシミュレーション
* 制限付き参照空間の使用 (en-US)
* 入力と入力ソース (en-US)