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

Durante la creazione delle prime app per Microsoft HoloLens, eravamo ansiosi di vedere fino a che punto potremmo spingere i limiti del mapping spaziale nel dispositivo. Jeff Evertt, un software engineer di Microsoft Studios, spiega come una nuova tecnologia è stata sviluppata fuori dalla necessità di 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 di ambiente strutturata e di alto livello progettata per semplificare lo sviluppo per applicazioni compatibili con l'ambiente.

Video

Oltre il mapping spaziale

Mentre stavamo lavorando a Fragments e Young Conker, due dei primi giochi per HoloLens, abbiamo scoperto che quando stavamo eseguendo il posizionamento procedurale degli ologrammi nel mondo fisico, avevamo bisogno di un livello superiore di comprensione dell'ambiente dell'utente. Ogni gioco aveva esigenze specifiche di posizionamento: 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 potevano sedersi i caratteri olografici di dimensioni di vita, ad esempio un divano o una sedia. In Young Conker volevamo che Conker e i suoi avversari siano in grado di 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, è possibile analizzare la stanza di un giocatore e identificare superfici come pareti, tavoli, sedie e pavimenti. Ci ha anche dato la possibilità di ottimizzare in base a 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 abbiamo creato una libreria che incapsula questa tecnologia. Microsoft e Asobo hanno ora open source questo codice e lo hanno reso disponibile in MixedRealityToolkit per l'uso nei propri progetti. Tutto il codice sorgente è incluso, consentendo di personalizzarlo in base alle proprie esigenze e condividere i miglioramenti con la community. Il codice per il risolutore C++ è stato sottoposto a wrapping in una DLL UWP ed esposto a Unity con un prefab drop-in contenuto in 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 in cui sedersi i caratteri e una miriade di altre query di comprensione spaziale.

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

  • Spazio di riproduzione a dimensione fissa: l'utente specifica la dimensione massima dello spazio di riproduzione nella chiamata init.
  • Processo di analisi una tantum: 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 al termine dell'analisi.
  • Playspace guidato dall'utente "pittura": durante la fase di analisi, l'utente si sposta e guarda intorno allo spazio di riproduzione, disegnando efficacemente le aree che devono essere incluse. La mesh generata è importante per fornire commenti e suggerimenti degli utenti durante questa fase.
  • Interni di casa o configurazione dell'ufficio: le funzioni di query sono progettate intorno a superfici piane e pareti ad angoli giusti. Si tratta di una limitazione flessibile. Tuttavia, durante la fase di analisi, viene completata un'analisi dell'asse principale per ottimizzare la tassellatura mesh lungo l'asse principale e secondario.

Processo di scansione sala

Quando si carica il modulo di comprensione spaziale, la prima cosa da fare è 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, guardi intorno alla tua stanza e "dipingi le aree che devono essere incluse nella scansione.

La mesh vista durante questa fase è un importante elemento 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 cubi voxel di dimensioni pari a 8 cm. 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.

Mesh di mapping spaziale in mesh bianca e comprensione dello spazio di riproduzione in verde

Mesh di mapping spaziale in mesh bianca e comprensione dello spazio di riproduzione in verde

Il file SpatialUnderstanding.cs incluso gestisce il processo della fase 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 disegno dello spazio di riproduzione, descritto in precedenza.
  • GeneratePlayspace_RequestFinish: chiamata per finalizzare lo spazio di riproduzione. Verranno utilizzate le aree "disegnate" 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, nonché eseguire una query sulla mesh personalizzata per fornire commenti e suggerimenti degli utenti.
  • Import_UnderstandingMesh: durante l'analisi, il comportamento SpatialUnderstandingCustomMesh fornito dal modulo e inserito nel prefab di comprensione eseguirà periodicamente una query sulla mesh personalizzata generata dal processo. Inoltre, questa operazione viene eseguita ancora una volta dopo la finalizzazione dell'analisi.

Il flusso di analisi, guidato dal comportamento SpatialUnderstanding chiama InitScan, quindi UpdateScan ogni fotogramma. Quando la query sulle statistiche segnala una copertura ragionevole, l'utente può chiamare RequestFinish per indicare la fine della fase di analisi. UpdateScan continua a essere chiamato fino a quando non viene restituito il 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 shape: questi usano i risultati delle query di topologia per trovare superfici orizzontali che corrispondono alle forme personalizzate definite.
  • Query di posizionamento degli oggetti: si tratta di 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, è disponibile un'interfaccia raycasting che può essere usata per recuperare i tipi di superficie con tag e è possibile copiare una mesh camera a tenuta chiusa personalizzata.

Query di topologia

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

L'euristica viene usata per determinare pavimento, soffitto e pareti. Ad esempio, la superficie orizzontale più grande e più 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 dal gestore della 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, specifico per il tipo di query. Nell'esempio seguente, l'utente specifica l'altezza minima & larghezza del volume desiderato, l'altezza minima di posizionamento sopra il pavimento e la quantità minima di distanza 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 preallocata di strutture TopologyResult . Il parametro locationCount specifica la lunghezza della matrice passata. Il valore restituito indica il numero di posizioni restituite. Questo numero non è mai maggiore del parametro locationCount passato.

TopologyResult contiene la posizione centrale del volume restituito, la direzione rivolta (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 di Unity, ognuna di queste query è collegata a un pulsante nel pannello dell'interfaccia utente virtuale. L'esempio imposta come hardcoded i parametri per ognuna di queste query su valori ragionevoli. Per altri esempi, vedere SpaceVisualizer.cs nel codice di esempio.

Query shape

All'interno della DLL, l'analizzatore forme (ShapeAnalyzer_W) usa l'analizzatore della topologia per la corrispondenza con forme personalizzate definite dall'utente. L'esempio Unity include un set predefinito di forme 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 dalla superficie del sedile piatto e dalla parte superiore piatta del divano posteriore. La query shape 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" è la 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 viene definita da un set di componenti della forma, ognuno con un set di vincoli di componente e un set di vincoli di forma che elenca le dipendenze tra i componenti. Questo esempio include tre vincoli in una singola definizione di componente e nessun vincolo di forma tra i componenti , poiché è presente un solo componente.

Al contrario, la forma divano ha due componenti della 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 .

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

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

Le caselle blu mostrano il risultato da tre query Place On Floor con regole

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 intersezione con il piano.

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, Responsabile ingegneria software presso 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 su 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