Bagikan melalui


Kerangka kerja penyajian II: Penyajian game

Catatan

Topik ini adalah bagian dari membuat permainan Platform Windows Universal sederhana (UWP) dengan seri tutorial DirectX. Topik di tautan tersebut mengatur konteks untuk seri.

Dalam Rendering framework I, kami telah membahas bagaimana kami mengambil info adegan dan menyajikannya ke layar tampilan. Sekarang, kita akan mengambil langkah mundur dan mempelajari cara menyiapkan data untuk penyajian.

Catatan

Jika Anda belum mengunduh kode game terbaru untuk sampel ini, buka game sampel Direct3D. Sampel ini adalah bagian dari koleksi besar sampel fitur UWP. Untuk petunjuk tentang cara mengunduh sampel, lihat Contoh aplikasi untuk pengembangan Windows.

Tujuan

Rekap cepat pada tujuan. Hal ini untuk memahami cara menyiapkan kerangka kerja penyajian dasar untuk menampilkan output grafis untuk game UWP DirectX. Kita dapat mengelompokkannya secara longgar ke dalam tiga langkah ini.

  1. Membuat koneksi ke antarmuka grafis kami
  2. Persiapan: Buat sumber daya yang kita butuhkan untuk menggambar grafik
  3. Menampilkan grafik: Merender bingkai

Merender kerangka kerja I: Penganut penyajian menjelaskan bagaimana grafik dirender, mencakup Langkah 1 dan 3.

Artikel ini menjelaskan cara menyiapkan bagian lain dari kerangka kerja ini dan menyiapkan data yang diperlukan sebelum penyajian dapat terjadi, yaitu Langkah 2 dari proses.

Mendesain perender

Perender bertanggung jawab untuk membuat dan memelihara semua objek D3D11 dan D2D yang digunakan untuk menghasilkan visual game. Kelas GameRenderer adalah perender untuk permainan sampel ini dan dirancang untuk memenuhi kebutuhan penyajian game.

Ini adalah beberapa konsep yang dapat Anda gunakan untuk membantu merancang perender untuk game Anda:

  • Karena API Direct3D 11 didefinisikan sebagai API COM , Anda harus memberikan referensi ComPtr ke objek yang ditentukan oleh API ini. Objek-objek ini secara otomatis dibesarkan ketika referensi terakhir mereka keluar dari cakupan saat aplikasi berakhir. Untuk informasi selengkapnya, lihat ComPtr. Contoh objek ini: buffer konstan, objek shader - shader vertex, pixel shader, dan objek sumber daya shader.
  • Buffer konstan didefinisikan dalam kelas ini untuk menyimpan berbagai data yang diperlukan untuk penyajian.
    • Gunakan beberapa buffer konstanta dengan frekuensi yang berbeda untuk mengurangi jumlah data yang harus dikirim ke GPU per bingkai. Sampel ini memisahkan konstanta menjadi buffer yang berbeda berdasarkan frekuensi yang harus diperbarui. Ini adalah praktik terbaik untuk pemrograman Direct3D.
    • Dalam permainan sampel ini, 4 buffer konstan didefinisikan.
      1. m_constantBufferNeverChanges berisi parameter pencahayaan. Ini diatur satu kali dalam metode FinalizeCreateGameDeviceResources dan tidak pernah berubah lagi.
      2. m_constantBufferChangeOnResize berisi matriks proyeksi. Matriks proyeksi tergantung pada ukuran dan rasio aspek jendela. Ini diatur dalam CreateWindowSizeDependentResources dan kemudian diperbarui setelah sumber daya dimuat dalam metode FinalizeCreateGameDeviceResources. Jika penyajian dalam 3D, rendering juga diubah dua kali per bingkai.
      3. m_constantBufferChangesEveryFrame berisi matriks tampilan. Matriks ini tergantung pada posisi kamera dan arah tampilan (normal ke proyeksi) dan berubah satu kali per bingkai dalam metode Render . Ini dibahas sebelumnya dalam Rendering framework I: Intro to rendering, di bawah metode GameRenderer::Render.
      4. m_constantBufferChangesEveryPrim berisi sifat matriks model dan material dari setiap primitif. Matriks model mengubah simpul dari koordinat lokal menjadi koordinat dunia. Konstanta ini khusus untuk setiap primitif dan diperbarui untuk setiap panggilan gambar. Ini dibahas sebelumnya dalam Rendering framework I: Intro to rendering, di bawah penyajian Primitif.
  • Objek sumber daya shader yang menyimpan tekstur untuk primitif juga didefinisikan dalam kelas ini.
    • Beberapa tekstur telah ditentukan sebelumnya (DDS adalah format file yang dapat digunakan untuk menyimpan tekstur terkompresi dan tidak terkompresi. Tekstur DDS digunakan untuk dinding dan lantai dunia serta bola amunisi.)
    • Dalam permainan sampel ini, objek sumber daya shader adalah: m_sphereTexture, m_cylinderTexture, m_ceilingTexture, m_floorTexture, m_wallsTexture.
  • Objek shader didefinisikan dalam kelas ini untuk menghitung primitif dan tekstur kita.
    • Dalam permainan sampel ini, objek shader m_vertexShader, m_vertexShaderFlat, dan m_pixelShader, m_pixelShaderFlat.
    • Shader vertex memproses primitif dan pencahayaan dasar, dan shader piksel (kadang-kadang disebut shader fragmen) memproses tekstur dan efek per piksel.
    • Ada dua versi shader ini (reguler dan datar) untuk merender primitif yang berbeda. Alasan kita memiliki versi yang berbeda adalah bahwa versi datar jauh lebih sederhana dan tidak melakukan sorotan spekular atau efek pencahayaan per piksel. Ini digunakan untuk dinding dan membuat penyajian lebih cepat pada perangkat bertenaga lebih rendah.

