Bagikan melalui


Tutorial: Mengintegrasikan Remote Rendering ke Aplikasi Holografik HoloLens

Dalam tutorial ini, Anda akan mempelajari:

  • Menggunakan Visual Studio untuk membuat Aplikasi Holografik yang dapat disebarkan ke HoloLens
  • Menambahkan cuplikan kode dan pengaturan proyek yang diperlukan untuk menggabungkan penyajian lokal dengan konten yang disajikan dari jarak jauh

Tutorial ini berfokus pada penambahan bit yang diperlukan ke sampel asli Holographic App untuk menggabungkan rendering lokal dengan Azure Remote Rendering. Satu-satunya jenis umpan balik status di aplikasi ini adalah melalui panel output debug di dalam Visual Studio, sehingga disarankan untuk memulai sampel dari dalam Visual Studio. Menambahkan umpan balik dalam aplikasi yang tepat berada di luar lingkup sampel ini, karena membangun panel teks dinamis dari awal melibatkan banyak pengkodean. Titik awal yang baik adalah kelas StatusDisplay, yang merupakan bagian dari Remoting Proyek sampel player di GitHub. Bahkan, versi pra-kalengan dari tutorial ini menggunakan salinan lokal dari kelas itu.

Tip

Repositori sampel ARR berisi hasil tutorial ini sebagai proyek Visual Studio yang siap digunakan. Ini juga diperkaya dengan pelaporan kesalahan dan status yang tepat melalui kelas UI StatusDisplay. Di dalam tutorial, semua tambahan khusus ARR dicakup oleh #ifdef USE_REMOTE_RENDERING / #endif, sehingga mudah untuk mengidentifikasi penambahan Remote Rendering.

Prasyarat

Untuk tutorial ini, Anda perlu:

  • Informasi akun Anda (ID akun, kunci akun, domain akun, ID langganan). Jika Anda tidak memiliki akun, buat akun.
  • Windows SDK 10.0.18362.0 (unduh).
  • Versi terbaru Visual Studio 2022 (unduh).
  • Alat Visual Studio untuk Mixed Reality. Secara khusus, penginstalan Beban Kerja berikut wajib:
    • Pengembangan desktop dengan C++
    • Pengembangan Universal Windows Platform (UWP)
  • Templat Aplikasi Mixed Reality Windows untuk Visual Studio (unduh).

Membuat sampel Aplikasi Holografik baru

Sebagai langkah pertama, kami membuat sampel saham yang menjadi dasar integrasi Remote Rendering. Buka Visual Studio dan pilih "Buat proyek baru" dan cari "Aplikasi DirectX 11 Holografik (Windows Universal) (C++/WinRT)"

Buat proyek baru

Ketik nama proyek pilihan Anda, pilih jalur dan pilih tombol "Buat". Dalam proyek baru, alihkan konfigurasi ke "Debug / ARM64". Anda sekarang harus dapat mengompilasi dan menyebarkannya ke perangkat HoloLens 2 yang terhubung. Jika kau menjalankannya di HoloLens, kau akan melihat kubus berputar di depanmu.

Menambahkan dependensi Remote Rendering melalui NuGet

Langkah pertama untuk menambahkan kapabilitas Remote Rendering adalah menambahkan dependensi pihak klien. Dependensi yang relevan tersedia sebagai paket NuGet. Di Penjelajah Solusi, klik kanan pada proyek dan pilih "Kelola Paket NuGet..." dari menu konteks.

Dalam dialog yang diminta, telusuri paket NuGet bernama "Microsoft.Azure.RemoteRendering.Cpp":

Telusuri paket NuGet

dan tambahkan ke proyek dengan memilih paket dan kemudian menekan tombol "Instal".

Paket NuGet menambahkan dependensi Remote Rendering ke proyek. Khususnya:

  • Link terhadap pustaka klien (RemoteRenderingClient.lib).
  • Siapkan dependensi .dll baru.
  • Atur jalur yang benar ke cakupan direktori.

Persiapan proyek

Kita perlu membuat perubahan kecil pada proyek yang ada. Perubahan ini halus, tetapi tanpa mereka Remote Rendering tidak akan berfungsi.

Aktifkan perlindungan multithread pada perangkat DirectX

