OculusブラウザでVR


OculusブラウザでWebVRなどを実装する方法の説明です (2019/7/8 新規作成)。

Oculus / WebVR / WebXR / コントローラー / トラッキング / 最適化 / ブラウザ機能 / API / 戻る / トップページ


Oculusブラウザ

OculusブラウザはChromiumをベースに、VR周りの対応を進めているブラウザです。 実機で chrome://version を開くことでバージョンを確認できます。 コンテンツのフルスクリーン表示時に自動で現れるVR化ボタンを押したときや、 VR機能を実装したページでVRを楽しむことが出来ます。
 
元となったChromiumのバージョンはやや古めで、 Oculusブラウザのバージョン情報 Release Notes からも知る事ができます。 最新のOculus Browser 7.0はChromium 77ベースになります。
 
Oculusブラウザ上での開発についての公式のドキュメントは Introduction to Oculus Browser 以下にあります。

WebVR

ライブラリを使う方が多いとは思いますが、ここでは素のAPIを使っていきます。
 
OculusブラウザでVRを実現するには WebVR 1.1 APIを使います。 navigator.getVRDisplays()でVRDisplayオブジェクトを取得して以後の処理に備えます。
	var vrDisplay;
	var frameData=new VRFrameData();
	
	function animate(timestamp)
	{
		vrDisplay.requestAnimationFrame(animate);
		if (vrDisplay.isPresenting)
		{
			vrDisplay.getFrameData(frameData);
			
			render();
		}
		vrDisplay.submitFrame();
	}
	navigator.getVRDisplays().then(function(displays) {
		for (var i=0;i<=displays.length-1;i++)
		{
			vrDisplay=displays[i];
			
			init();
			
			vrDisplay.requestAnimationFrame(animate);
			break;
		}
	});
	
	document.getElementById("vr").addEventListener("click",function(event) {
		vrDisplay.requestPresent([{ source: canvas }]);
	},false);
VRDisplayにrequestAnimationFrame()関数を呼んで、 VR用のアニメーションループを回るようにします。 VRDisplayにrequestPresent()関数を呼ぶとVRモードに入りますが、 ユーザーがボタンを押すなどの行動を取ったタイミングでしか この関数を呼べない点に注意が必要です。
 
そしてWebVR APIは描画周りにWebGL APIの利用が想定されているため、 これも使うことになります。 OculusブラウザはWebGL2、GLSL ES 3.0に対応しています。
	var canvas=document.getElementById("canvas");
	var gl=canvas.getContext("webgl2");
	
	function init()
	{
		canvas.width =Math.max(vrDisplay.getEyeParameters("left").renderWidth ,vrDisplay.getEyeParameters("right").renderWidth )*2;
		canvas.height=Math.max(vrDisplay.getEyeParameters("left").renderHeight,vrDisplay.getEyeParameters("right").renderHeight);
	}
	function render()
	{
		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
		
		gl.viewport(0,0,canvas.width*0.5,canvas.height);
		// frameData.leftViewMatrix,frameData.leftProjectionMatrixで左目用を描画
		gl.viewport(canvas.width*0.5,0,canvas.width*0.5,canvas.height);
		// frameData.rightViewMatrix,frameData.rightProjectionMatrixで右目用を描画
		
		gl.flush();
	}

WebXR

WebVRの後継にWebXRがあります。 これはVR/AR/MRを統合して統一的に扱えるようにした新しいAPIで、 Oculusブラウザ7.0からデフォルトで利用できるようになりました。 WebVRはWebXRへ統合されるために2.0の仕様が破棄されているため、 将来的にはWebVRを使うことはなくなり、WebXRを使うのがデフォルトになります。 ただし、だいぶ落ち着いたとは言えWebXRはまだドラフト段階で、 仕様変更される可能性があります。
 
ライブラリを使う方が多いとは思いますが、ここでは素のAPIを使っていきます。 WebXR Device APIの仕様はかなり流動的だったため、 古い仕様で書かれている記事も多くみかけますので注意して下さい。 Oculusブラウザで実装されているWebXR Device API仕様は W3Cのサイトに 掲載 されていますので、これを参考にしましょう。
 
