Unity の空間マッピング
空間マッピングを使用すると、HoloLens デバイスの周囲の空間内のサーフェスを表す三角形メッシュを取得できます。 配置、オクルージョン、ルーム分析にサーフェス データを使用することで、Unity プロジェクトの没入感を高めることができます。
Unity でフル サポートされる空間マッピングは、次のような形で開発者に公開されています。
- MixedRealityToolkit に空間マッピング コンポーネントが付属しています。初めてでもすばやく簡単に空間マッピングに取り組むことができます。
- より深いレベルの空間マッピング API は、完全な制御手段を備え、アプリケーションごとにより高度なカスタマイズを行うことができます。
アプリで空間マッピングを使用するには、AppxManifest で SpatialPerception 機能を設定する必要があります。
デバイス サポート
機能 | HoloLens (第 1 世代) | HoloLens 2 | イマーシブ ヘッドセット |
---|---|---|---|
空間マッピング | ✔️ | ✔️ | ❌ |
SpatialPerception 機能の設定
空間マッピング データをアプリで使用するためには、SpatialPerception 機能を有効にする必要があります。
SpatialPerception 機能を有効にするには:
- Unity エディターの [Edit]\(編集\) > [Project Settings]\(プロジェクトの設定\) > [Player]\(プレーヤー\) で、[Player Settings]\(プレーヤーの設定\) ペインを開きます
- [Windows ストア] タブを選択します。
- [公開設定] を展開し、[機能] 一覧で [SpatialPerception] 機能のチェック ボックスをオンにします。
Note
Visual Studio ソリューションに既に Unity プロジェクトをエクスポートしている場合は、新しいフォルダーにエクスポートするか、または手動で Visual Studio で AppxManifest にこの機能を設定する必要があります。
また、空間マッピングには、10.0.10586.0 以上の MaxVersionTested が必要となります。
- Visual Studio のソリューション エクスプローラーで [Package.appxmanifest] を右クリックし、[コードの表示] を選択します。
- TargetDeviceFamily が指定されている行を見つけて、MaxVersionTested="10.0.10240.0" を MaxVersionTested="10.0.10586.0" に変更します。
- Package.appxmanifest を保存します。
Unity でマッピングを追加する方法
空間認識システム
各種空間メッシュ オブザーバーの設定については、MRTK の「空間認識の概要」ガイドを参照してください。
オンデバイス オブザーバーについては、「デバイスのメッシュ オブザーバーの構成」ガイドを参照してください。
シーン理解オブザーバーについては、シーン理解オブザーバー ガイドを参照してください。
ハイレベルのメッシュ分析: 空間理解
注意事項
空間理解は非推奨化され、シーンの理解に置き換えられました。
MixedRealityToolkit は、Unity のホログラフィック API に基づくホログラフィック開発のためのユーティリティ コードのコレクションです。
空間理解
物理世界にホログラムを配置するとき、空間マッピングのメッシュやサーフェス平面だけでは済まないことはよくあります。 配置がひととおり完了したら、より高いレベルの環境認識が求められます。 そのためには通常、床と天井と壁の認識が必要となります。 また、ホログラフィック オブジェクトの最適な物理位置を決定するために、一連の配置制約を背景にした最適化もできなければなりません。
Asobo Studios は、Young Conker と Fragments の開発中、まさにこの問題に対抗する手段として、ルーム ソルバー (room solver) を開発しました。 どちらのゲームも固有のニーズを抱えていましたが、核となる空間理解テクノロジは共通しています。 このテクノロジは HoloToolkit.SpatialUnderstanding ライブラリにカプセル化されています。このライブラリを使用することで、壁面の何もない空間をすぐに見つけたり、天井にオブジェクトを配置したり、マスコットが座る場所を認識したりするなど、ありとあらゆる空間理解クエリを実行することができます。
すべてのソース コードが含まれているため、ニーズに合わせてカスタマイズし、改善点をコミュニティと共有できます。 C++ ソルバーのコードは UWP dll にラップされ、MixedRealityToolkit 内に含まれるドロップイン プレハブを使用して Unity に公開されています。
モジュールについて
モジュールによって公開されている主要なインターフェイスは 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;
};
内部的には、レイキャストは、プレイスペースの計算された 8cm キューブ ボクセル表現に対して計算されます。 各ボクセルには、処理されたトポロジ データ (サーフェルとも呼ばれる) を伴う面要素のセットが含まれます。 交差するボクセル セルに含まれているサーフェルが比較され、トポロジ情報の検索に使用される最適な一致が得られます。 このトポロジ データには、"SurfaceTypes" 列挙型の形式で返されるラベルと、交差するサーフェスの表面領域が含まれます。
Unity のサンプルでは、カーソルは各フレームに射線をキャストします。 まず Unity のコライダーに対してであり、 2 番目にモジュールの世界表現を理解するためのものです。 最後に UI 要素に対して実行します。 このアプリケーションでは、UI は優先順位を取得し、次に結果を理解し、最後に Unity のコライダーを取得します。 SurfaceType は、カーソルの横にテキストとしてレポートされます。
カーソルの横にサーフェスの種類が表示される
トポロジ クエリ
DLL 内では、トポロジ マネージャーは環境のラベル付けを処理します。 前述のように、データの多くが、ボクセル ボリューム内に含まれるサーフェル内に格納されます。 さらに、"PlaySpaceInfos" 構造体は、プレイスペースに関する情報を格納するために使用されます。これには、空間の配置 (下記の詳細情報を参照)、床、天井の高さが含まれます。 ヒューリスティックは、床、天井、および壁面を決定するために使用されます。 たとえば、表面積が 1 m2 を超える最大および最低の水平サーフェスは、床と見なされます。
Note
このプロセスでは、スキャン プロセス中のカメラ パスも使用されます。
トポロジ マネージャーによって公開されるクエリのサブセットは、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;
};
Note
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.cs" の "ShapeComponentConstraint" 構造体と "ShapeConstraint" 構造体内で確認できます。
このサーフェスに見つかった四角形のシェイプ
オブジェクト配置ソルバー
オブジェクト配置ソルバーを使用すると、オブジェクトを配置する物理的な部屋の理想的な場所を識別できます。 ソルバーは、オブジェクトのルールと制約を指定して、最適な場所を見つけます。 また、オブジェクト クエリは、オブジェクトが "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" ファイルには、クエリの例が多数含まれています。
図 3: 青いボックスには、カメラ位置からの距離ルールを使用して、3 つの床への配置クエリの結果が表示されます。
レベルまたはアプリケーションのシナリオに必要な複数のオブジェクトの配置位置を解決する場合は、まず、必要以上に大きなオブジェクトを解決して、スペースが見つかる確率を最大化します。 配置順序は重要です。 オブジェクトの配置が見つからない場合は、制限の少ない構成を試してください。 一連のフォールバック構成を設定することは、多くの部屋構成で機能をサポートするために不可欠です。
室内スキャン プロセス
HoloLens によって提供される空間マッピング ソリューションは、問題のある空間全体のニーズを満たすのに十分な汎用的な設計ですが、空間理解モジュールは、2 つの特定のゲームのニーズをサポートするように構築されました。 そのソリューションは、以下に要約した特定のプロセスと一連の前提条件に基づいて構成されます。
Fixed size playspace – The user specifies the maximum playspace size in the init call.
One-time scan process –
The process requires a discrete scanning phase where the user walks around,
defining the playspace.
Query functions will not function until after the scan has been finalized.
ユーザー駆動のプレイスペース "ペインティング" – スキャン フェーズ中に、ユーザーはプレイスペースを移動して見回し、含める必要がある領域を効果的に塗り分けます。 生成されたメッシュは、このフェーズでユーザーからのフィードバックを提供するために重要です。 屋内の自宅または職場の環境 – クエリ関数は、フラットなサーフェスと壁面を中心に適切な角度で設計されています。 これは、ソフトな制限です。 ただし、スキャン フェーズでは、主要軸と補助軸に沿ってメッシュ テセレーションを最適化するために、主軸分析が完了します。 含まれている SpatialUnderstanding ファイルは、スキャン フェーズ プロセスを管理します。 これは、次の関数を呼び出します。
SpatialUnderstanding_Init – Called once at the start.
GeneratePlayspace_InitScan – Indicates that the scan phase should begin.
GeneratePlayspace_UpdateScan_DynamicScan –
Called each frame to update the scanning process. The camera position and
orientation is passed in and is used for the playspace painting process,
described above.
GeneratePlayspace_RequestFinish –
Called to finalize the playspace. This will use the areas “painted” during
the scan phase to define and lock the playspace. The application can query
statistics during the scanning phase as well as query the custom mesh for
providing user feedback.
Import_UnderstandingMesh –
During scanning, the “SpatialUnderstandingCustomMesh” behavior provided by
the module and placed on the understanding prefab will periodically query the
custom mesh generated by the process. In addition, this is done once more
after scanning has been finalized.
"SpatialUnderstanding" 動作によって実行されるスキャン フローは、InitScan、UpdateScan を各フレームに対して呼び出します。 統計クエリが妥当な範囲をレポートすると、ユーザーはエアタップを使用して RequestFinish を呼び出し、スキャン フェーズの終了を示します。 戻り値が dll の処理が完了したと示されるまで、UpdateScan は引き続き呼び出されます。
空間を理解するためのメッシュ
Understanding dll では、内部的に大きさ 8 cm のボクセル キューブのグリッドとしてプレイスペースが格納されます。 スキャンの初期部分では、主要なコンポーネント分析が完了して、部屋の軸が決定されます。 内部的には、これらの軸に合わせてボクセル領域を格納します。 メッシュは、ボクセル ボリュームから等密度面を抽出することによって、約 1 秒ごとに生成されます。
ボクセル ボリュームから生成されたメッシュ
トラブルシューティング
- SpatialPerception 機能が設定済みであることを確認します。
- トラッキングが失われた場合、次の OnSurfaceChanged イベントですべてのメッシュが削除されます。
Mixed Reality Toolkit での空間マッピング
Mixed Reality Toolkit での空間マッピングの使用について詳しくは、MRTK ドキュメントの空間認識に関するセクションを参照してください。
次の開発チェックポイント
Microsoft による Unity 開発体験に沿って進んでいるなら、ここでは MRTK の主要構成要素を扱います。 ここから、次の構成要素を続けることができます。
または、Mixed Reality プラットフォームの機能と API に移動します。
いつでも Unity 開発チェックポイントに戻ることができます。