Compartilhar via


Reprojeção de fase tardia

A LSR (reprojeção de estágio tardio) é um recurso de hardware que ajuda a estabilizar os hologramas quando o usuário se move.

É esperado que modelos estáticos mantenham visualmente a posição quando você se movimenta em torno deles. Se parecerem instáveis, esse comportamento poderá ser um indício de problemas de LSR. Lembre-se de que as transformações dinâmicas adicionais, como animações ou exibições de explosão, podem mascarar esse comportamento.

Você pode escolher entre dois modos de LSR diferentes, ou seja, LSR Planar ou LSR de Profundidade. Ambos os modos LSR aprimoram a estabilidade do holograma, embora tenham suas limitações distintas. Comece experimentando LSR de profundidade, pois podemos dizer que isso gera melhores resultados na maioria dos casos.

Como definir o modo LSR

Qual dos modos de LSR será usado é determinado pelo fato do aplicativo cliente enviar um buffer de profundidade. Se o buffer de profundidade for enviado, ele usará o Depth LSR e o Planar LSR.

Os parágrafos a seguir explicam como o envio do buffer de profundidade é realizado em aplicativos do Unity e nativos, respectivamente.

Unity

No editor do Unity, vá para File > Build Settings. Selecione Player Settings na parte inferior esquerda e, em Player > XR Settings > Virtual Reality SDKs > Windows Mixed Reality, verifique se Enable Depth Buffer Sharing está marcado:

Sinalizador habilitado para compartilhamento de buffer de profundidade

Se estiver, seu aplicativo usará LSR de profundidade, caso contrário, usará LSR planar.

Ao usar o OpenXR, o buffer de profundidade sempre deve ser enviado. A configuração pode ser encontrada em XR Plug-in Management > OpenXR. O modo de reprodução pode ser alterado por meio de uma extensão no plug-in OpenXR:

using Microsoft.MixedReality.OpenXR;

public class OverrideReprojection : MonoBehaviour
{
    void OnEnable()
    {
        RenderPipelineManager.endCameraRendering += RenderPipelineManager_endCameraRendering;
    }
    void OnDisable()
    {
        RenderPipelineManager.endCameraRendering -= RenderPipelineManager_endCameraRendering;
    }

    // When using the Universal Render Pipeline, OnPostRender has to be called manually.
    private void RenderPipelineManager_endCameraRendering(ScriptableRenderContext context, Camera camera)
    {
        OnPostRender();
    }

    // Called directly when using Unity's legacy renderer.
    private void OnPostRender()
    {
        ReprojectionSettings reprojectionSettings = default;
        reprojectionSettings.ReprojectionMode = ReprojectionMode.PlanarManual; // Or your favorite reprojection mode.
        
        // In case of PlanarManual you also need to provide a focus point here.
        reprojectionSettings.ReprojectionPlaneOverridePosition = ...;
        reprojectionSettings.ReprojectionPlaneOverrideNormal = ...;
        reprojectionSettings.ReprojectionPlaneOverrideVelocity = ...;

        foreach (ViewConfiguration viewConfiguration in ViewConfiguration.EnabledViewConfigurations)
        {
            if (viewConfiguration.IsActive && viewConfiguration.SupportedReprojectionModes.Contains(reprojectionSettings.ReprojectionMode))
            {
                viewConfiguration.SetReprojectionSettings(reprojectionSettings);
            }
        }
    }
}

Aplicativos C++ nativos

O envio do buffer de profundidade está totalmente sob o controle do código de associação do C++ nativo, independentemente da versão do WMR ou OpenXR. A única condição que precisa ser atendida é que no momento que GraphicsBinding::BlitRemoteFrame é chamado, um buffer de profundidade deve estar associado à API de gráficos.

LSR de profundidade

Para que a LSR de profundidade funcione, o aplicativo cliente deve fornecer um buffer de profundidade válido que contenha toda a geometria relevante a ser considerada durante a LSR.

A LSR de profundidade tenta estabilizar o quadro de vídeo com base no conteúdo do buffer de profundidade fornecido. Como consequência, o conteúdo que não foi renderizado para ele, como objetos transparentes, não pode ser ajustado por LSR e pode mostrar artefatos de instabilidade e de reprojeção.

Para reduzir a instabilidade de reprojeção para objetos transparentes, você pode forçar a gravação de buffer de profundidade. Confira o sinalizador de material TransparencyWritesDepth para os materiais de Cor e PBR. No entanto, observe que a qualidade visual da interação do objeto transparente/opaco pode ser prejudicada ao habilitar esse sinalizador.

LSR planar

