1. Introduction
バーチャルリアリティ(Virtual Reality)アプリケーションを動作させるハードウェアは,満足できる体験を提供するために,高精度で低レイテンシ(遅延)のインターフェイスを必要とします. デバイスオリエンテーションイベントのような他のインターフェイスをVR入力に代用することも可能ですが,その場合,そのインターフェイスの元々の意図を薄めてしまいますし,しばしば高品質VRに必要な精度を提供していません. WebVR APIは、開発者が魅力的で快適なVR体験を構築できるように,VRハードウェアへVR専用のインターフェイスを提供します.
2. DOM Interfaces
このセクションでは,上述の機能へ実行時アクセスをサポートするため,DOMに追加されるインターフェイスと機能を説明します.
2.1. VRDisplay
VRDisplay
インターフェイスは,このAPIによってサポートされるすべてのVRデバイスのベースとなるものです. このインターフェイスはデバイスIDやデバイスの説明のような一般的な情報を含んでいます.
interface VRDisplay : EventTarget { readonly attribute boolean isConnected; readonly attribute boolean isPresenting; /** * VRDisplayのキャパビリティのディクショナリ */ [Constant] readonly attribute VRDisplayCapabilities capabilities; /** * このVRDisplayがルームスケール体験をサポートしている場合, * 選択可能(Optional)なステージ属性はルームスケールパラメータの詳細を含みます. */ readonly attribute VRStageParameters? stageParameters; /* 指定した眼の現在の VREyeParameters を返します. */ VREyeParameters getEyeParameters(VREye whichEye); /** * この VRDisplay 固有の識別子.Gamepad API との関連付けに用います. */ [Constant] readonly attribute unsigned long displayId; /** * 表示名,ユーザがデバイスを識別するための可読な名前. */ [Constant] readonly attribute DOMString displayName; /** * 現在のフレームが表示される時点における VRDisplay の未来推定ポーズを含んだ VRPose を返します. * この戻り値は,JavaScriptがブラウザへ制御を返すまで変更されないでしょう. * * VRPose は位置,向き,速度,加速度をそれぞれプロパティとして持つことができます. */ [NewObject] VRPose getPose(); /** * VRDisplay の現時点のポーズを返します.これには予測は適用されない状態の値です. */ [NewObject] VRPose getImmediatePose(); /** * このディスプレイのポーズをリセットして,現在の位置と向きを"原点/ゼロ"値として扱います. * VRPose.position, VRPose.orientation, そして, * VRStageParameters.sittingToStandingTransform はresetPose() を呼出した時に * 更新されるでしょう. このメソッドは座位状態の体験中だけで呼出されるべきです. */ void resetPose(); /** * 視錐台のnear平面を定義する z-depth で,シーンの座標系に付随させる * レンダーターゲットの深度値のマッピングに利用できます. * 初期値は 0.01 です. */ attribute double depthNear; /** * 視錐台のfar平面を定義する z-depth で,シーンの座標系に付随させる * レンダーターゲットの深度値のマッピングに利用できます. * 初期値は 10000.0 です. */ attribute double depthFar; /** * `requestAnimationFrame` へ渡されるコールバックで,新しいフレームが * 描画されるタイミングで呼び出されます.VRDisplay が表示されているときは, * コールバックは,そのHMDのネイティブなリフレッシュレートで呼びだされます. * もし VRDisplay に表示されていない場合は,この関数は window.requestAnimationFrame * の動きと全く同じ動作をします.コンテンツは特定のフレームレートや垂直同期(vsync)の振る舞いを Content should * 仮定してはいけません.HMDは他のディスプレイと非同期に動作し,リフレッシュレートは異なります. */ [Throws] long requestAnimationFrame(FrameRequestCallback callback); /** * `requestAnimationFrame`から返される値が, * `cancelAnimationFrame` へ渡されて,コールバックの登録解除を行います. */ [Throws] void cancelAnimationFrame(long handle); /** * VRDisplayへの表示を開始します.ユーザ操作への応答として呼び出してください. * 表示中に繰り返し呼びだされた場合は,表示されるVRLayerをアップデートします. */ Promise<void> requestPresent(sequence<VRLayer> layers); /** * VRDisplayへの表示を停止します. */ Promise<void> exitPresent(); /** * 現在表示されているレイヤを取得します. */ sequence<VRLayer> getLayers(); /** * VRDisplay へ提供される VRLayer はキャプチャされて,HMDの中に表示されます. * この関数を呼び出すことは,ソースキャンバス上でソースイメージを使うのと同じ効果を持ち, * preserveDrawingBuffer をtrueに設定せずに作成されたcanvasはクリアされます. */ void submitFrame(optional VRPose pose); };
2.1.1. Attributes
isConnected isConnected
属性は必ず VRDisplay
の接続状態を返します.
isPresenting isPresenting
属性は必ず VRDisplay
の表示状態を返します.
capabilities capabilities
属性は,必ず VRDisplay
の VRDisplayCapabilities
オブジェクトを返します. VRDisplayCapabilities
はその VRDisplay
で利用可能な機能のディクショナリです.
getEyeParameters() 指定した眼の現在の VREyeParameters
を返します.アイパラメータは *おそらく* 外部要因(例えばハードウェア側でIPD調整するなど)によっていつでも変化される可能性があります.
結果的に,これらの値はキャッシュするのではなくフレーム毎に取得することが推奨されます.
getPose() VRDisplay
の位置,向き,加速度を保持する VRPose
を返します.
この情報を,シーンの次のフレームをレンダリングする時に使わなければなりません.
ユーザエージェントは,予測技術を用いて,次のフレームがユーザに表示されるタイミングのポーズを推定する場合があります.
取得したポーズは,JavaScriptがブラウザへ制御を返すまでの間に変更してはいけません.
getImmediatePose() この呼出しが実行された時点の VRDisplay
の位置,向き,加速度を保持する VRPose
を返します.
ユーザエージェントは,ポーズを構築時に予測技術を使用してはいけません
resetPose() 現在の位置と向きを"原点/ゼロ"として扱うことで VRDisplay
のポーズをリセットします. getPose()
で得られた予測ポーズと getImmediatePose()
は,最後に resetPose()
が呼び出された時の位置からの相対的な位置を持ち,
最後に resetPose()
が呼び出された時のディスプレイのヨー(yaw)を前方向として扱います. VRDisplay
のロールとピッチは, resetPose()
の呼び出しでは変化せず,重力加速度方向に対する相対的な値になります. resetPose()
を呼び出すことで, VRStageParameters
の sittingToStandingTransform
行列が変化する場合があります.
requestAnimationFrame() VRDisplay
に表示されていない場合,window.requestAnimationFrame と機能的に等価です. VRDisplay
に表示されている場合は,このコールバックは VRDisplay
のネイティブなリフレッシュレートに従って呼び出されます.
cancelAnimationFrame() requestAnimationFrame()
の返り値を渡すことで,コールバック登録を解除できます.
requestPresent() VRDisplay
上の指定した VRLayer
配列のコンテンツの表示を開始し,表示が開始されたら値の設定されたpromiseを返します. canPresent
が false の場合は必ずpromiseをリジェクトしなければなりません. VRLayer
配列が maxLayers
以上の要素を含んでいる場合は必ずpromiseをリジェクトしなければなりません.
ユーザエージェントが,その他の理由でpromiseをリジェクトする可能性があります. requestPresent()
の呼出し時に VRDisplay
が既に表示されていたら,その VRDisplay
に表示中の VRLayer
リストを更新するべきです. VRDisplay
の表示中にrequestPresent()
がリジェクトされた場合は,その VRDisplay
の表示を終了しなければなりません.
exitPresent() VRDisplay
の表示を終了し,完全に終了した時に値の入った promise が返されます. VRDisplay
が表示されていない場合は,promise はリジェクトされなければなりません.
getLayers() 現在表示中の VRLayer
配列を返します. VRDisplay
が表示されていない場合は空の配列を返さなければなりません. VRDisplay
が表示されている場合は,最後に requestPresent()
へ渡された VRLayer
を含む配列を返さなければなりません.
submitFrame() 現在の表示されている VRLayer
の状態をキャプチャし,それを VRDisplay
へ描画します.オプションとして, VRLayer
コンテンツをレンダリングするのに用いるポーズを指定する VRPose
を提供することができます.ユーザエージェントが知覚的な遅延を改善するためにそのポーズをレイヤーコンテンツ操作に使う場合があります(MAY).
もし VRPose
が提供されない場合は, getPose
で取得された最後のポーズが使用されます.
2.2. VRLayer
VRLayer
インターフェイスは VRDisplay
に提供され,HMDへ表示されます.
typedef (HTMLCanvasElement or OffscreenCanvas) VRSource; dictionary VRLayer { VRSource? source = null; sequence<float>? leftBounds = null; sequence<float>? rightBounds = null; };
2.2.1. Attributes
source source
属性は,VRDisplay
.submitFrame()
が呼び出された時に VRDisplay
に表示されるコンテンツとなる canvas を定義します.
leftBounds leftBounds
属性は,眼に表示する source
canvasのテクスチャ境界を決定するUV空間における4つの値を含んでいます: [0]
範囲 (0.0 - 1.0) の左オフセット; [1]
範囲 (0.0 - 1.0) の上オフセット; [2]
範囲 (0.0 - 1.0) の幅; [3]
範囲 (0.0 - 1.0) の高さ. leftBounds
のデフォルト値は [0.0, 0.0, 0.5, 1.0]
でなければなりません(MUST).
rightBounds leftBounds
属性は,眼に表示する source
canvasのテクスチャ境界矩形を決定するUV空間における4つの値を含んでいます: [0]
範囲 (0.0 - 1.0) の左オフセット; [1]
範囲 (0.0 - 1.0) の上オフセット; [2]
範囲 (0.0 - 1.0) の幅; [3]
範囲 (0.0 - 1.0) の高さ. rightBounds
のデフォルト値は [0.5, 0.0, 0.5, 1.0]
でなければなりません(MUST).
2.3. VRDisplayCapabilities
VRDisplayCapabilities
インターフェイスは VRDisplay
の利用可能な機能を記述しています.これらはデバイス毎/ユーザ毎に静的なことが期待されてます.
interface VRDisplayCapabilities { readonly attribute boolean hasPosition; readonly attribute boolean hasOrientation; readonly attribute boolean hasExternalDisplay; readonly attribute boolean canPresent; readonly attribute unsigned long maxLayers; };
2.3.1. Attributes
hasPosition hasPosition
属性は VRDisplay
が位置をトラッキング可能かどうかを返さなければなりません(MUST).
hasOrientation hasOrientation
属性は VRDisplay
が向きをトラッキング可能かどうかを返さなければなりません(MUST).
hasExternalDisplay hasExternalDisplay
属性は VRDisplay
がデバイスのプライマリディスプレイとは分離しているかどうかを返さなければなりません(MUST).
表示している VR コンテンツがデバイスの他のコンテンツに隠されている場合,これはfalseになります.
falseのとき,コンテンツは可視ではないため,アプリケーションはVRコンテンツがミラーされていたり,非VRなUIが更新されることを仮定してはいけません.
canPresent canPresent
属性は VRDisplay
がコンテンツをHMDまたは擬似デバイスへ表示可能か否かを返します(MUST).
6自由度トラッキング可能な"魔法のウィンドウ"デバイスを示すために使うことが可能ですが,その場合 VRDisplay
.requestPresent()
は意味がありません.
もし false ならば VRDisplay
.requestPresent()
を呼び出すと常に失敗となり, VRDisplay
.getEyeParameters()
は NULL を返します.
maxLayers requestPresent()
が受け取れる最大の配列長を表します.canPresent
が true ならば常に1で,それ以外は0となります (MUST).
Note: 本仕様の将来的な版では,複数のレイヤーに対応し,もっと複雑なレンダリング効果(例えば,WebGLやDOM要素の合成など)が可能となるでしょう.今回の版では,そのような機能は許可されていません.
2.4. VREye
enum VREye { "left", "right" };
2.5. VRFieldOfView
VRFieldOfView
インターフェイスは,中心点からの4つの角度で記述される視野(Field of Vew)を表現します.
interface VRFieldOfView { readonly attribute double upDegrees; readonly attribute double rightDegrees; readonly attribute double downDegrees; readonly attribute double leftDegrees; };
VRFieldOfView
からWebGL互換の投影行列を作成しています.
function fieldOfViewToProjectionMatrix (fov, zNear, zFar) { var upTan = Math.tan(fov.upDegrees * Math.PI / 180.0); var downTan = Math.tan(fov.downDegrees * Math.PI / 180.0); var leftTan = Math.tan(fov.leftDegrees * Math.PI / 180.0); var rightTan = Math.tan(fov.rightDegrees * Math.PI / 180.0); var xScale = 2.0 / (leftTan + rightTan); var yScale = 2.0 / (upTan + downTan); var out = new Float32Array(16); out[0] = xScale; out[1] = 0.0; out[2] = 0.0; out[3] = 0.0; out[4] = 0.0; out[5] = yScale; out[6] = 0.0; out[7] = 0.0; out[8] = -((leftTan - rightTan) * xScale * 0.5); out[9] = ((upTan - downTan) * yScale * 0.5); out[10] = -(zNear + zFar) / (zFar - zNear); out[11] = -1.0; out[12] = 0.0; out[13] = 0.0; out[14] = -(2.0 * zFar * zNear) / (zFar - zNear); out[15] = 0.0; return out; }
2.6. VRPose
The VRPose インターフェイスは指定したタイムステップにおけるセンサーの状態を表します.
interface VRPose { readonly attribute DOMHighResTimeStamp timestamp; readonly attribute Float32Array? position; readonly attribute Float32Array? linearVelocity; readonly attribute Float32Array? linearAcceleration; readonly attribute Float32Array? orientation; readonly attribute Float32Array? angularVelocity; readonly attribute Float32Array? angularAcceleration; };
2.6.1. Attributes
timestamp ハードウェアから位置状態データが更新されたかを決定するのに使える単調増加する値. 値は単調増加するので,更新の順序を決めるためにそれを比較できます.新しい値は常に古い値以上になります.
position timestamp
における VRDisplay
の位置を表す3Dベクトルです.
原点からの位置をメートル単位で与えられます.原点は,センサが最初に読み込んだ値あるいは restPose()
を最後に呼出した時のセンサ位置のどちらかになります.
座標系は次の3軸が使用されます:
-
正の X はユーザの右.
-
正の Y は上.
-
正の Z はユーザの後ろ.
すべての位置は座った空間内での一意の向きに対して与えられます.
この点をVRStageParameters
.sittingToStandingTransform
でこの点を立っている空間へ座標変換します.
センサーが位置データを利用できない場合は,おそらくNULLとなるでしょう(MAY).
ユーザエージェントはネックモデリングのようなテクニックでエミュレートした位置の値を提供する場合もあります(MAY)が,
その場合 VRDisplayCapabilities
.hasPosition
は false としてレポートされなければなりません(SHOULD).
NULL出なければ,3要素の配列にしなければなりません(MUST).
linearVelocity timestamp
におけるセンサの線形の速度(メートル/秒)です.
センサが線形速度を利用できない場合はおそらくNULLになります(MAY).
NULLではない場合は3要素の配列となります(MUST).
linearAcceleration timestamp
におけるセンサの線形の加速度(メートル/秒)です.
センサが線形加速度を利用できない場合はおそらくNULLになります(MAY).
NULLではない場合は3要素の配列となります(MUST).
orientation timestamp
におけるセンサの向きをクォータニオンとして表します.
向きのヨー(Y軸周りの回転)は,センサ起動時の初期値または最後に resetPose()
が呼び出された時のヨー値からの相対的な値です.
{x: 0, y: 0, z: 0, w: 1} の向きは,"前方" と仮定されています.
センサが向きデータを利用できない場合はおそらくNULLになります(MAY).
NULLではない場合は4要素の配列となります(MUST).
angularVelocity timestamp
におけるセンサの角速度(ラジアン/秒)です.
センサが角速度を利用できない場合はおそらくNULLになります(MAY).
NULLではない場合は3要素の配列となります(MUST).
angularAcceleration timestamp
におけるセンサの角加速度(ラジアン/秒)です.
センサが角加速度を利用できない場合はおそらくNULLになります(MAY).
NULLではない場合は3要素の配列となります(MUST).
VRPose
からWebGL互換の行列を作成するコードスニペットです:
function poseToMatrix (pose) { var out = new Float32Array(16); // orientation または position が NULL ならデフォルト値を与えます. var q = pose.orientation ? pose.orientation : [0, 0, 0, 1]; var v = pose.position ? pose.position : [0, 0, 0]; // クォータニオンでいくつかの値を計算 var x2 = q[0] + q[0]; var y2 = q[1] + q[1]; var z2 = q[2] + q[2]; var xx = q[0] * x2; var xy = q[0] * y2; var xz = q[0] * z2; var yy = q[1] * y2; var yz = q[1] * z2; var zz = q[2] * z2; var wx = q[3] * x2; var wy = q[3] * y2; var wz = q[3] * z2; out[0] = 1 - (yy + zz); out[1] = xy + wz; out[2] = xz - wy; out[3] = 0; out[4] = xy - wz; out[5] = 1 - (xx + zz); out[6] = yz + wx; out[7] = 0; out[8] = xz + wy; out[9] = yz - wx; out[10] = 1 - (xx + yy); out[11] = 0; out[12] = v[0]; out[13] = v[1]; out[14] = v[2]; out[15] = 1; return out; }
2.7. VREyeParameters
VREyeParameters
インターフェイスは,指定のeye用のシーンを正しくレンダリングするために必要なすべての情報を表現します.
interface VREyeParameters { [Constant, Cached] readonly attribute Float32Array offset; [Constant, Cached] readonly attribute VRFieldOfView fieldOfView; [Constant, Cached] readonly attribute unsigned long renderWidth; [Constant, Cached] readonly attribute unsigned long renderHeight; };
2.7.1. Attributes
offset ユーザの両眼の中心から指定した眼までのオフセット量(メートル)です. この値は瞳孔間距離(IPD)の半分を表さなければなりません(SHOULD)が,指定した眼側のレンズ中心とヘッドセットの中心の距離になっている可能性もあります. 左目用の値は負の値,右目は正の値でなければなりません(MUST).
fieldOfView 指定した眼の現在の視野角(FOV; field of view)です.ユーザが調整したヘッドセットIPDによって変化します.
renderWidth それぞれの眼のビューポートの推奨されるレンダーターゲット幅をピクセル単位で示します.
複数の眼が単一のレンダーターゲット内でレンダリングされる場合は,そのレンダーターゲットは両方のビューポートをフィットさせるのに十分な大きさを持って作成されるべきです.
左右の眼のrenderWidth
は決してオーバーラップさせてはならず(MUST NOT),右目の renderWidth
は左目の renderWidth
の右側になければなりません(MUST).
renderHeight それぞれの眼のビューポートの推奨されるレンダーターゲット高さをピクセル単位で示します.
複数の眼が単一のレンダーターゲット内でレンダリングされる場合は,そのレンダーターゲットは両方のビューポートをフィットさせるのに十分な大きさを持って作成されるべきです.
左右の眼のrenderWidth
は決してオーバーラップさせてはならず(MUST NOT),右目の renderWidth
は左目の renderWidth
の右側になければなりません(MUST).
renderWidth
と renderHeight
から次のように計算できます:
var leftEye = vrDisplay.getEyeParameters("left"); var rightEye = vrDisplay.getEyeParameters("right"); canvas.width = Math.max(leftEye.renderWidth, rightEye.renderWidth) * 2; canvas.height = Math.max(leftEye.renderHeight, rightEye.renderHeight);
2.8. VRStageParameters
VRStageParameters
インターフェイスは,ルームスケール体験をサポートしているデバイス用のステージ/プレイ領域を示す値です.
interface VRStageParameters { readonly attribute Float32Array sittingToStandingTransform; readonly attribute float sizeX; readonly attribute float sizeZ; };
2.8.1. Attributes
sittingToStandingTransform sittingToStandingTransform
属性は,4x4の変換行列からなる16要素の配列です.
この行列は,getPose()
/getImmediatePose()
によって返される着座姿勢空間の位置を立ち姿勢の位置へ変換します.
sizeX プレイ領域の境界の幅をメートル単位で表します. 境界は,床上の軸に沿った(axis-aligned)矩形として定義されます. 矩形の中心は立ち姿勢の座標系の (0,0,0) です. これらの境界は,安全を目的として決められています. コンテンツはユーザがこの境界超えて移動するようなことをしてはいけません; しかし,ユーザは境界を無視してこの矩形の外側に行くことも可能です.
sizeZ プレイ領域の境界の奥行きをメートル単位で表します. 境界は,床上の軸に沿った(axis-aligned)矩形として定義されます. 矩形の中心は立ち姿勢の座標系の (0,0,0) です. これらの境界は,安全を目的として決められています. コンテンツはユーザがこの境界超えて移動するようなことをしてはいけません; しかし,ユーザは境界を無視してこの矩形の外側に行くことも可能です.
2.9. Navigator インターフェイスの拡張 Navigator Interface extension
partial interface Navigator { Promise<sequence<VRDisplay>> getVRDisplays(); readonly attribute sequence<VRDisplay> activeVRDisplays; };
2.9.1. Attributes
getVRDisplays() 利用可能な VRDisplay
のリストを決定するためのPromiseを返します.
アプリケーションは,デバイスのフル機能を使うために,このリストを繰り返し処理して,displayId
が共通しているデバイスを関連付けするべきです.
activeVRDisplays activeVRDisplays
は,現在,表示状態のすべての VRDisplay
含んでいます.
VRDisplay
を見つけます.
var vrDisplay; navigator.getVRDisplays().then(function (displays) { // リストからそれが利用可能であれば最初のディスプレイを使います. // 複数のディスプレイがある場合は,あなたはユーザに利用するディスプレイを選択させることも可能です. if (displays.length > 0) { vrDisplay = displays[0]; } });
2.10. ウィンドウインターフェイスの拡張 Window Interface extension
partial interface Window { attribute EventHandler onvrdisplayconnected; attribute EventHandler onvrdisplaydisconnected; attribute EventHandler onvrdisplaypresentchange; };
2.11. Gamepad インターフェイスの拡張 Gamepad Interface extension
partial interface Gamepad { [Constant] readonly attribute unsigned long displayId; };
2.11.1. Attributes
displayId VRDisplay
に紐付いた displayId
を返します.
3. セキュリティの考慮 Security Considerations
API インターフェイスとWeb IDLには直接影響しませんが,セキュリティモデルはユーザの期待するWeb上のプライバシーを維持しなければなりません.
-
Gamepad API は,そのゲームパッド入力とHMDポーズがフォーカス中のタブだけで利用できるように更新されるでしょう.
-
フォーカス外のタブは
Gamepad
とVRDisplay
を列挙することは可能ですが,最後に受けとった状態あるいはデフォルト状態のままに見えるでしょう. -
すべてのゲームパッドはフォーカス中のタブによって所有されていると仮定されるので,それらをパスワードフィールドのようなセキュアな入力に使うことができます.
-
ブラウザによって表示される信頼性のあるUI要素は,信頼できないコンテンツに露出した
VRDisplay
に関連するGLコンテキストからはアクセスできません. -
信頼性あるUIはchromeのみがレンダリングし,JavaScriptは独立したGLコンテキストを持ちます.
-
"VR Compositor" はコンテンツから非同期に動作し,信頼性のあるコンテンツと信頼性のないコンテンツの合成に関して責任を持ちます.コンテンツが実行しないあるいはフレームを提示しないならば,ブラウザはレスポンシブなフロントエンドを表示し続けることが可能であるべきです.
-
コンテンツの処理が予期せず終了するときのイベント内で,ブラウザはVRモードを終了しないでしょう.VR compositor は信頼できるブラウザのUI要素を表示し続けている間にコンテンツレイヤを破棄するでしょう.
-
HMDポーズとその他のVR入力はフォーカス中のWebVRページ用にだけ更新されます.これはキーボードとマウスの入力と同様の方法で実装することができます.
-
コンテンツはVR HMD表示のためにユーザパーミッションを要求する必要はありません; しかし,ブラウザによって表示されるUIは通常2Dページコンテンツ用ですが,VRサイトを信頼して訪れることをユーザが望むことを保証するために,ページのロードやリンク移動時にはHMD内に表示されるでしょう.
-
ユーザがVR世界内で気持ち悪くなったとしても,ユーザは視野全体が覆われているためディスプレイから目をそらすことができません.代わりにユーザは目を閉じて,デフォルトページヘ逃げるために視覚を必要としないアクションを実行します(例えば,予約されたボタンやモーションコントロールでジェスチャを実行する).
-
CORS関連の脆弱性を防止するため,各ページはWebVR APIによって返されたオブジェクト(例えば
VRDisplay
)の独立したインスタンスを見るでしょう.あるページでセットされた属性(例:VRLayer
.source
)が他から読めないことを保証することに気をつけなければなりません.