ケース スタディ: HoloLens の空間マッピング能力を拡張する

Microsoft HoloLens 用アプリを初めて作成するとき、デバイス上の空間マッピングの境界をどこまで押し広げられるのかを知りたいと考えていました。 Microsoft スタジオのソフトウェア エンジニアである Jeff Evertt は、ホログラムをユーザーの実世界の環境に配置する方法を詳細に制御する必要から、新しいテクノロジがどのように開発されたのかについて説明します。

Note

HoloLens 2 は、Mixed Reality の開発者がさまざまな環境に対応したアプリケーションの開発を直感的に行うことができるように設計された、高レベルの構造化環境表現を利用できる新しいシーンの理解ランタイムを実装します。

ビデオを見る

空間マッピングの先へ

HoloLens の最初の 2 つのゲームである FragmentsYoung Conker に取り組む間に、物理世界でホログラムの手続き型配置を行っているときに、ユーザーの環境についてより高いレベルの理解が必要である点がわかっています。 各ゲームには固有の配置ニーズがあります。Fragments では、たとえば、床やテーブルなど、さまざまなサーフェスを区別して、関連する場所に手がかりを配置したいと考えました。 また、人間大のホログラフィック キャラクターが座る可能性がある面 (カウチや椅子など) を特定できるようにしたいと考えました。 Young Conker では、Conker と彼の敵対者が、プレーヤーの部屋で立ち上がった表面をプラットフォームとして使用できる必要があります。

これらのゲームの開発パートナーである Asobo Studio は、この問題に直面し、HoloLens の空間マッピング機能を拡張するテクノロジを作成しました。 これを使用して、プレーヤーの部屋を分析し、壁、テーブル、椅子、床などの表面を特定できます。 これにより、ホログラフィック オブジェクトの最適な配置を決定するために、一連の制約に対して最適化する機能も提供されます。

空間理解コード

Asobo の元のコードを使用し、このテクノロジをカプセル化するライブラリを作成しました。 Microsoft と Asobo では、このコードをオープンソース化し、MixedRealityToolkit で独自のプロジェクトで使用できるようにしました。 すべてのソース コードが含まれているため、ニーズに合わせてカスタマイズし、改善点をコミュニティと共有できます。 C++ ソルバーのコードは UWP DLL にラップされ、MixedRealityToolkit 内に含まれるドロップイン プレハブを使用して Unity に公開されています。

Unity サンプルには便利なクエリが多数含まれています。このサンプルでは、壁に空きスペースを見つけたり、天井にオブジェクトを配置したり、床の大きな空間にオブジェクトを配置したり、キャラクターを配置する場所を特定したり、その他の空間理解クエリを無数に識別したりすることができます。

HoloLens によって提供される空間マッピング ソリューションは、問題のある空間全体のニーズを満たすのに十分な汎用的な設計ですが、空間理解モジュールは、2 つの特定のゲームのニーズをサポートするように構築されました。 そのため、そのソリューションは、特定のプロセスと一連の前提条件に基づいて構成されます。

  • 固定サイズのプレイスペース: ユーザーは init 呼び出しで最大プレイスペース サイズを指定します。
  • 1 回限りのスキャン プロセス: このプロセスには、ユーザーが操作を行い、プレイスペースを定義する個別のスキャン フェーズが必要です。 クエリ機能は、スキャンが終了するまで機能しません。
  • ユーザー駆動のプレイスペース "ペインティング": スキャン フェーズ中に、ユーザーはプレイスペースを移動して見回し、含める必要がある領域を効果的に塗り分けます。 生成されたメッシュは、このフェーズでユーザーからのフィードバックを提供するために重要です。
  • 屋内の自宅または職場の環境: クエリ関数は、フラットなサーフェスと壁面を中心に適切な角度で設計されています。 これは、ソフトな制限です。 ただし、スキャン フェーズでは、主要軸と補助軸に沿ってメッシュ テセレーションを最適化するために、主軸分析が完了します。

室内スキャン プロセス