Perangkat DirectX11 harus mengaktifkan perlindungan multithread. Untuk mengubahnya, buka file DeviceResources.cpp di folder "Umum", dan masukkan kode berikut di akhir fungsi DeviceResources::CreateDeviceResources():

// Enable multi thread protection as now multiple threads use the immediate context.
Microsoft::WRL::ComPtr<ID3D11Multithread> contextMultithread;
if (context.As(&contextMultithread) == S_OK)
{
    contextMultithread->SetMultithreadProtected(true);
}

Mengaktifkan kapabilitas jaringan dalam manifes aplikasi

Kemampuan jaringan harus diaktifkan secara eksplisit untuk aplikasi yang disebarkan. Tanpa ini dikonfigurasi, kueri koneksi akan menghasilkan waktu habis pada akhirnya. Untuk mengaktifkan, klik dua kali pada item package.appxmanifest di penjelajah solusi. Di UI berikutnya, masuk ke tab Kapabilitas dan pilih:

  • Internet (Server & Klien)
  • Internet (Klien)

Kapabilitas jaringan

Integrasikan Remote Rendering

Sekarang proyek sudah disiapkan, kita bisa mulai dengan kodenya. Titik masuk yang baik ke dalam aplikasi adalah kelas HolographicAppMain(file HolographicAppMain.h/cpp) karena memiliki semua kait yang diperlukan untuk inisialisasi, de-inisialisasi, dan rendering.

Mencakup

Kita mulai dengan menambahkan cakupan yang diperlukan. Tambahkan yang berikut ini ke file HolographicAppMain.h:

#include <AzureRemoteRendering.h>

... dan arahan include tambahan ini untuk mengajukan HolographicAppMain.cpp:

#include <AzureRemoteRendering.inl>
#include <RemoteRenderingExtensions.h>
#include <windows.perception.spatial.h>

Untuk kesederhanaan kode, kami mendefinisikan pintasan namespace berikut di bagian atas file HolographicAppMain.h, setelah arahan include:

namespace RR = Microsoft::Azure::RemoteRendering;

Pintasan ini berguna sehingga kita tidak perlu menuliskan ruang nama lengkap di mana-mana tetapi masih dapat mengenali struktur data khusus ARR. Tentu saja, kita juga bisa menggunakan arahan using namespace....

Inisialisasi Remote Rendering

Kita perlu memegang beberapa objek untuk sesi dll selama masa pakai aplikasi. Seumur hidup bertepatan dengan masa pakai objek HolographicAppMain aplikasi, jadi kami menambahkan objek kami sebagai anggota ke kelas HolographicAppMain. Langkah selanjutnya adalah menambahkan anggota kelas berikut dalam file HolographicAppMain.h:

class HolographicAppMain
{
    ...
    // members:
    std::string m_sessionOverride;                // if we have a valid session ID, we specify it here. Otherwise a new one is created
    RR::ApiHandle<RR::RemoteRenderingClient> m_client;  // the client instance
    RR::ApiHandle<RR::RenderingSession> m_session;    // the current remote rendering session
    RR::ApiHandle<RR::RenderingConnection> m_api;       // the API instance, that is used to perform all the actions. This is just a shortcut to m_session->Connection()
    RR::ApiHandle<RR::GraphicsBindingWmrD3d11> m_graphicsBinding; // the graphics binding instance
}

Tempat yang baik untuk melakukan implementasi aktual adalah konstruktor kelas HolographicAppMain. Kita harus melakukan tiga jenis inisialisasi di sana:

  1. Inisialisasi satu kali sistem Remote Rendering
  2. Pembuatan klien (autentikasi)
  3. Pembuatan sesi

Kami melakukan semua itu secara berurutan di konstruktor. Namun, dalam kasus penggunaan nyata mungkin tepat untuk melakukan langkah-langkah ini secara terpisah.

Tambahkan kode berikut ke awal badan konstruktor dalam file HolographicAppMain.cpp:

HolographicAppMain::HolographicAppMain(std::shared_ptr<DX::DeviceResources> const& deviceResources) :
    m_deviceResources(deviceResources)
{
    // 1. One time initialization
    {
        RR::RemoteRenderingInitialization clientInit;
        clientInit.ConnectionType = RR::ConnectionType::General;
        clientInit.GraphicsApi = RR::GraphicsApiType::WmrD3D11;
        clientInit.ToolId = "<sample name goes here>"; // <put your sample name here>
        clientInit.UnitsPerMeter = 1.0f;
        clientInit.Forward = RR::Axis::NegativeZ;
        clientInit.Right = RR::Axis::X;
        clientInit.Up = RR::Axis::Y;
        if (RR::StartupRemoteRendering(clientInit) != RR::Result::Success)
        {
            // something fundamental went wrong with the initialization
            throw std::exception("Failed to start remote rendering. Invalid client init data.");
        }
    }


    // 2. Create Client
    {
        // Users need to fill out the following with their account data and model
        RR::SessionConfiguration init;
        init.AccountId = "00000000-0000-0000-0000-000000000000";
        init.AccountKey = "<account key>";
        init.RemoteRenderingDomain = "westus2.mixedreality.azure.com"; // <change to the region that the rendering session should be created in>
        init.AccountDomain = "westus2.mixedreality.azure.com"; // <change to the region the account was created in>
        m_modelURI = "builtin://Engine";
        m_sessionOverride = ""; // If there is a valid session ID to re-use, put it here. Otherwise a new one is created
        m_client = RR::ApiHandle(RR::RemoteRenderingClient(init));
    }

    // 3. Open/create rendering session
    {
        auto SessionHandler = [&](RR::Status status, RR::ApiHandle<RR::CreateRenderingSessionResult> result)
        {
            if (status == RR::Status::OK)
            {
                auto ctx = result->GetContext();
                if (ctx.Result == RR::Result::Success)
                {
                    SetNewSession(result->GetSession());
                }
                else
                {
                    SetNewState(AppConnectionStatus::ConnectionFailed, ctx.ErrorMessage.c_str());
                }
            }
            else
            {
                SetNewState(AppConnectionStatus::ConnectionFailed, "failed");
            }
        };

        // If we had an old (valid) session that we can recycle, we call async function m_client->OpenRenderingSessionAsync
        if (!m_sessionOverride.empty())
        {
            m_client->OpenRenderingSessionAsync(m_sessionOverride, SessionHandler);
            SetNewState(AppConnectionStatus::CreatingSession, nullptr);
        }
        else
        {
            // create a new session
            RR::RenderingSessionCreationOptions init;
            init.MaxLeaseInMinutes = 10; // session is leased for 10 minutes
            init.Size = RR::RenderingSessionVmSize::Standard;
            m_client->CreateNewRenderingSessionAsync(init, SessionHandler);
            SetNewState(AppConnectionStatus::CreatingSession, nullptr);
        }
    }

    // Rest of constructor code:
    ...
}

Kode memanggil fungsi anggota SetNewSession dan SetNewState, yang akan kita terapkan dalam paragraf berikutnya bersama dengan sisa kode mesin negara.

Perhatikan bahwa kredensial dikodekan dengan keras dalam sampel dan perlu diisi di tempat (ID akun, kunci akun, domain akun, dan domain penyajian jarak jauh).

Kami melakukan de-inisialisasi secara simetris dan dalam urutan terbalik di ujung tubuh destruktor:

HolographicAppMain::~HolographicAppMain()
{
    // Existing destructor code:
    ...
    
    // Destroy session:
    if (m_session != nullptr)
    {
        m_session->Disconnect();
        m_session = nullptr;
    }

    // Destroy front end:
    m_client = nullptr;

    // One-time de-initialization:
    RR::ShutdownRemoteRendering();
}

Mesin negara bagian

Pada Remote Rendering, fungsi kunci untuk membuat sesi dan memuat model adalah fungsi asinkron. Untuk memperhitungkan hal ini, kita membutuhkan mesin status sederhana yang pada dasarnya bertransisi melalui negara-negara berikut secara otomatis:

Inisialisasi -> Pembuatan sesi -> Sesi dimulai -> Pemuatan model (dengan kemajuan)

Dengan demikian, sebagai langkah selanjutnya, kami menambahkan sedikit penanganan mesin negara ke kelas. Kami menyatakan enum kami sendiri AppConnectionStatus untuk berbagai negara bagian bahwa aplikasi kami dapat masuk Ini mirip dengan RR::ConnectionStatus, tetapi memiliki status tambahan untuk koneksi yang gagal.

Tambahkan anggota dan fungsi berikut ke deklarasi kelas:

namespace HolographicApp
{
    // Our application's possible states:
    enum class AppConnectionStatus
    {
        Disconnected,

