Case study - Espansione delle funzionalità di mapping spaziale di HoloLens

Quando creiamo le prime app per Microsoft HoloLens, eravamo ansiosi di vedere fino a quando potremmo spingere i limiti del mapping spaziale nel dispositivo. Jeff Evertt, un tecnico software di Microsoft Studios, spiega come è stata sviluppata una nuova tecnologia per un maggiore controllo sul modo in cui gli ologrammi vengono inseriti nell'ambiente reale di un utente.

Nota

HoloLens 2 implementa un nuovo runtime di Scene Understanding, che fornisce agli sviluppatori Realtà mista una rappresentazione dell'ambiente strutturata e di alto livello progettata per rendere intuitivo lo sviluppo di applicazioni con riconoscimento ambientale.

Video

Oltre il mapping spaziale

Mentre stavamo lavorando su Frammenti e Young Conker, due dei primi giochi per HoloLens, abbiamo scoperto che quando stavamo eseguendo il posizionamento procedurale degli ologrammi nel mondo fisico, abbiamo bisogno di un livello superiore di comprensione sull'ambiente dell'utente. Ogni gioco aveva le proprie esigenze di posizionamento specifiche: in Frammenti, ad esempio, volevamo essere in grado di distinguere tra superfici diverse, ad esempio il pavimento o un tavolo, per inserire indizi in posizioni pertinenti. Volevamo anche essere in grado di identificare le superfici su cui i caratteri olografici delle dimensioni della vita potevano sedersi, ad esempio un divano o una sedia. In Young Conker, volevamo che Conker e i suoi avversari possano usare superfici alzate nella stanza di un giocatore come piattaforme.

Asobo Studios, il nostro partner di sviluppo per questi giochi, ha affrontato questo problema e ha creato una tecnologia che estende le funzionalità di mapping spaziale di HoloLens. Usando questo, potremmo analizzare la stanza di un giocatore e identificare superfici come pareti, tavoli, sedie e pavimenti. Ci ha anche dato la possibilità di ottimizzare su un set di vincoli per determinare la posizione migliore per gli oggetti olografici.

Codice di comprensione spaziale

Abbiamo preso il codice originale di Asobo e creato una libreria che incapsula questa tecnologia. Microsoft e Asobo hanno ora aperto questo codice e reso disponibile in MixedRealityToolkit per usarlo nei propri progetti. Tutto il codice sorgente è incluso, consentendo di personalizzarlo alle proprie esigenze e condividere i miglioramenti con la community. Il codice per il risolutore C++ è stato eseguito il wrapping in una DLL UWP ed è stato esposto a Unity con un prefab a discesa contenuto all'interno di MixedRealityToolkit.

Nell'esempio unity sono incluse molte query utili che consentono di trovare spazi vuoti su pareti, posizionare oggetti sul soffitto o su grandi spazi sul pavimento, identificare i luoghi per i caratteri da sedersi e una miriade di altre query di comprensione spaziale.

Mentre la soluzione di mapping spaziale fornita da HoloLens è progettata per essere abbastanza generica per soddisfare le esigenze dell'intera gamma di spazi di problemi, il modulo di comprensione spaziale è stato creato per supportare le esigenze di due giochi specifici. Di conseguenza, la sua soluzione è strutturata intorno a un processo specifico e a un set di presupposti:

  • Playspace di dimensioni fisse: l'utente specifica la dimensione massima dello spazio di riproduzione nella chiamata init.
  • Processo di analisi monouso: il processo richiede una fase di analisi discreta in cui l'utente cammina, definendo lo spazio di riproduzione. Le funzioni di query non funzioneranno fino a quando non è stata finalizzata l'analisi.
  • Playspace guidato dall'utente "pittura": durante la fase di analisi, l'utente si sposta e guarda intorno allo spazio di gioco, disegnare in modo efficace le aree che devono essere incluse. La mesh generata è importante per fornire commenti e suggerimenti degli utenti durante questa fase.
  • Installazione all'interno di casa o ufficio: le funzioni di query sono progettate intorno alle superfici flat e alle pareti ad angolo destro. Si tratta di una limitazione temporanea. Tuttavia, durante la fase di analisi, viene completata un'analisi dell'asse primario per ottimizzare la tessellazione mesh lungo l'asse principale e secondario.