まず、navigator.xrにrequestSession("immersive-vr")関数を呼んで VRモードに入ります。 ユーザーがボタンを押すなどの行動を取ったタイミングでしか この関数を呼べない点に注意が必要です。 そして、XRSessionのrequestAnimationFrame()関数を呼んで、 XR用のアニメーションループを回るようにします。
	var session;
	
	function animate(time,frame)
	{
		session.requestAnimationFrame(onAnimationFrame);
		
		render(time,frame);
	}
	
	document.getElementById("vr").addEventListener("click",function(event) {
		navigator.xr.requestSession("immersive-vr").then(function(xrSession) {
			session=xrSession;
			
			session.requestAnimationFrame(animate);
		});
	},false);
WebXR Device APIも描画周りにWebGL APIの利用が想定されているため、使います。 WebGLRenderingContext/WebGL2RenderingContextを取得する際に 属性 { xrCompatible: true } を指定する必要があります。
	var canvas=document.getElementById("canvas");
	var gl=canvas.getContext("webgl2",{ xrCompatible: true });
	
	var referenceSpace;
	
	function render(time,frame)
	{
		var webGLLayer=session.renderState.baseLayer;
		var pose=frame.getViewerPose(referenceSpace);
		
		gl.bindFramebuffer(gl.FRAMEBUFFER,webGLLayer.framebuffer);
		
		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
		
		for (var i=0;i<=pose.views.length-1;i++)
		{
			var viewport=webGLLayer.getViewport(pose.views[i]);
			gl.viewport(viewport.x,viewport.y,viewport.width,viewport.height);
			
			// pose.views[i].transform.inverse.matrix (=viewMatrix), pose.views[i].projectionMatrix で片目用を描画
		}
		
		gl.flush();
	}
	
	document.getElementById("vr").addEventListener("click",function(event) {
		navigator.xr.requestSession("immersive-vr").then(function(xrSession) {
			session=xrSession;
			
			session.updateRenderState({ baseLayer: new XRWebGLLayer(session,gl) });
			
			session.requestReferenceSpace("local").then((xrReferenceSpace) => {
				referenceSpace=xrReferenceSpace;
				
				session.requestAnimationFrame(animate);
			});
		});
	},false);
VRモードに入ったところで、WebGLRenderingContext/WebGL2RenderingContext を指定したXRWebGLLayerを作って、XRSessionに設定します。 あとはレンダリング時にフレームバッファやビューポート、 視点のView/Projection情報を引っ張り出して使います。

コントローラー

WebVRの場合
 
コントローラーの情報参照・操作は Gamepads APIを介してアクセス します。 Gamepads APIの情報はVRモードになっている間だけ得られます (通常時のコントローラーはマウス操作のエミュレーションに使われます)。
 
コントローラーをロストした時などに情報が失われるため、 (特定の機種前提でコードを書いたとしても) navigator.getGamepads()の値が動的に変化して 空だったり順番が変わったりする可能性があるため、 常にIDをチェックして処理する必要があります。
	var gamepads=navigator.getGamepads();
	for (var i=0;i<=gamepads.length-1;i++)
	{
		if (gamepads[i].id.match(/^Oculus Touch \([^\)]+\)$/))
		{
			if (gamepads[i].hand=="right")
			{
				// gamepads[i].buttons[] などで右手の判定
			}
			if (gamepads[i].hand=="left")
			{
				// gamepads[i].buttons[] などで左手の判定
			}
		}
	}
 
WebXRの場合
 
コントローラーの情報もWebXR経由で取ることができます。 XRInputSourceのhandednessでどちらの手か、 profilesでどの機種用のコントローラーか、 gamepadでボタンやスティックの押下情報が取れます。 gamepad値は WebXR Gamepads Moduleに対応しているようで、 得られる値はGamepads APIと同じ内容が取れます。
	for (var i=0;i<=session.inputSources.length-1;i++)
	{
		if (session.inputSources[i].profiles.indexOf("oculus-touch")!=-1)
		{
			if (session.inputSources[i].handedness=="right")
			{
				// session.inputSources[i].gamepad.buttons[] などで右手の判定
			}
			if (session.inputSources[i].handedness=="left")
			{
				// session.inputSources[i].gamepad.buttons[] などで左手の判定
			}
		}
	}
