Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Beim Erstellen unserer ersten Apps für Microsoft HoloLens waren wir gespannt darauf, wie weit wir die Grenzen der räumlichen Zuordnung auf dem Gerät verschieben können. Jeff Evertt, Softwareentwickler bei Microsoft Studios, erklärt, wie eine neue Technologie aus dem Bedarf an mehr Kontrolle darüber entwickelt wurde, wie Hologramme in der realen Umgebung eines Benutzers platziert werden.
Hinweis
HoloLens 2 implementiert eine neue Scene Understanding Runtime, die Mixed Reality Entwicklern eine strukturierte, allgemeine Umgebungsdarstellung bietet, die die Entwicklung für umweltbewusste Anwendungen intuitiv gestalten soll.
Video ansehen
Über räumliche Zuordnung hinaus
Während wir an Fragmenten und Young Conkerarbeiteten, zwei der ersten Spiele für HoloLens, stellten wir fest, dass wir bei der prozeduralen Platzierung von Hologrammen in der physischen Welt ein höheres Maß an Verständnis über die Umgebung des Benutzers benötigten. Jedes Spiel hatte seine eigenen spezifischen Platzierungsanforderungen: In Fragmenten wollten wir beispielsweise zwischen verschiedenen Oberflächen wie dem Boden oder einem Tisch unterscheiden können, um Hinweise an relevanten Stellen zu platzieren. Wir wollten auch Oberflächen identifizieren können, auf denen lebensgroße holografische Figuren sitzen könnten, wie z. B. eine Couch oder einen Stuhl. In Young Conker wollten wir, dass Conker und seine Gegner in der Lage sind, erhöhte Oberflächen in einem Spielerraum als Plattformen zu verwenden.
Asobo Studios, unser Entwicklungspartner für diese Spiele, stellte sich diesem Problem und schuf eine Technologie, die die Räumlichen Kartenfunktionen von HoloLens erweitert. Damit könnten wir den Raum eines Spielers analysieren und Oberflächen wie Wände, Tische, Stühle und Böden identifizieren. Es gab uns auch die Möglichkeit, mit einer Reihe von Einschränkungen zu optimieren, um die beste Platzierung für holografische Objekte zu bestimmen.
Der Code des räumlichen Verständnisses
Wir haben den ursprünglichen Code von Asobo übernommen und eine Bibliothek erstellt, die diese Technologie kapselt. Microsoft und Asobo haben diesen Code jetzt als Open-Source-Code bereitgestellt und in MixedRealityToolkit zur Verfügung gestellt, damit Sie ihn in Ihren eigenen Projekten verwenden 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 den C++-Solver wurde in eine UWP-DLL umschlossen und mit einem Drop-In-Prefab in MixedRealityToolkit für Unity verfügbar gemacht.
Das Unity-Beispiel enthält viele nützliche Abfragen, mit denen Sie leere Räume an Wänden finden, Objekte an der Decke oder auf großen Flächen auf dem Boden platzieren, Orte identifizieren können, an denen Zeichen sitzen können, und eine Vielzahl anderer Abfragen zum räumlichen Verständnis.
Während die von HoloLens bereitgestellte Raumzuordnungslösung 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. Daher ist die Lösung nach einem bestimmten Prozess und einer Reihe von Annahmen strukturiert:
- Playspace mit fester Größe: Der Benutzer gibt die maximale Playspacegröße im Initialisierungsaufruf an.
- Einmaliger Scanprozess: Der Prozess erfordert eine diskrete Überprüfungsphase, in der der Benutzer herumläuft und den Playspace definiert. Abfragefunktionen funktionieren erst, nachdem die Überprüfung abgeschlossen wurde.
- Benutzergesteuertes Playspace "Malen": Während der Scanphase bewegt sich der Benutzer und sieht sich im Playspace um und malt effektiv die Bereiche, die einbezogen werden sollen. 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.
Raumscanprozess
Wenn Sie das Modul für räumliches Verständnis laden, scannen Sie zunächst Ihren Raum, damit alle verwendbaren Oberflächen – z. B. Boden, Decke und Wände – identifiziert und beschriftet werden. Während des Scanvorgangs schauen Sie sich in Ihrem Raum um und "malen" die Bereiche, die in den Scan einbezogen werden sollen.
Das Gitter, das in dieser Phase gesehen wird, ist ein wichtiger Teil des visuellen Feedbacks, mit dem Benutzer wissen können, welche Teile des Raums gescannt werden. Die DLL für das Spatial Understanding-Modul speichert den Playspace intern als Raster von 8cm 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.
Räumliches Mapping-Gitter in Weiß und Verstehen des Playspace-Gitters in Grün
Die enthaltene SpatialUnderstanding.cs-Datei verwaltet den Überprüfungsphaseprozess. Es ruft die folgenden Funktionen auf:
- SpatialUnderstanding_Init: Wird einmal am Anfang aufgerufen.
- GeneratePlayspace_InitScan: Gibt an, dass die Überprüfungsphase beginnen soll.
- GeneratePlayspace_UpdateScan_DynamicScan: Jeder Frame wurde aufgerufen, um den Überprüfungsprozess zu aktualisieren. Die Kameraposition und -ausrichtung wird übergeben und für den oben beschriebenen Playspace-Malprozess verwendet.
- GeneratePlayspace_RequestFinish: Wird aufgerufen, um den Playspace fertig zu stellen. Dadurch werden die während der Scanphase "gemalten" Bereiche verwendet, um den Playspace zu definieren und zu sperren. Die Anwendung kann Statistiken während der Überprüfungsphase abfragen und das benutzerdefinierte Gitternetz abfragen, um Benutzerfeedback bereitzustellen.
- Import_UnderstandingMesh: Während der Überprüfung fragt das vom Modul bereitgestellte und im Understanding-Prefab platzierte SpatialUnderstandingCustomMesh-Verhalten in regelmäßigen Abständen das vom Prozess generierte benutzerdefinierte Gitter ab. Darüber hinaus erfolgt dies erneut, nachdem die Überprüfung abgeschlossen wurde.
Der Scanflow, der durch das SpatialUnderstanding-Verhalten 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 anzugeben. UpdateScan wird weiterhin aufgerufen, bis der Rückgabewert angibt, dass die DLL die Verarbeitung abgeschlossen hat.
Die Abfragen
Nach Abschluss der Überprüfung können Sie auf drei verschiedene Arten von Abfragen in der Schnittstelle zugreifen:
- Topologieabfragen: Hierbei handelt es sich um schnelle Abfragen, die auf der Topologie des gescannten Raums basieren.
- Shape-Abfragen: Diese verwenden die Ergebnisse Ihrer Topologieabfragen, um horizontale Oberflächen zu finden, die eine gute Übereinstimmung mit benutzerdefinierten Shapes sind, die Sie definieren.
- Objektplatzierungsabfragen: Dies sind komplexere Abfragen, die den am besten geeigneten Speicherort basierend auf einer Reihe von Regeln und Einschränkungen für das Objekt finden.
Zusätzlich zu den drei primären Abfragen gibt es eine Raycasting-Schnittstelle, die zum Abrufen von markierten Oberflächentypen verwendet werden kann, und ein benutzerdefiniertes wasserdichtes Raumgitter kann herauskopiert werden.
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. Beachten Sie, dass der Kamerapfad während des Scanvorgangs auch in diesem Prozess verwendet wird.
Eine Teilmenge der Abfragen, die vom Topologie-Manager verfügbar gemacht werden, wird über die DLL verfügbar gemacht. Die verfügbaren 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 locationCount-Parameter 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 locationCount-Parameter .
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;
};
Beachten Sie, dass im Unity-Beispiel jede dieser Abfragen mit einer Schaltfläche im Bereich der virtuellen Benutzeroberfläche verknüpft ist. 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
Innerhalb der DLL verwendet das Shape Analyzer (ShapeAnalyzer_W) das Topologieanalysetool, um mit benutzerdefinierten Shapes abzugleichen, die vom Benutzer definiert werden. Das Unity-Beispiel enthält einen vordefinierten Satz von Shapes, die im Abfragemenü auf der Registerkarte Form angezeigt werden.
Beachten Sie, dass die Shape-Analyse nur auf horizontalen Oberflächen funktioniert. 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 Couchsitz und die obere Rückseite der Couch 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 einen Satz 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. Beachten Sie, dass Komponenten durch ihren Index in der Komponentenliste des Benutzers identifiziert werden (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 .
Das blaue Rechteck hebt die Ergebnisse der Abfrage der Stuhlform hervor.
Objektplatzierungs-Solver
Objektplatzierungsabfragen können 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, sodass eine eingeschränkte Platzierung mit mehreren Objekten möglich ist.
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 das Erstellen von Regeln und Einschränkungen zu vereinfachen. Die Platzierungsdefinition enthält den Abfragetyp, d. a. eine 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 ObjectPlacementDefinition-Struktur 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 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, 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 LevelSolver.cs-Datei im Unity-Beispiel enthält weitere Beispielabfragen.
Die blauen Felder zeigen das Ergebnis von drei Place On Floor-Abfragen mit Regeln für die Kameraposition ab.
Tipps:
- Lösen Sie bei der Lösung für die Platzierungsposition mehrerer Objekte, die für ein Level- oder Anwendungsszenario erforderlich sind, 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.
Strahlguss
Zusätzlich zu den drei primären Abfragen kann eine Belichtungsschnittstelle verwendet werden, um markierte Oberflächentypen abzurufen, und ein benutzerdefiniertes wasserdichtes Playspace-Gitter kann kopiert werden. Nachdem der Raum gescannt und abgeschlossen wurde, werden intern Bezeichnungen für Oberflächen wie Boden, Decke und Wände generiert. Die PlayspaceRaycast-Funktion nimmt einen Strahl an 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 8cm-Voxeldarstellung des Playspace berechnet. Jedes Voxel enthält eine Reihe von Oberflächenelementen mit verarbeiteten Topologiedaten (auch als Surfel bezeichnet). Die in der überschneidenden Voxelzelle enthaltenen Surfel werden verglichen und die beste Übereinstimmung verwendet, um die Topologieinformationen nachzuschlagen. Diese Topologiedaten enthalten die in Form der SurfaceTypes-Enumeration zurückgegebene Beschriftung 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 für die Ui-Elemente. In dieser Anwendung erhält die Benutzeroberfläche Priorität, dann das Verständnisergebnis und schließlich die Collider von Unity. SurfaceType wird als Text neben dem Cursor gemeldet.
Schnittmenge des Raycast-Ergebnisses mit dem Boden.
Den Code abrufen
Der Open-Source-Code ist in MixedRealityToolkit verfügbar. Teilen Sie uns dies in den HoloLens-Entwicklerforen mit, wenn Sie den Code in einem Projekt verwenden. Wir können es kaum erwarten zu sehen, was Sie damit machen!
Über den Autor
![]() | Jeff Evertt ist ein Leitender Softwareentwicklungsleiter, der seit den frühen Tagen an HoloLens gearbeitet hat, von der Inkubation bis zur Entwicklung von Erfahrungen. Vor HoloLens arbeitete er auf der Xbox Kinect und in der Spieleindustrie auf einer Vielzahl von Plattformen und Spielen. Jeff ist leidenschaftlich für Robotik, Grafiken und Dinge mit auffälligen Lichtern, die piepsen. Er lernt gerne Neues und arbeitet an Software, Hardware und vor allem in dem Raum, in dem sich die beiden überschneiden. |