Estudo de caso – Expandindo os recursos de mapeamento espacial de HoloLens

Ao criar nossos primeiros aplicativos para Microsoft HoloLens, estávamos ansiosos para ver o quão longe poderíamos ultrapassar os limites do mapeamento espacial no dispositivo. Jeff Evertt, engenheiro de software do Microsoft Studios, explica como uma nova tecnologia foi desenvolvida com a necessidade de mais controle sobre como os hologramas são colocados no ambiente real de um usuário.

Observação

HoloLens 2 implementa um novo Runtime de Reconhecimento de Cena, que fornece aos desenvolvedores Realidade Misturada uma representação de ambiente estruturada e de alto nível projetada para tornar o desenvolvimento de aplicativos com reconhecimento ambiental intuitivo.

Assista ao vídeo

Além do mapeamento espacial

Enquanto estávamos trabalhando em Fragments e Young Conker, dois dos primeiros jogos para HoloLens, descobrimos que, quando estávamos fazendo o posicionamento processual de hologramas no mundo físico, precisávamos de um nível mais alto de compreensão sobre o ambiente do usuário. Cada jogo tinha suas próprias necessidades de posicionamento específicas: em Fragmentos, por exemplo, queríamos ser capazes de distinguir entre diferentes superfícies, como o chão ou uma tabela, para colocar pistas em locais relevantes. Também queríamos ser capazes de identificar superfícies em que caracteres holográficos em tamanho real poderiam se sentar, como um sofá ou uma cadeira. Em Young Conker, queríamos que Conker e seus oponentes fossem capazes de usar superfícies elevadas na sala de um jogador como plataformas.

O Asobo Studios, nosso parceiro de desenvolvimento para esses jogos, enfrentou esse problema de frente e criou uma tecnologia que estende os recursos de mapeamento espacial de HoloLens. Usando isso, podemos analisar o quarto de um jogador e identificar superfícies como paredes, mesas, cadeiras e pisos. Ele também nos deu a capacidade de otimizar em relação a um conjunto de restrições para determinar o melhor posicionamento para objetos holográficos.

O código de compreensão espacial

Pegamos o código original da Asobo e criamos uma biblioteca que encapsula essa tecnologia. A Microsoft e a Asobo já disponibilizaram esse código de software livre e o disponibilizaram no MixedRealityToolkit para você usar em seus próprios projetos. Todo o código-fonte está incluído, permitindo que você o personalize para suas necessidades e compartilhe suas melhorias com a comunidade. O código do solucionador C++ foi encapsulado em uma DLL UWP e exposto ao Unity com um pré-fabricado suspenso contido no MixedRealityToolkit.

Há muitas consultas úteis incluídas no exemplo do Unity que permitirão que você encontre espaços vazios nas paredes, coloque objetos no teto ou em espaços grandes no chão, identifique locais para os caracteres se sentarem e uma miríade de outras consultas de compreensão espacial.

Embora a solução de mapeamento espacial fornecida pelo HoloLens seja projetada para ser genérica o suficiente para atender às necessidades de toda a gama de espaços problemáticos, o módulo de compreensão espacial foi criado para dar suporte às necessidades de dois jogos específicos. Dessa forma, sua solução é estruturada em torno de um processo específico e conjunto de suposições:

  • Playspace de tamanho fixo: o usuário especifica o tamanho máximo do playspace na chamada inicial.
  • Processo de verificação única: o processo requer uma fase de verificação discreta em que o usuário anda por aí, definindo o playspace. As funções de consulta só funcionarão depois que a verificação for finalizada.
  • "Pintura" do playspace controlado pelo usuário: durante a fase de verificação, o usuário move e examina o playspace, pintando efetivamente as áreas que devem ser incluídas. A malha gerada é importante para fornecer comentários do usuário durante essa fase.
  • Instalação interna da casa ou do escritório: as funções de consulta são projetadas em torno de superfícies planas e paredes em ângulos retos. Essa é uma limitação suave. No entanto, durante a fase de verificação, uma análise de eixo primário é concluída para otimizar a tessellação de malha ao longo do eixo principal e secundário.