requestAnimationFrame()を呼び出してから値が得られるようになるようです。
 
Gamepadの値
属性名 データ内容
Oculus Quest
Oculus Touchコントローラー (右手側)
Oculus Quest
Oculus Touchコントローラー (左手側)
Oculus Go
コントローラー
Gear VR
id "Oculus Touch (Right)" "Oculus Touch (Left)" "Oculus Go Controller" "Gear VR Touchpad"
※外部コントローラーの時は"Gear VR Controller"
hand "right" "left" "right" "right" ?
axes[0] 右アナログスティックの倒し量
(左が-1.0、右が1.0)
左アナログスティックの倒し量
(左が-1.0、右が1.0)
Touchサーフェス
(左スワイプが-1.0、右スワイプが1.0)
Touchサーフェス
(左スワイプが-1.0、右スワイプが1.0)
axes[1] 右アナログスティックの倒し量
(上が-1.0、下が1.0)
左アナログスティックの倒し量
(上が-1.0、下が1.0)
Touchサーフェス
(上スワイプが-1.0、下スワイプが1.0)
Touchサーフェス
(上スワイプが-1.0、下スワイプが1.0)
buttons[0] 右アナログスティック押し込み 左アナログスティック押し込み Touchサーフェスのタッチ タッチ
buttons[1] 右手側人差し指トリガー 左手側人差し指トリガー トリガーボタン
buttons[2] 右手側中指トリガー 左手側中指トリガー
buttons[3] Aボタン Xボタン
buttons[4] Bボタン Yボタン
buttons[5] Oculusボタン ? メニューボタン ?
pose.position 右手の位置 左手の位置 手の位置 手の位置
pose.orientation 右手の向き 左手の向き 手の向き 手の向き
pose.linearVelocity 右手の速度 左手の速度 手の速度 手の速度
pose.angularVelocity 右手の角速度 左手の角速度 手の角速度 手の角速度
pose.linearAcceleration 右手の加速度 左手の加速度 手の加速度 手の加速度
pose.angularAcceleration 右手の角加速度 左手の角加速度 手の角加速度 手の角加速度
hapticActuators[] (関数) 右手側振動制御 左手側振動制御
 
Oculus Touchコントローラーでは、 buttons[]は .pressed でボタンを押したかどうかが取れます。 .touched でボタンに指を触れているかどうかが取れますが、中指トリガー (buttons[2]) だけは取れません (トリガーを押して初めて true になる)。 トリガーボタン (buttons[1], buttons[2]) は .value で押し込み量が 0.0〜1.0 の範囲で取れます。
 
Goコントローラーでは、 buttons[]は .pressed でボタンを押したかどうかが取れます。 Touchサーフェス (buttons[0]) は .touched でサーフェスに指を触れているかどうかが取れます。 トリガーボタン (buttons[1]) は .value で押し込み量が 0.0〜1.0 の範囲で取れます。
 
メニューボタン (戻るボタン)、Oculusボタンはブラウザの機能(VRモード終了)が 割り当てられているため、実質的には使えません。

トラッキング

WebVRの場合
 
自分の頭や手の位置は複数のAPIの値を元に算出します。 VR関連の全ての情報はヘッドセットの初期位置を原点としているためです。 VRStageParametersが足元から頭までの高さ情報を持っているので、 各pose値をこれでずらすことで得られます。
 
頭の位置はVRFrameDataのpose値から算出します。
	var head=poseToMatrix(frameData.pose);
	// VRPoseを4×4行列に変換するposeToMatrix()はこちらを参照
	var sittingToStanding=vrDisplay.stageParameters.sittingToStandingTransform;
	// sittingToStanding×headが頭の位置を表すモデル行列
手の位置はGamepadのpose値から算出します。
	var gamepads=navigator.getGamepads();
	for (var i=0;i<=gamepads.length-1;i++)
	{
		if (gamepads[i]!=null && gamepads[i].id.match(/^Oculus Touch \([^\)]+\)$/))
		{
			var hand=poseToMatrix(gamepads[i].pose);
			var sittingToStanding=vrDisplay.stageParameters.sittingToStandingTransform;
			
			if (gamepads[i].hand=="right")
			{
				// sittingToStanding×handが右手の位置を表すモデル行列
			}
			if (gamepads[i].hand=="left")
			{
				// sittingToStanding×handが左手の位置を表すモデル行列
			}
		}
	}