GameRenderer.h

Sekarang mari kita lihat kode dalam objek kelas penyaji permainan sampel.

// Class handling the rendering of the game
class GameRenderer : public std::enable_shared_from_this<GameRenderer>
{
public:
    GameRenderer(std::shared_ptr<DX::DeviceResources> const& deviceResources);

    void CreateDeviceDependentResources();
    void CreateWindowSizeDependentResources();
    void ReleaseDeviceDependentResources();
    void Render();
    // --- end of async related methods section

    winrt::Windows::Foundation::IAsyncAction CreateGameDeviceResourcesAsync(_In_ std::shared_ptr<Simple3DGame> game);
    void FinalizeCreateGameDeviceResources();
    winrt::Windows::Foundation::IAsyncAction LoadLevelResourcesAsync();
    void FinalizeLoadLevelResources();

    Simple3DGameDX::IGameUIControl* GameUIControl() { return &m_gameInfoOverlay; };

    DirectX::XMFLOAT2 GameInfoOverlayUpperLeft()
    {
        return DirectX::XMFLOAT2(m_gameInfoOverlayRect.left, m_gameInfoOverlayRect.top);
    };
    DirectX::XMFLOAT2 GameInfoOverlayLowerRight()
    {
        return DirectX::XMFLOAT2(m_gameInfoOverlayRect.right, m_gameInfoOverlayRect.bottom);
    };
    bool GameInfoOverlayVisible() { return m_gameInfoOverlay.Visible(); }
    // --- end of rendering overlay section
...
private:
    // Cached pointer to device resources.
    std::shared_ptr<DX::DeviceResources>        m_deviceResources;

    ...

    // Shader resource objects
    winrt::com_ptr<ID3D11ShaderResourceView>    m_sphereTexture;
    winrt::com_ptr<ID3D11ShaderResourceView>    m_cylinderTexture;
    winrt::com_ptr<ID3D11ShaderResourceView>    m_ceilingTexture;
    winrt::com_ptr<ID3D11ShaderResourceView>    m_floorTexture;
    winrt::com_ptr<ID3D11ShaderResourceView>    m_wallsTexture;

    // Constant buffers
    winrt::com_ptr<ID3D11Buffer>                m_constantBufferNeverChanges;
    winrt::com_ptr<ID3D11Buffer>                m_constantBufferChangeOnResize;
    winrt::com_ptr<ID3D11Buffer>                m_constantBufferChangesEveryFrame;
    winrt::com_ptr<ID3D11Buffer>                m_constantBufferChangesEveryPrim;

    // Texture sampler
    winrt::com_ptr<ID3D11SamplerState>          m_samplerLinear;

    // Shader objects: Vertex shaders and pixel shaders
    winrt::com_ptr<ID3D11VertexShader>          m_vertexShader;
    winrt::com_ptr<ID3D11VertexShader>          m_vertexShaderFlat;
    winrt::com_ptr<ID3D11PixelShader>           m_pixelShader;
    winrt::com_ptr<ID3D11PixelShader>           m_pixelShaderFlat;
    winrt::com_ptr<ID3D11InputLayout>           m_vertexLayout;
};

Konstruktor

Selanjutnya, mari kita periksa konstruktor GameRenderer game sampel dan membandingkannya dengan konstruktor Sample3DSceneRenderer yang disediakan dalam templat Aplikasi DirectX 11.

// Constructor method of the main rendering class object
GameRenderer::GameRenderer(std::shared_ptr<DX::DeviceResources> const& deviceResources) : ...
    m_gameInfoOverlay(deviceResources),
    m_gameHud(deviceResources, L"Windows platform samples", L"DirectX first-person game sample")
{
    // m_gameInfoOverlay is a GameHud object to render text in the top left corner of the screen.
    // m_gameHud is Game info rendered as an overlay on the top-right corner of the screen,
    // for example hits, shots, and time.

    CreateDeviceDependentResources();
    CreateWindowSizeDependentResources();
}

Membuat dan memuat sumber daya grafis DirectX

Dalam contoh game (dan di templat Aplikasi DirectX 11 Visual Studio (Universal Windows ), membuat dan memuat sumber daya game diimplementasikan menggunakan dua metode ini yang dipanggil dari konstruktor GameRenderer :

Metode CreateDeviceDependentResources

Dalam templat Aplikasi DirectX 11, metode ini digunakan untuk memuat vertex dan piksel shader secara asinkron, membuat shader dan buffer konstan, membuat jala dengan simpul yang berisi info posisi dan warna.

Dalam permainan sampel, operasi objek adegan ini sebaliknya dibagi di antara metode CreateGameDeviceResourcesAsync dan FinalizeCreateGameDeviceResources.

Untuk permainan sampel ini, apa yang masuk ke metode ini?

  • Variabel yang diinstansiasi (m_gameResourcesLoaded = false dan m_levelResourcesLoaded = false) yang menunjukkan apakah sumber daya telah dimuat sebelum melanjutkan ke render, karena kami memuatnya secara asinkron.
  • Karena PENYAJIAN HUD dan overlay berada di objek kelas terpisah, panggil metode GameHud::CreateDeviceDependentResources dan GameInfoOverlay::CreateDeviceDependentResources di sini.

Berikut adalah kode untuk GameRenderer::CreateDeviceDependentResources.

// This method is called in GameRenderer constructor when it's created in GameMain constructor.
void GameRenderer::CreateDeviceDependentResources()
{
    // instantiate variables that indicate whether resources were loaded.
    m_gameResourcesLoaded = false;
    m_levelResourcesLoaded = false;

    // game HUD and overlay are design as separate class objects.
    m_gameHud.CreateDeviceDependentResources();
    m_gameInfoOverlay.CreateDeviceDependentResources();
}

Di bawah ini adalah daftar metode yang digunakan untuk membuat dan memuat sumber daya.

  • CreateDeviceDependentResources
    • CreateGameDeviceResourcesAsync (Ditambahkan)
    • FinalizeCreateGameDeviceResources (Ditambahkan)
  • CreateWindowSizeDependentResources

Sebelum menyelam ke metode lain yang digunakan untuk membuat dan memuat sumber daya, mari kita buat perender terlebih dahulu dan lihat bagaimana cocoknya dengan perulangan game.

Membuat perender

GameRenderer dibuat di konstruktor GameMain. Ini juga memanggil dua metode lain, CreateGameDeviceResourcesAsync dan FinalizeCreateGameDeviceResources yang ditambahkan untuk membantu membuat dan memuat sumber daya.

GameMain::GameMain(std::shared_ptr<DX::DeviceResources> const& deviceResources) : ...
{
    m_deviceResources->RegisterDeviceNotify(this);

    // Creation of GameRenderer
    m_renderer = std::make_shared<GameRenderer>(m_deviceResources);

    ...

    ConstructInBackground();
}

winrt::fire_and_forget GameMain::ConstructInBackground()
{
    ...

    // Asynchronously initialize the game class and load the renderer device resources.
    // By doing all this asynchronously, the game gets to its main loop more quickly
    // and in parallel all the necessary resources are loaded on other threads.
    m_game->Initialize(m_controller, m_renderer);

    co_await m_renderer->CreateGameDeviceResourcesAsync(m_game);

    // The finalize code needs to run in the same thread context
    // as the m_renderer object was created because the D3D device context
    // can ONLY be accessed on a single thread.
    // co_await of an IAsyncAction resumes in the same thread context.
    m_renderer->FinalizeCreateGameDeviceResources();

    InitializeGameState();

    ...
}

Metode CreateGameDeviceResourcesAsync

CreateGameDeviceResourcesAsync dipanggil dari metode konstruktor GameMain dalam perulangan create_task karena kami memuat sumber daya game secara asinkron.

CreateDeviceResourcesAsync adalah metode yang berjalan sebagai serangkaian tugas asinkron terpisah untuk memuat sumber daya game. Karena diharapkan berjalan pada utas terpisah, itu hanya memiliki akses ke metode perangkat Direct3D 11 (yang ditentukan pada ID3D11Device) dan bukan metode konteks perangkat (metode yang ditentukan pada ID3D11DeviceContext), sehingga tidak melakukan penyajian apa pun.

Metode FinalizeCreateGameDeviceResources berjalan pada utas utama dan memang memiliki akses ke metode konteks perangkat Direct3D 11.

Pada prinsipnya:

  • Gunakan hanya metode ID3D11Device di CreateGameDeviceResourcesAsync karena metode tersebut bebas utas, yang berarti mereka dapat berjalan pada utas apa pun. Diharapkan juga bahwa mereka tidak berjalan pada utas yang sama dengan yang dibuat GameRenderer .
  • Jangan gunakan metode di ID3D11DeviceContext di sini karena mereka perlu berjalan pada satu utas dan pada utas yang sama dengan GameRenderer.
  • Gunakan metode ini untuk membuat buffer konstanta.
  • Gunakan metode ini untuk memuat tekstur (seperti file .dds) dan info shader (seperti file .cso) ke dalam shader.

Metode ini digunakan untuk:

  • Buat 4 buffer konstan: m_constantBufferNeverChanges, m_constantBufferChangeOnResize, m_constantBufferChangesEveryFrame, m_constantBufferChangesEveryPrim
  • Membuat objek sampler-state yang merangkum informasi pengambilan sampel untuk tekstur
  • Buat grup tugas yang berisi semua tugas asinkron yang dibuat oleh metode . Ini menunggu penyelesaian semua tugas asinkron ini, lalu memanggil FinalizeCreateGameDeviceResources.
  • Buat loader menggunakan Basic Loader. Tambahkan operasi pemuatan asinkron loader sebagai tugas ke dalam grup tugas yang dibuat sebelumnya.
  • Metode seperti BasicLoader::LoadShaderAsync dan BasicLoader::LoadTextureAsync digunakan untuk memuat:
    • objek shader yang dikompilasi (VertextShader.cso, VertexShaderFlat.cso, PixelShader.cso, dan PixelShaderFlat.cso). Untuk informasi selengkapnya, buka Berbagai format file shader.
    • tekstur spesifik game (Aset\seafloor.dds, metal_texture.dds, cellceiling.dds, cellfloor.dds, cellwall.dds).
IAsyncAction GameRenderer::CreateGameDeviceResourcesAsync(_In_ std::shared_ptr<Simple3DGame> game)
{
    auto lifetime = shared_from_this();

    // Create the device dependent game resources.
    // Only the d3dDevice is used in this method. It is expected
    // to not run on the same thread as the GameRenderer was created.
    // Create methods on the d3dDevice are free-threaded and are safe while any methods
    // in the d3dContext should only be used on a single thread and handled
    // in the FinalizeCreateGameDeviceResources method.
    m_game = game;

    auto d3dDevice = m_deviceResources->GetD3DDevice();

    // Define D3D11_BUFFER_DESC. See
    // https://learn.microsoft.com/windows/win32/api/d3d11/ns-d3d11-d3d11_buffer_desc
    D3D11_BUFFER_DESC bd;
    ZeroMemory(&bd, sizeof(bd));

    // Create the constant buffers.
    bd.Usage = D3D11_USAGE_DEFAULT;
    ...

    // Create the constant buffers: m_constantBufferNeverChanges, m_constantBufferChangeOnResize,
    // m_constantBufferChangesEveryFrame, m_constantBufferChangesEveryPrim
    // CreateBuffer is used to create one of these buffers: vertex buffer, index buffer, or 
    // shader-constant buffer. For CreateBuffer API ref info, see
    // https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11device-createbuffer.
    winrt::check_hresult(
        d3dDevice->CreateBuffer(&bd, nullptr, m_constantBufferNeverChanges.put())
        );

    ...

    // Define D3D11_SAMPLER_DESC. For API ref, see
    // https://learn.microsoft.com/windows/win32/api/d3d11/ns-d3d11-d3d11_sampler_desc.
    D3D11_SAMPLER_DESC sampDesc;

    // ZeroMemory fills a block of memory with zeros. For API ref, see
    // https://learn.microsoft.com/previous-versions/windows/desktop/legacy/aa366920(v=vs.85).
    ZeroMemory(&sampDesc, sizeof(sampDesc));

    sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
    ...

    // Create a sampler-state object that encapsulates sampling information for a texture.
    // The sampler-state interface holds a description for sampler state that you can bind to any 
    // shader stage of the pipeline for reference by texture sample operations.
    winrt::check_hresult(
        d3dDevice->CreateSamplerState(&sampDesc, m_samplerLinear.put())
        );

    // Start the async tasks to load the shaders and textures.

    // Load compiled shader objects (VertextShader.cso, VertexShaderFlat.cso, PixelShader.cso, and PixelShaderFlat.cso).
    // The BasicLoader class is used to convert and load common graphics resources, such as meshes, textures, 
    // and various shader objects into the constant buffers. For more info, see
    // https://learn.microsoft.com/windows/uwp/gaming/complete-code-for-basicloader.
    BasicLoader loader{ d3dDevice };

    std::vector<IAsyncAction> tasks;

    uint32_t numElements = ARRAYSIZE(PNTVertexLayout);

    // Load shaders asynchronously with the shader and pixel data using the
    // BasicLoader::LoadShaderAsync method. Push these method calls into a list of tasks.
    tasks.push_back(loader.LoadShaderAsync(L"VertexShader.cso", PNTVertexLayout, numElements, m_vertexShader.put(), m_vertexLayout.put()));
    tasks.push_back(loader.LoadShaderAsync(L"VertexShaderFlat.cso", nullptr, numElements, m_vertexShaderFlat.put(), nullptr));
    tasks.push_back(loader.LoadShaderAsync(L"PixelShader.cso", m_pixelShader.put()));
    tasks.push_back(loader.LoadShaderAsync(L"PixelShaderFlat.cso", m_pixelShaderFlat.put()));

    // Make sure the previous versions if any of the textures are released.
    m_sphereTexture = nullptr;
    ...

    // Load Game specific textures (Assets\\seafloor.dds, metal_texture.dds, cellceiling.dds,
    // cellfloor.dds, cellwall.dds).
    // Push these method calls also into a list of tasks.
    tasks.push_back(loader.LoadTextureAsync(L"Assets\\seafloor.dds", nullptr, m_sphereTexture.put()));
    ...

    // Simulate loading additional resources by introducing a delay.
    tasks.push_back([]() -> IAsyncAction { co_await winrt::resume_after(GameConstants::InitialLoadingDelay); }());

    // Returns when all the async tasks for loading the shader and texture assets have completed.
    for (auto&& task : tasks)
    {
        co_await task;
    }
}

Metode FinalizeCreateGameDeviceResources

Metode FinalizeCreateGameDeviceResources dipanggil setelah semua tugas sumber daya beban yang ada dalam metode CreateGameDeviceResourcesAsync selesai.

  • Inisialisasi constantBufferNeverChanges dengan posisi dan warna cahaya. Memuat data awal ke buffer konstanta dengan panggilan metode konteks perangkat ke ID3D11DeviceContext::UpdateSubresource.
  • Karena sumber daya yang dimuat secara asinkron telah selesai dimuat, saatnya untuk mengaitkannya dengan objek game yang sesuai.
  • Untuk setiap objek game, buat jala dan materi menggunakan tekstur yang telah dimuat. Kemudian kaitkan jala dan materi ke objek permainan.
  • Untuk objek game target, tekstur yang terdiri dari cincin berwarna konsentris, dengan nilai numerik di bagian atas, tidak dimuat dari file tekstur. Sebaliknya, ini dihasilkan secara procedurural menggunakan kode dalam TargetTexture.cpp. Kelas TargetTexture membuat sumber daya yang diperlukan untuk menggambar tekstur ke sumber daya di luar layar pada waktu inisialisasi. Tekstur yang dihasilkan kemudian dikaitkan dengan objek game target yang sesuai.

FinalizeCreateGameDeviceResources dan CreateWindowSizeDependentResources berbagi bagian kode yang serupa untuk ini:

  • Gunakan SetProjParams untuk memastikan bahwa kamera memiliki matriks proyeksi yang tepat. Untuk informasi selengkapnya, buka Kamera dan koordinasikan ruang.
  • Tangani rotasi layar dengan pasca mengalikan matriks rotasi 3D ke matriks proyeksi kamera. Kemudian perbarui buffer konstanta ConstantBufferChangeOnResize dengan matriks proyeksi yang dihasilkan.
  • Atur variabel global m_gameResourcesLoaded Boolean untuk menunjukkan bahwa sumber daya sekarang dimuat di buffer, siap untuk langkah berikutnya. Ingat bahwa kami pertama kali menginisialisasi variabel ini sebagai FALSE dalam metode konstruktor GameRenderer, melalui metode GameRenderer::CreateDeviceDependentResources.
  • Ketika m_gameResourcesLoaded ini TRUE, penyajian objek adegan dapat terjadi. Ini tercakup dalam artikel Rendering framework I: Intro to rendering , di bawah metode GameRenderer::Render.
// This method is called from the GameMain constructor.
// Make sure that 2D rendering is occurring on the same thread as the main rendering.
void GameRenderer::FinalizeCreateGameDeviceResources()
{
    // All asynchronously loaded resources have completed loading.
    // Now associate all the resources with the appropriate game objects.
    // This method is expected to run in the same thread as the GameRenderer
    // was created. All work will happen behind the "Loading ..." screen after the
    // main loop has been entered.

    // Initialize the Constant buffer with the light positions
    // These are handled here to ensure that the d3dContext is only
    // used in one thread.

    auto d3dDevice = m_deviceResources->GetD3DDevice();

    ConstantBufferNeverChanges constantBufferNeverChanges;
    constantBufferNeverChanges.lightPosition[0] = XMFLOAT4(3.5f, 2.5f, 5.5f, 1.0f);
    ...
    constantBufferNeverChanges.lightColor = XMFLOAT4(0.25f, 0.25f, 0.25f, 1.0f);

    // CPU copies data from memory (constantBufferNeverChanges) to a subresource 
    // created in non-mappable memory (m_constantBufferNeverChanges) which was created in the earlier 
    // CreateGameDeviceResourcesAsync method. For UpdateSubresource API ref info, 
    // go to: https://msdn.microsoft.com/library/windows/desktop/ff476486.aspx
    // To learn more about what a subresource is, go to:
    // https://msdn.microsoft.com/library/windows/desktop/ff476901.aspx

    m_deviceResources->GetD3DDeviceContext()->UpdateSubresource(
        m_constantBufferNeverChanges.get(),
        0,
        nullptr,
        &constantBufferNeverChanges,
        0,
        0
        );

    // For the objects that function as targets, they have two unique generated textures.
    // One version is used to show that they have never been hit and the other is 
    // used to show that they have been hit.
    // TargetTexture is a helper class to procedurally generate textures for game
    // targets. The class creates the necessary resources to draw the texture into 
    // an off screen resource at initialization time.

    TargetTexture textureGenerator(
        d3dDevice,
        m_deviceResources->GetD2DFactory(),
        m_deviceResources->GetDWriteFactory(),
        m_deviceResources->GetD2DDeviceContext()
        );

    // CylinderMesh is a class derived from MeshObject and creates a ID3D11Buffer of
    // vertices and indices to represent a canonical cylinder (capped at
    // both ends) that is positioned at the origin with a radius of 1.0,
    // a height of 1.0 and with its axis in the +Z direction.
    // In the game sample, there are various types of mesh types:
    // CylinderMesh (vertical rods), SphereMesh (balls that the player shoots), 
    // FaceMesh (target objects), and WorldMesh (Floors and ceilings that define the enclosed area)

    auto cylinderMesh = std::make_shared<CylinderMesh>(d3dDevice, (uint16_t)26);
    ...

    // The Material class maintains the properties that represent how an object will
    // look when it is rendered.  This includes the color of the object, the
    // texture used to render the object, and the vertex and pixel shader that
    // should be used for rendering.

    auto cylinderMaterial = std::make_shared<Material>(
        XMFLOAT4(0.8f, 0.8f, 0.8f, .5f),
        XMFLOAT4(0.8f, 0.8f, 0.8f, .5f),
        XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f),
        15.0f,
        m_cylinderTexture.get(),
        m_vertexShader.get(),
        m_pixelShader.get()
        );

    ...

    // Attach the textures to the appropriate game objects.
    // We'll loop through all the objects that need to be rendered.
    for (auto&& object : m_game->RenderObjects())
    {
        if (object->TargetId() == GameConstants::WorldFloorId)
        {
            // Assign a normal material for the floor object.
            // This normal material uses the floor texture (cellfloor.dds) that was loaded asynchronously from
            // the Assets folder using BasicLoader::LoadTextureAsync method in the earlier 
            // CreateGameDeviceResourcesAsync loop

            object->NormalMaterial(
                std::make_shared<Material>(
                    XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f),
                    XMFLOAT4(0.8f, 0.8f, 0.8f, 1.0f),
                    XMFLOAT4(0.3f, 0.3f, 0.3f, 1.0f),
                    150.0f,
                    m_floorTexture.get(),
                    m_vertexShaderFlat.get(),
                    m_pixelShaderFlat.get()
                    )
                );
            // Creates a mesh object called WorldFloorMesh and assign it to the floor object.
            object->Mesh(std::make_shared<WorldFloorMesh>(d3dDevice));
        }
        ...
        else if (auto cylinder = dynamic_cast<Cylinder*>(object.get()))
        {
            cylinder->Mesh(cylinderMesh);
            cylinder->NormalMaterial(cylinderMaterial);
        }
        else if (auto target = dynamic_cast<Face*>(object.get()))
        {
            const int bufferLength = 16;
            wchar_t str[bufferLength];
            int len = swprintf_s(str, bufferLength, L"%d", target->TargetId());
            auto string{ winrt::hstring(str, len) };

            winrt::com_ptr<ID3D11ShaderResourceView> texture;
            textureGenerator.CreateTextureResourceView(string, texture.put());
            target->NormalMaterial(
                std::make_shared<Material>(
                    XMFLOAT4(0.8f, 0.8f, 0.8f, 0.5f),
                    XMFLOAT4(0.8f, 0.8f, 0.8f, 0.5f),
                    XMFLOAT4(0.3f, 0.3f, 0.3f, 1.0f),
                    5.0f,
                    texture.get(),
                    m_vertexShader.get(),
                    m_pixelShader.get()
                    )
                );

            texture = nullptr;
            textureGenerator.CreateHitTextureResourceView(string, texture.put());
            target->HitMaterial(
                std::make_shared<Material>(
                    XMFLOAT4(0.8f, 0.8f, 0.8f, 0.5f),
                    XMFLOAT4(0.8f, 0.8f, 0.8f, 0.5f),
                    XMFLOAT4(0.3f, 0.3f, 0.3f, 1.0f),
                    5.0f,
                    texture.get(),
                    m_vertexShader.get(),
                    m_pixelShader.get()
                    )
                );

            target->Mesh(targetMesh);
        }
        ...
    }

    // The SetProjParams method calculates the projection matrix based on input params and
    // ensures that the camera has been initialized with the right projection
    // matrix.  
    // The camera is not created at the time the first window resize event occurs.

    auto renderTargetSize = m_deviceResources->GetRenderTargetSize();
    m_game->GameCamera().SetProjParams(
        XM_PI / 2,
        renderTargetSize.Width / renderTargetSize.Height,
        0.01f,
        100.0f
        );

    // Make sure that the correct projection matrix is set in the ConstantBufferChangeOnResize buffer.

    // Get the 3D rotation transform matrix. We are handling screen rotations directly to eliminate an unaligned 
    // fullscreen copy. So it is necessary to post multiply the 3D rotation matrix to the camera's projection matrix
    // to get the projection matrix that we need.

    auto orientation = m_deviceResources->GetOrientationTransform3D();

    ConstantBufferChangeOnResize changesOnResize;

    // The matrices are transposed due to the shader code expecting the matrices in the opposite
    // row/column order from the DirectX math library.

    // XMStoreFloat4x4 takes a matrix and writes the components out to sixteen single-precision floating-point values at the given address. 
    // The most significant component of the first row vector is written to the first four bytes of the address, 
    // followed by the second most significant component of the first row, and so on. The second row is then written out in a 
    // like manner to memory beginning at byte 16, followed by the third row to memory beginning at byte 32, and finally 
    // the fourth row to memory beginning at byte 48. For more API ref info, go to: 
    // https://msdn.microsoft.com/library/windows/desktop/microsoft.directx_sdk.storing.xmstorefloat4x4.aspx

    XMStoreFloat4x4(
        &changesOnResize.projection,
        XMMatrixMultiply(
            XMMatrixTranspose(m_game->GameCamera().Projection()),
            XMMatrixTranspose(XMLoadFloat4x4(&orientation))
            )
        );

    // UpdateSubresource method instructs CPU to copy data from memory (changesOnResize) to a subresource 
    // created in non-mappable memory (m_constantBufferChangeOnResize ) which was created in the earlier 
    // CreateGameDeviceResourcesAsync method.

    m_deviceResources->GetD3DDeviceContext()->UpdateSubresource(
        m_constantBufferChangeOnResize.get(),
        0,
        nullptr,
        &changesOnResize,
        0,
        0
        );

    // Finally we set the m_gameResourcesLoaded as TRUE, so we can start rendering.
    m_gameResourcesLoaded = true;
}