空間理解モジュールを読み込む場合、最初にスペースをスキャンします。そのため、使用できるすべてのサーフェス (床、天井、壁など) が識別され、ラベル付けされます。 スキャン プロセス中に、部屋を見回し、スキャンに含める必要がある領域を "塗り分け" します。

このフェーズで見られるメッシュは、部屋のスキャン中の部分をユーザーに知らされる重要な視覚的フィードバックです。 空間理解モジュールの DLL は、8 cm サイズのボクセル キューブのグリッドとしてプレイスペースを内部的に格納します。 スキャンの初期部分では、主要なコンポーネント分析が完了して、部屋の軸が決定されます。 内部的には、これらの軸に合わせてボクセル領域を格納します。 メッシュは、ボクセル ボリュームから等密度面を抽出することによって、約 1 秒ごとに生成されます。

Spatial mapping mesh in white and understanding playspace mesh in green

白の空間マッピング メッシュと緑のプレイスペース メッシュの理解

含まれている SpatialUnderstanding ファイルは、スキャン フェーズ プロセスを管理します。 これは、次の機能を呼び出します。

  • SpatialUnderstanding_Init: 開始時に 1 回呼び出されます。
  • GeneratePlayspace_InitScan: スキャン フェーズを開始する必要があることが示されます。
  • GeneratePlayspace_UpdateScan_DynamicScan: 各フレームを呼び出してスキャン プロセスを更新します。 カメラの位置と向きが渡され、前述のプレイスペースの塗り分けプロセスに使用されます。
  • GeneratePlayspace_RequestFinish: プレイスペースを最終決定するために呼び出されます。 これにより、スキャン フェーズ中に "塗り分け" された領域を使用して、プレイスペースを定義してロックします。 アプリケーションでは、スキャン フェーズ中に統計に対してクエリを実行できるだけでなく、カスタム メッシュに対してクエリを実行してユーザー フィードバックを提供することもできます。
  • Import_UnderstandingMesh: スキャン中に、SpatialUnderstandingCustomMesh 動作がモジュールによって提供され、プレハブを理解するために配置され、これにより、プロセスが生成したカスタム メッシュに対して定期的にクエリが実行されます。 さらに、これはスキャンが完了した後にもう一度行われます。

SpatialUnderstanding 動作によって実行されるスキャン フローは、InitScanUpdateScan を各フレームに対して呼び出します。 統計クエリが妥当な範囲を報告すると、ユーザーはエアタップを使用して RequestFinish を呼び出し、スキャン フェーズの終了を示します。 戻り値が DLL の処理が完了したと示されるまで、UpdateScan は引き続き呼び出されます。

クエリ

スキャンが完了すると、インターフェイスで次の 3 種類のクエリにアクセスできます。

  • トポロジ クエリ: これらは、スキャンされた部屋のトポロジに基づく高速クエリです。
  • シェイプ クエリ: トポロジ クエリの結果を利用して、定義したカスタム図形に合う水平サーフェスを見つけます。
  • オブジェクト配置クエリ: オブジェクトのルールと制約のセットに基づいて最適な場所を見つける、より複雑なクエリです。

3 つの主要なクエリに加えて、タグ付けされたサーフェスの種類を取得するために使用できるレイキャスト インターフェイスがあります。カスタムの防水室メッシュはコピーできます。

トポロジ クエリ

DLL 内では、トポロジ マネージャーは環境のラベル付けを処理します。 前述のように、データの多くが、ボクセル ボリューム内に含まれるサーペル内に格納されます。 さらに、PlaySpaceInfos 構造体は、再生スペースに関する情報を格納するために使用されます。これには、世界の配置 (下記の詳細情報を参照)、床、天井の高さが含まれます。

ヒューリスティックは、床、天井、および壁面を決定するために使用されます。 たとえば、表面積が 1 平方メートルを超える最大および最低の水平サーフェスは、床と見なされます。 このプロセスでは、スキャン プロセス中のカメラ パスも使用されます。

