圖形系結
若要能夠在自定義應用程式中使用 Azure 遠端轉譯,它必須整合到應用程式的轉譯管線中。 此整合是圖形系結的責任。
設定之後,圖形系結會存取影響轉譯影像的各種函式。 這些函式可以分成兩個類別:一律可用的一般函式,以及只與所選 Microsoft.Azure.RemoteRendering.GraphicsApiType
相關的特定函式。
Unity 中的圖形系結
在 Unity 中,整個系結是由 RemoteUnityClientInit
傳遞至 RemoteManagerUnity.InitializeManager
的結構來處理。 若要設定圖形模式, GraphicsApiType
欄位必須設定為選擇的系結。 視 XRDevice 是否存在而定,欄位會自動填入。 您可以使用下列行為手動覆寫行為:
- HoloLens 2: 根據使用中的 Unity XR 外掛程式,會使用 OpenXR 或 Windows Mixed Reality 圖形系結。
- 一般 UWP 傳統型應用程式: 一律會使用模擬 。
- Unity 編輯器: 除非已連接 WMR VR 頭戴式裝置,否則會一律使用模擬 ,在此情況下會停用 ARR,以允許偵錯應用程式的非 ARR 相關部分。 另 請參閱全像攝影遠端處理。
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 可以選取、 OpenXrD3D11
WmrD3D11
和 SimD3D11
。 第四個存在, 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_UNORM或DXGI_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
在另一個會話上呼叫。
轉譯迴圈更新
轉譯迴圈更新是由多個步驟所組成:
- 在進行任何轉譯之前,
GraphicsBindingSimD3d11.Update
會使用傳送至要轉譯之伺服器的目前相機轉換來呼叫每個畫面。 同時,傳回的 Proxy 轉換應該套用至 Proxy 相機,以轉譯成 Proxy 轉譯目標。 如果傳回的 Proxy 更新SimulationUpdate.frameId
為 Null,則尚未有遠端數據。 在此情況下,不應轉譯為 Proxy 轉譯目標,而是應該使用目前的相機數據直接轉譯到後台緩衝區,並略過後續兩個步驟。 - 應用程式現在應該繫結 Proxy 轉譯目標並呼叫
GraphicsBindingSimD3d11.BlitRemoteFrameToProxy
。 這會將遠端色彩和深度資訊填入 Proxy 轉譯目標。 現在可以使用 Proxy 相機轉換,將任何本機內容轉譯到 Proxy 上。 - 接下來,後端緩衝區必須系結為轉譯目標,並
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 | 用於目前遠端框架之投影矩陣的遠平面距離。 |
當啟用立體轉譯時,立體配對 ViewTransform
和 FieldOfView
允許設定兩個眼部相機值。 否則, 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 文件
- C# RemoteManagerStatic.StartupRemoteRendering()
- C# GraphicsBinding 類別
- C# GraphicsBindingWmrD3d11 類別
- C# GraphicsBindingSimD3d11 類別
- C++ RemoteRenderingInitialization 結構
- C++ GraphicsBinding 類別
- C++ GraphicsBindingWmrD3d11 類別
- C++ GraphicsBindingSimD3d11 類別