">
FlatIsleロゴ

Flat Isle 日誌

BABYLON.jsによるXRカメラ表示

2023-02-08

前回はローダーを用いてglTF形式の3Dオブジェクトの表示までを行いました。
今回は初回に作成したシンプルなボールと「モンキー」をXRカメラで表示してみます。
テストにはAndroidスマートフォンと、Google Cardboard等のVRゴーグルを準備して下さい。

VR表示

以前までBABYLON.jsでのVR表示には、「createDefaultVRExperience()」のVRカメラのヘルパー関数が用意されていました。現在はVR表示とAR表示両方に対応するXRカメラのヘルパー関数「createDefaultXRExperienceAsync()」が用意されていますので、そちらを使用してVR表示を行ってみます。
追加する処理は以下のヘルパー関数の呼び出しのみです。

scene.createDefaultXRExperienceAsync({
	floorMeshes: [oGnd]
});

上記関数を初回の表示に足したものは以下のコードになります。

<!DOCTYPE html>
<html lang="ja">
<head>
	<meta  http-equiv="Content-Type" content="text/html" charset="utf-8"/>
	<title>Babylon VR Test</title>
	<script src="https://cdn.babylonjs.com/babylon.js"></script>
	<script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>
	<style>
		html, body {
			overflow: hidden;
			width: 100%;
			height: 100%;
			margin: 0;
			padding: 0;
		}
		#renderCanvas {
			width: 100%; 
			height: 100%;
			touch-action: none;
		}
	</style>
</head>
<body>
	<canvas id="renderCanvas"></canvas>
<script>
	let canvas, engine, scene;
	window.onload = function(){
		canvas = document.getElementById("renderCanvas");
		engine = new BABYLON.Engine(canvas, true);
		scene                   = new BABYLON.Scene(engine);
		scene.gravity           = new BABYLON.Vector3(0, -0.98, 0);
		scene.collisionsEnabled = true;
		scene.clearColor        = new BABYLON.Color3.Black;

		let camera = new BABYLON.UniversalCamera ("Camera", new BABYLON.Vector3(0,  1.2,-5), scene);
		let lightD = new BABYLON.DirectionalLight("LightD", new BABYLON.Vector3(0,    0, 0), scene);
		let lightH = new BABYLON.HemisphericLight("LightH", new BABYLON.Vector3(1,    1, 0), scene);
		lightD.intensity = 0.8;
		lightH.intensity = 0.6;
		let oGnd = BABYLON.MeshBuilder.CreateGround("Ground", {width: 100, height: 100});
		oGnd.checkCollisions    = true;

		camera.ellipsoid        = new BABYLON.Vector3(0.25, 0.8, 0.25);
		camera.checkCollisions  = true;
		camera.applyGravity     = true;
		camera.speed            = 0.3;
		camera.attachControl(canvas, true);
		camera.cameraDirection  = new BABYLON.Vector3(0, 1.7, 0);

		var oSph   = BABYLON.Mesh.CreateIcoSphere("Sphere", {radius:0.5, subdivisions:2}, scene);
		oSph.position.y = 2;
		oSph.checkCollisions = true;

		scene.registerBeforeRender(function(){
			oSph.rotation.y += 0.001*scene.getEngine().getDeltaTime();
			oSph.rotation.x += 0.001*scene.getEngine().getDeltaTime();
		});
		scene.createDefaultXRExperienceAsync({
			floorMeshes: [oGnd]
		}).then(function(xrExperience){
			console.log("Done, WebXR is enabled.");
		});

		window.addEventListener("resize", function(){engine.resize();});
		engine.runRenderLoop(function(){scene.render();});
	}
	</script>
</body>
</html>

実際に動かすと以下の通りになります。

VR表示機器の無いパソコンでは、初回と同様の表示のみになります。
本ページをスマートフォンで表示した場合、画面右下にVR表示切替ボタンが表示されます

AR表示

次にAR表示も行ってみましょう。
VR表示時と同様に以下のヘルパー関数を呼び出します。

scene.createDefaultXRExperienceAsync({
	uiOptions:{
		sessionMode:        "immersive-ar",
		referenceSpaceType: "local-floor"
	}
});

前回の「モンキー」(スザンヌさん)をAR表示するサンプルは以下の通りになります。
AR表示には地面が不要なため、地面のメッシュ及び、重力を無効にします。
また、透過色をsceneに指定します。

<!DOCTYPE html>
<html lang='ja'>
<head>
	<meta  http-equiv='Content-Type' content='text/html' charset='utf-8'/>
	<title>Babylon AR Test</title>
	<script src='https://cdn.babylonjs.com/babylon.js'></script>
	<script src='https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js'></script>
	<style>
		html, body {
			width: 100%;
			height: 100%;
			margin: 0;
			padding: 0;
			overflow: hidden;
		}
		#renderCanvas {
			width: 100%;
			height: 100%;
			touch-action: none;
		}
	</style>