自分が実際に立って/座っているヘッドセットの位置を基準に描画して欲しいなら、 ビュー行列も同様にずらすのが良いです。
	var sittingToStanding=vrDisplay.stageParameters.sittingToStandingTransform;
	var view=frameData.leftViewMatrix;
	// view×sittingToStanding-1を左目描画時のビュー行列に
	var view=frameData.rightViewMatrix;
	// view×sittingToStanding-1を右目描画時のビュー行列に
 
WebXRの場合
 
自分が実際に立って/座っているヘッドセットの位置を基準に描画して欲しいなら、 requestReferenceSpace()関数に"local-floor"を指定します。 この指定をするにはrequestSession時にrequiredFeatures属性で使うことを 事前に指定しておく必要があります。
	document.getElementById("vr").addEventListener("click",function(event) {
		navigator.xr.requestSession("immersive-vr",{ requiredFeatures: ["local-floor"] }).then(function(xrSession) {
			session=xrSession;
			
			session.updateRenderState({ baseLayer: new XRWebGLLayer(session,gl) });
			
			session.requestReferenceSpace("local-floor").then((xrReferenceSpace) => {
				referenceSpace=xrReferenceSpace;
				
				session.requestAnimationFrame(animate);
			});
		});
	},false);
頭の位置はXRViewerPoseから得られます。
	var head=frame.getViewerPose(referenceSpace).transform.matrix;
	// headが頭の位置を表すモデル行列
手の位置はXRInputSourceから得られます。
	for (var i=0;i<=session.inputSources.length-1;i++)
	{
		if (session.inputSources[i].profiles.indexOf("oculus-touch")!=-1)
		{
			var hand=frame.getPose(session.inputSources[i].gripSpace,referenceSpace).transform.matrix;
			if (session.inputSources[i].handedness=="right")
			{
				// handが右手の位置を表すモデル行列
			}
			if (session.inputSources[i].handedness=="left")
			{
				// handが左手の位置を表すモデル行列
			}
		}
	}

最適化

リフレッシュレート
 
OculusブラウザのVRモードはデフォルトで60Hzで描画しますが、requestPresent()を呼ぶ際にattributes引数に highRefreshRate:true を指定する ことで72Hzの描画に引き上げることができます。
	vrDisplay.requestPresent([{ source: canvas, attributes: { highRefreshRate: true } }]);
FFR
 
requestPresent()を呼ぶ際にattributes引数に foveationLevelを指定する ことで、FFR (Fixed Foveated Rendering)を有効化できます。 FFRは人の目が視界の端をきちんと見えていないことを利用して、 画面の端の解像度を落とすことで速度を稼ぐ機能です。
	vrDisplay.requestPresent([{ source: canvas, attributes: { foveationLevel: 3 } }]);
Multiview Rendering (バージョン6.0以降)
 
VRでは左目用と右目用の2つの画像を毎フレーム描画するため、 処理速度の足かせとなります。 左右の目用の描画は、視点位置以外の頂点座標やテクスチャ情報は同じなので、 一度の描画命令で両目を同時に処理することで、無駄を省く機能 です。 これにより、CPU負荷が25%〜50%軽くなると言われています。
 
