Case study- Uitbreiden van de mogelijkheden voor ruimtelijke toewijzing van HoloLens

Bij het maken van onze eerste apps Microsoft HoloLens willen we graag zien hoe ver we de grenzen van ruimtelijke toewijzing op het apparaat kunnen verleggen. Jeff Evertt, softwaretechnicus bij Microsoft Studio, legt uit hoe een nieuwe technologie is ontwikkeld uit de behoefte aan meer controle over hoe hologrammen in de echte wereld van een gebruiker worden geplaatst.

Notitie

HoloLens 2 implementeert een nieuwe Scene Understanding Runtime,die Mixed Reality-ontwikkelaars een gestructureerde, hoogwaardige omgevingsweergave biedt die is ontworpen om het ontwikkelen voor intuïtieve toepassingen intuïtief te maken.

Video bekijken

Naast ruimtelijke toewijzing

Terwijl we aan fragmenten werken en Young Conker, twee van de eerste games voor HoloLens, hebben we ontdekt dat we bij de procedurele plaatsing van hologrammen in de fysieke wereld meer inzicht nodig hadden in de omgeving van de gebruiker. Elk spel had zijn eigen specifieke plaatsingsbehoeften: In fragmenten wilden we bijvoorbeeld onderscheid kunnen maken tussen verschillende oppervlakken, zoals de vloer of een tabel, om aanwijzingen op relevante locaties te plaatsen. We wilden ook de oppervlakten kunnen identificeren waar holografische tekens van levensformaat op kunnen zitten, zoals een bank of een bank. In Young Conker wilden we dat Conker en zijn collega's verhoogde oppervlakken als platforms in de ruimte van een speler kunnen gebruiken.

Asobo Studio,onze ontwikkelingspartner voor deze games, kreeg dit probleem front-on en heeft een technologie ontwikkeld die de mogelijkheden voor ruimtelijke toewijzing van HoloLens. Op basis van deze gegevens kunnen we de ruimte van een speler analyseren en oppervlakken identificeren, zoals muren, tabellen, stenen en verdiepingen. Het bood ons ook de mogelijkheid om te optimaliseren tegen een set beperkingen om de beste plaatsing voor holografische objecten te bepalen.

De code voor ruimtelijk inzicht

We hebben de oorspronkelijke code van Asobo gebruikt en een bibliotheek gemaakt die deze technologie inkapseld. Microsoft en Asobo hebben deze code nu open source gemaakt en beschikbaar gesteld op MixedRealityToolkit, die u in uw eigen projecten kunt gebruiken. Alle broncode is opgenomen, zodat u deze kunt aanpassen aan uw behoeften en uw verbeteringen kunt delen met de community. De code voor de C++ solver is verpakt in een UWP-DLL en beschikbaar gesteld aan Unity met een drop-in prefab in MixedRealityToolkit.

Er zijn veel nuttige query's opgenomen in het Unity-voorbeeld waarmee u lege ruimten op muren kunt vinden, objecten aan het plafond of op grote ruimten op de vloer kunt plaatsen, plaatsen waar tekens moeten worden geplaatst en talloze andere ruimtelijke inzichtquery's kunt vinden.

Hoewel de oplossing voor ruimtelijke toewijzing van HoloLens is ontworpen om algemeen genoeg te zijn om te voldoen aan de behoeften van het gehele scala aan probleemruimten, is de module voor ruimtelijke kennis gebouwd ter ondersteuning van de behoeften van twee specifieke games. Als zodanig is de oplossing gestructureerd rond een specifiek proces en een reeks veronderstellingen:

  • Playspace met vaste grootte: de gebruiker geeft de maximale grootte van de playspace op in de init-aanroep.
  • Een keer scannen: het proces vereist een discrete scanfase waarin de gebruiker de playspace definieren. Queryfuncties werken pas nadat de scan is afgerond.
  • Door de gebruiker gestuurde playspace 'schilderij': Tijdens de scanfase verplaatst de gebruiker de playspace en bekijkt deze de gebieden die moeten worden opgenomen. Het gegenereerde mesh is belangrijk om feedback van gebruikers te geven tijdens deze fase.
  • Installatie van binnen of op kantoor: de queryfuncties zijn ontworpen rond vlakke oppervlakken en muren in rechterhoeken. Dit is een zachte beperking. Tijdens de scanfase wordt echter een analyse van de primaire as voltooid om de mesh-tessellation op de primaire en secundaire as te optimaliseren.

Scanproces voor ruimte

