Freigeben über


Räumliche Abbildung in Unity

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

Unity umfasst vollständige Unterstützung für die räumliche Zuordnung, die Entwicklern auf folgende Weise verfügbar gemacht wird:

  1. Spatial mapping components available in the MixedRealityToolkit, which provide a praktisch and rapid path for getting started with spatial mapping
  2. ApIs für räumliche Zuordnungen auf niedrigerer Ebene, die Vollzugriff bieten und komplexere anwendungsspezifische Anpassungen ermöglichen

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

Unterstützung für Geräte

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.

So aktivieren Sie die SpatialPerception-Funktion:

  1. Öffnen Sie im Unity-Editor den Bereich "Playereinstellungen" (Projekteinstellungen-Player > bearbeiten>)
  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 manuell im AppxManifest in Visual Studio festlegen.

Für die räumliche Zuordnung ist außerdem ein MaxVersionTested 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 das Package.appxmanifest.

Hinzufügen von Zuordnungen in Unity

System für die räumliche Wahrnehmung

Schauen Sie sich in MRTK den Leitfaden für die ersten Schritte des räumlichen Bewusstseins an, um Informationen über die Einrichtung verschiedener räumlicher Gitterbeobachter zu erhalten.

Informationen zu Gerätebeobachtern finden Sie in der Konfiguration von Gitterbeobachtern für die Geräteführung .

Informationen zu Szenenverständnisbeobachtern finden Sie im Beobachterleitfaden "Szeneverständnis".

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

Achtung

Räumliches Verständnis ist zugunsten von Szenenverständnis veraltet.

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

Räumliches Verständnis

Wenn Hologramme in der physischen Welt platziert werden, 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 Umweltverständnis wünschenswert. Dies erfordert in der Regel Entscheidungen darüber, was Boden, Decke und Wände sind. Sie haben auch die Möglichkeit, anhand einer Reihe von Platzierungseinschränkungen zu optimieren, um die besten physischen Standorte für holografische Objekte zu ermitteln.

Während der Entwicklung von Young Conker und Fragmenten sahen sich Asobo Studios diesem Problem gegenüber, indem sie einen Raumlöser entwickeln. Jedes dieser Spiele hatte spielspezifische Anforderungen, aber sie teilten die kerntechnische Raumverständnistechnologie. Die HoloToolkit.SpatialUnderstanding-Bibliothek kapselt diese Technologie, sodass Sie schnell leere Räume an den Wänden finden, Objekte an der Decke platzieren, für Zeichen zu sitzende Zeichen identifizieren und eine Vielzahl anderer Räumlicher Verständnisabfragen erkennen können.

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 das C++-Solver wurde in eine UWP-DLL umschlossen und Unity mit einem Drop in 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 das 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 Interface verwendet werden, um markierte Oberflächentypen abzurufen, und ein benutzerdefiniertes wasserdichtes Playspace-Gitter kann ausgeschrieben werden.

Ray Casting

Nach Abschluss des Raumscans werden Etiketten intern für Oberflächen wie Boden, Decke und Wände generiert. Die PlayspaceRaycast Funktion nimmt einen Strahl und gibt zurück, wenn der Strahl mit einer bekannten Oberfläche kollidiert und wenn ja, Informationen über diese Oberfläche in Form einer .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 anhand der berechneten 8-cm-würfeligen Voxeldarstellung des Playspace berechnet. Jedes Voxel enthält eine Reihe von Oberflächenelementen mit verarbeiteten Topologiedaten (auch als Surfel bezeichnet). Die Surfel in der überschneidenden Voxelzelle werden verglichen und die beste Übereinstimmung verwendet, um die Topologieinformationen nachzuschlagen. Diese Topologiedaten enthalten die in Form der Aufzählung "SurfaceTypes" zurückgegebene Beschriftung sowie den Oberflächenbereich der überschnittenen Oberfläche.