        CreatingSession,
        StartingSession,
        Connecting,
        Connected,

        // error state:
        ConnectionFailed,
    };

    class HolographicAppMain
    {
        ...
        // Member functions for state transition handling
        void OnConnectionStatusChanged(RR::ConnectionStatus status, RR::Result error);
        void SetNewState(AppConnectionStatus state, const char* statusMsg);
        void SetNewSession(RR::ApiHandle<RR::RenderingSession> newSession);
        void StartModelLoading();

        // Members for state handling:

        // Model loading:
        std::string m_modelURI;
        RR::ApiHandle<RR::LoadModelAsync> m_loadModelAsync;

        // Connection state machine:
        AppConnectionStatus m_currentStatus = AppConnectionStatus::Disconnected;
        std::string m_statusMsg;
        RR::Result m_connectionResult = RR::Result::Success;
        RR::Result m_modelLoadResult = RR::Result::Success;
        bool m_isConnected = false;
        bool m_sessionStarted = false;
        RR::ApiHandle<RR::SessionPropertiesAsync> m_sessionPropertiesAsync;
        bool m_modelLoadTriggered = false;
        float m_modelLoadingProgress = 0.f;
        bool m_modelLoadFinished = false;
        double m_timeAtLastRESTCall = 0;
        bool m_needsCoordinateSystemUpdate = true;
    }

Di sisi implementasi dalam file .cpp, tambahkan badan fungsi ini:

void HolographicAppMain::StartModelLoading()
{
    m_modelLoadingProgress = 0.f;

    RR::LoadModelFromSasOptions options;
    options.ModelUri = m_modelURI.c_str();
    options.Parent = nullptr;

    // start the async model loading
    m_api->LoadModelFromSasAsync(options,
        // completed callback
        [this](RR::Status status, RR::ApiHandle<RR::LoadModelResult> result)
        {
            m_modelLoadResult = RR::StatusToResult(status);
            m_modelLoadFinished = true;

            if (m_modelLoadResult == RR::Result::Success)
            {
                RR::Double3 pos = { 0.0, 0.0, -2.0 };
                result->GetRoot()->SetPosition(pos);
            }
        },
        // progress update callback
            [this](float progress)
        {
            // progress callback
            m_modelLoadingProgress = progress;
            m_needsStatusUpdate = true;
        });
}



void HolographicAppMain::SetNewState(AppConnectionStatus state, const char* statusMsg)
{
    m_currentStatus = state;
    m_statusMsg = statusMsg ? statusMsg : "";

    // Some log for the VS output panel:
    const char* appStatus = nullptr;

    switch (state)
    {
        case AppConnectionStatus::Disconnected: appStatus = "Disconnected"; break;
        case AppConnectionStatus::CreatingSession: appStatus = "CreatingSession"; break;
        case AppConnectionStatus::StartingSession: appStatus = "StartingSession"; break;
        case AppConnectionStatus::Connecting: appStatus = "Connecting"; break;
        case AppConnectionStatus::Connected: appStatus = "Connected"; break;
        case AppConnectionStatus::ConnectionFailed: appStatus = "ConnectionFailed"; break;
    }

    char buffer[1024];
    sprintf_s(buffer, "Remote Rendering: New status: %s, result: %s\n", appStatus, m_statusMsg.c_str());
    OutputDebugStringA(buffer);
}

void HolographicAppMain::SetNewSession(RR::ApiHandle<RR::RenderingSession> newSession)
{
    SetNewState(AppConnectionStatus::StartingSession, nullptr);

    m_sessionStartingTime = m_timeAtLastRESTCall = m_timer.GetTotalSeconds();
    m_session = newSession;
    m_api = m_session->Connection();
    m_graphicsBinding = m_session->GetGraphicsBinding().as<RR::GraphicsBindingWmrD3d11>();
    m_session->ConnectionStatusChanged([this](auto status, auto error)
        {
            OnConnectionStatusChanged(status, error);
        });

};

void HolographicAppMain::OnConnectionStatusChanged(RR::ConnectionStatus status, RR::Result error)
{
    const char* asString = RR::ResultToString(error);
    m_connectionResult = error;

    switch (status)
    {
    case RR::ConnectionStatus::Connecting:
        SetNewState(AppConnectionStatus::Connecting, asString);
        break;
    case RR::ConnectionStatus::Connected:
        if (error == RR::Result::Success)
        {
            SetNewState(AppConnectionStatus::Connected, asString);
        }
        else
        {
            SetNewState(AppConnectionStatus::ConnectionFailed, asString);
        }
        m_modelLoadTriggered = m_modelLoadFinished = false;
        m_isConnected = error == RR::Result::Success;
        break;
    case RR::ConnectionStatus::Disconnected:
        if (error == RR::Result::Success)
        {
            SetNewState(AppConnectionStatus::Disconnected, asString);
        }
        else
        {
            SetNewState(AppConnectionStatus::ConnectionFailed, asString);
        }
        m_modelLoadTriggered = m_modelLoadFinished = false;
        m_isConnected = false;
        break;
    default:
        break;
    }
    
}

Pembaruan per bingkai

Kita harus memperbarui klien sekali per simulasi tick dan melakukan beberapa pembaruan status tambahan. Fungsi HolographicAppMain::Update menyediakan kait yang baik untuk pembaruan per bingkai.

Pembaruan mesin status

Kita perlu polling status sesi dan melihat apakah telah bertransisi ke negara bagian Ready. Jika kita telah berhasil terhubung, kita akhirnya memulai pemuatan model melalui StartModelLoading.

Tambahkan kode berikut ke isi fungsi HolographicAppMain::Update:

// Updates the application state once per frame.
HolographicFrame HolographicAppMain::Update()
{
    if (m_session != nullptr)
    {
        // Tick the client to receive messages
        m_api->Update();

        if (!m_sessionStarted)
        {
            // Important: To avoid server-side throttling of the requests, we should call GetPropertiesAsync very infrequently:
            const double delayBetweenRESTCalls = 10.0;

            // query session status periodically until we reach 'session started'
            if (m_sessionPropertiesAsync == nullptr && m_timer.GetTotalSeconds() - m_timeAtLastRESTCall > delayBetweenRESTCalls)
            {
                m_timeAtLastRESTCall = m_timer.GetTotalSeconds();
                m_session->GetPropertiesAsync([this](RR::Status status, RR::ApiHandle<RR::RenderingSessionPropertiesResult> propertiesResult)
                    {
                        if (status == RR::Status::OK)
                        {
                            auto ctx = propertiesResult->GetContext();
                            if (ctx.Result == RR::Result::Success)
                            {
                                auto res = propertiesResult->GetSessionProperties();
                                switch (res.Status)
                                {
                                case RR::RenderingSessionStatus::Ready:
                                {
                                    // The following ConnectAsync is async, but we'll get notifications via OnConnectionStatusChanged
                                    m_sessionStarted = true;
                                    SetNewState(AppConnectionStatus::Connecting, nullptr);
                                    RR::RendererInitOptions init;
                                    init.IgnoreCertificateValidation = false;
                                    init.RenderMode = RR::ServiceRenderMode::Default;
                                    m_session->ConnectAsync(init, [](RR::Status, RR::ConnectionStatus) {});
                                }
                                break;
                                case RR::RenderingSessionStatus::Error:
                                    SetNewState(AppConnectionStatus::ConnectionFailed, "Session error");
                                    break;
                                case RR::RenderingSessionStatus::Stopped:
                                    SetNewState(AppConnectionStatus::ConnectionFailed, "Session stopped");
                                    break;
                                case RR::RenderingSessionStatus::Expired:
                                    SetNewState(AppConnectionStatus::ConnectionFailed, "Session expired");
                                    break;
                                }
                            }
                            else
                            {
                                SetNewState(AppConnectionStatus::ConnectionFailed, ctx.ErrorMessage.c_str());
                            }
                        }
                        else
                        {
                            SetNewState(AppConnectionStatus::ConnectionFailed, "Failed to retrieve session status");
                        }
                        m_sessionPropertiesQueryInProgress = false; // next try
                    });                }
            }
        }
        if (m_isConnected && !m_modelLoadTriggered)
        {
            m_modelLoadTriggered = true;
            StartModelLoading();
        }
    }

