はじめに

QuestProの発売からもう半年以上経過し、Quest3の発表もありましたね。
そして、AppleからもついにVisionProが発表されました。
これらの機種の共通の機能としてカラーパススルーがあります。
今後は現実と仮想を融合したような表現が求められてくるのではないかと思います。
そこで、改めてMetaQuestの開発者から見た、
以下3つの現実空間のアクセス方法をまとめてみました。

  • ガーディアン
  • パススルー
  • ルーム設定

パススルーとルーム設定を利用したTheWorldBeyondというMeta公式アプリもあるので、
まだの方は一度体験することをオススメします。
Gitも公開されています。

※この記事では、設定方法の紹介はしません。

開発環境

  • Unity 2022.2.18
  • Oculus Integration v54.1
  • MetaQuestPro(システムバージョン:54)

ガーディアン

OVRBoundaryクラスを参照することによって、
設定済みのガーディアンにアクセスすることが出来ます。
下記のソースコードは各種参照方法のサンプルです。

//OVRBoundaryの参照取得
OVRBoundary boundary = OVRManager.boundary;
//ガーディアン情報を取得できるか?
bool isGuardian = boundary.GetConfigured();
//ガーディアンの地面のポイント(4~256)の取得(Y座標は地面固定)
Vector3[] geometry = boundary.GetGeometry(OVRBoundary.BoundaryType.PlayArea);
//ガーディアンの大きさの取得(x:幅 、y:高さは0固定 、z:奥行き )
Vector3 dimensions = boundary.GetDimensions(OVRBoundary.BoundaryType.PlayArea);

ドキュメントには記載されていないですが、
以下4つの関数とBoundaryTypeは、
"Deprecated. This function will not be supported in OpenXR"
となっており、OpenXRには対応していないので機能しません。

  • GetVisible()
  • SetVisible()
  • TestNode()
  • TestPoint()
  • OVRPlugin.BoundaryType.OuterBoundary

Unityの最新環境ではOpenXRを半自動的に使用するので、やむを得ず使用したい場合は、
UnityやOculus Integrationのバージョンを古いものを使う必要があります。
しかし、その場合だと後述のパススルーやルーム設定にアクセスが出来なくなるので注意が必要です。
開発者フォーラムでも同様の悩みを抱えている人がおり解決されていないので、
ガーディアンを超えたか?の判定は自力で行う必要がありそうです。

パススルー

下記のような設定を行うことで、背景がパススルー化されます。
なお、パススルーの映像を取得するAPIは用意されていないので気をつけてください。

HierarchyのOVRCameraRigを選択。OVRManagerのQuestFeaturesを開く。

  • Passthrough Support -> Supported
  • Enable Passthrough -> チェック

OVRCameraRigにOVRPassthroughLayerをアタッチ。

  • Placement -> Underlay

OVRCameraRig配下のCenterEyeAnchorを選択。カメラの以下の設定を変更。

  • Clear Flags -> Solid Color
  • Background -> (0,0,0,0)

パススルーの有効、無効を切り替えたい時は下記のようなコードで可能です。

//OVRManagerの参照の取得
OVRManager manager = OVRManager.instance;
//パススルーの有効・無効の取得
bool passthroughEnabled = manager.isInsightPassthroughEnabled;
//パススルーの有効・無効の設定
manager.isInsightPassthroughEnabled = !passthroughEnabled;

他にはパススルーの見た目をカスタイズする方法も用意されています。
一つの記事が書けそうなぐらい膨大な量になるので今回は省略します。

ルーム設定

OVRSceneManagerクラスを参照することによって、
ルーム設定で配置した、壁や天井、ベッド等にアクセスすることが出来ます。

Assets/Oculus/VR/Prefab/OVRSceneManager.prefabが用意されているので、
これをHierarchyにドラッグ&ドロップすれば、簡易に利用することが出来ます。

OVRSceneManagerには読み込んだシーンモデルを表示するのにPrefabの設定が必要です。
PlanePrefabとVolumePrefabの構成は後述します。

  • PlanePrefab : 天井、窓、壁等の平面に使用されるPrefab
  • VolumePrefab : 机、ソファー等の3D立体に使用されるPrefab
  • Prefab Overrides : 天井、机等の任意のラベルごとにPrefabを上書きできます。

Prefab OverridesのPrefabには PlanePrefab、又は、VolumePrefabと同じ構成の物が使用できます。

