Vinculação de gráficos
Para poder usar a Renderização Remota do Azure em um aplicativo personalizado, ela precisa ser integrada ao pipeline de renderização do aplicativo. Esta integração é da responsabilidade da ligação gráfica.
Uma vez configurada, a ligação gráfica dá acesso a várias funções que afetam a imagem renderizada. Estas funções podem ser separadas em duas categorias: funções gerais que estão sempre disponíveis e funções específicas que são relevantes apenas para o selecionado Microsoft.Azure.RemoteRendering.GraphicsApiType
.
Vinculação de gráficos no Unity
No Unity, toda a RemoteUnityClientInit
ligação é tratada pela struct passada para RemoteManagerUnity.InitializeManager
. Para definir o modo gráfico, o GraphicsApiType
campo tem de ser definido para a ligação escolhida. O campo será preenchido automaticamente dependendo da presença de um XRDevice. O comportamento pode ser substituído manualmente com os seguintes comportamentos:
- HoloLens 2: a ligação gráfica OpenXR ou Windows Mixed Reality é usada dependendo do plug-in Unity XR ativo.
- Aplicativo de desktop UWP simples: a simulação é sempre usada.
- Editor Unity: A simulação é sempre usada, a menos que um fone de ouvido WMR VR esteja conectado, caso em que o ARR será desativado para permitir a depuração das partes não relacionadas ao ARR do aplicativo. Ver também comunicação remota holográfica.
A única outra parte relevante para Unity é acessar a vinculação básica, todas as outras seções abaixo podem ser ignoradas.
Configuração de vinculação de gráficos em aplicativos personalizados
Para selecionar uma associação gráfica, execute as duas etapas a seguir: Primeiro, a ligação gráfica deve ser inicializada estaticamente quando o programa é inicializado:
RemoteRenderingInitialization managerInit = new RemoteRenderingInitialization();
managerInit.GraphicsApi = GraphicsApiType.OpenXrD3D11;
managerInit.ConnectionType = ConnectionType.General;
managerInit.Right = ///...
RemoteManagerStatic.StartupRemoteRendering(managerInit);
RemoteRenderingInitialization managerInit;
managerInit.GraphicsApi = GraphicsApiType::OpenXrD3D11;
managerInit.ConnectionType = ConnectionType::General;
managerInit.Right = ///...
StartupRemoteRendering(managerInit); // static function in namespace Microsoft::Azure::RemoteRendering
A chamada acima deve ser chamada antes que qualquer outra API de renderização remota seja acessada.
Da mesma forma, a função RemoteManagerStatic.ShutdownRemoteRendering();
de-init correspondente deve ser chamada depois que todos os outros objetos de renderização remota já estiverem destruídos.
Para WMR StartupRemoteRendering
também precisa ser chamado antes de qualquer API holográfica é chamada. Para OpenXR o mesmo se aplica a quaisquer APIs relacionadas ao OpenXR.
Acessando a vinculação de gráficos
Uma vez que um cliente é configurado, a ligação gráfica básica pode ser acessada com o RenderingSession.GraphicsBinding
getter. Como exemplo, as estatísticas do último quadro podem ser recuperadas assim:
RenderingSession currentSession = ...;
if (currentSession.GraphicsBinding != null)
{
FrameStatistics frameStatistics;
if (currentSession.GraphicsBinding.GetLastFrameStatistics(out frameStatistics) == Result.Success)
{
...
}
}
ApiHandle<RenderingSession> currentSession = ...;
if (ApiHandle<GraphicsBinding> binding = currentSession->GetGraphicsBinding())
{
FrameStatistics frameStatistics;
if (binding->GetLastFrameStatistics(&frameStatistics) == Result::Success)
{
...
}
}
APIs gráficas
Atualmente, há três APIs gráficas que podem ser selecionadas, OpenXrD3D11
WmrD3D11
e SimD3D11
. Um quarto Headless
existe, mas ainda não é suportado no lado do cliente.
OpenXR
GraphicsApiType.OpenXrD3D11
é a ligação padrão para ser executada no HoloLens 2. Criará a GraphicsBindingOpenXrD3d11
vinculação. Neste modo, a Renderização Remota do Azure cria uma camada de API OpenXR para se integrar ao tempo de execução do OpenXR.
Para acessar as ligações gráficas derivadas, a base GraphicsBinding
deve ser lançada.
Há três coisas que precisam ser feitas para usar a ligação OpenXR:
Pacote personalizado OpenXR layer json
Para usar a renderização remota com OpenXR, a camada de API OpenXR personalizada precisa ser ativada. Isso é feito por meio de chamadas StartupRemoteRendering
mencionadas na seção anterior. No entanto, como pré-requisito, o XrApiLayer_msft_holographic_remoting.json
aplicativo precisa ser empacotado com o aplicativo para que ele possa ser carregado. Isso é feito automaticamente se o pacote NuGet "Microsoft.Azure.RemoteRendering.Cpp" for adicionado a um projeto.
Informar a renderização remota do espaço XR usado
Isso é necessário para alinhar o conteúdo remoto e renderizado localmente.
RenderingSession currentSession = ...;
ulong space = ...; // XrSpace cast to ulong
GraphicsBindingOpenXrD3d11 openXrBinding = (currentSession.GraphicsBinding as GraphicsBindingOpenXrD3d11);
if (openXrBinding.UpdateAppSpace(space) == Result.Success)
{
...
}
ApiHandle<RenderingSession> currentSession = ...;
XrSpace space = ...;
ApiHandle<GraphicsBindingOpenXrD3d11> openXrBinding = currentSession->GetGraphicsBinding().as<GraphicsBindingOpenXrD3d11>();
#ifdef _M_ARM64
if (openXrBinding->UpdateAppSpace(reinterpret_cast<uint64_t>(space)) == Result::Success)
#else
if (openXrBinding->UpdateAppSpace(space) == Result::Success)
#endif
{
...
}
Onde o acima XrSpace
é o usado pelo aplicativo que define o sistema de coordenadas do espaço mundial no qual as coordenadas na API são expressas.
Renderizar imagem remota (OpenXR)
No início de cada quadro, o quadro remoto precisa ser renderizado no buffer traseiro. Isso é feito chamando BlitRemoteFrame
, que preencherá as informações de cor e profundidade para ambos os olhos no destino de renderização vinculado no momento. Assim, é importante fazê-lo depois de vincular o buffer de retorno completo como um destino de renderização.
Aviso
Depois que a imagem remota foi gravada no backbuffer, o conteúdo local deve ser renderizado usando uma técnica de renderização estéreo de uma única passagem, por exemplo, usando SV_RenderTargetArrayIndex. O uso de outras técnicas de renderização estéreo, como renderizar cada olho em uma passagem separada, pode resultar em grande degradação de desempenho ou artefatos gráficos e deve ser evitado.
RenderingSession currentSession = ...;
GraphicsBindingOpenXrD3d11 openXrBinding = (currentSession.GraphicsBinding as GraphicsBindingOpenXrD3d11);
openXrBinding.BlitRemoteFrame();
ApiHandle<RenderingSession> currentSession = ...;
ApiHandle<GraphicsBindingOpenXrD3d11> openXrBinding = currentSession->GetGraphicsBinding().as<GraphicsBindingOpenXrD3d11>();
openXrBinding->BlitRemoteFrame();
Windows Mixed Reality
GraphicsApiType.WmrD3D11
é a ligação gráfica usada anteriormente para ser executada no HoloLens 2. Criará a GraphicsBindingWmrD3d11
vinculação. Neste modo, a Renderização Remota do Azure se conecta diretamente às APIs holográficas.
Para acessar as ligações gráficas derivadas, a base GraphicsBinding
deve ser lançada.
Há duas coisas que precisam ser feitas para usar a ligação WMR:
Informar a renderização remota do sistema de coordenadas usado
Isso é necessário para alinhar o conteúdo remoto e renderizado localmente.
RenderingSession currentSession = ...;
IntPtr ptr = ...; // native pointer to ISpatialCoordinateSystem
GraphicsBindingWmrD3d11 wmrBinding = (currentSession.GraphicsBinding as GraphicsBindingWmrD3d11);
if (wmrBinding.UpdateUserCoordinateSystem(ptr) == Result.Success)
{
...
}
ApiHandle<RenderingSession> currentSession = ...;
void* ptr = ...; // native pointer to ISpatialCoordinateSystem
ApiHandle<GraphicsBindingWmrD3d11> wmrBinding = currentSession->GetGraphicsBinding().as<GraphicsBindingWmrD3d11>();
if (wmrBinding->UpdateUserCoordinateSystem(ptr) == Result::Success)
{
...
}
Onde o acima ptr
deve ser um ponteiro para um objeto nativo ABI::Windows::Perception::Spatial::ISpatialCoordinateSystem
que define o sistema de coordenadas do espaço mundial no qual as coordenadas na API são expressas.
Renderizar imagem remota (WMR)
As mesmas considerações que no caso OpenXR acima se aplicam aqui. As chamadas de API têm esta aparência:
RenderingSession currentSession = ...;
GraphicsBindingWmrD3d11 wmrBinding = (currentSession.GraphicsBinding as GraphicsBindingWmrD3d11);
wmrBinding.BlitRemoteFrame();
ApiHandle<RenderingSession> currentSession = ...;
ApiHandle<GraphicsBindingWmrD3d11> wmrBinding = currentSession->GetGraphicsBinding().as<GraphicsBindingWmrD3d11>();
wmrBinding->BlitRemoteFrame();
Simulação
GraphicsApiType.SimD3D11
é a ligação de simulação e, se selecionada, cria a GraphicsBindingSimD3d11
ligação gráfica. Esta interface é usada para simular o movimento da cabeça, por exemplo, em um aplicativo de desktop e renderiza uma imagem monoscópica.
Para implementar a ligação de simulação, é importante entender a diferença entre a câmera local e o quadro remoto, conforme descrito na página da câmera .
São necessárias duas câmaras:
- Câmera local: esta câmera representa a posição atual da câmera que é impulsionada pela lógica do aplicativo.
- Câmera proxy: Esta câmera corresponde ao quadro remoto atual que foi enviado pelo servidor. Como há um atraso de tempo entre o cliente solicitando um quadro e sua chegada, o quadro remoto está sempre um pouco atrás do movimento da câmera local.
A abordagem básica aqui é que tanto a imagem remota quanto o conteúdo local são renderizados em um alvo fora da tela usando a câmera proxy. A imagem proxy é então reprojetada no espaço da câmera local, o que é explicado na reprojeção de estágio tardio.
GraphicsApiType.SimD3D11
também suporta renderização estereoscópica, que precisa ser ativada durante a chamada de InitSimulation
configuração abaixo. A configuração é um pouco mais envolvida e funciona da seguinte forma:
Criar destino de renderização de proxy
O conteúdo remoto e local precisa ser renderizado para um alvo de renderização de cor / profundidade fora da tela chamado 'proxy' usando os dados da GraphicsBindingSimD3d11.Update
câmera proxy fornecidos pela função.
O proxy deve corresponder à resolução do buffer traseiro e deve estar no formato DXGI_FORMAT_R8G8B8A8_UNORM ou DXGI_FORMAT_B8G8R8A8_UNORM . No caso da renderização estereoscópica, tanto a textura do proxy de cor e, se a profundidade for usada, a textura do proxy de profundidade precisam ter duas camadas de matriz em vez de uma. Quando uma sessão estiver pronta, GraphicsBindingSimD3d11.InitSimulation
precisa ser chamada antes de se conectar a ela:
RenderingSession currentSession = ...;
IntPtr d3dDevice = ...; // native pointer to ID3D11Device
IntPtr color = ...; // native pointer to ID3D11Texture2D
IntPtr depth = ...; // native pointer to ID3D11Texture2D
float refreshRate = 60.0f; // Monitor refresh rate up to 60hz.
bool flipBlitRemoteFrameTextureVertically = false;
bool flipReprojectTextureVertically = false;
bool stereoscopicRendering = false;
GraphicsBindingSimD3d11 simBinding = (currentSession.GraphicsBinding as GraphicsBindingSimD3d11);
simBinding.InitSimulation(d3dDevice, depth, color, refreshRate, flipBlitRemoteFrameTextureVertically, flipReprojectTextureVertically, stereoscopicRendering);
ApiHandle<RenderingSession> currentSession = ...;
void* d3dDevice = ...; // native pointer to ID3D11Device
void* color = ...; // native pointer to ID3D11Texture2D
void* depth = ...; // native pointer to ID3D11Texture2D
float refreshRate = 60.0f; // Monitor refresh rate up to 60hz.
bool flipBlitRemoteFrameTextureVertically = false;
bool flipReprojectTextureVertically = false;
bool stereoscopicRendering = false;
ApiHandle<GraphicsBindingSimD3d11> simBinding = currentSession->GetGraphicsBinding().as<GraphicsBindingSimD3d11>();
simBinding->InitSimulation(d3dDevice, depth, color, refreshRate, flipBlitRemoteFrameTextureVertically, flipReprojectTextureVertically, stereoscopicRendering);
A função init precisa ser fornecida com ponteiros para o dispositivo d3d nativo, bem como para a textura de cor e profundidade do destino de renderização do proxy. Uma vez inicializado, e Disconnect
pode ser chamado várias vezes, RenderingSession.ConnectAsync
mas ao mudar para uma sessão diferente, GraphicsBindingSimD3d11.DeinitSimulation
precisa ser chamado primeiro na sessão antiga antes GraphicsBindingSimD3d11.InitSimulation
de poder ser chamado em outra sessão.
Atualização de loop de renderização
A atualização do loop de renderização consiste em várias etapas:
- Cada quadro, antes de qualquer renderização ocorrer,
GraphicsBindingSimD3d11.Update
é chamado com a transformação de câmera atual que é enviada para o servidor a ser renderizado. Ao mesmo tempo, a transformação de proxy retornada deve ser aplicada à câmera de proxy para renderizar no destino de renderização de proxy. Se a atualizaçãoSimulationUpdate.frameId
de proxy retornada for nula, ainda não há dados remotos. Nesse caso, em vez de renderizar no destino de renderização do proxy, qualquer conteúdo local deve ser renderizado para o buffer traseiro diretamente usando os dados atuais da câmera e as próximas duas etapas são ignoradas. - O aplicativo agora deve vincular o destino de renderização de proxy e chamar
GraphicsBindingSimD3d11.BlitRemoteFrameToProxy
. Isso preencherá as informações remotas de cor e profundidade no destino de renderização do proxy. Qualquer conteúdo local agora pode ser renderizado no proxy usando a transformação da câmera proxy. - Em seguida, o buffer de retorno precisa ser vinculado como um destino de renderização e
GraphicsBindingSimD3d11.ReprojectProxy
chamado em que ponto o buffer de retorno pode ser apresentado.
RenderingSession currentSession = ...;
GraphicsBindingSimD3d11 simBinding = (currentSession.GraphicsBinding as GraphicsBindingSimD3d11);
SimulationUpdateParameters updateParameters = new SimulationUpdateParameters();
// Fill out camera data with current camera data
// (see "Simulation Update structures" section below)
...
SimulationUpdateResult updateResult = new SimulationUpdateResult();
simBinding.Update(updateParameters, out updateResult);
// Is the frame data valid?
if (updateResult.FrameId != 0)
{
// Bind proxy render target
simBinding.BlitRemoteFrameToProxy();
// Use proxy camera data to render local content
...
// Bind back buffer
simBinding.ReprojectProxy();
}
else
{
// Bind back buffer
// Use current camera data to render local content
...
}
ApiHandle<RenderingSession> currentSession;
ApiHandle<GraphicsBindingSimD3d11> simBinding = currentSession->GetGraphicsBinding().as<GraphicsBindingSimD3d11>();
SimulationUpdateParameters updateParameters;
// Fill out camera data with current camera data
// (see "Simulation Update structures" section below)
...
SimulationUpdateResult updateResult;
simBinding->Update(updateParameters, &updateResult);
// Is the frame data valid?
if (updateResult.FrameId != 0)
{
// Bind proxy render target
simBinding->BlitRemoteFrameToProxy();
// Use proxy camera data to render local content
...
// Bind back buffer
simBinding->ReprojectProxy();
}
else
{
// Bind back buffer
// Use current camera data to render local content
...
}
Estruturas de atualização de simulação
Cada quadro, a atualização do loop de renderização da seção anterior requer que você insira uma gama de parâmetros da câmera correspondentes à câmera local e retorna um conjunto de parâmetros da câmera que correspondem à próxima câmera do quadro disponível. Estes dois conjuntos são capturados SimulationUpdateParameters
no e nas SimulationUpdateResult
estruturas, respectivamente:
public struct SimulationUpdateParameters
{
public int FrameId;
public StereoMatrix4x4 ViewTransform;
public StereoCameraFov FieldOfView;
};
public struct SimulationUpdateResult
{
public int FrameId;
public float NearPlaneDistance;
public float FarPlaneDistance;
public StereoMatrix4x4 ViewTransform;
public StereoCameraFov FieldOfView;
};
Os membros da estrutura têm o seguinte significado:
Membro | Description |
---|---|
FrameId | Identificador de quadro contínuo. Necessário para a entrada SimulationUpdateParameters e precisa ser continuamente incrementado para cada novo quadro. Será 0 em SimulationUpdateResult se ainda não houver dados de quadro disponíveis. |
ViewTransform | Par estéreo esquerda-direita-estéreo das matrizes de transformação da visão da câmara do quadro. Para renderização monoscópica, apenas o Left membro é válido. |
Campo de visão | Par estéreo esquerda-direita-de-campos de visão da câmara de fotogramas na convenção de campo de visão OpenXR. Para renderização monoscópica, apenas o Left membro é válido. |
NearPlaneDistance | distância plana próxima usada para a matriz de projeção do quadro remoto atual. |
FarPlaneDistance | distância de plano distante usada para a matriz de projeção do quadro remoto atual. |
Os pares estéreo e FieldOfView
permitem definir ambos os valores da câmera ocular no caso de ViewTransform
a renderização estereoscópica estar ativada. Caso contrário, os Right
membros serão ignorados. Como você pode ver, apenas a transformação da câmera é passada como matrizes de transformação 4x4 simples, enquanto nenhuma matriz de projeção é especificada. As matrizes reais são calculadas pela Renderização Remota do Azure internamente usando os campos de exibição especificados e o conjunto atual de plano próximo e plano distante na API CameraSettings.
Como você pode alterar o plano próximo e o plano distante nas CameraSettings durante o tempo de execução, conforme desejado, e o serviço aplica essas configurações de forma assíncrona, cada SimulationUpdateResult também carrega o plano próximo e o plano distante específicos usados durante a renderização do quadro correspondente. Você pode usar esses valores de plano para adaptar suas matrizes de projeção para renderizar objetos locais para corresponder à renderização remota de quadros.
Finalmente, embora a chamada Simulation Update exija o campo de visão na convenção OpenXR, por motivos de padronização e segurança algorítmica, você pode usar as funções de conversão ilustradas nos seguintes exemplos de população de estrutura:
public SimulationUpdateParameters CreateSimulationUpdateParameters(int frameId, Matrix4x4 viewTransform, Matrix4x4 projectionMatrix)
{
SimulationUpdateParameters parameters = default;
parameters.FrameId = frameId;
parameters.ViewTransform.Left = viewTransform;
if (parameters.FieldOfView.Left.FromProjectionMatrix(projectionMatrix) != Result.Success)
{
// Invalid projection matrix
throw new ArgumentException("Invalid projection settings");
}
return parameters;
}
public void GetCameraSettingsFromSimulationUpdateResult(SimulationUpdateResult result, out Matrix4x4 projectionMatrix, out Matrix4x4 viewTransform, out int frameId)
{
projectionMatrix = default;
viewTransform = default;
frameId = 0;
if (result.FrameId == 0)
{
// Invalid frame data
return;
}
// Use the screenspace depth convention you expect for your projection matrix locally
if (result.FieldOfView.Left.ToProjectionMatrix(result.NearPlaneDistance, result.FarPlaneDistance, DepthConvention.ZeroToOne, out projectionMatrix) != Result.Success)
{
// Invalid field-of-view
return;
}
viewTransform = result.ViewTransform.Left;
frameId = result.FrameId;
}
SimulationUpdateParameters CreateSimulationUpdateParameters(uint32_t frameId, Matrix4x4 viewTransform, Matrix4x4 projectionMatrix)
{
SimulationUpdateParameters parameters;
parameters.FrameId = frameId;
parameters.ViewTransform.Left = viewTransform;
if (FovFromProjectionMatrix(projectionMatrix, parameters.FieldOfView.Left) != Result::Success)
{
// Invalid projection matrix
return {};
}
return parameters;
}
void GetCameraSettingsFromSimulationUpdateResult(const SimulationUpdateResult& result, Matrix4x4& projectionMatrix, Matrix4x4& viewTransform, uint32_t& frameId)
{
if (result.FrameId == 0)
{
// Invalid frame data
return;
}
// Use the screenspace depth convention you expect for your projection matrix locally
if (FovToProjectionMatrix(result.FieldOfView.Left, result.NearPlaneDistance, result.FarPlaneDistance, DepthConvention::ZeroToOne, projectionMatrix) != Result::Success)
{
// Invalid field-of-view
return;
}
viewTransform = result.ViewTransform.Left;
frameId = result.FrameId;
}
Essas funções de conversão permitem alternar rapidamente entre a especificação do campo de visão e uma matriz de projeção de perspetiva 4x4 simples, dependendo de suas necessidades de renderização local. Essas funções de conversão contêm lógica de verificação e retornarão erros, sem definir um resultado válido, caso as matrizes de projeção de entrada ou campos de visão de entrada sejam inválidos.
Documentação da API
- C# RemoteManagerStatic.StartupRemoteRendering()
- C# GraphicsBinding classe
- Classe C# GraphicsBindingWmrD3d11
- Classe C# GraphicsBindingSimD3d11
- C++ RemoteRenderingInitialization struct
- C++ GraphicsBinding classe
- Classe C++ GraphicsBindingWmrD3d11
- Classe C++ GraphicsBindingSimD3d11