Share via


圖形系結

若要能夠在自定義應用程式中使用 Azure 遠端轉譯,它必須整合到應用程式的轉譯管線中。 此整合是圖形系結的責任。

設定之後,圖形系結會存取影響轉譯影像的各種函式。 這些函式可以分成兩個類別:一律可用的一般函式,以及只與所選 Microsoft.Azure.RemoteRendering.GraphicsApiType相關的特定函式。

Unity 中的圖形系結

在 Unity 中,整個系結是由 RemoteUnityClientInit 傳遞至 RemoteManagerUnity.InitializeManager的結構來處理。 若要設定圖形模式, GraphicsApiType 欄位必須設定為選擇的系結。 視 XRDevice 是否存在而定,欄位會自動填入。 您可以使用下列行為手動覆寫行為:

Unity 的唯一其他相關部分是存取 基本系結,可以略過下列所有其他區段。

自訂應用程式中的圖形系結設定

若要選取圖形系結,請執行下列兩個步驟:首先,初始化程式時必須以靜態方式初始化圖形系結:

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

必須先呼叫上述呼叫,才能存取任何其他 遠端轉譯 API。 同樣地,在所有其他 遠端轉譯 物件都已經終結之後,應該呼叫對應的 de-init 函RemoteManagerStatic.ShutdownRemoteRendering();式。 針對WMR StartupRemoteRendering ,也必須在呼叫任何全像攝影 API 之前呼叫。 對於OpenXR,同樣適用於任何OpenXR相關 API。

存取圖形系結

設定客戶端之後,即可使用 RenderingSession.GraphicsBinding getter 存取基本圖形系結。 例如,最後一個畫面統計數據可以像這樣擷取:

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)
    {
        ...
    }
}

圖形 API

目前有三個圖形 API 可以選取、 OpenXrD3D11WmrD3D11SimD3D11。 第四個存在, Headless 但用戶端尚不支援。

OpenXR

GraphicsApiType.OpenXrD3D11 是在 HoloLens 2 上執行的預設系結。 它會建立系 GraphicsBindingOpenXrD3d11 結。 在此模式中,Azure 遠端轉譯 會建立OpenXR API層,以將自己整合到OpenXR運行時間。

若要存取衍生的圖形系結,必須轉換基底 GraphicsBinding 。 有三件事需要完成才能使用 OpenXR 系結:

封裝自定義 OpenXR 層 json

若要搭配 OpenXR 使用 遠端轉譯,必須啟用自定義 OpenXR API 層。 這是藉由呼叫 StartupRemoteRendering 上一節中所述來完成。 不過,作為必要條件, XrApiLayer_msft_holographic_remoting.json 需要與應用程式一起封裝,才能載入。 如果將 「Microsoft.Azure.RemoteRendering.Cpp」 NuGet 套件新增至專案,就會自動完成此作業。

通知已使用 XR 空間的 遠端轉譯

這需要對齊遠端和本機轉譯的內容。

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

其中,上述 XrSpace 是應用程式所使用的應用程式用來定義 API 中座標之座標所在的世界空間座標系統。

轉譯遠端影像 (OpenXR)

在每個畫面的開頭,遠端畫面必須轉譯到後台緩衝區。 這是藉由呼叫 BlitRemoteFrame來完成,這會將兩個眼睛的色彩和深度資訊填入目前系結的轉譯目標。 因此,在將完整後台緩衝區系結為轉譯目標之後,請務必這麼做。

警告

遠端影像被點入后緩衝區之後,應該使用單一傳遞立體聲轉譯技術來轉譯本機內容,例如使用 SV_RenderTargetArrayIndex。 使用其他立體轉譯技術,例如在不同的階段中轉譯每個眼睛,可能會導致主要效能降低或圖形化成品,並應避免。

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 是先前用來在 HoloLens 2 上執行的圖形系結。 它會建立系 GraphicsBindingWmrD3d11 結。 在此模式中,Azure 遠端轉譯 直接連結至全像攝影 API。

若要存取衍生的圖形系結,必須轉換基底 GraphicsBinding 。 有兩件事需要完成才能使用 WMR 系結:

通知已使用座標系統的 遠端轉譯

這需要對齊遠端和本機轉譯的內容。

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)
{
    ...
}

其中,上述 ptr 必須是原生物件的指標,該物件 ABI::Windows::Perception::Spatial::ISpatialCoordinateSystem 會定義 API 中表示座標所在的世界空間座標系統。

轉譯遠端影像 (WMR)

此處適用上述 OpenXR 案例中的相同考慮。 API 呼叫看起來像這樣:

RenderingSession currentSession = ...;
GraphicsBindingWmrD3d11 wmrBinding = (currentSession.GraphicsBinding as GraphicsBindingWmrD3d11);
wmrBinding.BlitRemoteFrame();
ApiHandle<RenderingSession> currentSession = ...;
ApiHandle<GraphicsBindingWmrD3d11> wmrBinding = currentSession->GetGraphicsBinding().as<GraphicsBindingWmrD3d11>();
wmrBinding->BlitRemoteFrame();

模擬

GraphicsApiType.SimD3D11 是仿真系結,如果選取它就會 GraphicsBindingSimD3d11 建立圖形系結。 此介面可用來模擬頭部移動,例如在傳統型應用程式中,並轉譯單色影像。

若要實作仿真系結,請務必瞭解本機相機與遠端畫面之間的差異,如相機頁面上所述

