Partager via


Framework de rendu II : rendu de jeu

Remarque

Cette rubrique fait partie de la série de tutoriels Créer un jeu simple de plateforme Windows universelle simple (UWP) avec DirectX. La rubrique accessible via ce lien définit le contexte de la série.

Dans l’infrastructure de rendu I, nous avons abordé la façon dont nous prenons les informations de scène et la présentons à l’écran d’affichage. À présent, nous allons revenir en arrière et apprendre à préparer les données pour le rendu.

Remarque

Si vous n’avez pas téléchargé le code du jeu le plus récent pour cet exemple, accédez à Exemple de jeu Direct3D. Cet exemple fait partie d’une grande collection d’exemples de fonctionnalités UWP. Pour obtenir des instructions sur le téléchargement de l’exemple, consultez Exemples d’applications pour le développement Windows.

Objectif

Récapitulatif rapide de l’objectif. Il est de comprendre comment configurer une infrastructure de rendu de base pour afficher la sortie graphique d’un jeu DirectX UWP. Nous pouvons les regrouper en trois étapes.

  1. Établir une connexion à notre interface graphique
  2. Préparation : Créer les ressources dont nous avons besoin pour dessiner les graphiques
  3. Afficher les graphiques : afficher le cadre

Infrastructure de rendu I : Présentation du rendu expliquant comment les graphiques sont rendus, couvrant les étapes 1 et 3.

Cet article explique comment configurer d’autres éléments de cette infrastructure et préparer les données requises avant que le rendu puisse se produire, qui est l’étape 2 du processus.

Concevoir le renderer

Le renderer est responsable de la création et de la maintenance de tous les objets D3D11 et D2D utilisés pour générer les visuels de jeu. La classe GameRenderer est le renderer pour cet exemple de jeu et est conçu pour répondre aux besoins de rendu du jeu.

Voici quelques concepts que vous pouvez utiliser pour concevoir le renderer pour votre jeu :

  • Étant donné que les API Direct3D 11 sont définies en tant qu’API COM , vous devez fournir des références ComPtr aux objets définis par ces API. Ces objets sont automatiquement libérés lorsque leur dernière référence est hors de portée lorsque l’application se termine. Pour plus d’informations, consultez ComPtr. Exemple de ces objets : mémoires tampons constantes, objets de nuanceur - nuanceur de vertex, nuanceur de pixels et objets de ressources de nuanceur.
  • Les mémoires tampons constantes sont définies dans cette classe pour contenir différentes données nécessaires au rendu.
    • Utilisez plusieurs mémoires tampons constantes avec différentes fréquences pour réduire la quantité de données qui doivent être envoyées au GPU par image. Cet exemple sépare les constantes en différentes mémoires tampons en fonction de la fréquence à laquelle elles doivent être mises à jour. Il s’agit d’une bonne pratique pour la programmation Direct3D.
    • Dans cet exemple de jeu, 4 mémoires tampons constantes sont définies.
      1. m_constantBufferNeverChanges contient les paramètres d’éclairage. Il est défini une fois dans la méthode FinaliseCreateGameDeviceResources et ne change jamais à nouveau.
      2. m_constantBufferChangeOnResize contient la matrice de projection. La matrice de projection dépend de la taille et du rapport d’aspect de la fenêtre. Il est défini dans CreateWindowSizeDependentResources, puis mis à jour une fois les ressources chargées dans la méthode FinaliseCreateGameDeviceResources. Si le rendu est en 3D, il est également modifié deux fois par image.
      3. m_constantBufferChangesEveryFrame contient la matrice d’affichage. Cette matrice dépend de la position de la caméra et de la direction de l’apparence (la normale à la projection) et change une fois par frame dans la méthode Render . Cela a été abordé précédemment dans l’infrastructure de rendu I : Présentation du rendu, sous la méthode GameRenderer ::Render.
      4. m_constantBufferChangesEveryPrim contient la matrice de modèle et les propriétés matérielles de chaque primitive. La matrice de modèle transforme les sommets des coordonnées locales en coordonnées mondiales. Ces constantes sont spécifiques à chaque primitive et sont mises à jour pour chaque appel de dessin. Cela a été abordé précédemment dans l’infrastructure de rendu I : Présentation du rendu, sous le rendu primitif.
  • Les objets de ressources de nuanceur qui contiennent des textures pour les primitives sont également définis dans cette classe.
    • Certaines textures sont prédéfinies (DDS est un format de fichier qui peut être utilisé pour stocker des textures compressées et non compressées. Les textures DDS sont utilisées pour les murs et le sol du monde ainsi que pour les sphères d’ammo.)
    • Dans cet exemple de jeu, les objets de ressources du nuanceur sont les suivants : m_sphereTexture, m_cylinderTexture, m_ceilingTexture, m_floorTexture, m_wallsTexture.
  • Les objets nuanceur sont définis dans cette classe pour calculer nos primitives et nos textures.
    • Dans cet exemple de jeu, les objets de nuanceur sont m_vertexShader, m_vertexShaderFlat et m_pixelShader, m_pixelShaderFlat.
    • Le nuanceur de vertex traite les primitives et l’éclairage de base, et le nuanceur de pixels (parfois appelé nuanceur de fragments) traite les textures et tous les effets par pixel.
    • Il existe deux versions de ces nuanceurs (standard et plat) pour le rendu de primitives différentes. La raison pour laquelle nous avons des versions différentes est que les versions plates sont beaucoup plus simples et ne font pas de surbrillances spéculaires ou d’effets d’éclairage par pixel. Ceux-ci sont utilisés pour les murs et rendent le rendu plus rapide sur les appareils à alimentation inférieure.