Im Unity-Beispiel wandelt der Cursor jeweils einen Strahl um. Zuerst gegen die Kollidierungen von Unity. Zweitens, gegen die Weltdarstellung des Moduls. Und schließlich wieder UI-Elemente. In dieser Anwendung erhält die Benutzeroberfläche Priorität, als Nächstes das Verständnisergebnis und schließlich die Kollidierungen von Unity. Der 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 behandelt der Topologie-Manager die Bezeichnung der Umgebung. Wie bereits erwähnt, werden viele der Daten in Surfels gespeichert, die in einem Voxelvolumen enthalten sind. Darüber hinaus wird die "PlaySpaceInfos"-Struktur verwendet, um Informationen über den Playspace zu speichern, einschließlich der Weltausrichtung (weitere Details hierzu unten), Boden- und Deckenhöhe. Heuristiken werden zur Bestimmung von Boden, Decke und Wänden verwendet. Die größte und niedrigste horizontale Fläche mit einer Fläche von mehr als 1 m2 wird beispielsweise als Boden betrachtet.

Hinweis

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

Eine Teilmenge der vom Topologie-Manager verfügbar gemachten Abfragen 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 Abfrage verfügt über eine Reihe von Parametern, die für den Abfragetyp spezifisch sind. Im folgenden Beispiel gibt der Benutzer die minimale Höhe und Breite des gewünschten Volumens, die minimale Platzierungshöhe über dem Boden und die mindeste Menge an Abstand vor dem Volume an. Alle Maße sind in Meter.

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 meldet die Anzahl der zurückgegebenen Speicherorte. Diese Zahl ist nie größer als der übergebene Parameter "locationCount".

Das "TopologyResult" enthält die mittlere Position des zurückgegebenen Volumes, die richtungsgerichtete Richtung (d. h. normal) und die Abmessungen des gefundenen Raums.

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

Hinweis

Im Unity-Beispiel wird 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 auf angemessene Werte festgelegt. Weitere Beispiele finden Sie unter SpaceVisualizer.cs im Beispielcode.

Shape-Abfragen

In der DLL verwendet der Shape Analyzer ("ShapeAnalyzer_W") die Topologieanalyse, um mit benutzerdefinierten Shapes abzugleichen, die vom Benutzer definiert wurden. Im Unity-Beispiel wird eine Reihe von Shapes definiert und die Ergebnisse über das In-App-Abfragemenü auf der Registerkarte "Form" verfügbar gemacht. Die Absicht besteht darin, dass der Benutzer seine eigenen Objekt-Shape-Abfragen definieren und diese nach Bedarf in seiner Anwendung nutzen kann.

Die Shape-Analyse funktioniert nur auf horizontalen Oberflächen. Eine Couch wird z. B. durch die flache Sitzfläche und die flache Oberseite der Couch zurück definiert. Die Shape-Abfrage sucht nach zwei Oberflächen einer bestimmten Größe, Höhe und seitenbereich, wobei die beiden Oberflächen ausgerichtet und verbunden sind. Bei verwendung der APIs-Terminologie handelt es sich bei der Couchsitz und der Rückseite um Shape-Komponentenkomponenten und die Ausrichtungsanforderungen um Einschränkungen der 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 eine Reihe von Komponenteneinschränkungen und eine Reihe von Shape-Einschränkungen enthalten, die Abhängigkeiten zwischen den Komponenten auflisten. Dieses Beispiel enthält drei Einschränkungen in einer einzelnen Komponentendefinition und keine Shape-Einschränkungen zwischen Komponenten (da nur eine Komponente vorhanden ist).

Im Gegensatz dazu verfügt das Couch-Shape über zwei Formkomponenten und vier Formeinschränkungen. Komponenten werden anhand ihres Indexes in der Komponentenliste des Benutzers (0 und 1 in diesem Beispiel) identifiziert.

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".

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

Objektplatzierung Solver