Processo di analisi della stanza

Quando si carica il modulo di comprensione spaziale, la prima cosa che si farà è analizzare lo spazio, quindi tutte le superfici utilizzabili, ad esempio il pavimento, il soffitto e le pareti, vengono identificate ed etichettate. Durante il processo di scansione, si guarda intorno alla stanza e "disegnare" le aree che devono essere incluse nell'analisi.

La mesh vista durante questa fase è un importante pezzo di feedback visivo che consente agli utenti di sapere quali parti della stanza vengono analizzate. La DLL per il modulo di comprensione spaziale archivia internamente lo spazio di riproduzione come griglia di 8 cm di cubo voxel di dimensioni. Durante la parte iniziale dell'analisi, viene completata un'analisi dei componenti primaria per determinare gli assi della stanza. Internamente, archivia lo spazio voxel allineato a questi assi. Una mesh viene generata approssimativamente ogni secondo estraendo l'isosurface dal volume voxel.

Spatial mapping mesh in white and understanding playspace mesh in green

Mesh di mapping spaziale in mesh di spazi vuoti e comprensione in verde

Il file SpatialUnderstanding.cs incluso gestisce il processo di analisi. Chiama le funzioni seguenti:

  • SpatialUnderstanding_Init: chiamata una sola volta all'inizio.
  • GeneratePlayspace_InitScan: indica che la fase di analisi deve iniziare.
  • GeneratePlayspace_UpdateScan_DynamicScan: chiamato ogni frame per aggiornare il processo di analisi. La posizione e l'orientamento della fotocamera vengono passati e vengono usati per il processo di pittura dello spazio di riproduzione, descritto sopra.
  • GeneratePlayspace_RequestFinish: chiamata per finalizzare lo spazio di gioco. Questa operazione userà le aree "dipinte" durante la fase di analisi per definire e bloccare lo spazio di riproduzione. L'applicazione può eseguire query sulle statistiche durante la fase di analisi e eseguire query sulla mesh personalizzata per fornire commenti e suggerimenti utente.
  • Import_UnderstandingMesh: durante l'analisi, il comportamento SpatialUnderstandingCustomMesh fornito dal modulo e inserito nel prefab di comprensione eseguirà periodicamente query sulla mesh personalizzata generata dal processo. Inoltre, questa operazione viene eseguita una volta di più dopo la finalizzazione dell'analisi.

Il flusso di analisi, basato sul comportamento SpatialUnderstanding chiama InitScan, quindi UpdateScan ogni frame. Quando la query statistiche segnala una copertura ragionevole, l'utente può chiamare RequestFinish per indicare la fine della fase di analisi. UpdateScan continua a essere chiamato finché non viene restituito valore indica che la DLL ha completato l'elaborazione.

Query

Al termine dell'analisi, sarà possibile accedere a tre diversi tipi di query nell'interfaccia:

  • Query di topologia: si tratta di query veloci basate sulla topologia della stanza analizzata.
  • Query di forma: questi usano i risultati delle query di topologia per trovare superfici orizzontali che corrispondono a forme personalizzate definite.
  • Query di posizionamento degli oggetti: queste sono query più complesse che trovano la posizione più adatta in base a un set di regole e vincoli per l'oggetto.

Oltre alle tre query primarie, è possibile copiare un'interfaccia di raycasting che può essere usata per recuperare i tipi di superficie contrassegnati e una mesh di sala a chiusura acqua personalizzata.

Query di topologia

All'interno della DLL, gestione topologia gestisce l'etichettatura dell'ambiente. Come accennato in precedenza, gran parte dei dati viene archiviata all'interno di surfels, che sono contenuti all'interno di un volume voxel. Inoltre, la struttura PlaySpaceInfos viene usata per archiviare informazioni sullo spazio playspace, tra cui l'allineamento del mondo (altri dettagli su questo sotto), il pavimento e l'altezza del soffitto.

Le euristiche vengono usate per determinare il pavimento, il soffitto e le pareti. Ad esempio, la superficie orizzontale più grande e bassa con una superficie superiore a 1 m2 viene considerata il pavimento. Si noti che il percorso della fotocamera durante il processo di analisi viene usato anche in questo processo.

Un subset delle query esposte dalla gestione topologia viene esposto tramite la DLL. Le query di topologia esposte sono le seguenti:

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