Wanneer u de module spatial understanding laadt, moet u eerst uw ruimte scannen, zodat alle bruikbaar oppervlak, zoals de vloer, het plafond en de muren, worden geïdentificeerd en gelabeld. Tijdens het scanproces kijkt u in uw kamer en 'verft' u de gebieden die moeten worden opgenomen in de scan.

De mesh die tijdens deze fase wordt gezien, is een belangrijk stukje visuele feedback waarmee gebruikers kunnen weten welke delen van de ruimte worden gescand. De DLL voor de spatial understanding-module slaat de playspace intern op als een raster van voxel-kubussen van 8 cm groot. Tijdens het eerste deel van het scannen wordt een analyse van het primaire onderdeel voltooid om de assen van de ruimte te bepalen. Intern slaat het de voxelruimte op die is uitgelijnd met deze assen. Er wordt ongeveer elke seconde een mesh gegenereerd door de isosurface uit het voxelvolume te extraheren.

Mesh voor ruimtelijke toewijzing in wit en inzicht in playspace mesh in het groen

Mesh voor ruimtelijke toewijzing in wit en inzicht in playspace mesh in het groen

Het opgenomen bestand SpatialUnderstanding.cs beheert het proces van de scanfase. De volgende functies worden aanroepen:

  • SpatialUnderstanding_Init: eenmaal aan het begin aangeroepen.
  • GeneratePlayspace_InitScan: geeft aan dat de scanfase moet beginnen.
  • GeneratePlayspace_UpdateScan_DynamicScan: elk frame aangeroepen om het scanproces bij te werken. De camerapositie en -stand wordt doorgegeven en wordt gebruikt voor het schilderij van de playspace, zoals hierboven wordt beschreven.
  • GeneratePlayspace_RequestFinish: aangeroepen om de playspace te maken. Hiermee worden de gebieden tijdens de scanfase gebruikt om de playspace te definiëren en te vergrendelen. De toepassing kan tijdens de scanfase query's uitvoeren op statistieken en query's uitvoeren op de aangepaste mesh om feedback van gebruikers te geven.
  • Import_UnderstandingMesh: tijdens het scannen zal het gedrag SpatialUnderstandingCustomMesh dat door de module wordt geleverd en op het begrip prefab wordt geplaatst, periodiek een query uitvoeren op de aangepaste mesh die door het proces wordt gegenereerd. Bovendien wordt dit nog een keer gedaan nadat het scannen is afgerond.

De scanstroom, aangestuurd door het gedrag SpatialUnderstanding, roept InitScan aan en vervolgens UpdateScan elk frame. Wanneer de query voor statistieken redelijke dekking rapporteert, kan de gebruiker een airtap uitvoeren om RequestFinish aan te roepen om het einde van de scanfase aan te geven. UpdateScan blijft aangeroepen totdat de retourwaarde aangeeft dat de DLL-verwerking is voltooid.

De query's

Zodra de scan is voltooid, hebt u toegang tot drie verschillende typen query's in de interface:

  • Topologiequery's: dit zijn snelle query's die zijn gebaseerd op de topologie van de gescande ruimte.
  • Shape-query's: deze maken gebruik van de resultaten van uw topologiequery's om horizontale oppervlakken te vinden die goed overeenkomen met aangepaste vormen die u definieert.
  • Objectplaatsingsquery's: dit zijn complexere query's die de meest geschikte locatie vinden op basis van een set regels en beperkingen voor het object.

Naast de drie primaire query's is er een raycasting-interface die kan worden gebruikt om getagde surface-typen op te halen en een aangepaste watertight room mesh kan worden gekopieerd.

Topologiequery's

Binnen de DLL verwerkt het topologiebeheer het labelen van de omgeving. Zoals hierboven vermeld, worden veel van de gegevens opgeslagen in surfels, die zich in een voxel-volume. Daarnaast wordt de structuur PlaySpaceInfos gebruikt om informatie over de playspace op te slaan, waaronder de werelduitlijning (meer informatie hieronder), de hoogte van de vloer en het plafond.

Heuristiek wordt gebruikt voor het bepalen van vloer, plafond en muren. Het grootste en laagste horizontale oppervlak met meer dan 1 m2 surface area wordt beschouwd als de vloer. Houd er rekening mee dat het camerapad tijdens het scanproces ook in dit proces wordt gebruikt.

Een subset van de query's die door Topology Manager worden blootgesteld, wordt via de DLL zichtbaar gemaakt. De belichte topologiequery's zijn als volgt:

  • QueryTopology_FindPositionsOnWalls
  • QueryTopology_FindLargePositionsOnWalls
  • QueryTopology_FindLargestWall
  • QueryTopology_FindPositionsOnFloor
  • QueryTopology_FindLargestPositionsOnFloor
  • QueryTopology_FindPositionsSittable