Der Objektplatzierungslöser kann verwendet werden, um ideale Positionen im physischen Raum zu identifizieren, um Ihre Objekte zu platzieren. Der Solver findet anhand der Objektregeln und Einschränkungen die beste Passende Position. Darüber hinaus werden Objektabfragen beibehalten, bis das Objekt mit "Solver_RemoveObject"- oder "Solver_RemoveAllObjects"-Aufrufen entfernt wird, wodurch die Platzierung eingeschränkter Objekte mit mehreren Objekten ermöglicht wird. 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 verwendet einen Objektnamen, eine Platzierungsdefinition und eine Liste von Regeln und Einschränkungen. Die C#-Wrapper bieten Konstruktionshilfsfunktionen, um die Regel- und Einschränkungskonstruktion einfach zu gestalten. 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 Platzierungstyp weist einen Satz von Parametern auf, 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 soll, 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 bereitstellen. Regeln können nicht verletzt werden. Mögliche Platzierungsorte, die den Typ und die Regeln erfüllen, werden dann für die Gruppe von Einschränkungen optimiert, um die optimale Platzierungsposition auszuwählen. Jede der Regeln und Einschränkungen kann durch die bereitgestellten statischen Erstellungsfunktionen erstellt werden. Nachfolgend finden Sie eine Beispielfunktion für Regel- und Einschränkungskonstruktion.

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 Meter Würfel am Rand einer Oberfläche platziert werden soll, 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 Felder, wie das Ergebnis von drei Platz auf Bodenabfragen ohne Kamerapositionsregeln entsteht

Bei der Lösung für die Platzierung von mehreren Objekten, 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. Platzierungsreihenfolge ist wichtig. Wenn Objektplatzierungen nicht gefunden werden können, versuchen Sie weniger eingeschränkte Konfigurationen. Eine Reihe von Fallbackkonfigurationen ist für die Unterstützung von Funktionen in vielen Raumkonfigurationen von entscheidender Bedeutung.

Prozess der Raumüberprüfung

Während die von der HoloLens bereitgestellte Raumzuordnungslösung generisch genug ist, um die Anforderungen der gesamten Bandbreite von Problemräumen zu erfüllen, wurde das Raumverständnismodul erstellt, um die Anforderungen von zwei spezifischen Spielen zu unterstützen. Seine Lösung ist um einen bestimmten Prozess und eine Reihe von Annahmen strukturiert, die unten zusammengefasst sind.

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 Playspace "Malen" – Während der Scanphase bewegt sich der Benutzer und sieht sich das Spieltempo an, wodurch die Bereiche effektiv bemalt werden, die einbezogen werden sollten. Das generierte Gitter ist wichtig, um während dieser Phase Benutzerfeedback zu geben. Innenheim oder Büroeinrichtung – Die Abfragefunktionen sind um flache Oberflächen und Wände in rechten Winkeln gestaltet. Dies ist eine weiche Einschränkung. Während der Scanphase wird jedoch eine Primärachsenanalyse abgeschlossen, um die Gittertsselation entlang der Haupt- und Hilfsachse zu optimieren. Die enthaltene SpatialUnderstanding.cs Datei verwaltet den Überprüfungsphasesprozess. 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 Scanfluss, der durch das Verhalten "SpatialUnderstanding" gesteuert wird, ruft InitScan auf und aktualisiert dann jeden Frame. Wenn die Statistikabfrage eine angemessene Abdeckung meldet, kann der Benutzer Airtap aufrufen, um RequestFinish aufzurufen, um das Ende der Überprüfungsphase anzugeben. UpdateScan wird weiterhin aufgerufen, bis der Rückgabewert angibt, dass die DLL die Verarbeitung abgeschlossen hat.

Grundlegendes zu Gittern

Die grundlegende DLL speichert den Playspace intern als Raster von 8 cm großen Voxelwürfeln. Während des ersten Teils der Überprüfung wird eine Analyse der primären Komponenten abgeschlossen, um die Achsen des Raums zu bestimmen. Intern speichert er seinen Voxelraum, der an diesen Achsen ausgerichtet ist. Ein Gitter wird etwa jede Sekunde erzeugt, indem die Isosurface aus dem Voxelvolumen extrahiert wird.

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 von Spatial Mapping mit Mixed Reality Toolkit finden Sie im Abschnitt "Räumliches Bewusstsein" der MRTK-Dokumentation.

Nächster Entwicklungsprüfpunkt

Wenn Sie die Unity-Entwicklungsreise verfolgt haben, die wir eingerichtet haben, 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.

Siehe auch