A LSR planar não tem informações de profundidade por pixel como a LSR de profundidade tem. Em vez disso, ela reprojeta todo o conteúdo com base em um plano que você deve fornecer a cada quadro.

A LSR planar reprojeta melhor objetos que estão perto do plano fornecido. Quanto mais distante um objeto estiver, mais instável será sua aparência. Embora a LSR de profundidade seja melhor em reprojetar objetos a diferentes profundidades, a LSR planar poderá funcionar melhor para conteúdo bem alinhado a um plano.

Configurar a LSR planar no Unity

Os parâmetros do plano são derivados de um denominado ponto de foco. Ao usar o WMR, o ponto de foco deve ser definido a cada quadro por meio de UnityEngine.XR.WSA.HolographicSettings.SetFocusPointForFrame. Confira a API do Ponto de Foco do Unity para obter detalhes. Para o OpenXR, o ponto de foco precisa ser definido por meio do ReprojectionSettings mostrado na seção anterior. Se você não definir um ponto de foco, um fallback será escolhido para você. No entanto, o fallback automático geralmente leva a resultados de qualidade não ideal.

Você pode calcular o ponto de foco por conta própria, embora possa fazer sentido baseá-lo em um calculado pelo host de Remote Rendering. Chame RemoteManagerUnity.CurrentSession.GraphicsBinding.GetRemoteFocusPoint para obtê-lo.

Geralmente, o cliente e o host renderizam conteúdo que o outro lado não reconhece, como elementos de interface do usuário no cliente. Portanto, pode fazer sentido combinar o ponto de foco remoto com um calculado localmente.

Os pontos de foco calculados em dois quadros sucessivos podem variar muito. Simplesmente usá-los no estado em que se encontram pode fazer com que os hologramas pareçam estar saltando. Para evitar esse comportamento, é aconselhável interpolar entre os pontos de foco anteriores e atuais.

Modos de pose de reprojeção

O escopo geral do problema com a renderização híbrida pode ser declarado da seguinte maneira: os conteúdos remotos e locais estão dentro de poses distintas (ou seja, espaços de coordenadas) porque a pose remota é prevista pelo servidor, enquanto que a pose local é o local atual. No entanto, no final de um quadro de renderização, o conteúdo remoto e local precisam ser alinhados e trazidos para a exibição. A ilustração a seguir mostra um exemplo em que as poses locais e remotas são convertidas em comparação com o visor de exibição:

Diagrama que ilustra as poses local e remota em relação ao visor de destino.

Dependendo do GraphicsBinding usado, o ARR fornece até três modos de reprojeção que funcionam ortogonalmente para o modo LSR discutido acima. Esses modos são chamados de Remote pose mode, Local pose mode, e Passthrough pose mode. Ao contrário do modo LSR, os modos de pose definem como o conteúdo local e remoto é combinado. A escolha do modo compensa a qualidade visual do conteúdo local com o desempenho do runtime, portanto os aplicativos devem considerar cuidadosamente qual é a melhor opção. Veja as considerações abaixo.

Remote pose mode

Remote pose mode é o modo padrão no ARR. Nesse modo, o conteúdo local é renderizado sobre o fluxo de imagem remota de entrada usando a pose remota do quadro remoto. Em seguida, o resultado combinado é encaminhado para o sistema operacional para a reprojeção final. Embora essa abordagem use apenas uma reprojeção, a correção final é baseada no intervalo de ida e volta para que o erro de reprojeção total seja aplicado ao conteúdo local também. Como consequência, o grande delta de correção pode resultar em distorções significativas da geometria local, incluindo elementos de interface do usuário.

Usando a ilustração acima, a seguinte transformação é aplicada no Remote pose mode:

Etapas de reprojeção no modo de pose remoto.

Local pose mode

Nesse modo, a reprojeção é dividida em duas etapas distintas: na primeira etapa, o conteúdo remoto é reprojetado no espaço de pose local, ou seja, o espaço em que o conteúdo local é renderizado em dispositivos VR/RA por padrão. Depois disso, o conteúdo local é renderizado sobre essa imagem pré-transformada usando a pose local usual. Na segunda etapa, o resultado combinado é encaminhado para o sistema operacional para a reprojeção final. Como essa segunda reprojeção incorre apenas em um pequeno delta, de fato, no mesmo delta que seria usado se o ARR não estivesse presente, os artefatos de distorção no conteúdo local são atenuados significativamente.

Da mesma forma, a ilustração tem esta aparência:

Etapas de reprojeção no modo de pose local.

Passthrough pose mode

Esse modo de pose se comporta essencialmente da mesma forma que Remote pose mode, o que significa que o conteúdo local e remoto é combinado no espaço remoto. No entanto, o conteúdo não será reprojetado após a combinação, mas permanecerá no espaço de pose remoto. A principal vantagem desse modo é que a imagem resultante não será afetada por artefatos de reprojeção.