Processo de verificação de sala

Quando você carrega o módulo de compreensão espacial, a primeira coisa que você fará é examinar seu espaço, para que todas as superfícies utilizáveis, como o chão, o teto e as paredes, sejam identificadas e rotuladas. Durante o processo de verificação, você olha ao redor da sala e "pinta" as áreas que devem ser incluídas na verificação.

A malha vista durante essa fase é uma parte importante dos comentários visuais que permite que os usuários saibam quais partes da sala estão sendo verificadas. A DLL para o módulo de compreensão espacial armazena internamente o playspace como uma grade de cubos voxel de tamanho de 8cm. Durante a parte inicial da verificação, uma análise de componente primária é concluída para determinar os eixos da sala. Internamente, armazena seu espaço voxel alinhado a esses eixos. Uma malha é gerada aproximadamente a cada segundo extraindo a isosurface do volume voxel.

Spatial mapping mesh in white and understanding playspace mesh in green

Malha de mapeamento espacial em branco e no entendimento da malha do playspace em verde

O arquivo SpatialUnderstanding.cs incluído gerencia o processo de fase de verificação. Ele chama as seguintes funções:

  • SpatialUnderstanding_Init: chamado uma vez no início.
  • GeneratePlayspace_InitScan: indica que a fase de verificação deve começar.
  • GeneratePlayspace_UpdateScan_DynamicScan: chamou cada quadro para atualizar o processo de verificação. A posição e a orientação da câmera são passadas e usadas para o processo de pintura do playspace, descrito acima.
  • GeneratePlayspace_RequestFinish: chamado para finalizar o playspace. Isso usará as áreas "pintadas" durante a fase de verificação para definir e bloquear o playspace. O aplicativo pode consultar estatísticas durante a fase de verificação, bem como consultar a malha personalizada para fornecer comentários do usuário.
  • Import_UnderstandingMesh: durante a verificação, o comportamento SpatialUnderstandingCustomMesh fornecido pelo módulo e colocado no prefab de compreensão consultará periodicamente a malha personalizada gerada pelo processo. Além disso, isso é feito mais uma vez depois que a verificação é finalizada.

O fluxo de verificação, controlado pelo comportamento SpatialUnderstanding , chama InitScan e, em seguida, UpdateScan cada quadro. Quando a consulta de estatísticas relata uma cobertura razoável, o usuário pode usar o airtap para chamar RequestFinish para indicar o fim da fase de verificação. UpdateScan continua a ser chamado até que seu valor de retorno indique que a DLL concluiu o processamento.

As consultas

Depois que a verificação for concluída, você poderá acessar três tipos diferentes de consultas na interface:

  • Consultas de topologia: são consultas rápidas baseadas na topologia da sala digitalizada.
  • Consultas de forma: elas utilizam os resultados de suas consultas de topologia para encontrar superfícies horizontais que correspondem às formas personalizadas que você define.
  • Consultas de posicionamento de objeto: são consultas mais complexas que encontram o local mais adequado com base em um conjunto de regras e restrições para o objeto.

Além das três consultas primárias, há uma interface de raycasting que pode ser usada para recuperar tipos de superfície marcados e uma malha de sala impermeável personalizada pode ser copiada.

Consultas de topologia

Dentro da DLL, o gerenciador de topologia manipula a rotulagem do ambiente. Conforme mencionado acima, grande parte dos dados é armazenada dentro de surfels, que estão contidos em um volume voxel. Além disso, a estrutura PlaySpaceInfos é usada para armazenar informações sobre o playspace, incluindo o alinhamento do mundo (mais detalhes sobre isso abaixo), piso e altura do teto.

A heurística é usada para determinar piso, teto e paredes. Por exemplo, a maior e mais baixa superfície horizontal com mais de 1 m2 de superfície é considerada o piso. Observe que o caminho da câmera durante o processo de verificação também é usado nesse processo.