Metode CreateWindowSizeDependentResource

Metode CreateWindowSizeDependentResources dipanggil setiap kali ukuran jendela, orientasi, penyajian dengan dukungan stereo, atau perubahan resolusi. Dalam permainan sampel, ia memperbarui matriks proyeksi di ConstantBufferChangeOnResize.

Sumber daya ukuran jendela diperbarui dengan cara ini:

  • Kerangka kerja Aplikasi mendapatkan salah satu dari beberapa kemungkinan peristiwa yang menunjukkan perubahan status jendela.
  • Perulangan permainan utama Anda kemudian diberitahu tentang peristiwa dan panggilan CreateWindowSizeDependentResources pada instans kelas utama (GameMain), yang kemudian memanggil implementasi CreateWindowSizeDependentResources di kelas perender game (GameRenderer).
  • Pekerjaan utama metode ini adalah memastikan visual tidak menjadi bingung atau tidak valid karena perubahan properti jendela.

Untuk permainan sampel ini, sejumlah panggilan metode sama dengan metode FinalizeCreateGameDeviceResources. Untuk panduan kode, buka bagian sebelumnya.

Penyesuaian rendering ukuran jendela HUD dan overlay game tercakup di bawah Tambahkan antarmuka pengguna.

// Initializes view parameters when the window size changes.
void GameRenderer::CreateWindowSizeDependentResources()
{
    // Game HUD and overlay window size rendering adjustments are done here
    // but they'll be covered in the UI section instead.

    m_gameHud.CreateWindowSizeDependentResources();

    ...

    auto d3dContext = m_deviceResources->GetD3DDeviceContext();
    // In Sample3DSceneRenderer::CreateWindowSizeDependentResources, we had:
    // Size outputSize = m_deviceResources->GetOutputSize();

    auto renderTargetSize = m_deviceResources->GetRenderTargetSize();

    ...

    m_gameInfoOverlay.CreateWindowSizeDependentResources(m_gameInfoOverlaySize);

    if (m_game != nullptr)
    {
        // Similar operations as the last section of FinalizeCreateGameDeviceResources method
        m_game->GameCamera().SetProjParams(
            XM_PI / 2, renderTargetSize.Width / renderTargetSize.Height,
            0.01f,
            100.0f
            );

        XMFLOAT4X4 orientation = m_deviceResources->GetOrientationTransform3D();

        ConstantBufferChangeOnResize changesOnResize;
        XMStoreFloat4x4(
            &changesOnResize.projection,
            XMMatrixMultiply(
                XMMatrixTranspose(m_game->GameCamera().Projection()),
                XMMatrixTranspose(XMLoadFloat4x4(&orientation))
                )
            );

        d3dContext->UpdateSubresource(
            m_constantBufferChangeOnResize.get(),
            0,
            nullptr,
            &changesOnResize,
            0,
            0
            );
    }
}

Langkah berikutnya

Ini adalah proses dasar untuk mengimplementasikan kerangka kerja penyajian grafis dari sebuah game. Semakin besar permainan Anda, semakin banyak abstraksi yang harus Anda tempatkan untuk menangani hierarki jenis objek dan perilaku animasi. Anda perlu menerapkan metode yang lebih kompleks untuk memuat dan mengelola aset seperti jala dan tekstur. Selanjutnya, mari kita pelajari cara menambahkan antarmuka pengguna.