    if (m_needsCoordinateSystemUpdate && m_stationaryReferenceFrame && m_graphicsBinding)
    {
        // Set the coordinate system once. This must be called again whenever the coordinate system changes.
        winrt::com_ptr<ABI::Windows::Perception::Spatial::ISpatialCoordinateSystem> ptr{ m_stationaryReferenceFrame.CoordinateSystem().as<ABI::Windows::Perception::Spatial::ISpatialCoordinateSystem>() };
        m_graphicsBinding->UpdateUserCoordinateSystem(ptr.get());
        m_needsCoordinateSystemUpdate = false;
    }

    // Rest of the body:
    ...
}

Mengkoordinasikan pembaruan sistem

Kita perlu setuju dengan layanan rendering pada sistem koordinat untuk digunakan. Untuk mengakses sistem koordinat yang ingin kita gunakan, kita membutuhkan m_stationaryReferenceFrame yang dibuat di akhir fungsi HolographicAppMain::OnHolographicDisplayIsAvailableChanged.

Sistem koordinat ini biasanya tidak berubah, jadi ini adalah inisialisasi satu kali. Ini harus dipanggil lagi jika aplikasi Anda mengubah sistem koordinat.

Kode di atas mengatur sistem koordinat sekali dalam fungsi Update segera setelah kita berdua memiliki sistem koordinat referensi dan sesi yang terhubung.