Um subconjunto das consultas expostas pelo gerenciador de Topologia é exposto por meio da DLL. As consultas de topologia expostas são as seguintes:

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

Cada uma das consultas tem um conjunto de parâmetros, específicos para o tipo de consulta. No exemplo a seguir, o usuário especifica a largura de altura & mínima do volume desejado, a altura mínima de posicionamento acima do piso e a quantidade mínima de espaço livre na frente do volume. Todas as medidas estão em metros.

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)

Cada uma dessas consultas usa uma matriz pré-alocada de estruturas TopologyResult . O parâmetro locationCount especifica o comprimento da matriz passada. O valor retornado relata o número de locais retornados. Esse número nunca é maior do que o parâmetro locationCount passado.

O TopologyResult contém a posição central do volume retornado, a direção de frente (ou seja, normal) e as dimensões do espaço encontrado.

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

Observe que, no exemplo do Unity, cada uma dessas consultas está vinculada a um botão no painel de interface do usuário virtual. O exemplo de código rígido codifica os parâmetros de cada uma dessas consultas para valores razoáveis. Consulte SpaceVisualizer.cs no código de exemplo para obter mais exemplos.

Consultas de forma

Dentro da DLL, o analisador de formas (ShapeAnalyzer_W) usa o analisador de topologia para corresponder às formas personalizadas definidas pelo usuário. O exemplo do Unity tem um conjunto predefinido de formas que são mostradas no menu de consulta, na guia forma.

Observe que a análise de forma funciona apenas em superfícies horizontais. Um sofá, por exemplo, é definido pela superfície do assento plano e pela parte superior plana do sofá para trás. A consulta de forma procura duas superfícies de um tamanho específico, altura e intervalo de aspectos, com as duas superfícies alinhadas e conectadas. Usando a terminologia de APIs, o assento do sofá e a parte superior da parte de trás do sofá são componentes de forma e os requisitos de alinhamento são restrições de componente de forma.

Uma consulta de exemplo definida no exemplo do Unity (ShapeDefinition.cs) para objetos "sittable" é a seguinte:

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

Cada consulta de forma é definida por um conjunto de componentes de forma, cada um com um conjunto de restrições de componente e um conjunto de restrições de forma que lista dependências entre os componentes. Este exemplo inclui três restrições em uma única definição de componente e nenhuma restrição de forma entre componentes (pois há apenas um componente).

Em contraste, a forma do sofá tem dois componentes de forma e quatro restrições de forma. Observe que os componentes são identificados pelo índice na lista de componentes do usuário (0 e 1 neste exemplo).

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

As funções wrapper são fornecidas no módulo Unity para facilitar a criação de definições de forma personalizadas. A lista completa de restrições de componente e forma pode ser encontrada em SpatialUnderstandingDll.cs dentro das estruturasShapeComponentConstraint e ShapeConstraint .

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

O retângulo azul realça os resultados da consulta de forma da cadeira.

Solucionador de posicionamento de objeto

As consultas de posicionamento de objeto podem ser usadas para identificar locais ideais na sala física para colocar seus objetos. O solucionador encontrará o local mais adequado, considerando as regras e restrições do objeto. Além disso, as consultas de objeto persistem até que o objeto seja removido com Solver_RemoveObject ou Solver_RemoveAllObjects chamadas, permitindo o posicionamento restrito de vários objetos.

As consultas de posicionamento de objeto consistem em três partes: tipo de posicionamento com parâmetros, uma lista de regras e uma lista de restrições. Para executar uma consulta, use a seguinte API:

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)

Essa função usa um nome de objeto, uma definição de posicionamento e uma lista de regras e restrições. Os wrappers C# fornecem funções auxiliares de construção para facilitar a construção de regras e restrições. A definição de posicionamento contém o tipo de consulta – ou seja, um dos seguintes:

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

Cada um dos tipos de posicionamento tem um conjunto de parâmetros exclusivos para o tipo. A estrutura ObjectPlacementDefinition contém um conjunto de funções auxiliares estáticas para criar essas definições. Por exemplo, para encontrar um lugar para colocar um objeto no chão, você pode usar a seguinte função:

public static ObjectPlacementDefinition Create_OnFloor(Vector3 halfDims)

Além do tipo de posicionamento, você pode fornecer um conjunto de regras e restrições. As regras não podem ser violadas. Os possíveis locais de posicionamento que atendem ao tipo e às regras são otimizados em relação ao conjunto de restrições para selecionar o local de posicionamento ideal. Cada uma das regras e restrições pode ser criada pelas funções de criação estática fornecidas. Uma função de construção de restrição e regra de exemplo é fornecida abaixo.

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

A consulta de posicionamento do objeto abaixo está procurando um lugar para colocar um cubo de meio metro na borda de uma superfície, longe de outros objetos anteriormente colocados e perto do centro da sala.

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 tiver êxito, uma estrutura ObjectPlacementResult que contém a posição de posicionamento, dimensões e orientação será retornada. Além disso, o posicionamento é adicionado à lista interna de objetos colocados da DLL. Consultas de posicionamento subsequentes levarão esse objeto em conta. O arquivo LevelSolver.cs no exemplo do Unity contém mais consultas de exemplo.

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

As caixas azuis mostram o resultado de três consultas Place On Floor com regras "longe da posição da câmera".

Dicas:

  • Ao resolver o local de posicionamento de vários objetos necessários para um cenário de nível ou aplicativo, primeiro resolva objetos indispensáveis e grandes para maximizar a probabilidade de que um espaço possa ser encontrado.
  • A ordem de posicionamento é importante. Se não for possível encontrar posicionamentos de objeto, tente configurações menos restritas. Ter um conjunto de configurações de fallback é essencial para dar suporte à funcionalidade em várias configurações de sala.

Conversão de raios

Além das três consultas primárias, uma interface de conversão de raios pode ser usada para recuperar tipos de superfície marcados e uma malha de playspace impermeável personalizada pode ser copiada após a sala ter sido verificada e finalizada, os rótulos são gerados internamente para superfícies como o chão, o teto e as paredes. A função PlayspaceRaycast usa um raio e retorna se o raio colidir com uma superfície conhecida e, se for o caso, informações sobre essa superfície na forma de um 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, o raycast é calculado em relação à representação computada de voxel em cubo de 8cm do playspace. Cada voxel contém um conjunto de elementos de superfície com dados de topologia processados (também conhecidos como surfels). Os surfels contidos na célula voxel interseccionada são comparados e a melhor correspondência usada para pesquisar as informações de topologia. Esses dados de topologia contêm a rotulagem retornada na forma da enumeração SurfaceTypes , bem como a área de superfície da superfície interseccionada.

No exemplo do Unity, o cursor converte um raio em cada quadro. Primeiro, contra colisores do Unity; segundo, em relação à representação mundial do módulo de compreensão; e, por fim, em relação aos elementos da interface do usuário. Neste aplicativo, a interface do usuário obtém prioridade, depois o resultado da compreensão e, por fim, os colisores do Unity. O SurfaceType é relatado como texto ao lado do cursor.

Raycast result reporting intersection with the floor.

Raycast result reporting intersecção com o chão.

Obter o código

O código de software livre está disponível no MixedRealityToolkit. Informe-nos nos HoloLens Fóruns do Desenvolvedor se você usar o código em um projeto. Mal podemos esperar para ver o que você faz com ele!

Sobre o autor

Jeff Evertt, Software Engineering Lead at Microsoft Jeff Evertt é um líder de engenharia de software que trabalhou em HoloLens desde os primeiros dias, desde a incubação até o desenvolvimento de experiências. Antes de HoloLens, trabalhou no Xbox Kinect e na indústria de jogos em uma ampla variedade de plataformas e jogos. Jeff é apaixonado por robótica, gráficos e coisas com luzes chamativas que vão bip. Ele gosta de aprender coisas novas e trabalhar em software, hardware e, particularmente, no espaço onde os dois se cruzam.

Veja também