</head>
<body>
	<canvas id='renderCanvas'></canvas>
<script>
	let canvas, engine, scene;
	canvas = document.getElementById('renderCanvas');
	engine = new BABYLON.Engine(canvas, true);
	scene  = new BABYLON.Scene(engine);
	scene.gravity           = new BABYLON.Vector3(0, -0.98, 0);
	scene.collisionsEnabled = true;
	scene.clearColor        = new BABYLON.Color3.Black;

	let camera = new BABYLON.UniversalCamera ('Camera', new BABYLON.Vector3(0,  1.2, 5), scene);
	let lightD = new BABYLON.DirectionalLight('LightD', new BABYLON.Vector3(0,    0, 0), scene);
	let lightH = new BABYLON.HemisphericLight('LightH', new BABYLON.Vector3(0, 1000, 0), scene);
	lightD.intensity = 0.8;
	lightH.intensity = 0.6;
	
	camera.ellipsoid        = new BABYLON.Vector3(0.25, 0.8, 0.25);
	camera.checkCollisions  = true;
	camera.speed            = 0.3;
	camera.attachControl(canvas, true);
	camera.rotation.y       = Math.PI;

	BABYLON.SceneLoader.ImportMeshAsync('', './', 'blender_to_babylon_blog.glb', scene)
	.then((result) => {
		for(let i = 1; i < result.meshes.length; i++){
			switch(result.meshes[i].name){
				case 'モンキー':
					result.meshes[i].scaling    = new BABYLON.Vector3(0.5, 1, 0.5);
					break;
				default: //Not working
			}
		}
	});
	
	scene.createDefaultXRExperienceAsync({
		uiOptions:{
			sessionMode:        'immersive-ar',
			referenceSpaceType: 'local-floor',
		}
	}).then(function(xrExperience){
		console.log('Done, WebXR is enabled.(iframe)');
	});
	window.addEventListener('resize', function(){engine.resize();});
	engine.runRenderLoop(function(){scene.render();});
</script>
</body>
</html>

実際に動かすと以下の通りになります。

ヘルパー関数

BABYLON.jsでは、XRカメラの作成を容易にするため、ヘルパー関数が用意されています。
公式マニュアルを参考に引数と返り値のpromiseについて記述していきます。

オプション名内容
disableDefaultUIXRに入るデフォルトUIを無効にするか?
disableNearInteraction近接対話(3Dオブジェクトをイベントトリガとして扱う際、そのオブジェクトが近くにある場合)を無効にするか?
disablePointerSelectionポインターを無効化するか?(テレポーテーションも動作しなくなる)
disableTeleportationテレポーテーションを無効化するか?
floorMeshesテレポーテーションでベースとなる地面を指定する
ignoreNativeCameraTransformation現在のカメラ位置情報を無視するか引き継ぐか?
inputOptionsXR入力の設定。デフォルトのコントローラメッシュを非表示にしたり、独自のコントローラメッシュを表示させる場合に使用する(詳細オプションは別表を参照)
nearInteractionOptions近接対話(3Dオブジェクトをイベントトリガとして扱う際、そのオブジェクトが近くにある場合)の設定。(詳細オプションは別表を参照)
optionalFeaturesセッション開始のための設定リストを指定する。または値としてtrueを指定すると全機能が有効になる
outputCanvasOptions出力先のcanvasの設定(詳細オプションは別表を参照)
pointerSelectionOptionsポインタ選択の設定(詳細オプションは別表を参照)
renderingGroupIdレンダリンググループIDを指定する(テレポーテーション、ポインタ、コントローラのメッシュ用)
teleportationOptionsテレポーテーションの設定
uiOptionsUIの設定。主にセッションと参照空間の設定に使用する(詳細オプションは別表を参照)
useStablePlugins安定版を使うか?(テレポーテーションとポインタを使う場合は安定版が推奨される)

inputOptionsの詳細オプションは下表の通りです。

オプション名内容
controllerOptionsコントローラ設定(inputOptionsによって上書きされる)
  • disableMotionControllerAnimation
    コントローラのボタン等のアニメーションを無効化するか?
  • doNotLoadControllerMesh
    コントローラのメッシュを読み込まないようにするか?(非表示にする場合に設定)
  • forceControllerProfile
    コントローラに適用するプロファイルを指定する
  • renderingGroupId
    デフォルトのコントローラのメッシュにレンダリンググループIDを指定する