トポロジ マネージャーによって公開されるクエリのサブセットは、DLL を介して公開されます。 公開されるトポロジ クエリは次のとおりです。

  • QueryTopology_FindPositionsOnWalls
  • QueryTopology_FindLargePositionsOnWalls
  • QueryTopology_FindLargestWall
  • QueryTopology_FindPositionsOnFloor
  • QueryTopology_FindLargestPositionsOnFloor
  • QueryTopology_FindPositionsSittable

各クエリには、クエリの種類に固有のパラメーターのセットがあります。 次の例では、必要なボリュームの高さと幅の最小値、床の上の最小配置高、およびボリュームの前面にある最小のクリアランスの量をユーザーが指定しています。 すべての測定値は、メートルで計算されます。

EXTERN_C __declspec(dllexport) int QueryTopology_FindPositionsOnWalls(
          _In_ float minHeightOfWallSpace,
          _In_ float minWidthOfWallSpace,
          _In_ float minHeightAboveFloor,
          _In_ float minFacingClearance,
          _In_ int locationCount,
          _Inout_ Dll_Interface::TopologyResult* locationData)

これらの各クエリは、TopologyResult 構造体の事前に割り当てられた配列を受け取ります。 locationCount パラメーターは、渡された配列の長さを指定します。 戻り値は、返された場所の数を報告します。 この数は、渡された locationCount パラメーターよりも大きくすることはできません。

TopologyResult には、返されたボリュームの中央の位置、対面する方向 (法線)、検出された領域のサイズが含まれます。

struct TopologyResult
     {
          DirectX::XMFLOAT3 position;
          DirectX::XMFLOAT3 normal;
          float width;
          float length;
     };

Unity サンプルでは、これらの各クエリが仮想 UI パネルのボタンにリンクされています。 サンプルでは、これらの各クエリのパラメーターを妥当な値にハード コーディングしています。 その他の例については、サンプル コードの SpaceVisualizer.cs を参照してください。

シェイプ クエリ

DLL 内で、図形アナライザー (ShapeAnalyzer_W) がトポロジ アナライザーを使用して、ユーザーが定義したカスタム図形と照合します。 Unity サンプルには、クエリ メニューの [図形] タブに表示される定義済みの図形のセットがあります。

図形分析は水平サーフェスでのみ機能します。 たとえば、ソファは、平らな座席の表面とソファの背もたれの上面によって定義されます。 シェイプ クエリでは、特定のサイズ、高さ、および縦横範囲の 2 つのサーフェスが検索され、2 つのサーフェスが整列され、接続されます。 API の用語を使用すると、カウチ シートとカウチの背面はシェイプ コンポーネントであり、配置要件はシェイプ コンポーネントの制約です。

Unity サンプル (ShapeDefinition.cs) に定義されているクエリの着席可能なオブジェクトの例を次に示します。

shapeComponents = new List<ShapeComponent>()
     {
          new ShapeComponent(
               new List<ShapeComponentConstraint>()
               {
                    ShapeComponentConstraint.Create_SurfaceHeight_Between(0.2f, 0.6f),
                    ShapeComponentConstraint.Create_SurfaceCount_Min(1),
                    ShapeComponentConstraint.Create_SurfaceArea_Min(0.035f),
               }),
     };
     AddShape("Sittable", shapeComponents);

各シェイプ クエリは、一連の図形コンポーネントによって定義されます。各図形コンポーネントには、コンポーネントの制約のセットと、コンポーネント間の依存関係を一覧表示する一連の図形制約があります。 この例には、1 つのコンポーネント定義に 3 つの制約が含まれています。また、コンポーネント間のシェイプ制約はありません (コンポーネントは 1 つのみのため)。

これに対し、ソファ図形には、2 つの図形コンポーネントと 4 つの図形制約があります。 コンポーネントは、ユーザーのコンポーネント リスト内のインデックスによって識別されることに注意してください (この例では 0 と 1)。

shapeConstraints = new List<ShapeConstraint>()
        {
              ShapeConstraint.Create_RectanglesSameLength(0, 1, 0.6f),
              ShapeConstraint.Create_RectanglesParallel(0, 1),
              ShapeConstraint.Create_RectanglesAligned(0, 1, 0.3f),
              ShapeConstraint.Create_AtBackOf(1, 0),
        };