OVRSceneModelLoaderはルーム設定を自動的に読み込んでくれるスクリプトです。
自分で管理したい場合は、OVRSceneManager#LoadSceneModel()で読み込む必要があります。

Plane Prefab

PrefabのルートにOVRScenePlaneをアタッチ。OVRSceneAnchorは自動でアタッチされます。
ScaleChildren,OffsetChildrenそれぞれにチェックを入れる。

子オブジェクトのメッシュにはQuadを使用し、Y軸は180に設定。
マテリアルは任意の物を設定可能です。

Volume Prefab

PrefabのルートにOVRSceneVolumeをアタッチ。OVRSceneAnchorは自動でアタッチされます。
ScaleChildren,OffsetChildrenそれぞれにチェックを入れる。

MeshのParentを作成

MeshにはCubeを使用。
マテリアルは任意の物を設定可能です。

ルーム読み込み後にOVRSemanticClassificationクラスを参照することで、
壁や机のオブジェクトを取得できます。
下記のソースコードはテーブルの中央上にキューブを配置するサンプルです。

//OVRSceneManagerの参照
[SerializeField] 
private OVRSceneManager _sceneManager;

private void Awake()
{
   //ルーム設定の読み込みが成功した時のコールバック登録
   _sceneManager.SceneModelLoadedSuccessfully += onSceneModelLoaded;   
}

//ルーム設定が読み込まれた時の処理
private void onSceneModelLoaded()
{
   //シーンモデル群のOVRSemanticClassification取得。
   var semanticAnchors = FindObjectsByType<OVRSemanticClassification>(FindObjectsSortMode.None);
   foreach (var anchor in semanticAnchors)
   {
      //テーブルか?の判定
      if (anchor.Contains(OVRSceneManager.Classification.Table))
      {
         var cubeOnTable = Instantiate(_testCubePrefab,anchor.transform);
         var cubeT = cubeOnTable.transform;
         cubeT.localPosition = Vector3.zero;
         var halfHeight  = cubeT.localScale.y / 2f;
         var addPos = new Vector3(0, halfHeight, 0);
         cubeT.position += addPos;
       }
    }
}

薄い青紫がテーブルで、その上に赤いキューブを乗せています。

これもガーディアン同様にドキュメントには記載されていないですが、
最新版だとOVRSceneManager#RoomLayoutは使用できません
RoomLayout is obsoleted. For each room's layout information (floor, ceiling, walls) see OVRSceneRoom
と警告が表示されており、下記のソースコードのようにOVRSceneRoomを参照できます。
これを利用して天井、床、壁の参照も可能です。

//ルーム設定が読み込まれた時の処理
private void onSceneModelLoaded()
{
     //OVRSceneRoomの参照取得
     OVRSceneRoom sceneRoom = FindAnyObjectByType<OVRSceneRoom>();
     //天井
     OVRScenePlane ceiling = sceneRoom.Ceiling;
     //床
     OVRScenePlane floor = sceneRoom.Floor;
     //壁
     OVRScenePlane[] walls = sceneRoom.Walls;
}

もしもルーム設定がされていなければ、
下記のコードのようにRequestSceneCapture()を呼ぶことで、ルーム設定をリクエスト出来ます。
一旦アプリから外れて、フォームでルーム設定を行うことになります。
ルーム設定完了後はアプリに戻り、戻り値にTrueが返ります。

//OVRSceneManagerの参照
[SerializeField] 
private OVRSceneManager _sceneManager;

private void Awake()
{
   //ルーム設定されていない等、シーンモデルが無かった時のコールバック登録
   _sceneManager.NoSceneModelToLoad += onNoSceneModelToLoad;
}

//シーンモデルが無かった時の処理
private void onNoSceneModelToLoad()
{
    //ルーム設定をリクエストする。
    bool success = _sceneManager.RequestSceneCapture();
    Debug.Log($"ルーム設定 : {success}");
}

まとめ

導入の間口は簡単ですが、より使いこなしてリッチな表現をするのは大変な印象を持ちました。
今回の記事でも紹介しきれていない部分が多く、
空間やシーンモデルの計算、シェーダー等考えることが多いです。
また、OpenXRに対応していないAPIが多かったのが残念で、今後のアップデートにも
期待したいところです。
Appleでも発表されていた「空間コンピューティング」というのは、まだまだこれから
だと思うので励んでいきたいです。



ギャップロを運営しているアップフロンティア株式会社では、一緒に働いてくれる仲間を随時、募集しています。 興味がある!一緒に働いてみたい!という方は下記よりご応募お待ちしております。
採用情報をみる