Räumliche Abbildung in Unity

Mit räumlicher Zuordnung können Sie Dreiecksgitter abrufen, die die Oberflächen in der Welt um ein HoloLens-Gerät darstellen. Sie können Oberflächendaten für Platzierung, Verdeckung und Raumanalyse verwenden, um Ihren Unity-Projekten eine zusätzliche Dosis Immersion zu verleihen.

Unity bietet vollständige Unterstützung für räumliche Zuordnungen, die Entwicklern auf folgende Weise zur Verfügung steht:

  1. Räumliche Zuordnungskomponenten, die im MixedRealityToolkit verfügbar sind und einen bequemen und schnellen Weg für die ersten Schritte mit der räumlichen Zuordnung bieten
  2. APIs für räumliche Zuordnungen auf niedriger Ebene, die vollständige Kontrolle bieten und eine komplexere anwendungsspezifische Anpassung ermöglichen

Um räumliche Zuordnungen in Ihrer App zu verwenden, muss die SpatialPerception-Funktion in Ihrem AppxManifest festgelegt werden.

Geräteunterstützung

Funktion HoloLens (1. Generation) HoloLens 2 Immersive Headsets
Räumliche Abbildung ✔️ ✔️

Festlegen der SpatialPerception-Funktion

Damit eine App Räumliche Zuordnungsdaten nutzen kann, muss die SpatialPerception-Funktion aktiviert sein.

Aktivieren der SpatialPerception-Funktion:

  1. Öffnen Sie im Unity-Editor den Bereich "Playereinstellungen" (Projekteinstellungen > bearbeiten > Player)
  2. Wählen Sie auf der Registerkarte "Windows Store" die Option aus.
  3. Erweitern Sie "Veröffentlichungseinstellungen", und überprüfen Sie die Funktion "SpatialPerception" in der Liste "Funktionen".

Hinweis

Wenn Sie Ihr Unity-Projekt bereits in eine Visual Studio-Projektmappe exportiert haben, müssen Sie entweder in einen neuen Ordner exportieren oder diese Funktion manuell im AppxManifest in Visual Studio festlegen.

Die räumliche Zuordnung erfordert auch einen MaxVersionTested von mindestens 10.0.10586.0:

  1. Klicken Sie in Visual Studio im Projektmappen-Explorer mit der rechten Maustaste auf Package.appxmanifest, und wählen Sie Code anzeigen aus.
  2. Suchen Sie die Zeile mit TargetDeviceFamily , und ändern Sie MaxVersionTested="10.0.10240.0" in MaxVersionTested="10.0.10586.0"
  3. Speichern Sie package.appxmanifest.

Hinzufügen einer Zuordnung in Unity

System für die räumliche Wahrnehmung

In MRTK finden Sie informationen zum Einrichten verschiedener räumlicher Gitterbeobachter im Leitfaden zu den ersten Schritten für räumliches Bewusstsein.

Informationen zu Beobachtern auf dem Gerät finden Sie unter Konfigurieren von Gitterbeobachtern für Geräte .

Informationen zu Szenenverständnisbeobachtern finden Sie im Leitfaden zu Szenenverständnis-Beobachtern .

Übergeordnete Gitteranalyse: Räumliches Verständnis

Achtung

Spatial Understanding wurde zugunsten von Scene Understanding veraltet.

Das MixedRealityToolkit ist eine Sammlung von Hilfsprogrammcode für die holografische Entwicklung, die auf den holografischen APIs von Unity basiert.

Räumliches Verständnis

Beim Platzieren von Hologrammen in der physischen Welt ist es oft wünschenswert, über die Gitter- und Oberflächenebenen der räumlichen Zuordnung hinauszugehen. Wenn die Platzierung prozedural erfolgt, ist ein höheres Maß an Umweltverständnis wünschenswert. Dies erfordert in der Regel Entscheidungen darüber, was Boden, Decke und Wände ist. Sie können auch eine Reihe von Platzierungseinschränkungen optimieren, um die besten physischen Standorte für holografische Objekte zu ermitteln.