カスタム図形の定義を簡単に作成するために、Unity モジュールにラッパー関数が用意されています。 コンポーネントとシェイプの制約の完全な一覧は、SpatialUnderstandingDll.csShapeComponentConstraint 構造と ShapeConstraint 構造内で参照してください。

The blue rectangle highlights the results of the chair shape query.

青い四角形は、椅子のシェイプ クエリ結果を強調表示しています。

オブジェクト配置のソルバー

オブジェクト配置クエリを使用すると、オブジェクトを配置する物理的な部屋の理想的な場所を識別できます。 ソルバーは、オブジェクトのルールと制約を指定して、最適な場所を見つけます。 また、オブジェクト クエリは、オブジェクトが Solver_RemoveObject または Solver_RemoveAllObjects の呼び出しで削除されるまで保持され、制約された複数オブジェクトの配置が可能になります。

オブジェクト配置クエリは、3 つの部分で構成されます。これらはパラメーターを使用した配置の種類、規則の一覧、および制約の一覧です。 クエリを実行するには、次の API を使用します。

public static int Solver_PlaceObject(
                [In] string objectName,
                [In] IntPtr placementDefinition,	// ObjectPlacementDefinition
                [In] int placementRuleCount,
                [In] IntPtr placementRules,     	// ObjectPlacementRule
                [In] int constraintCount,
                [In] IntPtr placementConstraints,	// ObjectPlacementConstraint
                [Out] IntPtr placementResult)

この関数は、オブジェクト名、配置定義、および規則と制約の一覧を受け取ります。 C# ラッパーは、規則と制約の構築を簡単にする構築ヘルパー関数を提供します。 配置定義には、次のいずれかのクエリの種類が含まれます。

public enum PlacementType
                {
                    Place_OnFloor,
                    Place_OnWall,
                    Place_OnCeiling,
                    Place_OnShape,
                    Place_OnEdge,
                    Place_OnFloorAndCeiling,
                    Place_RandomInAir,
                    Place_InMidAir,
                    Place_UnderFurnitureEdge,
                };

各配置型には、型に固有のパラメーターのセットがあります。 ObjectPlacementDefinition 構造体には、これらの定義を作成するための静的ヘルパー関数のセットが含まれています。 たとえば、床にオブジェクトを配置する場所を見つけるには、次の関数を使用します。

public static ObjectPlacementDefinition Create_OnFloor(Vector3 halfDims)

配置の種類に加えて、一連のルールと制約を指定することができます。 規則に違反することはできません。 型とルールを満たす配置場所は、最適な配置場所を選択するために、一連の制約に対して最適化されます。 各ルールと制約は、指定された静的作成関数によって作成できます。 ルールと制約の構築関数の例を以下に示します。

public static ObjectPlacementRule Create_AwayFromPosition(
                    Vector3 position, float minDistance)
               public static ObjectPlacementConstraint Create_NearPoint(
                    Vector3 position, float minDistance = 0.0f, float maxDistance = 0.0f)

次に示すオブジェクト配置クエリでは、画面の端に半分のメーター キューブを配置し、その他の場所にある他のオブジェクトから、ルームの中央付近に移動するための場所を探しています。

List<ObjectPlacementRule> rules = 
          new List<ObjectPlacementRule>() {
               ObjectPlacementRule.Create_AwayFromOtherObjects(1.0f),
          };

     List<ObjectPlacementConstraint> constraints = 
          new List<ObjectPlacementConstraint> {
               ObjectPlacementConstraint.Create_NearCenter(),
          };

     Solver_PlaceObject(
          “MyCustomObject”,
          new ObjectPlacementDefinition.Create_OnEdge(
          new Vector3(0.25f, 0.25f, 0.25f), 
          new Vector3(0.25f, 0.25f, 0.25f)),
          rules.Count,
          UnderstandingDLL.PinObject(rules.ToArray()),
          constraints.Count,
          UnderstandingDLL.PinObject(constraints.ToArray()),
          UnderstandingDLL.GetStaticObjectPlacementResultPtr());

