グラフィックスのバインド

カスタム アプリケーションで Azure Remote Rendering を使用できるようにするには、それをアプリケーションのレンダリング パイプラインに統合する必要があります。 この統合は、グラフィックスのバインドで行われます。

グラフィックスのバインドを設定すると、レンダリングされた画像に影響を与えるさまざまな関数にアクセスできるようになります。 これらの関数は、常に使用できる一般的な関数と、選択した Microsoft.Azure.RemoteRendering.GraphicsApiType にのみ関係する特定の関数の 2 つのカテゴリに分類できます。

Unity でのグラフィックスのバインド

Unity では、RemoteManagerUnity.InitializeManager に渡される RemoteUnityClientInit 構造体によってバインド全体が処理されます。 グラフィックス モードを設定するには、GraphicsApiType フィールドを、選択したバインドに設定する必要があります。 このフィールドは、XRDevice が存在するかどうかに応じて自動的に設定されます。 この動作は、次の動作で手動でオーバーライドできます。

  • HoloLens 2: アクティブな Unity XR プラグインに応じ、OpenXR または Windows Mixed Reality のグラフィックスのバインドが使用されます。
  • フラット UWP デスクトップ アプリ: 常にシミュレーションを使います。
  • Unity エディター: WMR VR ヘッドセットが接続されていない限り、常にシミュレーションを使います。その場合、ARR は無効になり、アプリケーションの ARR 関連以外の部分をデバッグできます。 Holographic Remoting もご覧ください。

Unity の他の唯一の関連部分は基本的なバインドにアクセスしているため、以下の他のすべてのセクションはスキップできます。

カスタム アプリケーションでのグラフィックスのバインドの設定

グラフィックスのバインドを選ぶには、次の 2 つの手順を実行します。まず、プログラムの初期化時に、グラフィックス バインドを静的に初期化する必要があります。

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

他の Remote Rendering API にアクセスする前に、上記の呼び出しを呼び出す必要があります。 同様に、他のすべての Remote Rendering オブジェクトの破棄後、対応する de-init 関数 RemoteManagerStatic.ShutdownRemoteRendering(); を呼び出す必要があります。 WMR では、任意のホログラフィック API が呼び出される前に StartupRemoteRendering も呼び出す必要があります。 OpenXR では、任意の OpenXR 関連の API にも同じことが当てはまります。

グラフィックスのバインドへのアクセス

クライアントがセットアップされると、RenderingSession.GraphicsBinding ゲッターを使用して基本的なグラフィックスのバインドにアクセスできます。 例として、最後のフレーム統計を次のように取得できます。

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

現在、OpenXrD3D11WmrD3D11 および SimD3D11 の 3 つのグラフィック API を選択できます。 4 つ目として Headless がありますが、クライアント側でまだサポートされていません。

OpenXR

GraphicsApiType.OpenXrD3D11 は、HoloLens 2 で実行される既定のバインドです。 これによって、GraphicsBindingOpenXrD3d11 バインドが作成されます。 このモードでは Azure Remote Rendering によって OpenXR API レイヤーが作成され、それ自体が OpenXR ランタイムに統合されます。

派生したグラフィックスのバインドにアクセスするには、ベースの GraphicsBinding をキャストする必要があります。 OpenXR バインドを使用するには、次の 3 つを行う必要があります。

カスタム OpenXR レイヤー json をパッケージ化する

OpenXR と共に Remote Rendering を使用するには、カスタム OpenXR API レイヤーをアクティブ化する必要があります。 これは、前のセクションで説明した StartupRemoteRendering を呼び出すことによって行われます。 ただし、前提条件として、XrApiLayer_msft_holographic_remoting.json をアプリケーションと一緒にパッケージ化して読み込む必要があります。 これは、"Microsoft.Azure.RemoteRendering.Cpp" NuGet パッケージがプロジェクトに追加されている場合、自動的に行われます。

使用されている XR Space を Remote Rendering に通知する

これは、リモートとローカルでレンダリングされたコンテンツをアラインするために必要です。

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 Remote Rendering が Holographic API に直接フックされます。

派生したグラフィックスのバインドにアクセスするには、ベースの GraphicsBinding をキャストする必要があります。 WMR のバインドを使用するには、次の 2 つの操作が必要です。

使用されている座標系を Remote Rendering に通知する

これは、リモートとローカルでレンダリングされたコンテンツをアラインするために必要です。

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 は、API の座標が表されるワールド空間座標系を定義するネイティブな ABI::Windows::Perception::Spatial::ISpatialCoordinateSystem オブジェクトへのポインターである必要があります。