customControllersRepositoryURLコントローラリポジトリのURLを指定する
disableControllerAnimationコントローラのアニメーションを無効化するか?
disableOnlineControllerRepositoryコントローラリポジトリからのロードを無効化するか?(Babylon単体で使えるコントローラを使用する場合に設定)
doNotLoadControllerMeshesコントローラのメッシュを読み込まないようにするか?
forceInputProfileコントローラに適用するプロファイルを指定する

nearInteractionOptionsの詳細オプションは下表の通りです。

オプション名内容
customUtilityLayerSceneメッシュの描画に使うsceneを指定する
disableSwitchOnClick近接対話のコントローラ切替えを無効化するか?
enableNearInteractionOnAllControllers全コントローラで近接対話を有効化するか?
farInteractionFeature遠距離対話(3Dオブジェクトをイベントトリガとして扱う際、そのオブジェクトが遠くにある場合)の設定(近接対話優先時は無効)
motionControllerOrbMaterialモーションコントローラの球体に適用するマテリアルを指定する
nearInteractionControllerModeモーションコントローラ使用時の近接対話設定
  • CENTERED_IN_FRONT
    コントローラの前を近接対話の対象とする
  • CENTERED_ON_CONTROLLER
    コントローラの中心を近接対話の対象とする
  • DISABLED
    近接対話を無効にする
preferredHandedness近接対話に優先的に使うハンドを指定する
useUtilityLayerメッシュを別レイヤーに表示するか?
xrInput近接対話に使用するXR入力の設定
  • controllers
    コントローラの設定
  • onControllerAddedObservable
    コントローラの追加時イベントの設定
  • onControllerRemovedObservable
    コントローラの削除時イベントの設定
  • xrCamera
    WebXRカメラの設定(テレポート移動に使用)
  • xrSessionManager
    XRセッションマネージャの設定

outputCanvasOptionsの詳細オプションは下表の通りです。

オプション名内容
canvasElementcanvasを指定する(未指定なら新規作成される)
canvasOptionsXRレイヤー出力設定
  • alpha
    アルファチャネルを使うか?
  • antialias
    アンチエイリアスを使うか?
  • depth
    レイヤー深度を使うか?
  • framebufferScaleFactor
    画像のスケールを指定する
  • multiview
    マルチビューを使うか?
  • stencil
    ステンシルレイヤーを使うか?
newCanvasCssStyle新規作成されるcanvas用のCSSを指定する

pointerSelectionOptionsの詳細オプションは下表の通りです。

オプション名内容
customUtilityLayerSceneメッシュを描画するsceneを指定する
disablePointerUpOnTouchOutXRコントローラが破棄された場合にポインタアップイベントを無効化するか?(ターゲットレイモードがgazeまたはscreenの場合に設定する)
disableScenePointerVectorUpdateシーンのpointerXとpointerYの更新を無効化するか?
disableSwitchOnClickポインタのコントローラ切替えを無効化するか?
enablePointerSelectionOnAllControllers全コントローラでポインタを有効化するか?
forceGazeModeポインタまたはコントローラの注視モードを使うか?
gazeCamera視線による選択を使うカメラを指定する
gazeModePointerMovedFactor注視モードのポインタ移動の係数を指定する
maxPointerDistanceポインタ選択の距離の最大値を指定する
overrideButtonId別のボタンを使用する場合に指定する
preferredHandednessポインタ選択を優先使用するハンドを指定する
renderingGroupIdレンダリンググループIDを指定する(ポインタのメッシュ用)
timeToSelect注視モードで選択状態になるまでの時間をミリ秒で指定する
useUtilityLayerメッシュを別レイヤーに表示するか?
xrInputポインタ選択に使用するXR入力の設定

uiOptionsの詳細オプションは下表の通りです。

オプション名内容
customButtonsXR機能の切替えボタンを指定する(未指定ならデフォルトの物が使われる)
ignoreSessionGrantedEventセッション許可イベントを無視し、ユーザの「enter XR」ボタン押下を強制するか?(未指定ならXRセッションが自動で開始される)
onErrorXR開始時にUIがエラーになった場合に実行する関数を定義する
optionalFeaturesセッション開始時のオプションを指定する
referenceSpaceTypeボタン作成時の参照空間タイプを指定する(デフォルトはlocal-floor)
renderTarget描画対象の設定
requiredFeaturesセッション開始時のオプションを指定する
sessionModeボタン作成時のセッションモードを指定する(デフォルトはimmersive-vr)

おまけ

VRで視力が回復する話もあります。
そこでその話に乗っかって、視力回復アプリを作ってみようかと思いました。
頭を動かさないで、画面のボールを眼で追いかけてください。