GameRenderer.h

Examinons maintenant le code de l’objet de classe renderer de l’exemple de jeu.

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

Constructeur

Examinons ensuite le constructeur GameRenderer de l’exemple de jeu et comparons-le au constructeur Sample3DSceneRenderer fourni dans le modèle d’application 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();
}

Créer et charger des ressources graphiques DirectX

Dans l’exemple de jeu (et dans le modèle d’application DirectX 11 (Windows universel) de Visual Studio, la création et le chargement de ressources de jeu sont implémentées à l’aide de ces deux méthodes appelées à partir du constructeur GameRenderer :

Méthode CreateDeviceDependentResources

Dans le modèle d’application DirectX 11, cette méthode est utilisée pour charger le nuanceur de vertex et le nuanceur de pixels de manière asynchrone, créer le nuanceur et la mémoire tampon constante, créer un maillage avec des sommets qui contiennent des informations de position et de couleur.

Dans l’exemple de jeu, ces opérations des objets de scène sont à la place divisées entre les méthodes CreateGameDeviceResourcesAsync et FinaliseCreateGameDeviceResources.

Pour cet exemple de jeu, qu’est-ce qui va dans cette méthode ?

  • Variables instanciées (m_gameResourcesLoaded = false et m_levelResourcesLoaded = false) qui indiquent si les ressources ont été chargées avant de passer au rendu, car nous les chargeons de manière asynchrone.
  • Étant donné que le rendu HUD et superposition se trouvent dans des objets de classe distincts, appelez les méthodes GameHud ::CreateDeviceDependentResources et GameInfoOverlay ::CreateDeviceDependentResources ici.

Voici le code de 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();
}

Vous trouverez ci-dessous la liste des méthodes utilisées pour créer et charger des ressources.

  • CreateDeviceDependentResources
    • CreateGameDeviceResourcesAsync (ajouté)
    • FinaliseCreateGameDeviceResources (ajouté)
  • CreateWindowSizeDependentResources

Avant de plonger dans les autres méthodes utilisées pour créer et charger des ressources, nous allons d’abord créer le renderer et voir comment il s’intègre dans la boucle de jeu.

Créer le renderer

Le GameRenderer est créé dans le constructeur de GameMain. Il appelle également les deux autres méthodes, CreateGameDeviceResourcesAsync et FinaliseCreateGameDeviceResources qui sont ajoutées pour aider à créer et charger des ressources.

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

    ...
}

Méthode CreateGameDeviceResourcesAsync

CreateGameDeviceResourcesAsync est appelé à partir de la méthode du constructeur GameMain dans la boucle create_task , car nous chargeons des ressources de jeu de façon asynchrone.

CreateDeviceResourcesAsync est une méthode qui s’exécute en tant qu’ensemble distinct de tâches asynchrones pour charger les ressources de jeu. Comme il est censé s’exécuter sur un thread distinct, il a uniquement accès aux méthodes d’appareil Direct3D 11 (celles définies sur ID3D11Device) et non aux méthodes de contexte d’appareil (les méthodes définies sur ID3D11DeviceContext), de sorte qu’elle n’effectue aucun rendu.

La méthode FinaliseCreateGameDeviceResources s’exécute sur le thread principal et a accès aux méthodes de contexte d’appareil Direct3D 11.

En principe:

  • Utilisez uniquement les méthodes ID3D11Device dans CreateGameDeviceResourcesAsync , car elles sont threadées libres, ce qui signifie qu’elles peuvent s’exécuter sur n’importe quel thread. Il est également prévu qu’ils ne s’exécutent pas sur le même thread que celui sur lequel GameRenderer a été créé.
  • N’utilisez pas de méthodes dans ID3D11DeviceContext ici, car elles doivent s’exécuter sur un seul thread et sur le même thread que GameRenderer.
  • Utilisez cette méthode pour créer des mémoires tampons constantes.
  • Utilisez cette méthode pour charger des textures (comme les fichiers .dds) et les informations du nuanceur (comme les fichiers .cso) dans les nuanceurs.