Während der Entwicklung von Young Conker und Fragments stellte sich Asobo Studios diesem Problem mit der Entwicklung eines Raumlösers. Jedes dieser Spiele hatte spielspezifische Anforderungen, aber sie teilten sich die Kerntechnologie des räumlichen Verständnisses. Die HoloToolkit.SpatialUnderstanding-Bibliothek kapselt diese Technologie, sodass Sie schnell leere Räume an den Wänden finden, Objekte an der Decke platzieren, platzieren können, damit Zeichen sitzen können, und eine Vielzahl anderer Abfragen des räumlichen Verständnisses.

Der gesamte Quellcode ist enthalten, sodass Sie ihn an Ihre Anforderungen anpassen und Ihre Verbesserungen mit der Community teilen können. Der Code für den C++-Solver wurde in eine UWP-DLL umschlossen und unity mit einem Drop im Prefab im MixedRealityToolkit verfügbar gemacht.

Grundlegendes zu Modulen

Es gibt drei primäre Schnittstellen, die vom Modul verfügbar gemacht werden: Topologie für einfache Oberflächen- und Räumliche Abfragen, Shape für die Objekterkennung und der Objektplatzierungslöser für die einschränkungsbasierte Platzierung von Objektsätzen. Beide Methoden werden im Folgenden beschrieben. Zusätzlich zu den drei primären Modulschnittstellen kann eine Ray Casting-Schnittstelle verwendet werden, um markierte Oberflächentypen abzurufen, und ein benutzerdefiniertes wasserdichtes Playspace-Gitter kann kopiert werden.

Ray Casting

Nach Abschluss des Raumscans werden intern Etiketten für Oberflächen wie Boden, Decke und Wände generiert. Die PlayspaceRaycast Funktion nimmt einen Strahl auf und gibt zurück, wenn der Strahl mit einer bekannten Oberfläche kollidiert, und wenn ja, Informationen über diese Oberfläche in Form eines 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;
};

Intern wird der Raycast mit der berechneten 8-cm-Voxeldarstellung des Playspace berechnet. Jedes Voxel enthält eine Reihe von Oberflächenelementen mit verarbeiteten Topologiedaten (auch bekannt als Surfel). Die in der sich überschneidenden Voxelzelle enthaltenen Oberflächen werden verglichen und die beste Übereinstimmung verwendet, um die Topologieinformationen zu suchen. Diese Topologiedaten enthalten die in Form der Enumeration "SurfaceTypes" zurückgegebene Bezeichnung sowie die Oberfläche der sich überschneidenden Oberfläche.

Im Unity-Beispiel wandelt der Cursor für jeden Frame einen Strahl um. Zunächst gegen die Collider von Unity. Zweitens, gegen die Weltdarstellung des Verständnismoduls. Und schließlich noch einmal UI-Elemente. In dieser Anwendung erhält die Benutzeroberfläche Priorität, neben dem Verständnisergebnis und schließlich den Collidern von Unity. SurfaceType wird als Text neben dem Cursor gemeldet.

Der Surface-Typ wird neben dem Cursor beschriftet.
Der Surface-Typ wird neben dem Cursor beschriftet.

Topologieabfragen

Innerhalb der DLL übernimmt der Topologie-Manager die Bezeichnung der Umgebung. Wie bereits erwähnt, werden viele der Daten in Surfeln gespeichert, die in einem Voxel-Volume enthalten sind. Darüber hinaus wird die "PlaySpaceInfos"-Struktur verwendet, um Informationen über den Playspace zu speichern, einschließlich der Weltausrichtung (weitere Details dazu unten), boden- und Deckenhöhe. Heuristik wird verwendet, um Boden, Decke und Wände zu bestimmen. Beispielsweise wird die größte und niedrigste horizontale Fläche mit einer Fläche von mehr als 1 m2 als Boden betrachtet.

Hinweis

Auch der Kamerapfad während des Scanvorgangs wird in diesem Prozess verwendet.

Eine Teilmenge der Abfragen, die vom Topologie-Manager verfügbar gemacht werden, werden über die DLL verfügbar gemacht. Die verfügbar gemachten Topologieabfragen sind wie folgt:

QueryTopology_FindPositionsOnWalls
QueryTopology_FindLargePositionsOnWalls
QueryTopology_FindLargestWall
QueryTopology_FindPositionsOnFloor
QueryTopology_FindLargestPositionsOnFloor
QueryTopology_FindPositionsSittable

Jede der Abfragen verfügt über einen Satz von Parametern, die für den Abfragetyp spezifisch sind. Im folgenden Beispiel gibt der Benutzer die Mindesthöhe & Breite des gewünschten Volumens, die mindeste Platzierungshöhe über dem Boden und den mindesten Abstand vor dem Volume an. Alle Messungen sind in Metern.

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)

Jede dieser Abfragen verwendet ein vorab zugeordnetes Array von "TopologyResult"-Strukturen. Der Parameter "locationCount" gibt die Länge des übergebenen Arrays an. Der Rückgabewert gibt die Anzahl der zurückgegebenen Speicherorte an. Diese Zahl ist niemals größer als der übergebene Parameter "locationCount".

Das "TopologyResult" enthält die Mittelposition des zurückgegebenen Volumes, die richtungsorientierte Richtung (d. h. normal) und die Dimensionen des gefundenen Raums.

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

Hinweis

Im Unity-Beispiel ist jede dieser Abfragen mit einer Schaltfläche im Bereich der virtuellen Benutzeroberfläche verknüpft. Im Beispiel werden die Parameter für jede dieser Abfragen hart in vernünftige Werte codieren. Weitere Beispiele finden Sie im Beispielcode unter SpaceVisualizer.cs.

Shape-Abfragen

In der DLL verwendet das Shape Analyzer ("ShapeAnalyzer_W") das Topologieanalysetool, um mit benutzerdefinierten Formen abzugleichen, die vom Benutzer definiert werden. Das Unity-Beispiel definiert eine Reihe von Formen und macht die Ergebnisse über das In-App-Abfragemenü auf der Registerkarte Shape verfügbar. Die Absicht ist, dass der Benutzer seine eigenen Objekt-Shape-Abfragen definieren und diese verwenden kann, je nach Bedarf für seine Anwendung.

Die Shape-Analyse funktioniert nur auf horizontalen Oberflächen. Eine Couch wird beispielsweise durch die flache Sitzfläche und die flache Oberseite des Couchrückens definiert. Die Shape-Abfrage sucht nach zwei Oberflächen mit einer bestimmten Größe, Höhe und einem bestimmten Seitenbereich, wobei die beiden Oberflächen ausgerichtet und verbunden sind. Bei Verwendung der APIs-Terminologie sind der Sitz und die Rückseite Formkomponenten, und die Ausrichtungsanforderungen sind Einschränkungen für Formkomponenten.

Eine im Unity-Beispiel (ShapeDefinition.cs) definierte Beispielabfrage für "sittable"-Objekte lautet wie folgt.

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);

Jede Shape-Abfrage wird durch eine Reihe von Shape-Komponenten definiert, die jeweils einen Satz von Komponenteneinschränkungen und eine Reihe von Shape-Einschränkungen aufweisen, die Abhängigkeiten zwischen den Komponenten auflisten. Dieses Beispiel enthält drei Einschränkungen in einer einzelnen Komponentendefinition und keine Formeinschränkungen zwischen Komponenten (da es nur eine Komponente gibt).

Im Gegensatz dazu weist die Couchform zwei Formkomponenten und vier Formeinschränkungen auf. Komponenten werden durch ihren Index in der Komponentenliste des Benutzers identifiziert (in diesem Beispiel 0 und 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),
};

Wrapperfunktionen werden im Unity-Modul bereitgestellt, um benutzerdefinierte Shape-Definitionen zu erstellen. Die vollständige Liste der Komponenten- und Formeinschränkungen finden Sie in "SpatialUnderstandingDll.cs" in den Strukturen "ShapeComponentConstraint" und "ShapeConstraint".

Rechteckform befindet sich auf dieser Oberfläche
Rechteckform befindet sich auf dieser Oberfläche

Objektplatzierungslöser

Der Objektplatzierungslöser kann verwendet werden, um ideale Orte im physischen Raum zu identifizieren, an denen Ihre Objekte platziert werden können. Der Solver findet anhand der Objektregeln und -einschränkungen die am besten geeignete Position. Darüber hinaus werden Objektabfragen beibehalten, bis das Objekt mit "Solver_RemoveObject"- oder "Solver_RemoveAllObjects"-Aufrufen entfernt wird, was eine eingeschränkte Platzierung mehrerer Objekte ermöglicht. Objektplatzierungsabfragen bestehen aus drei Teilen: platzierungstyp mit Parametern, einer Liste von Regeln und einer Liste von Einschränkungen. Verwenden Sie die folgende API, um eine Abfrage auszuführen.

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)

Diese Funktion akzeptiert einen Objektnamen, eine Platzierungsdefinition und eine Liste von Regeln und Einschränkungen. Die C#-Wrapper bieten Konstruktionshilfsfunktionen, um die Erstellung von Regeln und Einschränkungen zu vereinfachen. Die Platzierungsdefinition enthält den Abfragetyp, d. h. einen der folgenden.

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

Jeder der Platzierungstypen verfügt über einen Satz von Parametern, die für den Typ eindeutig sind. Die Struktur "ObjectPlacementDefinition" enthält einen Satz statischer Hilfsfunktionen zum Erstellen dieser Definitionen. Um beispielsweise einen Ort zu finden, an dem ein Objekt auf dem Boden platziert werden kann, können Sie die folgende Funktion verwenden. public static ObjectPlacementDefinition Create_OnFloor(Vector3 halfDims) Zusätzlich zum Platzierungstyp können Sie eine Reihe von Regeln und Einschränkungen angeben. Regeln können nicht verletzt werden. Mögliche Platzierungsorte, die dem Typ und den Regeln entsprechen, werden dann für den Satz von Einschränkungen optimiert, um den optimalen Platzierungsort auszuwählen. Jede der Regeln und Einschränkungen kann von den bereitgestellten statischen Erstellungsfunktionen erstellt werden. Unten finden Sie ein Beispiel für die Regel- und Einschränkungskonstruktionsfunktion.

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

Die folgende Objektplatzierungsabfrage sucht nach einem Ort, an dem ein Würfel mit einer halben Meter am Rand einer Oberfläche platziert werden kann, weg von anderen zuvor platzierten Objekten und in der Nähe der Mitte des Raums.

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());

Bei erfolgreicher Ausführung wird eine "ObjectPlacementResult"-Struktur zurückgegeben, die die Platzierungsposition, Dimensionen und Ausrichtung enthält. Darüber hinaus wird die Platzierung der internen Liste der platzierten Objekte der DLL hinzugefügt. Nachfolgende Platzierungsabfragen berücksichtigen dieses Objekt. Die Datei "LevelSolver.cs" im Unity-Beispiel enthält weitere Beispielabfragen.

Ergebnisse der Objektplatzierung
Abbildung 3: Die blauen Kästchen, wie das Ergebnis von Abfragen von drei Stellen auf dem Boden ohne Kamerapositionsregeln