リモート イメージをレンダリングする (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 グラフィックスのバインドを作成します。 このインターフェイスは、デスクトップ アプリケーションなどで頭部の移動をシミュレートするために使用され、モノスコピック画像をレンダリングします。

シミュレーションのバインドを実装するには、カメラのページで説明されているように、ローカル カメラとリモート フレームの違いを理解することが重要です。

2 つのカメラが必要です。

  • ローカル カメラ: このカメラは、アプリケーション ロジックによって駆動している現在のカメラ位置を表します。
  • プロキシ カメラ: このカメラは、サーバーから送信された現在のリモート フレームと一致します。 クライアントがフレームを要求してからその到着までに待機時間があるため、"リモート フレーム" は常にローカル カメラの動きより少し遅れます。

ここでの基本的なアプローチは、プロキシ カメラを使用してリモート画像とローカル コンテンツの両方をオフスクリーン ターゲットにレンダリングすることです。 次に、プロキシ画像がローカル カメラ領域に再投影されます。これについては、「Late Stage Reprojection」で詳しく説明します。

GraphicsApiType.SimD3D11 ではステレオスコピック レンダリングもサポートされています。これは、以下の InitSimulation 設定の呼び出し中に有効にする必要があります。 設定は少し複雑で、次のように機能します。

プロキシ レンダー ターゲットを作成する

リモート コンテンツとローカル コンテンツは、GraphicsBindingSimD3d11.Update 関数によって提供されるプロキシ カメラ データを使用して、'プロキシ' という画面外の色/深度のレンダー ターゲットにレンダリングする必要があります。

プロキシはバック バッファーの解像度と一致する必要があります。また、DXGI_FORMAT_R8G8B8A8_UNORM または DXGI_FORMAT_B8G8R8A8_UNORM 形式である必要があります。 ステレオスコピック レンダリングの場合、カラー プロキシ テクスチャと、深度の両方が使用されている場合は、深度プロキシ テクスチャに 1 つではなく 2 つの配列レイヤーが必要です。 セッションの準備ができたら、接続する前に 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);

初期化関数には、ネイティブな d3d デバイスへのポインターと、プロキシ レンダー ターゲットの色と深度テクスチャへのポインターが渡される必要があります。 初期化されたら、RenderingSession.ConnectAsyncDisconnect を何度も呼び出すことができますが、別のセッションに切り替えるときは、最初に古いセッションで GraphicsBindingSimD3d11.DeinitSimulation を呼び出してから、別のセッションで GraphicsBindingSimD3d11.InitSimulation を呼び出す必要があります。

ループの更新をレンダリングする

ループの更新のレンダリングは、次の複数の手順で構成されています。

  1. 各フレームで、レンダリングが行われる前に、レンダリング対象のサーバーに送信される現在のカメラ変換を使用して GraphicsBindingSimD3d11.Update が呼び出されます。 同時に、返されたプロキシ変換がプロキシ カメラに適用されて、プロキシ レンダー ターゲットにレンダリングできるようになります。 返されたプロキシ更新 SimulationUpdate.frameId が null の場合、リモート データはまだありません。 この場合、プロキシ レンダー ターゲットにレンダリングするのではなく、現在のカメラ データを使用してローカル コンテンツをバック バッファーに直接レンダリングする必要があり、次の 2 つの手順はスキップされます。
  2. アプリケーションでプロキシ レンダー ターゲットがバインドされ、GraphicsBindingSimD3d11.BlitRemoteFrameToProxy が呼び出されます。 これにより、リモートの色と深度の情報がプロキシ レンダー ターゲットに入力されます。 プロキシ カメラ変換を使用して、すべてのローカル コンテンツをプロキシにレンダリングできるようになりました。
  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
    ...
}

シミュレーション更新の構造

各フレーム (前のセクションの「ループの更新をレンダリングする」) にはローカル カメラに対応するカメラ パラメーターの範囲を入力する必要があります。これで、次に使用可能なフレームのカメラに対応するカメラのパラメーターのセットが返されます。 これらの 2 つのセットは、それぞれ SimulationUpdateParametersSimulationUpdateResult の構造体でキャプチャされます。

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

構造体メンバーには次の意味があります。

メンバー 説明
FrameId 連続フレーム識別子。 SimulationUpdateParameters の入力に必要であり、新しいフレームごとに継続的にインクリメントする必要があります。 フレーム データをまだ使用できない場合、SimulationUpdateResult は 0 になります。
ViewTransform フレームのカメラ ビューの変換行列の左右ステレオのペア。 モノスコピック レンダリングの場合、Left メンバーのみが有効です。
FieldOfView OpenXR 視野規則におけるフレーム カメラの視野の左右ステレオ ペア。 モノスコピック レンダリングの場合、Left メンバーのみが有効です。
NearPlaneDistance 現在のリモート フレームの射影行列に使用される前方面の距離。
FarPlaneDistance 現在のリモート フレームの射影行列に使用される後方面の距離。

ステレオペア ViewTransformFieldOfView を使用すると、ステレオスコピック レンダリングが有効な場合に、両方のアイカメラ値を設定できます。 そうしないと、Right メンバーは無視されます。 ご覧のとおり、射影行列が指定されていない場合、カメラの変換のみが平面の 4x4 変換行列として渡されます。 実際の行列は、指定された視野と、CameraSettings API で設定された現在の前方面と後方面を使用して、Azure Remote Rendering によって内部的に計算されます。

実行時に CameraSettings で前方面と後方面を必要に応じて変更することができ、サービスによってこれらの設定が非同期に適用されるので、各 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 ドキュメント

次のステップ