Sdílet prostřednictvím


Architektura vykreslování II: Vykreslování her

Poznámka:

Toto téma je součástí Vytvoření jednoduché hry pro Univerzální platformu Windows (UPW) pomocí série kurzů DirectX. Téma na tomto odkazu nastaví kontext pro řadu.

Ve rendering frameworku jsempokryli způsob, jakým bereme informace o scéně a prezentujeme je na obrazovku. Teď se vrátíme k dalšímu kroku a naučíme se, jak připravit data na vykreslování.

Poznámka:

Pokud jste si nestáhli nejnovější kód hry pro tuto ukázku, přejděte na ukázkovou hru Direct3D. Tato ukázka je součástí velké kolekce ukázek funkcí UPW. Pokyny ke stažení ukázky najdete v tématu Ukázkové aplikace pro vývoj ve Windows.

Účel

Rychlá rekapitulace cíle. Je potřeba se naučit, jak nastavit základní vykreslovací rámec pro zobrazení grafického výstupu pro hru DirectX UWP. Můžeme je volně seskupit do těchto tří kroků.

  1. Navázání připojení k našemu grafickému rozhraní
  2. Příprava: Vytvoření prostředků, které potřebujeme k vykreslení grafiky
  3. Zobrazení grafiky: Vykreslení rámce

Vykreslovací architektura I: Úvod k vykreslování vysvětlil, jak se vykreslují grafiky, které pokrývají kroky 1 a 3.

Tento článek vysvětluje, jak nastavit další části této architektury a připravit požadovaná data před vykreslením, což je krok 2 procesu.

Návrh rendereru

Renderer zodpovídá za vytváření a údržbu všech objektů D3D11 a D2D používaných k vygenerování herních vizuálů. Třída GameRenderer je renderer pro tuto ukázkovou hru a je navržená tak, aby vyhovovala potřebám vykreslování hry.

Toto jsou některé koncepty, které můžete použít k návrhu rendereru pro hru:

  • Vzhledem k tomu, že rozhraní API Direct3D 11 jsou definována jako rozhraní API modelu COM, musíte zadat comPtr odkazy na objekty definované těmito rozhraními API. Tyto objekty se automaticky uvolní, když jejich poslední odkaz zanikne a aplikace se ukončí. Další informace viz ComPtr. cs-CZ: Příklad těchto objektů: konstantní vyrovnávací paměti, shader objekty – vrcholový shader, pixelový shadera objekty prostředků shaderu.
  • Konstantní buffery jsou definovány v této třídě pro ukládání různých dat potřebných k renderování.
    • Použijte více konstantních vyrovnávacích pamětí s různými frekvencemi ke snížení množství dat, která se musí odesílat do GPU za snímek. Tento příklad odděluje konstanty do různých pufrů na základě toho, jak často je třeba je aktualizovat. Toto je osvědčený postup pro programování Direct3D.
    • V této ukázkové hře jsou definovány 4 konstantní buffery.
      1. m_constantBufferNeverChanges obsahuje parametry osvětlení. To je nastaveno jednou v metodě FinalizeCreateGameDeviceResources a nikdy se znovu nezmění.
      2. m_constantBufferChangeOnResize obsahuje matici projekce. Matice projekce závisí na velikosti a poměru stran okna. Je nastavena v CreateWindowSizeDependentResources a poté aktualizována po načtení prostředků v metodě FinalizeCreateGameDeviceResources. Při vykreslení v 3D se také změní dvakrát za snímek.
      3. m_constantBufferChangesEveryFrame obsahuje matici zobrazení. Tato matice je závislá na poloze kamery a směru vzhledu (normální projekci) a mění se jednou za snímek v metodě Render. To bylo popsáno dříve v rendering framework I: Úvod k vykreslování, v části GameRenderer::Render metoda.
      4. m_constantBufferChangesEveryPrim obsahuje matici modelu a vlastnosti materiálu každého primitiva. Matice modelu transformuje vrcholy z místních souřadnic na světové souřadnice. Tyto konstanty jsou specifické pro každou primitivu a aktualizují se pro každé vykreslování. To bylo popsáno dříve v rendering framework I: Úvod k vykreslování, v Primitivní vykreslování.
  • Objekty prostředků Shader, které obsahují textury pro primitivy, jsou také definovány v této třídě.
    • Některé textury jsou předdefinované (DDS je formát souboru, který lze použít k ukládání komprimovaných a nekomprimovaných textur. Textury DDS se používají pro stěny a podlahu světa a také ammo spheres.)
    • V této ukázkové hře jsou objekty zdrojů shaderu: m_sphereTexture, m_cylinderTexture, m_ceilingTexture, m_floorTexture, m_wallsTexture.
  • Objekty shaderu jsou definovány v této třídě pro výpočet našich primitiv a textur.
    • V této ukázkové hře jsou objekty shaderu m_vertexShader, m_vertexShaderFlata m_pixelShader, m_pixelShaderFlat.
    • Vrcholový shader zpracovává primitivy a základní osvětlení. Pixlový shader (někdy označovaný jako fragment shader) zpracovává textury a všechny efekty na úrovni pixelů.
    • Existují dvě verze těchto shaderů (normální a ploché) pro vykreslování různých primitiv. Důvod, proč máme různé verze, je, že ploché verze jsou mnohem jednodušší a nevytvářejí zrcadlové odlesky ani žádné efekty osvětlení na úrovni pixelů. Tyto se používají pro stěny a tím zrychlují vykreslování na méně výkonných zařízeních.