Elk van de query's heeft een set parameters die specifiek zijn voor het querytype. In het volgende voorbeeld geeft de gebruiker de minimale hoogte & breedte van het gewenste volume, minimale plaatsingshoogte boven de vloer en de minimale hoeveelheid ruimte voor het volume op. Alle metingen zijn in meters.

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)

Elk van deze query's maakt gebruik van een vooraf toegewezen matrix van TopologyResult-structuren. De parameter locationCount geeft de lengte van de doorgegeven matrix aan. De geretourneerde waarde rapporteert het aantal geretourneerde locaties. Dit getal is nooit groter dan de parameter passed-in locationCount.

TopologyResult bevat de middelste positie van het geretourneerde volume, de richting (normaal) en de dimensies van de gevonden ruimte.

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

Houd er rekening mee dat in het Unity-voorbeeld elk van deze query's is gekoppeld aan een knop in het deelvenster van de virtuele gebruikersinterface. In het voorbeeld worden de parameters voor elk van deze query's in code opgenomen in redelijke waarden. Zie SpaceVisualizer.cs in de voorbeeldcode voor meer voorbeelden.

Shape-query's

Binnen de DLL gebruikt de shape analyzer (ShapeAnalyzer_W) de topologieanalyse om te matchen met aangepaste vormen die door de gebruiker zijn gedefinieerd. Het Unity-voorbeeld heeft een vooraf gedefinieerde set vormen die worden weergegeven in het querymenu op het tabblad Vorm.

Houd er rekening mee dat de vormanalyse alleen werkt op horizontale oppervlakken. Een bank wordt bijvoorbeeld gedefinieerd door het vlakke zitoppervlak en de vlakke bovenkant van de bank. De shape-query zoekt naar twee vlakken met een specifieke grootte, hoogte en hoogtebereik, met de twee vlakken uitgelijnd en verbonden. Met behulp van de TERMINOLOGIE van API's zijn de bank en de bovenkant van de achterkant van de bank vormonderdelen en zijn de uitlijningsvereisten beperkingen van de vormcomponenten.

Een voorbeeldquery die is gedefinieerd in het Unity-voorbeeld (ShapeDefinition.cs) voor 'sittable'-objecten is als volgt:

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

Elke shape-query wordt gedefinieerd door een set shape-onderdelen, elk met een set componentbeperkingen en een set vormbeperkingen die afhankelijkheden tussen de onderdelen vermeldt. Dit voorbeeld bevat drie beperkingen in één onderdeeldefinitie en geen vormbeperkingen tussen onderdelen (omdat er slechts één onderdeel is).

De vorm van de bank heeft daarentegen twee vormonderdelen en vier vormbeperkingen. Houd er rekening mee dat onderdelen worden geïdentificeerd door hun index in de lijst met onderdelen van de gebruiker (0 en 1 in dit voorbeeld).

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

Wrapper-functies zijn beschikbaar in de Unity-module voor het eenvoudig maken van aangepaste vormdefinities. De volledige lijst met beperkingen voor onderdelen en vormen vindt u in SpatialUnderstandingDll.cs in de structuren ShapeComponentConstraint en ShapeConstraint.

De blauwe rechthoek markeert de resultaten van de shape-query.

De blauwe rechthoek markeert de resultaten van de shape-query.

Oplossing voor objectplaatsing

Objectplaatsingsquery's kunnen worden gebruikt om ideale locaties in de fysieke ruimte te identificeren om uw objecten te plaatsen. De solver vindt de meest geschikte locatie op de manier die het best past bij de objectregels en beperkingen. Bovendien blijven objectquery's bestaan totdat het object wordt verwijderd met Solver_RemoveObject- of Solver_RemoveAllObjects-aanroepen, waardoor beperkte plaatsing van meerdere objecten mogelijk is.

Objectplaatsingsquery's bestaan uit drie delen: plaatsingstype met parameters, een lijst met regels en een lijst met beperkingen. Gebruik de volgende API om een query uit te voeren:

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)

Deze functie gebruikt een objectnaam, plaatsingsdefinitie en een lijst met regels en beperkingen. De C#-wrappers bieden bouwhulpfuncties om het bouwen van regels en beperkingen gemakkelijk te maken. De plaatsingsdefinitie bevat het querytype, dat wil zeggen, een van de volgende:

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

Elk van de plaatsingstypen heeft een set parameters die uniek zijn voor het type. De structuur ObjectPlacementDefinition bevat een set statische helperfuncties voor het maken van deze definities. Als u bijvoorbeeld een plaats wilt vinden om een object op de vloer te plaatsen, kunt u de volgende functie gebruiken:

public static ObjectPlacementDefinition Create_OnFloor(Vector3 halfDims)

Naast het plaatsingstype kunt u een set regels en beperkingen bieden. Regels kunnen niet worden geschonden. Mogelijke plaatsingslocaties die voldoen aan het type en de regels, worden vervolgens geoptimaliseerd op de set beperkingen om de optimale plaatsingslocatie te selecteren. Elk van de regels en beperkingen kan worden gemaakt met de opgegeven statische aanmaakfuncties. Hieronder vindt u een voorbeeld van een regel en een beperkingsconstructiefunctie.

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

De onderstaande objectplaatsingsquery is op zoek naar een plaats waar u een kubus van een halve meter aan de rand van een oppervlak kunt plaatsen, weg van andere eerder geplaatste objecten en in het midden van de ruimte.

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

Als dit lukt, wordt een ObjectPlacementResult-structuur met de plaatsingspositie, dimensies en oriëntatie geretourneerd. Bovendien wordt de plaatsing toegevoegd aan de interne lijst met geplaatste objecten van de DLL. Bij volgende plaatsingsquery's wordt rekening gehouden met dit object. Het bestand LevelSolver.cs in het Unity-voorbeeld bevat meer voorbeeldquery's.

De blauwe vakken geven het resultaat weer van drie Place On Floor-query's met regels voor 'geen camerapositie'.

De blauwe vakken geven het resultaat weer van drie Place On Floor-query's met regels voor 'geen camerapositie'.

Tips:

  • Bij het oplossen van de plaatsingslocatie van meerdere objecten die vereist zijn voor een niveau- of toepassingsscenario, moet u eerst de verloederen en grote objecten oplossen om de kans te maximaliseren dat er een ruimte kan worden gevonden.
  • Plaatsingsorder is belangrijk. Als objectplaatsingen niet kunnen worden gevonden, probeert u minder beperkte configuraties. Een set terugvalconfiguraties is essentieel voor de ondersteuning van functionaliteit voor veel configuraties in de ruimte.

RayCasting

Naast de drie primaire query's kan een raycastinginterface worden gebruikt om getagde surface types op te halen en kan een aangepaste watertight playspace mesh worden gekopieerd Nadat de ruimte is gescand en afgerond, worden er intern labels gegenereerd voor oppervlakken zoals de vloer, het plafond en de muren. De functie PlayspaceRaycast neemt een straal en retourneert als de straal met een bekend oppervlak botst, en zo ja, informatie over dat oppervlak in de vorm van een 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 wordt de raycast berekend op de berekende voxel-weergave met kubussen van 8 cm van de playspace. Elke voxel bevat een set surface-elementen met verwerkte topologiegegevens (ook wel surfels genoemd). De surfels in de kruisgeheerde voxel-cel worden vergeleken en de beste overeenkomst die wordt gebruikt om de topologiegegevens op te zoeken. Deze topologiegegevens bevatten de labels die worden geretourneerd in de vorm van de enum SurfaceTypes, evenals de surface area van het door elkaar gespecialiseerde oppervlak.

In het Unity-voorbeeld wordt met de cursor elk frame een straal gecast. Ten eerste tegen de colliders van Unity; ten tweede, ten opzichte van de wereldweergave van de understanding-module; en ten slotte op basis van de elementen van de gebruikersinterface. In deze toepassing krijgt de gebruikersinterface prioriteit, vervolgens het inzichtresultaat en ten slotte de bots van Unity. Het SurfaceType wordt gerapporteerd als tekst naast de cursor.

Raycast-resultaatrapportage snijpunt met de vloer.

Raycast-resultaatrapportage snijpunt met de vloer.

Code ophalen

De opensource-code is beschikbaar in MixedRealityToolkit. Laat het ons weten op de HoloLens ontwikkelaarsforums als u de code in een project gebruikt. We kunnen niet wachten om te zien wat u er mee doet.

Over de auteur

Jeff Evertt, Software Engineering Lead at Microsoft Jeff Evertt is een software engineering-lead die sinds de eerste HoloLens heeft gewerkt, van ontwikkeling tot ontwikkeling. Voordat HoloLens, werkte hij aan de Xbox Kinect en in de gamesbranche op een groot aantal verschillende platforms en games. Jeff is enthousiast over robotica, afbeeldingen en dingen met knipperende lampen die gaan knipperen. Hij vindt het leuk om nieuwe dingen te leren en te werken aan software, hardware, en met name in de ruimte waar de twee elkaar kruisen.

Zie ook