Pembaruan kamera

Kita perlu memperbarui pesawat klip kamera sehingga kamera server tetap sinkron dengan kamera lokal. Kita dapat melakukannya di akhir fungsi Update:

    ...
    if (m_isConnected)
    {
        // Any near/far plane values of your choosing.
        constexpr float fNear = 0.1f;
        constexpr float fFar = 10.0f;
        for (HolographicCameraPose const& cameraPose : prediction.CameraPoses())
        {
            // Set near and far to the holographic camera as normal
            cameraPose.HolographicCamera().SetNearPlaneDistance(fNear);
            cameraPose.HolographicCamera().SetFarPlaneDistance(fFar);
        }

        // The API to inform the server always requires near < far. Depth buffer data will be converted locally to match what is set on the HolographicCamera.
        auto settings = m_api->GetCameraSettings();
        settings->SetNearAndFarPlane(std::min(fNear, fFar), std::max(fNear, fFar));
        settings->SetEnableDepth(true);
    }

    // The holographic frame will be used to get up-to-date view and projection matrices and
    // to present the swap chain.
    return holographicFrame;
}

Rendering

Hal terakhir yang harus dilakukan adalah memanggil penyajian konten jarak jauh. Kita harus melakukan panggilan ini di posisi yang tepat dalam saluran rendering, setelah target render jelas dan mengatur viewport. Masukkan cuplikan berikut ke dalam penguncian UseHolographicCameraResources di dalam fungsi HolographicAppMain::Render:

        ...
        // Existing clear function:
        context->ClearDepthStencilView(depthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
        
        // ...

        // Existing check to test for valid camera:
        bool cameraActive = pCameraResources->AttachViewProjectionBuffer(m_deviceResources);


        // Inject remote rendering: as soon as we are connected, start blitting the remote frame.
        // We do the blitting after the Clear, and before cube rendering.
        if (m_isConnected && cameraActive)
        {
            m_graphicsBinding->BlitRemoteFrame();
        }

        ...

Jalankan sampel

Sampel sekarang harus dalam keadaan di mana ia mengkompilasi dan berjalan.

Ketika sampel berjalan dengan benar, itu menunjukkan kubus berputar tepat di depan Anda, dan setelah beberapa pembuatan sesi dan pemuatan model, itu membuat model mesin terletak pada posisi kepala saat ini. Pembuatan sesi dan pemuatan model mungkin memakan waktu hingga beberapa menit. Status saat ini hanya ditulis ke panel output Visual Studio. Dengan demikian disarankan untuk memulai sampel dari dalam Visual Studio.

Perhatian

Klien terputus dari server ketika fungsi kutu tidak dipanggil selama beberapa detik. Jadi memicu breakpoint dapat dengan mudah menyebabkan aplikasi terputus.

Untuk tampilan status yang tepat dengan panel teks, lihat versi pra-kaleng tutorial ini di GitHub.

Langkah berikutnya

Dalam tutorial ini, Anda mempelajari semua langkah yang diperlukan untuk menambahkan Remote Rendering ke sampel Aplikasi Holografik C++/DirectX11 saham. Untuk mengonversi model Anda sendiri, lihat mulai cepat berikut: