Freigeben über


Räumliche Zuordnung in Unity

Mit der räumlichen 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 Portion Immersion zu verleihen.

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

  1. Im MixedRealityToolkit verfügbare Komponenten für räumliche Zuordnungen, die einen bequemen und schnellen Weg für die ersten Schritte mit der räumlichen Zuordnung bieten
  2. APIs für die räumliche Zuordnung auf niedrigerer Ebene, die vollständige Kontrolle bieten und eine komplexere anwendungsspezifische Anpassung ermöglichen

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

Geräteunterstützung

Feature HoloLens (erste Generation) HoloLens 2 Immersive Headsets
Räumliche Zuordnung ✔️ ✔️

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" 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 in AppxManifest in Visual Studio manuell festlegen.

Für die räumliche Zuordnung ist außerdem ein MaxVersionTested-Wert von mindestens 10.0.10586.0 erforderlich:

  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, die TargetDeviceFamily angibt, 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

Räumliches Bewusstseinssystem

Informationen zum Einrichten verschiedener Räumlicher Gitterbeobachter finden Sie im Leitfaden zu den ersten Schritten im MRTK.

Informationen zu Beobachtern auf Geräten finden Sie im Leitfaden Konfigurieren von Gitterbeobachtern für Geräte .

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

Gitteranalyse auf höherer Ebene: Räumliches Verständnis

Achtung

Spatial Understanding wurde zugunsten von Scene Understanding als veraltet gekennzeichnet.

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 haben auch die Möglichkeit, eine Optimierung anhand einer Reihe von Platzierungseinschränkungen durchzuführen, um die besten physischen Standorte für holografische Objekte zu bestimmen.

Während der Entwicklung von Young Conker and Fragments stellte sich Asobo Studios diesem Problem, indem sie einen Raumlöser entwickelten. Jedes dieser Spiele hatte spielspezifische Anforderungen, aber sie teilten die Kerntechnologie des räumlichen Verständnisses. Die HoloToolkit.SpatialUnderstanding-Bibliothek kapselt diese Technologie, mit der Sie schnell leere Räume an den Wänden finden, Objekte an der Decke platzieren, erkennen können, dass zeichen gesetzt werden kann, 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 mit einem Drop in Prefab im MixedRealityToolkit für Unity 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 Objektplatzierungs-Solver für die einschränkungsbasierte Platzierung von Objektsätzen. Jede dieser Optionen wird unten beschrieben. Zusätzlich zu den drei primären Modulschnittstellen kann eine Strahlgussschnittstelle verwendet werden, um markierte Oberflächentypen abzurufen, und ein benutzerdefiniertes wasserdichtes Playspace-Gitter kann herauskopiert 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 entgegen 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 überschneidenden Voxelzelle enthaltenen Surfel werden verglichen und die beste Übereinstimmung verwendet, um die Topologieinformationen nachzuschlagen. Diese Topologiedaten enthalten die Bezeichnung, die in Form der Enumeration "SurfaceTypes" zurückgegeben wird, sowie die Oberfläche der sich überschneidenden Oberfläche.

Im Unity-Beispiel wirft der Cursor für jeden Frame einen Strahl. Erstens, 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.

Surface-Typ ist neben dem Cursor beschriftet
Surface-Typ ist neben dem Cursor beschriftet

Topologieabfragen

Innerhalb der DLL übernimmt der Topologie-Manager die Bezeichnung der Umgebung. Wie oben 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), des Bodens und der Deckenhöhe. Heuristik wird zur Bestimmung von Boden, Decke und Wänden verwendet. Beispielsweise wird die größte und niedrigste horizontale Fläche mit einer Fläche von mehr als 1 m2 als Boden betrachtet.

Hinweis

Der Kamerapfad während des Scanvorgangs wird auch in diesem Prozess verwendet.

Eine Teilmenge der Abfragen, die vom Topologie-Manager verfügbar gemacht werden, wird ü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 Mindestabstand vor dem Volume an. Alle Messungen sind in Metern angegeben.

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 nie größer als der übergebene Parameter "locationCount".

Das "TopologyResult" enthält die Mittelposition des zurückgegebenen Volumes, die Ausrichtungsrichtung (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 unter SpaceVisualizer.cs im Beispielcode.

Shape-Abfragen

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

Die Shape-Analyse funktioniert nur auf horizontalen Oberflächen. Eine Couch wird beispielsweise durch die flache Sitzfläche und die flache Oberseite der Couchrücken 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 von Shape-Komponenten.

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 einfach zu erstellen. Die vollständige Liste der Komponenten- und Shape-Einschränkungen finden Sie in "SpatialUnderstandingDll.cs" in den Strukturen "ShapeComponentConstraint" und "ShapeConstraint".

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

Objektplatzierungs-Solver

Der Objektplatzierungs-Solver 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 bleiben Objektabfragen bestehen, 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 eine Reihe 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 dürfen 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 halber Würfel am Rand einer Oberfläche, fern von anderen zuvor platzierten Objekten und in der Nähe der Mitte des Raums platziert werden kann.

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, die Dimensionen und die 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 an drei Stellen auf dem Boden ohne Kamerapositionsregeln

Bei der Lösung für die 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.

Raumscanprozess

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 Problemräumen zu erfüllen, wurde das Modul für räumliches Verständnis erstellt, 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 Playspaces: Während der Scanphase bewegt sich der Benutzer, sieht sich im Spieltempo um und malt so effektiv die Bereiche, die einbezogen werden sollten. Das generierte Gitter ist wichtig, um Während dieser Phase Benutzerfeedback zu geben. Inneneinrichtung zu Hause oder im Büro – Die Abfragefunktionen sind um flache Oberflächen und Wände im rechten Winkel ausgelegt. Dies ist eine weiche Einschränkung. Während der Scanphase wird jedoch eine Primärachsenanalyse abgeschlossen, um die Gittertesselation entlang der Haupt- und Nebenachse zu optimieren. Die enthaltene SpatialUnderstanding.cs-Datei verwaltet den Überprüfungsphaseprozess. 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 das Airtap aufrufen, um RequestFinish aufzurufen, um das Ende der Überprüfungsphase anzugeben. UpdateScan wird weiterhin aufgerufen, bis der Rückgabewert angibt, dass die Verarbeitung der DLL abgeschlossen wurde.

Grundlegendes zu Gittern

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 seinen Voxelraum an diesen Achsen ausgerichtet. Ein Gitter wird ungefähr jede Sekunde durch Extrahieren der Isosurface aus dem Voxelvolumen erzeugt.

Generiertes Gitter aus dem Voxelvolumen
Generiertes Gitter aus dem Voxelvolumen

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 verfolgen, befinden Sie sich mitten in der Erkundung der MRTK-Kernbausteine. Von hier aus können Sie mit dem nächsten Baustein fortfahren:

Oder wechseln Sie zu Mixed Reality Plattformfunktionen und APIs:

Sie können jederzeit zu den Unity-Entwicklungsprüfpunkten zurückkehren.

Siehe auch