Beim Lösen der Platzierungsposition mehrerer Objekte, die für ein Level- oder Anwendungsszenario erforderlich sind, lösen Sie zunächst unverzichtbare und große Objekte, um die Wahrscheinlichkeit zu maximieren, dass ein Raum gefunden werden kann. Die Platzierungsreihenfolge ist wichtig. Wenn Objektplatzierungen nicht gefunden werden können, versuchen Sie es mit weniger eingeschränkten Konfigurationen. Eine Reihe von Fallbackkonfigurationen ist entscheidend für die Unterstützung der Funktionalität in vielen Raumkonfigurationen.

Prozess der Raumüberprüfung

Während die von holoLens bereitgestellte Lösung für die räumliche Zuordnung so konzipiert ist, dass sie generisch genug ist, um die Anforderungen der gesamten Bandbreite von Problembereichen zu erfüllen, wurde das Modul für räumliches Verständnis entwickelt, um die Anforderungen von zwei spezifischen Spielen zu unterstützen. Die Lösung ist nach einem bestimmten Prozess und einer Reihe von Annahmen strukturiert, die unten zusammengefasst werden.

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.

Benutzergesteuertes "Malen" des Spielraums : Während der Scanphase bewegt sich der Benutzer und schaut sich im Spieltempo um und malt effektiv die Bereiche, die einbezogen werden sollten. Das generierte Gitter ist wichtig, um während dieser Phase Feedback vom Benutzer zu geben. Einrichtung von Innen- oder Büroobjekten – Die Abfragefunktionen sind um flache Oberflächen und Wände im rechten Winkel gestaltet. Dies ist eine weiche Einschränkung. Während der Überprüfungsphase wird jedoch eine Analyse der primären Achse abgeschlossen, um die Netz-Tesselation entlang der Haupt- und Nebenachse zu optimieren. Die enthaltene Datei SpatialUnderstanding.cs verwaltet den Prozess der Überprüfungsphase. Sie ruft die folgenden Funktionen auf.

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.

Der Scanflow, der durch das Verhalten "SpatialUnderstanding" gesteuert wird, ruft InitScan und dann UpdateScan für jeden Frame auf. Wenn die Statistikabfrage eine angemessene Abdeckung meldet, kann der Benutzer requestFinish aufrufen, um das Ende der Überprüfungsphase anzuzeigen. UpdateScan wird weiterhin aufgerufen, bis der Rückgabewert angibt, dass die DLL die Verarbeitung abgeschlossen hat.

Grundlegendes zu Mesh

Die Understanding-DLL speichert den Playspace intern als Raster von 8 cm großen Voxelwürfeln. Während des ersten Teils der Überprüfung wird eine Primäre Komponentenanalyse abgeschlossen, um die Achsen des Raums zu bestimmen. Intern speichert er den Voxelraum, der an diesen Achsen ausgerichtet ist. Ein Gitter wird etwa jede Sekunde durch Extrahieren der Isosurface aus dem Voxelvolumen erzeugt.

Generiertes Gitter, das aus dem Voxelvolumen erzeugt wird
Generiertes Gitter, das aus dem Voxelvolumen erzeugt wird

Problembehandlung

  • Stellen Sie sicher, dass Sie die SpatialPerception-Funktion festgelegt haben.
  • Wenn die Nachverfolgung verloren geht, entfernt das nächste OnSurfaceChanged-Ereignis alle Gitter.

Räumliche Zuordnung im Mixed Reality Toolkit

Weitere Informationen zur Verwendung der räumlichen Zuordnung mit Mixed Reality Toolkit finden Sie im Abschnitt zur räumlichen Wahrnehmung der MRTK-Dokumentation.

Nächster Entwicklungsprüfpunkt

Wenn Sie die von uns beschriebene Unity-Entwicklungsreise befolgen, befinden Sie sich mitten in der Erkundung der MRTK-Kernbausteine. Von hier aus können Sie mit dem nächsten Baustein fortfahren:

Oder fahren Sie mit den Funktionen und APIs der Mixed Reality-Plattform fort:

Sie können jederzeit zu den Prüfpunkten für die Unity-Entwicklung zurückkehren.

Weitere Informationen