最新版ではOCULUS_multiview拡張を使います。 Multiviewを実現するために始めに提案された OVR_multiview拡張は改良版のOVR_multiview2拡張に変わっていますが、 OVR_multiview2拡張は標準化提案の草案状態なので、 さらに上位互換のアンチエイリアシング機能を足された OCULUS_multiview拡張のみがデフォルトで利用可能になっているためです。 なお、WebGL 2.0+GLSL ES 3.0の利用が必須です。
	gl.getExtension("OCULUS_multiview");											// Multiview機能を有効化
	var samples=2;				// 2以上でアンチエイリアシング (最大値:gl.getParameter(gl.MAX_SAMPLES))
	
	var defaultFramebuffer=gl.getParameter(gl.FRAMEBUFFER_BINDING);
	
	var multiviewFramebuffer=gl.createFramebuffer();
	gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER,multiviewFramebuffer);
	
	var multiviewColorTexture=gl.createTexture();
	gl.bindTexture(gl.TEXTURE_2D_ARRAY,multiviewColorTexture);
	gl.texStorage3D(gl.TEXTURE_2D_ARRAY,1,gl.RGBA8,canvas.width/2,canvas.height,2);
	if (samples>1)
	{
		multiview.framebufferTextureMultisampleMultiviewOVR(gl.DRAW_FRAMEBUFFER,gl.COLOR_ATTACHMENT0,multiviewColorTexture,0,samples,0,2);
	}
	else
	{
		multiview.framebufferTextureMultiviewOVR(gl.DRAW_FRAMEBUFFER,gl.COLOR_ATTACHMENT0,multiviewColorTexture,0,0,2);
	}
	
	var depthStencilTexture=gl.createTexture();
	gl.bindTexture(gl.TEXTURE_2D_ARRAY,depthStencilTexture);
	gl.texStorage3D(gl.TEXTURE_2D_ARRAY,1,gl.DEPTH32F_STENCIL8,canvas.width/2,canvas.height,2);
	if (samples>1)
	{
		multiview.framebufferTextureMultisampleMultiviewOVR(gl.DRAW_FRAMEBUFFER,gl.DEPTH_STENCIL_ATTACHMENT,depthStencilTexture,0,samples,0,2);
	}
	else
	{
		multiview.framebufferTextureMultiviewOVR(gl.DRAW_FRAMEBUFFER,gl.DEPTH_STENCIL_ATTACHMENT,depthStencilTexture,0,0,2);
	}
	
	function render()
	{
		gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER,multiviewFramebuffer);
		gl.viewport(0,0,canvas.width/2,canvas.height);
		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
		
		gl.uniformMatrix4fv(gl.getUniformLocation(shaderProgram,"vpMatrixLeft"),false,vpMatrixLeft);	// frameData.leftProjectionMatrix×frameData.leftViewMatrix
		gl.uniformMatrix4fv(gl.getUniformLocation(shaderProgram,"vpMatrixRight"),false,vpMatrixRight);	// frameData.rightProjectionMatrix×frameData.rightViewMatrix
		// ここで描画
		
		gl.invalidateFramebuffer(gl.DRAW_FRAMEBUFFER,[ gl.DEPTH_STENCIL_ATTACHMENT ]);
		gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER,defaultFramebuffer);
		
		// multiviewColorTextureを画面全体に書き写し (こちらのblit()を参照)
		
		gl.flush();
	}
このMultiview処理では、左目用と右目用の両方の画像を持つ 3Dテクスチャを描画対象にして一括で描画し、 そのテクスチャを本来のフレームバッファへ書き写すことで実現します。
 
一括描画処理は 1回のドローコールで頂点シェーダーがgl_ViewID_OVRの値のみが変化して 2回走ります。 そのため、左右両方の目の情報をシェーダーに渡しておいて、 シェーダー側で振り分けるように書きます。
	<script type="x-shader/x-vertex">
	#version 300 es
	#extension GL_OVR_multiview : require	// Multiview機能用頂点シェーダー
	layout(num_views=2) in;				// Multiview用指定 (2は同時に描画する数)
	in vec3 position;
	in vec4 color;
	in vec3 normal;
	uniform mat4 vpMatrixLeft;
	uniform mat4 vpMatrixRight;
	uniform mat4 mMatrix;
	out vec4 vColor;
	out vec3 vNormal;
	void main()
	{
		mat4 vpMatrix = gl_ViewID_OVR == 0u ? vpMatrixLeft : vpMatrixRight;		// 左右のどちらの描画かによって振り分ける
		gl_Position=vpMatrix*mMatrix*vec4(position,1.0);
		vColor=color;
		vNormal=normalize(normal);
	}
	</script>
アンチエイリアシング (マルチサンプリング) を無効で使うと、 特に直線などが汚くなってしまいます。 有効にするとMultiviewを使わないときと同様に綺麗になりますが、 処理速度が落ちてしまうようで、まだ発展途上の機能かもしれません。
 
Multiview Rendering (バージョン5系)
 
バージョン5系では、OVR_multiview拡張を読み込んで (デフォルトではWEBGL_multiviewではなくOVR_multiviewを使うようになっています)、 requestPresent()を呼ぶ際にMultiviewを要求します。 描画処理ではVRViewからフレームバッファを受け取ってWebGL側に設定します。 こちらの手法では3Dテクスチャの経由は不要で、WebGL 1.0でも実現可能です。
	gl.getExtension("OVR_multiview");											// Multiview機能を有効化
	
	vrDisplay.requestPresent([{ source: canvas, attributes: { multiview: true, depth: true } }]);	// Multiview機能を要求
	
	function render()
	{
		var views=vrDisplay.getViews();		// getViewsはWebVR 2.0の機能で、大半のブラウザでは未定義(バージョン6以降含む)なので注意
		gl.enable(gl.SCISSOR_TEST);
		for (var i=0;i<=views.length-1;i++)
		{
			var viewport=views[i].getViewport();
			gl.bindFramebuffer(gl.FRAMEBUFFER,views[i].framebuffer);
			gl.viewport(viewport.x,viewport.y,viewport.width,viewport.height);
			gl.scissor(viewport.x,viewport.y,viewport.width,viewport.height);
			gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
			// Multiview機能が動いているか views[i].getAttributes().multiview で要確認
			
			gl.uniformMatrix4fv(gl.getUniformLocation(shaderProgram,"vpMatrixLeft"),false,vpMatrixLeft);	// frameData.leftProjectionMatrix×frameData.leftViewMatrix
			gl.uniformMatrix4fv(gl.getUniformLocation(shaderProgram,"vpMatrixRight"),false,vpMatrixRight);	// frameData.rightProjectionMatrix×frameData.rightViewMatrix
			// ここで描画
		}
		gl.disable(gl.SCISSOR_TEST);
		
		gl.flush();
	}
シェーダー側の処理はバージョン6.0以降と全く同じで、 gl_ViewID_OVRの値で左右どちら用か振り分けます。
 
VRDisplay.getViews()はWebVR 2.0の機能が前倒し導入されたものですが、 WebVR 2.0の仕様は放棄されています (WebXR Device APIへ統合するためです)。 また、OVR_multiview拡張は見ての通りOculusのベンダープリフィックスが付いています。 そのため、Oculusブラウザバージョン5系以外ではこの方法は使えません。

ブラウザ機能

OculusブラウザのVR以外の機能について書きます。
 
PC/Macでリモートデバッグ
 
開発者モードをONにしてPC/MacとUSBケーブルで繋いでいれば、 PC/Mac上のGoogle Chromeで chrome://inspect/#devices を開いて、 Oculusブラウザで開いているページをデバッグできます。 adb connectしていれば、無線接続でもできます。
 
PC/MacからURLを送る
 
adbでOculusヘッドセットとやりとり出来るように環境設定出来ていれば、 PC/Macで指定したURLをOculusブラウザで開くことができます。
	adb shell am start 開きたいURL
PC/Macから文字を入力する
 
adbでOculusヘッドセットとやりとり出来るように環境設定出来ていれば、 ブラウザで文字入力画面になっているときに、 PC/Macで指定した文字列を打ち込ませることができます。 日本語は無理ですが (文字ではなくキー入力を送っているため)、 パスワード入力などに便利です。
	adb shell input text 打ち込みたい文字列
日本語入力
 
日本語入力は現状実装されていません。 有志の方が作ったBookmarklet を使うことで、擬似的に日本語を入力することはできます。
 
Bookmarklet
 
「ブックマークを作成/編集」でURLを javascript:で始めることで、 Bookmarkletを保存して使うことが出来ます。 ただし、URLの頭にスペースを入れないと保存できない点に注意が必要です。
 
chrome://アドレス
 
OculusブラウザはChromiumベースなので、chrome://アドレスが使えます (chrome://chrome-urls/ で使えるアドレス一覧を表示できます)。 chrome://flags で実験的な機能を先行利用したり、 ブラウザの調子が悪いときに chrome://restart でブラウザだけ再起動できます。
 
file:///アドレス
 
file:///アドレスは使えないようです。検索語扱いされてしまいます。
 