Ognuna delle query ha un set di parametri, specifici del tipo di query. Nell'esempio seguente, l'utente specifica la larghezza minima dell'altezza del volume desiderato, l'altezza & minima di posizionamento sopra il pavimento e la quantità minima di spaziatura davanti al volume. Tutte le misurazioni sono in metri.

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)

Ognuna di queste query accetta una matrice pre-allocata di strutture TopologyResult . Il parametro locationCount specifica la lunghezza della matrice passata. Il valore restituito segnala il numero di posizioni restituite. Questo numero non è mai maggiore del parametro locationCount passato.

La topologiaResult contiene la posizione centrale del volume restituito, la direzione di fronte (ad esempio normale) e le dimensioni dello spazio trovato.

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

Si noti che nell'esempio Unity, ognuna di queste query è collegata a un pulsante nel pannello dell'interfaccia utente virtuale. Il codice rigido di esempio codici i parametri per ognuna di queste query ai valori ragionevoli. Per altri esempi, vedere SpaceVisualizer.cs nel codice di esempio.

Query di forma

All'interno della DLL, l'analizzatore forma (ShapeAnalyzer_W) usa l'analizzatore della topologia per corrispondere a forme personalizzate definite dall'utente. L'esempio Unity include un set di forme pre-definito che vengono visualizzate nel menu di query, nella scheda forma.

Si noti che l'analisi della forma funziona solo su superfici orizzontali. Un divano, ad esempio, è definito dall'area di seduta piatta e dalla parte superiore piatta del divano indietro. La query di forma cerca due superfici di una dimensione, un'altezza e un intervallo di aspetti specifici, con le due superfici allineate e connesse. Usando la terminologia delle API, il sedile del divano e la parte superiore del divano sono componenti della forma e i requisiti di allineamento sono vincoli dei componenti della forma.

Una query di esempio definita nell'esempio unity (ShapeDefinition.cs), per gli oggetti "sittable" è il seguente:

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

Ogni query di forma è definita da un set di componenti di forma, ognuno con un set di vincoli di componente e un set di vincoli di forma che elenca le dipendenze tra i componenti. In questo esempio sono inclusi tre vincoli in una singola definizione del componente e non sono presenti vincoli di forma tra i componenti, poiché è presente un solo componente.

Al contrario, la forma del divano ha due componenti di forma e quattro vincoli di forma. Si noti che i componenti sono identificati dall'indice nell'elenco dei componenti dell'utente (0 e 1 in questo esempio).

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

Le funzioni wrapper vengono fornite nel modulo Unity per semplificare la creazione di definizioni di forme personalizzate. L'elenco completo dei vincoli di componente e forma è disponibile in SpatialUnderstandingDll.cs all'interno delle strutture ShapeComponentConstraint e ShapeConstraint .

The blue rectangle highlights the results of the chair shape query.

Il rettangolo blu evidenzia i risultati della query della forma della sedia.

Risolutore di posizionamento degli oggetti

Le query di posizionamento degli oggetti possono essere usate per identificare le posizioni ideali nella sala fisica per posizionare gli oggetti. Il risolutore troverà la posizione più adatta in base alle regole e ai vincoli dell'oggetto. Inoltre, le query sugli oggetti vengono mantenute fino a quando l'oggetto non viene rimosso con chiamate Solver_RemoveObject o Solver_RemoveAllObjects , consentendo il posizionamento a più oggetti vincolati.

Le query di posizionamento degli oggetti sono costituite da tre parti: tipo di posizionamento con parametri, un elenco di regole e un elenco di vincoli. Per eseguire una query, usare l'API seguente:

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)

Questa funzione accetta un nome dell'oggetto, una definizione di posizionamento e un elenco di regole e vincoli. I wrapper C# forniscono funzioni helper di costruzione per semplificare la costruzione di regole e vincoli. La definizione di posizionamento contiene il tipo di query, ovvero uno dei seguenti:

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

Ognuno dei tipi di posizionamento ha un set di parametri univoci per il tipo. La struttura ObjectPlacementDefinition contiene un set di funzioni helper statiche per la creazione di queste definizioni. Ad esempio, per trovare un posto per inserire un oggetto sul pavimento, è possibile usare la funzione seguente:

public static ObjectPlacementDefinition Create_OnFloor(Vector3 halfDims)

Oltre al tipo di posizionamento, è possibile fornire un set di regole e vincoli. Non è possibile violare le regole. Le possibili posizioni di posizionamento che soddisfano il tipo e le regole vengono quindi ottimizzate rispetto al set di vincoli per selezionare la posizione di posizionamento ottimale. Ognuna delle regole e dei vincoli può essere creata dalle funzioni di creazione statiche fornite. Di seguito è riportata una regola di esempio e una funzione di costruzione dei vincoli.

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

La query di posizionamento dell'oggetto seguente cerca un posto per inserire un cubo di mezzo metro sul bordo di una superficie, lontano da altri oggetti posto in precedenza e vicino al centro della stanza.

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

Se ha esito positivo, viene restituita una struttura ObjectPlacementResult contenente la posizione di posizionamento, le dimensioni e l'orientamento. Inoltre, il posizionamento viene aggiunto all'elenco interno della DLL degli oggetti inseriti. Le query di posizionamento successive tengono conto di questo oggetto. Il file LevelSolver.cs nell'esempio Unity contiene più query di esempio.

The blue boxes show the result from three Place On Floor queries with

Le caselle blu mostrano il risultato da tre query Place On Floor con regole "lontano dalla posizione della fotocamera".

Suggerimenti:

  • Quando si risolve la posizione di posizionamento di più oggetti necessari per uno scenario di livello o applicazione, risolvere prima oggetti indispensabile e di grandi dimensioni per massimizzare la probabilità che sia possibile trovare uno spazio.
  • L'ordine di posizionamento è importante. Se non è possibile trovare i posizionamenti degli oggetti, provare configurazioni meno vincolate. La presenza di un set di configurazioni di fallback è fondamentale per supportare le funzionalità in molte configurazioni della sala.

Cast ray

Oltre alle tre query primarie, è possibile usare un'interfaccia di cast a raggi per recuperare i tipi di superficie contrassegnati e una mesh di playspace impermeabile personalizzata può essere copiata dopo che la stanza è stata analizzata e finalizzata, le etichette vengono generate internamente per superfici come il pavimento, il soffitto e le pareti. La funzione PlayspaceRaycast accetta un raggio e restituisce se il raggio si scontra con una superficie nota e, in tal caso, informazioni sulla superficie sotto forma di 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;
     };

Internamente, il raycast viene calcolato in base alla rappresentazione voxel cubo calcolata 8cm dello spazio playspace. Ogni voxel contiene un set di elementi di superficie con dati di topologia elaborati (noti anche come surfels). I surfels contenuti all'interno della cella voxel intersecata vengono confrontati e la corrispondenza migliore utilizzata per cercare le informazioni sulla topologia. Questi dati di topologia contengono l'etichettatura restituita sotto forma di enumerazione SurfaceTypes , nonché l'area di superficie della superficie intersecata.

Nell'esempio unity il cursore esegue il cast di un raggio ogni fotogramma. Prima di tutto, contro i collideri di Unity; in secondo luogo, rispetto alla rappresentazione mondiale del modulo di comprensione; e infine, rispetto agli elementi dell'interfaccia utente. In questa applicazione l'interfaccia utente ottiene la priorità, quindi il risultato di comprensione e infine i collider di Unity. SurfaceType viene segnalato come testo accanto al cursore.

Raycast result reporting intersection with the floor.

Raycast result reporting intersezione con il piano.

Ottenere il codice

Il codice open source è disponibile in MixedRealityToolkit. Inviare informazioni sui forum per sviluppatori HoloLens se si usa il codice in un progetto. Non possiamo aspettare di vedere quello che fai con esso!

Informazioni sull'autore

Jeff Evertt, Software Engineering Lead at Microsoft Jeff Evertt è un responsabile tecnico del software che ha lavorato su HoloLens fin dai primi giorni, dall'incubazione all'esperienza di sviluppo. Prima di HoloLens, ha lavorato sulla Xbox Kinect e nel settore dei giochi su una vasta gamma di piattaforme e giochi. Jeff è appassionato di robotica, grafica e cose con luci lampeggianti che vannoep. Si gode di imparare nuove cose e lavorare su software, hardware e in particolare nello spazio in cui i due intersecano.

Vedere anche