需要兩個相機:

  • 本機相機:此相機代表應用程式邏輯所驅動目前的相機位置。
  • Proxy 相機:此相機符合伺服器所傳送的目前 遠程畫面 。 由於要求畫面的用戶端與其抵達之間有時間延遲, 遠程畫面 一律會落後於本機相機的移動。

此處的基本方法是使用 Proxy 相機將遠端影像和本機內容轉譯成螢幕外目標。 Proxy 影像接著會重新投影到本機相機空間,這會在後期重新投影進一步說明。

GraphicsApiType.SimD3D11 也支援立體轉譯,必須在下列安裝呼叫期間 InitSimulation 啟用。 安裝程式會更加相關,且運作方式如下:

建立 Proxy 轉譯目標

遠端和本機內容必須使用函式提供的 GraphicsBindingSimD3d11.Update Proxy 相機數據,轉譯成稱為 「proxy」 的螢幕外色彩/深度轉譯目標。

Proxy 必須符合後端緩衝區的解析度,而且應該以DXGI_FORMAT_R8G8B8A8_UNORMDXGI_FORMAT_B8G8R8A8_UNORM格式來格式化。 在立體轉譯的情況下,色彩 Proxy 紋理和如果使用深度,深度 Proxy 紋理必須有兩個陣列層,而不是一個數位層。 一旦會話準備就緒,必須先呼叫 , GraphicsBindingSimD3d11.InitSimulation 才能連線到它:

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

init 函式必須提供原生 d3d 裝置的指標,以及 Proxy 轉譯目標的色彩和深度紋理。 一旦初始化,而且Disconnect可以多次呼叫,RenderingSession.ConnectAsync但在切換至不同的會話時,必須先在舊會話上呼叫,GraphicsBindingSimD3d11.DeinitSimulation才能GraphicsBindingSimD3d11.InitSimulation在另一個會話上呼叫。

轉譯迴圈更新

轉譯迴圈更新是由多個步驟所組成:

  1. 在進行任何轉譯之前, GraphicsBindingSimD3d11.Update 會使用傳送至要轉譯之伺服器的目前相機轉換來呼叫每個畫面。 同時,傳回的 Proxy 轉換應該套用至 Proxy 相機,以轉譯成 Proxy 轉譯目標。 如果傳回的 Proxy 更新 SimulationUpdate.frameId 為 Null,則尚未有遠端數據。 在此情況下,不應轉譯為 Proxy 轉譯目標,而是應該使用目前的相機數據直接轉譯到後台緩衝區,並略過後續兩個步驟。
  2. 應用程式現在應該繫結 Proxy 轉譯目標並呼叫 GraphicsBindingSimD3d11.BlitRemoteFrameToProxy。 這會將遠端色彩和深度資訊填入 Proxy 轉譯目標。 現在可以使用 Proxy 相機轉換,將任何本機內容轉譯到 Proxy 上。
  3. 接下來,後端緩衝區必須系結為轉譯目標,並 GraphicsBindingSimD3d11.ReprojectProxy 呼叫后緩衝區可以呈現。
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
    ...
}

模擬更新結構

上一節的每個畫面, Render 迴圈更新 都需要您輸入對應至本機相機的相機參數範圍,並傳回一組對應至下一個可用畫面相機相機的相機參數。 這兩個集合分別在 和 SimulationUpdateResult 結構中SimulationUpdateParameters擷取:

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

結構成員具有下列意義:

member 描述
FrameId 連續框架識別碼。 SimulationUpdateParameters 輸入的必要專案,而且必須針對每個新畫面持續遞增。 如果尚未提供框架數據,則 SimulationUpdateResult 中將會是 0。
ViewTransform 框架相機檢視轉換矩陣的左-右-立體配對。 若為單一複本轉譯,只有 Left 成員有效。
FieldOfView 在OpenXR檢視慣例,框架相機視野的左右立體配對。 若為單一複本轉譯,只有 Left 成員有效。
NearPlaneDistance 用於目前遠端框架之投影矩陣的近平面距離。
FarPlaneDistance 用於目前遠端框架之投影矩陣的遠平面距離。

當啟用立體轉譯時,立體配對 ViewTransformFieldOfView 允許設定兩個眼部相機值。 否則, Right 將會忽略成員。 如您所見,只有相機的轉換會以純 4x4 轉換矩陣的形式傳遞,而未指定投影矩陣。 實際的矩陣是由 Azure 遠端轉譯 在內部使用指定的檢視欄位,以及 相機設定 API設定的目前近平面和遠平面計算。

由於您可以視需要變更 相機設定 上的近平面和遠平面,且服務會以異步方式套用這些設定,因此每個 SimulationUpdateResult 也會攜帶對應框架轉譯期間所使用的特定近平面和遠平面。 您可以使用這些平面值來調整投影矩陣,以便轉譯本機物件以符合遠端畫面格轉譯。

最後,雖然 模擬更新 呼叫需要 OpenXR 慣例中的檢視欄位,但基於標準化和演算法安全考慮,您可以使用下列結構母體擴展範例中說明的轉換函式:

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

這些轉換函式可讓您根據本機轉譯的需求,快速切換檢視字段規格和純 4x4 透視投影矩陣。 這些轉換函式包含驗證邏輯,而且會傳回錯誤,而不會設定有效的結果,以防輸入投影矩陣或輸入檢視字段無效。

API 文件

下一步