Foco na cabeça e entrada com foco nos olhos no DirectX

Observação

Este artigo está relacionado às APIs nativas herdadas do WinRT. Para novos projetos de aplicativo nativo, é recomendável usar a API OpenXR.

Em Windows Mixed Reality, a entrada de foco com os olhos e a cabeça é usada para determinar o que o usuário está olhando. Você pode usar os dados para conduzir modelos de entrada primários, como foco na cabeça e confirmação, e fornecer contexto para diferentes tipos de interação. Há dois tipos de vetores de foco disponíveis por meio da API: foco na cabeça e foco com os olhos. Ambos são fornecidos como um raio tridimensional com uma origem e direção. Os aplicativos podem então raycast em suas cenas, ou no mundo real, e determinar o que o usuário está direcionando.

O foco na cabeça representa a direção em que a cabeça do usuário está apontada. Pense no foco de cabeça como a posição e a direção para a frente do próprio dispositivo, com a posição como o ponto central entre as duas telas. O foco na cabeça está disponível em todos os dispositivos Realidade Misturada.

O olhar representa a direção que os olhos do usuário estão olhando. A origem está localizada entre os olhos do usuário. Ele está disponível em dispositivos Realidade Misturada que incluem um sistema de acompanhamento ocular.

Os raios de foco de cabeça e de olho são acessíveis por meio da API SpatialPointerPose . Chame SpatialPointerPose::TryGetAtTimestamp para receber um novo objeto SpatialPointerPose no carimbo de data/hora e no sistema de coordenadas especificados. Este SpatialPointerPose contém uma origem e uma direção de foco de cabeça. Ele também contém uma origem e direção de foco com os olhos se o acompanhamento ocular estiver disponível.

Suporte a dispositivos

Recurso HoloLens (1ª geração) HoloLens 2 Headsets imersivos
Foco com a cabeça ✔️ ✔️ ✔️
Olhar para os olhos ✔️

Usando o foco na cabeça

Para acessar o foco principal, comece chamando SpatialPointerPose::TryGetAtTimestamp para receber um novo objeto SpatialPointerPose. Passe os parâmetros a seguir.

  • Um SpatialCoordinateSystem que representa o sistema de coordenadas desejado para o foco principal. Isso é representado pela variável coordinateSystem no código a seguir. Para obter mais informações, visite nosso guia de desenvolvedor de sistemas de coordenadas .
  • Um carimbo de data/hora que representa a hora exata da pose de cabeça solicitada. Normalmente, você usará um carimbo de data/hora que corresponde à hora em que o quadro atual será exibido. Você pode obter esse carimbo de data/hora de exibição previsto de um objeto HolographicFramePrediction , que pode ser acessado por meio do HolographicFrame atual. Esse objeto HolographicFramePrediction é representado pela variável de previsão no código a seguir.

Depois que você tiver um SpatialPointerPose válido, a posição da cabeça e a direção para a frente estarão acessíveis como propriedades. O código a seguir mostra como acessá-los.

using namespace winrt::Windows::UI::Input::Spatial;
using namespace winrt::Windows::Foundation::Numerics;

SpatialPointerPose pointerPose = SpatialPointerPose::TryGetAtTimestamp(coordinateSystem, prediction.Timestamp());
if (pointerPose)
{
	float3 headPosition = pointerPose.Head().Position();
	float3 headForwardDirection = pointerPose.Head().ForwardDirection();

	// Do something with the head-gaze
}

Usando foco com os olhos

Para que os usuários usem a entrada com foco nos olhos, cada usuário precisa passar por uma calibragem do usuário de rastreamento ocular na primeira vez que usar o dispositivo. A API de foco com os olhos é semelhante ao foco de cabeça. Ele usa a mesma API SpatialPointerPose , que fornece uma origem e direção de raios que você pode raycast em sua cena. A única diferença é que você precisa habilitar explicitamente o acompanhamento ocular antes de usá-lo:

  1. Solicite permissão do usuário para usar o acompanhamento ocular em seu aplicativo.
  2. Habilite a funcionalidade "Entrada de Foco" no manifesto do pacote.