Conceitualmente, esse modo pode ser comparado a aplicativos convencionais de streaming de nuvem. Devido à alta latência que ele incorre, ele não é adequado para cenários montados na cabeça, mas é uma alternativa viável para Desktop e outros aplicativos de tela simples em que a qualidade de imagem mais alta é desejada. Portanto, ele só está disponível em GraphicsBindingSimD3D11 por enquanto.

Considerações sobre desempenho e qualidade

A escolha do modo de pose tem implicações de desempenho e qualidade visual. O custo adicional de runtime no lado do cliente para fazer a reprojeção extra no Local pose mode em um dispositivo HoloLens 2 tem o valor de cerca de 1 milissegundo por quadro de tempo de GPU. Esse custo extra precisa ser levado em consideração se o aplicativo cliente já estiver próximo ao planejamento de 16 milissegundos por quadro. Por outro lado, há tipos de aplicativos sem conteúdo local ou com conteúdo local que não é propenso a artefatos de distorção. Nesses casos, o Local pose mode não tem nenhum benefício visual porque a qualidade da reprojeção do conteúdo remoto não é afetada.

Portanto, o conselho geral seria testar os modos de acordo com cada caso de uso e ver se o ganho na qualidade visual justifica a sobrecarga adicional de desempenho. Também é possível alternar o modo dinamicamente, por exemplo, habilitar o modo local somente quando as UIs importantes forem mostradas.

Como alterar o Pose mode em runtime

A API de cliente a seguir pode ser usada para alterar o modo em runtime:

RenderingSession session = ...;
session.GraphicsBinding.SetPoseMode(PoseMode.Local); // set local pose mode
ApiHandle<RenderingSession> session = ...;
session->GetGraphicsBinding()->SetPoseMode(PoseMode::Local); // set local pose mode

Em geral, o modo pode ser alterado sempre que o objeto de associação de gráficos estiver disponível. Há uma distinção importante para GraphicsBindingSimD3D11: o modo de pose só pode ser alterado para PoseMode.Remote, se tiver sido inicializado com texturas de proxy. Se esse não for o caso, o modo pose só poderá ser alternado entre PoseMode.Local e PoseMode.Passthrough até que a associação de gráficos seja reinicializada. Veja as duas sobrecargas de GraphicsBindingSimD3d11.InitSimulation, que usam ponteiros nativos para objetos ID3D11Texture2D (caminho de proxy) ou o width e o height do visor de usuário desejado (caminho não proxy).

Considerações de tempo de execução do Desktop Unity

Devido à experiência técnica do GraphicsBindingSimD3D11 e ao modo como a renderização fora da tela funciona no Unity, o tempo de execução do ARR Unity exige que o usuário especifique o modo de pose desejado na inicialização do RemoteManagerUnity da seguinte maneira:

public static void InitRemoteManager(Camera camera)
{
    RemoteUnityClientInit clientInit = new RemoteUnityClientInit(camera, PoseMode.Remote);
    RemoteManagerUnity.InitializeManager(clientInit);
}

Se PoseMode.Remote for especificado, a associação de gráficos será inicializada com texturas de proxy fora da tela, e toda a renderização será redirecionada da câmera principal da cena do Unity para uma câmera de proxy. Esse caminho do código só é recomendado para uso se forem necessárias alterações no modo de pose do runtime para PoseMode.Remote. Se nenhum modo de pose for especificado, o runtime do ARR Unity selecionará um padrão apropriado, dependendo da plataforma atual.

Aviso

O redirecionamento da câmera proxy pode ser incompatível com outras extensões do Unity que esperam que a renderização da cena ocorra com a câmera principal. A câmera do proxy poderá ser recuperada por meio da propriedade RemoteManagerUnity.ProxyCamera se precisar ser consultada ou registrada em outro lugar. Especificamente para o plug-in Cinemachine, consulte esta entrada de solução de problemas: O plug-in Cinemachine do Unity não funciona no modo pose remota.

Se PoseMode.Local ou PoseMode.Passthrough for usado em vez disso, a associação de gráficos não será inicializada com texturas de proxy fora da tela, e será usado um caminho rápido usando a câmera principal da cena do Unity para renderizar. Se o respectivo caso de uso exigir o modo de pose remota no runtime, PoseMode.Remote deverá ser especificado na inicialização RemoteManagerUnity. A renderização direta com a câmera principal do Unity é mais eficiente e pode evitar problemas com outras extensões do Unity. Portanto, é recomendável usar o caminho de renderização não proxy.

Próximas etapas