GameRenderer.h

Teď se podíváme na kód v objektu třídy rendereru ukázkové hry.

// 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

Dále se podíváme na konstruktor vzorové hry GameRenderer a porovnáme ho s konstruktorem Sample3DSceneRenderer zadaným v šabloně aplikace 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();
}

Vytváření a načítání grafických prostředků DirectX

V ukázkové hře (a v šabloně aplikace DirectX 11 sady Visual Studio) se vytváření a načítání herních prostředků implementují pomocí těchto dvou metod, které se volají z GameRenderer konstruktoru:

Metoda CreateDeviceDependentResources

V šabloně aplikace DirectX 11 se tato metoda používá k asynchronnímu načtení vertex a pixel shaderu, vytvoření shaderu a konstantního bufferu a vytvoření meshe s vrcholy, které obsahují informace o pozici a barvě.

V ukázkové hře jsou metody pro tyto operace objektů scény rozděleny mezi CreateGameDeviceResourcesAsync a FinalizeCreateGameDeviceResources.

Co se pro tuto ukázkovou hru týká této metody?

  • Instance proměnných (m_gameResourcesLoaded = false a m_levelResourcesLoaded = false), které signalizují, zda byly prostředky načteny, než se přistoupí k vykreslení, protože je načítáme asynchronně.
  • Vzhledem k tomu, že vykreslování HUD a překrytí jsou v samostatných objektech třídy, volejte GameHud::CreateDeviceDependentResources a GameInfoOverlay::CreateDeviceDependentResources metody zde.

Tady je kód pro 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();
}

Níže je seznam metod, které se používají k vytváření a načítání prostředků.

  • VytvořitZdrojeZávisléNaZařízení
    • CreateGameDeviceResourcesAsync (přidáno)
    • FinalizeCreateGameDeviceResources (přidáno)
  • VytvořZdrojeZávisléNaVelikostiOkna

Než se ponořme do dalších metod, které se používají k vytváření a načítání prostředků, nejprve vytvoříme renderer a podíváme se, jak zapadá do herní smyčky.

Vytvoření rendereru

GameRenderer je vytvořen v konstruktoru GameMain. Volá také další dvě metody, CreateGameDeviceResourcesAsync a FinalizeCreateGameDeviceResources, které jsou přidané na pomoc při vytváření a načítání prostředků.

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

    ...
}

Metoda CreateGameDeviceResourcesAsync

CreateGameDeviceResourcesAsync se volá z metody konstruktoru GameMain v rámci smyčky create_task, protože načítáme herní prostředky asynchronně.

CreateDeviceResourcesAsync je metoda, která se spouští jako samostatná sada asynchronních úloh pro načtení herních prostředků. Vzhledem k tomu, že se očekává, že se spustí v samostatném vlákně, má přístup pouze k metodám zařízení Direct3D 11 (ty definované na ID3D11Device) a ne k metodám kontextu zařízení (metody definované na ID3D11DeviceContext), takže neprovádí žádné vykreslování.

FinalizeCreateGameDeviceResources metoda běží v hlavním vlákně a má přístup k metodám kontextu zařízení Direct3D 11.

V zásadě:

  • Ve CreateGameDeviceResourcesAsync používejte pouze metody ID3D11Device, protože jsou bezvláknové, což znamená, že se mohou spouštět na libovolném vlákně. Očekává se také, že nebudou běžet na stejném vlákně, na kterém byl vytvořen GameRenderer.
  • Nepoužívejte metody v ID3D11DeviceContext zde, protože musí běžet na jednom vlákně a ve stejném vlákně jako GameRenderer.
  • Tuto metodu použijte k vytvoření konstantních bufferů.
  • Pomocí této metody načtěte textury (například soubory .dds) a informace o shaderech (například soubory .cso) do shaderů.

Tato metoda se používá k:

  • Vytvořte 4 konstantní vyrovnávací paměti: m_constantBufferNeverChanges, m_constantBufferChangeOnResize, m_constantBufferChangesEveryFrame, m_constantBufferChangesEveryPrim
  • Vytvořte objekt sampler-state, který obsahuje informace o vzorkování pro texturu
  • Vytvořte skupinu úloh, která obsahuje všechny asynchronní úlohy vytvořené metodou. Čeká na dokončení všech těchto asynchronních úloh a potom volá FinalizeCreateGameDeviceResources.
  • Vytvořte zavaděč pomocí Základního zavaděče. Přidejte operace asynchronního načítání zavaděče jako úlohy do skupiny úloh vytvořené dříve.
  • Metody jako BasicLoader::LoadShaderAsync a BasicLoader::LoadTextureAsync se používají k načtení:
    • kompilované objekty shaderu (VertextShader.cso, VertexShaderFlat.cso, PixelShader.cso a PixelShaderFlat.cso). Další informace najdete v různé formáty shaderových souborů.
    • textury specifické pro hru (Assets\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;
    }
}

Metoda FinalizeCreateGameDeviceResources

Metoda FinalizeCreateGameDeviceResources se volá po dokončení všech úloh načítání prostředků, které jsou v metodě CreateGameDeviceResourcesAsync.

  • Inicializuje constantBufferNeverChanges se světelnými pozicemi a barvou. Načte počáteční data do konstantních vyrovnávacích pamětí pomocí volání metody kontextu zařízení ID3D11DeviceContext::UpdateSubresource.
  • Protože asynchronně načtené prostředky dokončily načítání, je čas je přidružit k příslušným herním objektům.
  • Pro každý herní objekt vytvořte síť a materiál pomocí textur, které byly načteny. Pak přidružte síť a materiál k hernímu objektu.
  • U herního objektu cíle, textura, která se skládá z soustředných barevných kroužků s číselnou hodnotou nahoře, není načtena ze souboru textury. Místo toho se procedurálně generuje pomocí kódu v TargetTexture.cpp. TargetTexture třída vytvoří potřebné prostředky k vykreslení textury do prostředku mimo obrazovku v době inicializace. Výsledná textura se pak přidružuje k příslušným cílovým herním objektům.

FinalizeCreateGameDeviceResources a CreateWindowSizeDependentResources sdílí podobné části kódu s těmito:

  • Pomocí SetProjParams zajistěte, aby fotoaparát měl správnou projekční matici. Pro více informací přejděte na Kamera a souřadnicový prostor.
  • Zpracovávejte otočení obrazovky tím, že vynásobíte 3D matici otáčení maticí projekce kamery. Potom aktualizujte vyrovnávací paměť konstanty ConstantBufferChangeOnResize výslednou projekční maticí.
  • Nastavte m_gameResourcesLoadedlogickou globální proměnnou, která indikuje, že prostředky jsou teď načtené do vyrovnávacích pamětí, připravené k dalšímu kroku. Vzpomeňte si, že jsme tuto proměnnou nejprve inicializovali jako FALSE v konstruktoru třídy GameRendererprostřednictvím metody GameRenderer::CreateDeviceDependentResources.
  • Pokud je m_gameResourcesLoadedTRUE, může být vykreslení objektů ve scéně provedeno. To bylo popsáno v Rendering framework I: Úvod k vykreslování článku v GameRenderer::Render metoda.
// 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;
}

Metoda CreateWindowSizeDependentResource

Metody CreateWindowSizeDependentResources se volají pokaždé, když se změní velikost okna, orientace, vykreslování s podporou sterea nebo rozlišení. V ukázkové hře aktualizuje projekční matici v ConstantBufferChangeOnResize.

Prostředky velikosti okna se aktualizují tímto způsobem:

  • Architektura aplikace získá jednu z několika možných událostí označujících změnu ve stavu okna.
  • Vaše hlavní herní smyčka je pak informována o události a zavolá CreateWindowSizeDependentResources na instanci hlavní třídy (GameMain), která pak zavolá implementaci CreateWindowSizeDependentResources v herním rendereru (GameRenderer).
  • Primární úlohou této metody je zajistit, aby se vizuály nezaměňily nebo zneplatnily kvůli změně vlastností okna.

Pro tuto ukázkovou hru je řada volání metod stejná jako metoda FinalizeCreateGameDeviceResources. Návod k kódu najdete v předchozí části.

Úpravy vykreslování velikosti rozhraní HUD hry a překryvného okna jsou zahrnuty v rámci Přidání uživatelského rozhraní.

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

Další kroky

Toto je základní proces implementace architektury vykreslování grafiky hry. Čím větší je vaše hra, tím více abstrakcí byste museli zavést pro zpracování hierarchií typů objektů a chování animace. Potřebujete implementovat složitější metody pro načítání a správu prostředků, jako jsou mřížky a textury. Dále se dozvíme, jak přidat uživatelské rozhraní.