Cette méthode est utilisée pour :

  • Créez les 4 mémoires tampons constantes : m_constantBufferNeverChanges, m_constantBufferChangeOnResize, m_constantBufferChangesEveryFrame, m_constantBufferChangesEveryPrim
  • Créer un objet sampler-state qui encapsule des informations d’échantillonnage pour une texture
  • Créez un groupe de tâches qui contient toutes les tâches asynchrones créées par la méthode. Il attend la fin de toutes ces tâches asynchrones, puis appelle FinaliseCreateGameDeviceResources.
  • Créez un chargeur à l’aide du chargeur de base. Ajoutez les opérations de chargement asynchrones du chargeur en tant que tâches dans le groupe de tâches créé précédemment.
  • Les méthodes telles que BasicLoader ::LoadShaderAsync et BasicLoader ::LoadTextureAsync sont utilisées pour charger :
    • objets nuanceurs compilés (VertextShader.cso, VertexShaderFlat.cso, PixelShader.cso et PixelShaderFlat.cso). Pour plus d’informations, accédez aux différents formats de fichier de nuanceur.
    • textures spécifiques au jeu (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;
    }
}

FinaliseCreateGameDeviceResources, méthode

La méthode FinaliseCreateGameDeviceResources est appelée une fois toutes les tâches de ressources de chargement qui se trouvent dans la méthode CreateGameDeviceResourcesAsync terminées.

  • Initialisez constantBufferNeverChanges avec les positions et la couleur de lumière. Charge les données initiales dans les mémoires tampons constantes avec un appel de méthode de contexte d’appareil à ID3D11DeviceContext ::UpdateSubresource.
  • Étant donné que les ressources chargées de manière asynchrone ont terminé le chargement, il est temps de les associer aux objets de jeu appropriés.
  • Pour chaque objet de jeu, créez le maillage et le matériau à l’aide des textures chargées. Ensuite, associez le maillage et le matériau à l’objet de jeu.
  • Pour l’objet de jeu cible, la texture composée d’anneaux colorés concentriques, avec une valeur numérique en haut, n’est pas chargée à partir d’un fichier de texture. Au lieu de cela, il est généré de façon procédurale à l’aide du code dans TargetTexture.cpp. La classe TargetTexture crée les ressources nécessaires pour dessiner la texture dans une ressource hors écran au moment de l’initialisation. La texture résultante est ensuite associée aux objets de jeu cibles appropriés.

FinaliseCreateGameDeviceResources et CreateWindowSizeDependentResources partagent des parties similaires du code pour ces éléments :

  • Utilisez SetProjParams pour vous assurer que la caméra a la matrice de projection appropriée. Pour plus d’informations, accédez à l’appareil photo et à l’espace de coordonnées.
  • Gérez la rotation de l’écran en multipliant la matrice de rotation 3D à la matrice de projection de la caméra. Ensuite, mettez à jour la mémoire tampon constante ConstantBufferChangeOnResize avec la matrice de projection résultante.
  • Définissez la m_gameResourcesLoaded variable globale booléenne pour indiquer que les ressources sont désormais chargées dans les mémoires tampons, prêtes pour l’étape suivante. Rappelez-vous que nous avons d’abord initialisé cette variable comme FALSE dans la méthode constructeur de GameRenderer, par le biais de la méthode GameRenderer ::CreateDeviceDependentResources.
  • Lorsque cette m_gameResourcesLoaded a la valeur TRUE, le rendu des objets de scène peut avoir lieu. Cela a été abordé dans l’infrastructure de rendu I : Présentation de l’article de rendu, sous La méthode 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;
}

Méthode CreateWindowSizeDependentResource

Les méthodes CreateWindowSizeDependentResources sont appelées chaque fois que la taille de la fenêtre, l’orientation, le rendu stéréo ou les modifications de résolution sont apportées. Dans l’exemple de jeu, il met à jour la matrice de projection dans ConstantBufferChangeOnResize.

Les ressources de taille de fenêtre sont mises à jour de cette façon :

  • L’infrastructure d’application obtient l’un des événements possibles indiquant une modification de l’état de la fenêtre.
  • Votre boucle de jeu principale est ensuite informée de l’événement et appelle CreateWindowSizeDependentResources sur l’instance de classe principale (GameMain), qui appelle ensuite l’implémentation CreateWindowSizeDependentResources dans la classe game renderer (GameRenderer).
  • Le travail principal de cette méthode est de s’assurer que les visuels ne sont pas confondus ou non valides en raison d’une modification des propriétés de fenêtre.

Pour cet exemple de jeu, un certain nombre d’appels de méthode sont identiques à la méthode FinaliseCreateGameDeviceResources. Pour la procédure pas à pas du code, accédez à la section précédente.

Les ajustements de rendu de la taille de fenêtre de superposition et huD du jeu sont couverts sous Ajouter une interface utilisateur.

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

Étapes suivantes

Il s’agit du processus de base de l’implémentation de l’infrastructure de rendu graphique d’un jeu. Plus votre jeu est grand, plus vous devrez mettre en place des abstractions pour gérer les hiérarchies des types d’objets et des comportements d’animation. Vous devez implémenter des méthodes plus complexes pour charger et gérer des ressources telles que des maillages et des textures. Ensuite, nous allons apprendre à ajouter une interface utilisateur.