成功した場合は、配置位置、次元、および向きを含む ObjectPlacementResult 構造体が返されます。 また、配置は、配置されたオブジェクトの DLL の内部リストに追加されます。 後続の配置クエリでは、このオブジェクトが考慮されます。 Unity サンプルの LevelSolver.cs ファイルには、クエリの例が多数含まれています。

The blue boxes show the result from three Place On Floor queries with

青いボックスには、カメラ位置からの距離ルールを使用して、3 つの床への配置クエリの結果が表示されます。

ヒント:

  • レベルまたはアプリケーションのシナリオに必要な複数のオブジェクトの配置位置を解決する場合は、まず、必要以上に大きなオブジェクトを解決して、スペースが見つかる確率を最大化します。
  • 配置順序は重要です。 オブジェクトの配置が見つからない場合は、制限の少ない構成を試してください。 一連のフォールバック構成を設定することは、多くの部屋構成で機能をサポートするために不可欠です。

レイの投射

3 つの主要なクエリに加えて、レイ キャスト インターフェイスを使用して、タグ付きサーフェス型を取得できます。また、ルームがスキャンされて完了した後に、カスタムの防水のプレイスペース メッシュをコピーできます。床、天井、壁などの表面には、ラベルが内部的に生成されます。 PlayspaceRaycast 関数は、レイを受け取り、レイが既知のサーフェスと競合している場合は、RaycastResult の形式でそのサーフェスに関する情報を返します。

struct RaycastResult
     {
          enum SurfaceTypes
          {
               Invalid,	// No intersection
               Other,
               Floor,
               FloorLike,         // Not part of the floor topology, 
                                  //     but close to the floor and looks like the floor
               Platform,          // Horizontal platform between the ground and 
                                  //     the ceiling
               Ceiling,
               WallExternal,
               WallLike,          // Not part of the external wall surface, 
                                  //     but vertical surface that looks like a 
                                  //	wall structure
               };
               SurfaceTypes SurfaceType;
               float SurfaceArea;	// Zero if unknown 
                                        //	(i.e. if not part of the topology analysis)
               DirectX::XMFLOAT3 IntersectPoint;
               DirectX::XMFLOAT3 IntersectNormal;
     };

内部的には、レイキャストは、プレイスペースの計算された 8 cm キューブ ボクセル表現に対して計算されます。 各ボクセルには、処理されたトポロジ データ (サーフェルとも呼ばれる) を持つ面要素のセットが含まれます。 交差するボクセル セルに含まれているサーフェルが比較され、トポロジ情報の検索に使用される最適な一致が得られます。 このトポロジ データには、SurfaceTypes 列挙型の形式で返されるラベルと、交差するサーフェスの表面領域が含まれます。

Unity のサンプルでは、カーソルは各フレームに射線をキャストします。 まず Unity のコライダーに対してであり、2 番目にモジュールの世界表現を理解するためのものです。最後に UI 要素に対して実行します。 このアプリケーションでは、UI は優先順位を取得し、結果を理解し、最後に Unity のコライダーを取得します。 SurfaceType は、カーソルの横にテキストとして報告されます。

Raycast result reporting intersection with the floor.

床との交差を報告する Raycast 結果。

コードを取得する

オープンソース コードは MixedRealityToolkit で入手できます。 プロジェクトでコードを使用する場合は、HoloLens 開発者フォーラムでお知らせください。 お客様がどのように利用されるかを楽しみにしております。

筆者について

Jeff Evertt, Software Engineering Lead at Microsoft Jeff Evertt は、HoloLens の萌芽期からエクスペリエンス開発まで、開発初期から HoloLens に携わってきたソフトウェア エンジニアリング リードです。 彼は HoloLens 以前、Xbox Kinect とゲーム業界でさまざまなプラットフォームやゲームに関わる仕事をしてきました。 Jeff は、ロボットやグラフィックス、そしてビープ音が鳴る派手なライトを使用する物づくりに情熱を持っています。 新しいものを学習し、ソフトウェア、ハードウェア、特にそれら 2 つが重なり合う領域で働くことを楽しんでいます。

関連項目