Solicitando acesso à entrada de foco com os olhos

Quando o aplicativo estiver sendo iniciado, chame EyesPose::RequestAccessAsync para solicitar acesso ao acompanhamento ocular. O sistema solicitará ao usuário, se necessário, e retornará GazeInputAccessStatus::Allowed depois que o acesso for concedido. Essa é uma chamada assíncrona, portanto, requer um pouco de gerenciamento extra. O exemplo a seguir cria um std::thread desanexado para aguardar o resultado, que ele armazena em uma variável de membro chamada m_isEyeTrackingEnabled.

using namespace winrt::Windows::Perception::People;
using namespace winrt::Windows::UI::Input;

std::thread requestAccessThread([this]()
{
	auto status = EyesPose::RequestAccessAsync().get();

	if (status == GazeInputAccessStatus::Allowed)
		m_isEyeTrackingEnabled = true;
	else
		m_isEyeTrackingEnabled = false;
});

requestAccessThread.detach();

Iniciar um thread desanexado é apenas uma opção para lidar com chamadas assíncronas. Você também pode usar a nova funcionalidade de co_await com suporte do C++/WinRT. Aqui está outro exemplo para solicitar a permissão do usuário:

  • EyesPose::IsSupported() permite que o aplicativo dispare a caixa de diálogo de permissão somente se houver um rastreador ocular.
  • GazeInputAccessStatus m_gazeInputAccessStatus; Isso é para evitar a exibição do prompt de permissão repetidamente.
GazeInputAccessStatus m_gazeInputAccessStatus; // This is to prevent popping up the permission prompt over and over again.

// This will trigger to show the permission prompt to the user.
// Ask for access if there is a corresponding device and registry flag did not disable it.
if (Windows::Perception::People::EyesPose::IsSupported() &&
   (m_gazeInputAccessStatus == GazeInputAccessStatus::Unspecified))
{ 
	Concurrency::create_task(Windows::Perception::People::EyesPose::RequestAccessAsync()).then(
	[this](GazeInputAccessStatus status)
	{
  		// GazeInputAccessStatus::{Allowed, DeniedBySystem, DeniedByUser, Unspecified}
    		m_gazeInputAccessStatus = status;
		
		// Let's be sure to not ask again.
		if(status == GazeInputAccessStatus::Unspecified)
		{
      			m_gazeInputAccessStatus = GazeInputAccessStatus::DeniedBySystem;	
		}
	});
}

Declarando a funcionalidade entrada de foco

Clique duas vezes no arquivo appxmanifest em Gerenciador de Soluções. Em seguida, navegue até a seção Funcionalidades e marcar a funcionalidade Entrada de Foco.

Recurso de entrada de foco

Isso adiciona as seguintes linhas à seção Pacote no arquivo appxmanifest:

  <Capabilities>
    <DeviceCapability Name="gazeInput" />
  </Capabilities>

Obtendo o raio do olhar

Depois de receber acesso ao ET, você estará livre para pegar o raio do olhar a cada quadro. Assim como acontece com o foco na cabeça, obtenha o SpatialPointerPose chamando SpatialPointerPose::TryGetAtTimestamp com um carimbo de data/hora desejado e um sistema de coordenadas. O SpatialPointerPose contém um objeto EyesPose por meio da propriedade Eyes . Isso não será nulo somente se o acompanhamento ocular estiver habilitado. A partir daí, você poderá marcar se o usuário no dispositivo tiver uma calibragem de acompanhamento ocular chamando EyesPose::IsCalibrationValid. Em seguida, use a propriedade Gaze para obter o SpatialRay que contém a posição e a direção do olhar. Às vezes, a propriedade Gaze pode ser nula, portanto, certifique-se de marcar para isso. Isso pode acontecer se um usuário calibrado fechar temporariamente os olhos.

O código a seguir mostra como acessar o raio de foco com os olhos.

using namespace winrt::Windows::UI::Input::Spatial;
using namespace winrt::Windows::Foundation::Numerics;