ファイルのダウンロード・アップロード
 
ダウンロードしたファイルは /Download フォルダ以下に保存されます。 WindowsはADB Drivers、 MacはAndroid File Transferを入れることで、USB接続時にPC/Macからローカルフォルダにアクセスできます。 ファイルのアップロードは出来ないようです。 <input type=file> ボタンは押しても無反応です。
 
WebXRのinlineモード
 
WebXRでrequestSession()に"inline"を指定するとWebページ上で、 VRモードと同じ処理を同じコードで動作させることができます。 requestReferenceSpace()の指定が"viewer"に変わる点のみ注意です。 Google Chrome 79以降などでも動作するため、検証に便利です。
	document.getElementById("vr").addEventListener("click",function(event) {
		navigator.xr.requestSession("inline").then(function(xrSession) {
			session=xrSession;
			
			session.updateRenderState({ baseLayer: new XRWebGLLayer(session,gl) });
			
			session.requestReferenceSpace("viewer").then((xrReferenceSpace) => {
				referenceSpace=xrReferenceSpace;
				
				session.requestAnimationFrame(animate);
			});
		});
	},false);

その他のAPI

OculusブラウザでVR以外も含めて、 各APIが使えるようになっているかどうか調べた結果を載せます。
 
機種判別
 
UserAgent中に特定の文字列が入っているか どうかで、機種判別できます。 また、VRDisplay.displayNameの値 でも機種判別できます。
 
機種判別
機種名 UserAgentに含まれる文字列 VRDisplay.displayName
Oculus Quest Quest Build Oculus Quest
Oculus Go Pacific Build Oculus Go
Gear VR SAMSUNG SM-G920F for a Samsung Galaxy S6など ?
 
WebRTC
 
WebRTCは一通り使えますが、 OculusヘッドセットのMediaDevicesは音声入出力しか取れないため (トラッキング用カメラは参照できるカメラ扱いされていません)、 映像は受信専用になります。 受け取ったMediaStreamをテクスチャに貼ったりも出来ます。
 
Screen Capture API
 
スクリーンキャプチャーはバージョン6.0から getDisplayMedia()が実装されましたが、 navigator.mediaDevices.getDisplayMedia()を呼ぶと "NotAllowedError" 例外が出るため、使えません。
 
Web Audio API
 
Web Audio APIは普通に使えます。 音の再生はもちろん、 MediaDevicesからマイクのMediaStreamを取得して MediaRecorderで録音もできます。
 
Web Speech API (SpeechRecognition)
 
音声認識はwebkitSpeechRecognitionが使えてユーザー許可も出来ますが、 start()を呼んだ直後にerrorイベントが返ってきて必ず失敗します。 Chromeではサーバーで処理することで実現しているため、 サーバー側が用意されていないのではないかと思われます。
 
Clipboard API
 
Oculusブラウザにはコピー&ペーストするUIがありませんが、 Clipboard APIは使えます。
 
Notifications API
 
Notificationクラスが存在しないため、通知は使えません。
 
Bluetooth API
 
navigator.bluetooth.requestDevice()を呼ぶと、 "Bluetooth Low Energy not available." 例外が発生するため、 使えないようになっているようです。
 
OffscreenCanvas
 
バージョン6.0以降ではOffscreenCanvasが使えます。 ですが、WebVRで使おうとするとrequestPresent()に渡したところで "OffscreenCanvas presentation not implemented." エラーになるため、 VRで直接使うことは出来ませんでした。
 
WebXRのAR
 
navigator.xr.isSessionSupported("immersive-ar")がfalseを返すのではなく、 未実装のエラーがでます。 仕様もまだ固まっていないため、当分は使えなさそうです。
 
ガーディアンの取得
 
WebXRのrequestReferenceSpace("bounded-floor")でガーディアンの情報が 取れるはずですが、"bounded-floor"指定は未実装のエラーがでるため、 現状は取れないようです。
 
ハンドトラッキング
 
Oculus Quest 12.0から実験機能としてハンドトラッキングが実装されましたが、 Oculusブラウザでは非VRモードではコントローラー操作と同様にポインター扱いで、 VRモードに入る時にハンドトラッキングの終了を促されるため、 直接扱うことはできません。

戻る