SpatialPointerPose pointerPose = SpatialPointerPose::TryGetAtTimestamp(coordinateSystem, prediction.Timestamp());
if (pointerPose)
{
	if (pointerPose.Eyes() && pointerPose.Eyes().IsCalibrationValid())
	{
		if (pointerPose.Eyes().Gaze())
		{
			auto spatialRay = pointerPose.Eyes().Gaze().Value();
			float3 eyeGazeOrigin = spatialRay.Origin;
			float3 eyeGazeDirection = spatialRay.Direction;
			
			// Do something with the eye-gaze
		}
	}
}

Fallback quando o acompanhamento ocular não está disponível

Conforme mencionado em nossos documentos de design de acompanhamento ocular, designers e desenvolvedores devem estar cientes de instâncias em que os dados de rastreamento ocular podem não estar disponíveis.

Há vários motivos para os dados estarem indisponíveis:

  • Um usuário que não está sendo calibrado
  • Um usuário negou ao aplicativo acesso aos dados de acompanhamento ocular
  • Interferências temporárias, como manchas no visor do HoloLens ou cabelos ocluindo os olhos do usuário.

Embora algumas das APIs já tenham sido mencionadas neste documento, no seguinte, fornecemos um resumo de como detectar que o acompanhamento ocular está disponível como uma referência rápida:

Talvez você também queira marcar que seus dados de acompanhamento ocular não estejam obsoletos adicionando um tempo limite entre as atualizações de dados de acompanhamento ocular recebidas e, caso contrário, faça fallback para o foco principal, conforme discutido abaixo. Visite nossas considerações de design de fallback para obter mais informações.


Correlacionando o foco com outras entradas

Às vezes, você pode achar que precisa de um SpatialPointerPose que corresponda a um evento no passado. Por exemplo, se o usuário fizer um Air Tap, talvez seu aplicativo queira saber o que ele estava olhando. Para essa finalidade, simplesmente usar SpatialPointerPose::TryGetAtTimestamp com o tempo de quadro previsto seria impreciso devido à latência entre o processamento de entrada do sistema e o tempo de exibição. Além disso, se estiver usando o olhar para direcionamento, nossos olhos tendem a seguir em frente mesmo antes de concluir uma ação de confirmação. Isso é menos um problema para um Air Tap simples, mas se torna mais crítico ao combinar comandos de voz longa com movimentos oculares rápidos. Uma maneira de lidar com esse cenário é fazer uma chamada adicional para SpatialPointerPose::TryGetAtTimestamp, usando um carimbo de data/hora histórico que corresponde ao evento de entrada.

No entanto, para a entrada que roteia por meio do SpatialInteractionManager, há um método mais fácil. SpatialInteractionSourceState tem sua própria função TryGetAtTimestamp. Chamar que fornecerá um SpatialPointerPose perfeitamente correlacionado sem a adivinhação. Para obter mais informações sobre como trabalhar com SpatialInteractionSourceStates, confira a documentação Mãos e Controladores de Movimento no DirectX .


Calibragem

Para que o acompanhamento ocular funcione com precisão, cada usuário precisa passar por uma calibragem de usuário de rastreamento ocular. Isso permite que o dispositivo ajuste o sistema para uma experiência de exibição mais confortável e de maior qualidade para o usuário e para garantir o acompanhamento preciso dos olhos ao mesmo tempo. Os desenvolvedores não precisam fazer nada de lado para gerenciar a calibragem do usuário. O sistema garantirá que o usuário seja solicitado a calibrar o dispositivo nas seguintes circunstâncias:

  • O usuário está usando o dispositivo pela primeira vez
  • O usuário rejeitou o processo de calibragem anteriormente
  • O processo de calibragem não teve sucesso na última vez que o usuário usou o dispositivo

Os desenvolvedores devem fornecer suporte adequado para usuários em que os dados de rastreamento ocular podem não estar disponíveis. Saiba mais sobre considerações sobre soluções de fallback no rastreamento de olhos em HoloLens 2.


Confira também