Infrastructure de rendu I : présentation du rendu

Notes

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

Jusqu’à présent, nous avons abordé comment structurer un jeu plateforme Windows universelle (UWP) et comment définir une machine à états pour gérer le flux du jeu. Il est maintenant temps d’apprendre à développer l’infrastructure de rendu. Examinons comment l’exemple de jeu affiche la scène de jeu à l’aide de Direct3D 11.

Direct3D 11 contient un ensemble d’API qui permettent d’accéder aux fonctionnalités avancées du matériel graphique hautes performances qui peuvent être utilisées pour créer des graphiques 3D pour des applications gourmandes en graphismes, telles que des jeux.

Le rendu des graphismes de jeu à l’écran signifie essentiellement le rendu d’une séquence d’images à l’écran. Dans chaque image, vous devez restituer les objets visibles dans la scène, en fonction de la vue.

Pour afficher un cadre, vous devez transmettre les informations de scène requises au matériel afin qu’elle puisse être affichée à l’écran. Si vous souhaitez que quelque chose s’affiche à l’écran, vous devez commencer le rendu dès que le jeu commence à s’exécuter.

Objectifs

Pour configurer une infrastructure de rendu de base afin d’afficher la sortie graphique d’un jeu DirectX UWP. Vous pouvez décomposer cela en trois étapes.

  1. Établissez une connexion à l’interface graphique.
  2. Créez les ressources nécessaires pour dessiner les graphiques.
  3. Affichez les graphiques en montrant le cadre.

Cette rubrique explique comment les graphiques sont rendus, en couvrant les étapes 1 et 3.

Infrastructure de rendu II : Le rendu du jeu couvre l’étape 2: comment configurer l’infrastructure de rendu et comment les données sont préparées avant que le rendu puisse se produire.

Bien démarrer

Il est judicieux de vous familiariser avec les concepts graphiques et de rendu de base. Si vous débutez avec Direct3D et le rendu, consultez Termes et concepts pour obtenir une brève description des graphiques et des termes de rendu utilisés dans cette rubrique.

Pour ce jeu, la classe GameRenderer représente le convertisseur de cet exemple de jeu. Il est responsable de la création et de la maintenance de tous les objets Direct3D 11 et Direct2D utilisés pour générer les visuels du jeu. Il conserve également une référence à l’objet Simple3DGame utilisé pour récupérer la liste des objets à afficher, ainsi que status du jeu pour l’affichage tête haute (HUD).

Dans cette partie du tutoriel, nous allons nous concentrer sur le rendu d’objets 3D dans le jeu.

Établir une connexion à l’interface graphique

Pour plus d’informations sur l’accès au matériel à des fins de rendu, consultez la rubrique Définir l’infrastructure d’application UWP du jeu .

Méthode App::Initialize

La fonction std::make_shared , comme indiqué ci-dessous, est utilisée pour créer un shared_ptr à DX::D eviceResources, qui fournit également l’accès à l’appareil.

Dans Direct3D 11, un appareil est utilisé pour allouer et détruire des objets, restituer des primitives et communiquer avec les graphiques carte via le pilote graphique.

void Initialize(CoreApplicationView const& applicationView)
{
    ...

    // At this point we have access to the device. 
    // We can create the device-dependent resources.
    m_deviceResources = std::make_shared<DX::DeviceResources>();
}

Afficher les graphiques en montrant le cadre

La scène de jeu doit s’afficher lorsque le jeu est lancé. Les instructions pour le rendu commencent dans la méthode GameMain::Run , comme indiqué ci-dessous.

Le flux simple est celui-ci.

  1. Mettre à jour
  2. Render
  3. Présent

GameMain::Run, méthode

void GameMain::Run()
{
    while (!m_windowClosed)
    {
        if (m_visible) // if the window is visible
        {
            switch (m_updateState)
            {
            ...
            default:
                CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);
                Update();
                m_renderer->Render();
                m_deviceResources->Present();
                m_renderNeeded = false;
            }
        }
        else
        {
            CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
        }
    }
    m_game->OnSuspending();  // Exiting due to window close, so save state.
}

Update

Consultez la rubrique Gestion des flux de jeu pour plus d’informations sur la façon dont les états du jeu sont mis à jour dans la méthode GameMain::Update .

Rendu

Le rendu est implémenté en appelant la méthode GameRenderer::Render à partir de GameMain::Run.

Si le rendu stéréo est activé, il existe deux passes de rendu : l’une pour l’œil gauche et l’autre pour la droite. Dans chaque passe de rendu, nous liez la cible de rendu et la vue de gabarit de profondeur à l’appareil. Nous effacerons également la vue profondeur-gabarit par la suite.

Notes

Le rendu stéréo peut être obtenu à l’aide d’autres méthodes, telles que la stéréo à passe unique à l’aide de l’instanciation de vertex ou de nuanceurs de géométrie. La méthode à deux passes de rendu est un moyen plus lent mais plus pratique d’obtenir un rendu stéréo.

Une fois le jeu en cours d’exécution et les ressources chargées, nous mettons à jour la matrice de projection, une fois par passe de rendu. Les objets sont légèrement différents de chaque vue. Ensuite, nous avons configuré le pipeline de rendu graphique.

Notes

Pour plus d’informations sur le chargement des ressources, consultez Créer et charger des ressources graphiques DirectX .

Dans cet exemple de jeu, le convertisseur est conçu pour utiliser une disposition de vertex standard sur tous les objets. Cela simplifie la conception du nuanceur et permet des modifications faciles entre les nuanceurs, indépendamment de la géométrie des objets.

GameRenderer::Render, méthode

Nous définissons le contexte Direct3D pour utiliser une disposition de vertex d’entrée. Les objets de disposition d’entrée décrivent comment les données de mémoire tampon de vertex sont diffusées dans le pipeline de rendu.

Ensuite, nous définissons le contexte Direct3D pour utiliser les mémoires tampons constantes définies précédemment, qui sont utilisées par l’étape de pipeline du nuanceur de vertex et l’étape de pipeline du nuanceur de pixels .

Notes

Pour plus d’informations sur la définition des mémoires tampons constantes , consultez Infrastructure de rendu II : Rendu du jeu .

Étant donné que la même disposition d’entrée et le même ensemble de mémoires tampons constantes sont utilisés pour tous les nuanceurs qui se trouvent dans le pipeline, il est configuré une fois par image.

void GameRenderer::Render()
{
    bool stereoEnabled{ m_deviceResources->GetStereoState() };

    auto d3dContext{ m_deviceResources->GetD3DDeviceContext() };
    auto d2dContext{ m_deviceResources->GetD2DDeviceContext() };

    int renderingPasses = 1;
    if (stereoEnabled)
    {
        renderingPasses = 2;
    }

    for (int i = 0; i < renderingPasses; i++)
    {
        // Iterate through the number of rendering passes to be completed.
        // 2 rendering passes if stereo is enabled.
        if (i > 0)
        {
            // Doing the Right Eye View.
            ID3D11RenderTargetView* const targets[1] = { m_deviceResources->GetBackBufferRenderTargetViewRight() };

            // Resets render targets to the screen.
            // OMSetRenderTargets binds 2 things to the device.
            // 1. Binds one render target atomically to the device.
            // 2. Binds the depth-stencil view, as returned by the GetDepthStencilView method, to the device.
            // For more info, see
            // https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-omsetrendertargets

            d3dContext->OMSetRenderTargets(1, targets, m_deviceResources->GetDepthStencilView());

            // Clears the depth stencil view.
            // A depth stencil view contains the format and buffer to hold depth and stencil info.
            // For more info about depth stencil view, go to: 
            // https://learn.microsoft.com/windows/uwp/graphics-concepts/depth-stencil-view--dsv-
            // A depth buffer is used to store depth information to control which areas of 
            // polygons are rendered rather than hidden from view. To learn more about a depth buffer,
            // go to: https://learn.microsoft.com/windows/uwp/graphics-concepts/depth-buffers
            // A stencil buffer is used to mask pixels in an image, to produce special effects. 
            // The mask determines whether a pixel is drawn or not,
            // by setting the bit to a 1 or 0. To learn more about a stencil buffer,
            // go to: https://learn.microsoft.com/windows/uwp/graphics-concepts/stencil-buffers

            d3dContext->ClearDepthStencilView(m_deviceResources->GetDepthStencilView(), D3D11_CLEAR_DEPTH, 1.0f, 0);

            // Direct2D -- discussed later
            d2dContext->SetTarget(m_deviceResources->GetD2DTargetBitmapRight());
        }
        else
        {
            // Doing the Mono or Left Eye View.
            // As compared to the right eye:
            // m_deviceResources->GetBackBufferRenderTargetView instead of GetBackBufferRenderTargetViewRight
            ID3D11RenderTargetView* const targets[1] = { m_deviceResources->GetBackBufferRenderTargetView() };

            // Same as the Right Eye View.
            d3dContext->OMSetRenderTargets(1, targets, m_deviceResources->GetDepthStencilView());
            d3dContext->ClearDepthStencilView(m_deviceResources->GetDepthStencilView(), D3D11_CLEAR_DEPTH, 1.0f, 0);

            // d2d -- Discussed later under Adding UI
            d2dContext->SetTarget(m_deviceResources->GetD2DTargetBitmap());
        }

        const float clearColor[4] = { 0.5f, 0.5f, 0.8f, 1.0f };

        // Only need to clear the background when not rendering the full 3D scene since
        // the 3D world is a fully enclosed box and the dynamics prevents the camera from
        // moving outside this space.
        if (i > 0)
        {
            // Doing the Right Eye View.
            d3dContext->ClearRenderTargetView(m_deviceResources->GetBackBufferRenderTargetViewRight(), clearColor);
        }
        else
        {
            // Doing the Mono or Left Eye View.
            d3dContext->ClearRenderTargetView(m_deviceResources->GetBackBufferRenderTargetView(), clearColor);
        }

        // Render the scene objects
        if (m_game != nullptr && m_gameResourcesLoaded && m_levelResourcesLoaded)
        {
            // This section is only used after the game state has been initialized and all device
            // resources needed for the game have been created and associated with the game objects.
            if (stereoEnabled)
            {
                // When doing stereo, it is necessary to update the projection matrix once per rendering pass.

                auto orientation = m_deviceResources->GetOrientationTransform3D();

                ConstantBufferChangeOnResize changesOnResize;
                // Apply either a left or right eye projection, which is an offset from the middle
                XMStoreFloat4x4(
                    &changesOnResize.projection,
                    XMMatrixMultiply(
                        XMMatrixTranspose(
                            i == 0 ?
                            m_game->GameCamera().LeftEyeProjection() :
                            m_game->GameCamera().RightEyeProjection()
                            ),
                        XMMatrixTranspose(XMLoadFloat4x4(&orientation))
                        )
                    );

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

            // Update variables that change once per frame.
            ConstantBufferChangesEveryFrame constantBufferChangesEveryFrameValue;
            XMStoreFloat4x4(
                &constantBufferChangesEveryFrameValue.view,
                XMMatrixTranspose(m_game->GameCamera().View())
                );
            d3dContext->UpdateSubresource(
                m_constantBufferChangesEveryFrame.get(),
                0,
                nullptr,
                &constantBufferChangesEveryFrameValue,
                0,
                0
                );

            // Set up the graphics pipeline. This sample uses the same InputLayout and set of
            // constant buffers for all shaders, so they only need to be set once per frame.
            // For more info about the graphics or rendering pipeline, see
            // https://learn.microsoft.com/windows/win32/direct3d11/overviews-direct3d-11-graphics-pipeline

            // IASetInputLayout binds an input-layout object to the input-assembler (IA) stage. 
            // Input-layout objects describe how vertex buffer data is streamed into the IA pipeline stage.
            // Set up the Direct3D context to use this vertex layout. For more info, see
            // https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-iasetinputlayout
            d3dContext->IASetInputLayout(m_vertexLayout.get());

            // VSSetConstantBuffers sets the constant buffers used by the vertex shader pipeline stage.
            // Set up the Direct3D context to use these constant buffers. For more info, see
            // https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-vssetconstantbuffers

            ID3D11Buffer* constantBufferNeverChanges{ m_constantBufferNeverChanges.get() };
            d3dContext->VSSetConstantBuffers(0, 1, &constantBufferNeverChanges);
            ID3D11Buffer* constantBufferChangeOnResize{ m_constantBufferChangeOnResize.get() };
            d3dContext->VSSetConstantBuffers(1, 1, &constantBufferChangeOnResize);
            ID3D11Buffer* constantBufferChangesEveryFrame{ m_constantBufferChangesEveryFrame.get() };
            d3dContext->VSSetConstantBuffers(2, 1, &constantBufferChangesEveryFrame);
            ID3D11Buffer* constantBufferChangesEveryPrim{ m_constantBufferChangesEveryPrim.get() };
            d3dContext->VSSetConstantBuffers(3, 1, &constantBufferChangesEveryPrim);

            // Sets the constant buffers used by the pixel shader pipeline stage. 
            // For more info, see
            // https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-pssetconstantbuffers

            d3dContext->PSSetConstantBuffers(2, 1, &constantBufferChangesEveryFrame);
            d3dContext->PSSetConstantBuffers(3, 1, &constantBufferChangesEveryPrim);
            ID3D11SamplerState* samplerLinear{ m_samplerLinear.get() };
            d3dContext->PSSetSamplers(0, 1, &samplerLinear);

            for (auto&& object : m_game->RenderObjects())
            {
                // The 3D object render method handles the rendering.
                // For more info, see Primitive rendering below.
                object->Render(d3dContext, m_constantBufferChangesEveryPrim.get());
            }
        }

        // Start of 2D rendering
        ...
    }
}

Rendu primitif

Lorsque vous effectuez le rendu de la scène, vous parcourez tous les objets qui doivent être rendus. Les étapes ci-dessous sont répétées pour chaque objet (primitive).

  • Mettez à jour la mémoire tampon constante (m_constantBufferChangesEveryPrim) avec la matrice de transformation mondiale et les informations matérielles du modèle.
  • Le m_constantBufferChangesEveryPrim contient des paramètres pour chaque objet. Il inclut la matrice de transformation objet-monde ainsi que les propriétés matérielles telles que la couleur et l’exposant spéculaire pour les calculs d’éclairage.
  • Définissez le contexte Direct3D pour utiliser la disposition de vertex d’entrée pour les données d’objet de maillage à diffuser dans l’étape d’assembleur d’entrée (IA) du pipeline de rendu.
  • Définissez le contexte Direct3D pour utiliser une mémoire tampon d’index à l’étape IA. Fournissez les informations primitives : type, ordre des données.
  • Envoyez un appel de dessin pour dessiner la primitive indexée sans instance. La méthode GameObject::Render met à jour la mémoire tampon de constante primitive avec les données spécifiques à une primitive donnée. Cela entraîne un appel DrawIndexed sur le contexte pour dessiner la géométrie de chaque primitive. Plus précisément, ce dessin d’appel met en file d’attente les commandes et les données vers l’unité de traitement graphique (GPU), comme paramétré par les données de mémoire tampon constantes. Chaque appel de dessin exécute le nuanceur de vertex une fois par sommet, puis le nuanceur de pixels une fois pour chaque pixel de chaque triangle de la primitive. Les textures font partie de l’état utilisé par le nuanceur de pixels pour effectuer le rendu.

Voici les raisons de l’utilisation de plusieurs mémoires tampons constantes.

  • Le jeu utilise plusieurs mémoires tampons constantes, mais il ne doit mettre à jour ces mémoires tampons qu’une seule fois par primitive. Comme mentionné précédemment, les mémoires tampons constantes sont comme les entrées des nuanceurs qui s’exécutent pour chaque primitive. Certaines données sont statiques (m_constantBufferNeverChanges) ; certaines données sont constantes sur le cadre (m_constantBufferChangesEveryFrame), telles que la position de la caméra ; et certaines données sont spécifiques à la primitive, telles que sa couleur et ses textures (m_constantBufferChangesEveryPrim).
  • Le convertisseur de jeu répartit ces données entrantes entre différentes mémoires tampons constantes pour optimiser la bande passante de mémoire utilisée par l’UC et le GPU. Cette approche permet également de réduire la quantité de données dont le GPU a besoin pour effectuer le suivi. Le GPU a une grande file d’attente de commandes, et chaque fois que le jeu appelle Draw, cette commande est mise en file d’attente avec les données qui lui sont associées. Lorsque le jeu met à jour la mémoire tampon constante de primitives et émet la commande Draw suivante, le pilote graphique ajoute cette commande suivante et les données associées à la file d’attente. Si le jeu dessine 100 primitives, la file d’attente peut contenir 100 copies des données de la mémoire tampon constante. Pour réduire la quantité de données que le jeu envoie au GPU, le jeu utilise une mémoire tampon constante primitive distincte qui contient uniquement les mises à jour de chaque primitive.

GameObject::Render, méthode

void GameObject::Render(
    _In_ ID3D11DeviceContext* context,
    _In_ ID3D11Buffer* primitiveConstantBuffer
    )
{
    if (!m_active || (m_mesh == nullptr) || (m_normalMaterial == nullptr))
    {
        return;
    }

    ConstantBufferChangesEveryPrim constantBuffer;

    // Put the model matrix info into a constant buffer, in world matrix.
    XMStoreFloat4x4(
        &constantBuffer.worldMatrix,
        XMMatrixTranspose(ModelMatrix())
        );

    // Check to see which material to use on the object.
    // If a collision (a hit) is detected, GameObject::Render checks the current context, which 
    // indicates whether the target has been hit by an ammo sphere. If the target has been hit, 
    // this method applies a hit material, which reverses the colors of the rings of the target to 
    // indicate a successful hit to the player. Otherwise, it applies the default material 
    // with the same method. In both cases, it sets the material by calling Material::RenderSetup, 
    // which sets the appropriate constants into the constant buffer. Then, it calls 
    // ID3D11DeviceContext::PSSetShaderResources to set the corresponding texture resource for the 
    // pixel shader, and ID3D11DeviceContext::VSSetShader and ID3D11DeviceContext::PSSetShader 
    // to set the vertex shader and pixel shader objects themselves, respectively.

    if (m_hit && m_hitMaterial != nullptr)
    {
        m_hitMaterial->RenderSetup(context, &constantBuffer);
    }
    else
    {
        m_normalMaterial->RenderSetup(context, &constantBuffer);
    }

    // Update the primitive constant buffer with the object model's info.
    context->UpdateSubresource(primitiveConstantBuffer, 0, nullptr, &constantBuffer, 0, 0);

    // Render the mesh.
    // See MeshObject::Render method below.
    m_mesh->Render(context);
}

MeshObject::Render, méthode

void MeshObject::Render(_In_ ID3D11DeviceContext* context)
{
    // PNTVertex is a struct. stride provides us the size required for all the mesh data
    // struct PNTVertex
    //{
    //  DirectX::XMFLOAT3 position;
    //  DirectX::XMFLOAT3 normal;
    //  DirectX::XMFLOAT2 textureCoordinate;
    //};
    uint32_t stride{ sizeof(PNTVertex) };
    uint32_t offset{ 0 };

    // Similar to the main render loop.
    // Input-layout objects describe how vertex buffer data is streamed into the IA pipeline stage.
    ID3D11Buffer* vertexBuffer{ m_vertexBuffer.get() };
    context->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);

    // IASetIndexBuffer binds an index buffer to the input-assembler stage.
    // For more info, see
    // https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-iasetindexbuffer.
    context->IASetIndexBuffer(m_indexBuffer.get(), DXGI_FORMAT_R16_UINT, 0);

    // Binds information about the primitive type, and data order that describes input data for the input assembler stage.
    // For more info, see
    // https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-iasetprimitivetopology.
    context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    // Draw indexed, non-instanced primitives. A draw API submits work to the rendering pipeline.
    // For more info, see
    // https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-drawindexed.
    context->DrawIndexed(m_indexCount, 0, 0);
}

DeviceResources::P resent, méthode

Nous appelons la méthode DeviceResources::P resent pour afficher le contenu que nous avons placé dans les mémoires tampons.

Nous utilisons le terme « chaîne d’échange » pour désigner une collection de mémoires tampons utilisées pour afficher des cadres à l’utilisateur. Chaque fois qu’une application présente une nouvelle image pour l’affichage, la première mémoire tampon de la chaîne d’échange remplace la mémoire tampon affichée. Ce processus est appelé permutation ou retournement. Pour plus d’informations, consultez Échanger des chaînes.

  • La méthode Present de l’interface IDXGISwapChain1 indique à DXGI de bloquer jusqu’à ce que la synchronisation verticale (VSync) se produise, mettant l’application en veille jusqu’au VSync suivant. Cela garantit que vous ne gaspillez pas les cycles de rendu des images qui ne seront jamais affichées à l’écran.
  • La méthode DiscardView de l’interface ID3D11DeviceContext3 ignore le contenu de la cible de rendu. Il s’agit d’une opération valide uniquement lorsque le contenu existant est entièrement remplacé. Si sale ou des rects de défilement sont utilisés, cet appel doit être supprimé.

Conseil

Pour obtenir une fréquence d’images fluide, vous devez vous assurer que la quantité de travail nécessaire au rendu d’une image correspond au temps entre les synchronisations virtuelles.

// Present the contents of the swap chain to the screen.
void DX::DeviceResources::Present()
{
    // The first argument instructs DXGI to block until VSync, putting the application
    // to sleep until the next VSync. This ensures we don't waste any cycles rendering
    // frames that will never be displayed to the screen.
    HRESULT hr = m_swapChain->Present(1, 0);

    // Discard the contents of the render target.
    // This is a valid operation only when the existing contents will be entirely
    // overwritten. If dirty or scroll rects are used, this call should be removed.
    m_d3dContext->DiscardView(m_d3dRenderTargetView.get());

    // Discard the contents of the depth stencil.
    m_d3dContext->DiscardView(m_d3dDepthStencilView.get());

    // If the device was removed either by a disconnection or a driver upgrade, we 
    // must recreate all device resources.
    if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
    {
        HandleDeviceLost();
    }
    else
    {
        winrt::check_hresult(hr);
    }
}

Étapes suivantes

Cette rubrique explique comment les graphiques sont rendus sur l’affichage et fournit une brève description de certains des termes de rendu utilisés (ci-dessous). En savoir plus sur le rendu dans la rubrique Rendu de l’infrastructure de rendu II : Rendu de jeu et découvrez comment préparer les données nécessaires avant le rendu.

Termes et concepts

Scène de jeu simple

Une scène de jeu simple est composée de quelques objets avec plusieurs sources de lumière.

La forme d’un objet est définie par un ensemble de coordonnées X, Y et Z dans l’espace. L’emplacement de rendu réel dans le monde du jeu peut être déterminé en appliquant une matrice de transformation aux coordonnées positionnelles X, Y et Z. Il peut également avoir un ensemble de coordonnées de texture (vous et V) qui spécifient la façon dont un matériau est appliqué à l’objet. Cela définit les propriétés de surface de l’objet et vous permet de voir si un objet a une surface rugueuse (comme une balle de tennis) ou une surface brillante lisse (comme une balle de bowling).

Les informations de scène et d’objet sont utilisées par l’infrastructure de rendu pour recréer la scène image par image, ce qui les rend actives sur votre moniteur d’affichage.

Pipeline de rendu

Le pipeline de rendu est le processus par lequel les informations de scène 3D sont traduites en image affichée à l’écran. Dans Direct3D 11, ce pipeline est programmable. Vous pouvez adapter les étapes pour prendre en charge vos besoins de rendu. Les étapes qui présentent des cœurs de nuanceur courants sont programmables à l’aide du langage de programmation HLSL. Il est également appelé pipeline de rendu graphique, ou simplement pipeline.

Pour vous aider à créer ce pipeline, vous devez vous familiariser avec ces détails.

Pour plus d’informations, consultez Comprendre le pipeline de rendu Direct3D 11 et le pipeline Graphics.

HLSL

HLSL est le langage de nuanceur de haut niveau pour DirectX. À l’aide de HLSL, vous pouvez créer des nuanceurs programmables de type C pour le pipeline Direct3D. Pour plus d’informations, consultez HLSL.

Nuanceurs

Un nuanceur peut être considéré comme un ensemble d’instructions qui déterminent l’affichage de la surface d’un objet lors du rendu. Ceux qui sont programmés à l’aide de HLSL sont appelés nuanceurs HLSL. Les fichiers de code source pour les nuanceurs [HLSL])(#hlsl) ont l’extension de .hlsl fichier. Ces nuanceurs peuvent être compilés au moment de la génération ou au moment de l’exécution et définis au moment de l’exécution dans la phase de pipeline appropriée. Un objet de nuanceur compilé a une .cso extension de fichier.

Les nuanceurs Direct3D 9 peuvent être conçus à l’aide du modèle de nuanceur 1, du modèle de nuanceur 2 et du modèle de nuanceur 3 ; Les nuanceurs Direct3D 10 peuvent être conçus uniquement sur le modèle de nuanceur 4. Les nuanceurs Direct3D 11 peuvent être conçus sur le modèle de nuanceur 5. Direct3D 11.3 et Direct3D 12 peuvent être conçus sur le modèle de nuanceur 5.1, et Direct3D 12 peut également être conçu sur le modèle de nuanceur 6.

Nuanceurs de vertex et nuanceurs de pixels

Les données entrent dans le pipeline graphique en tant que flux de primitives et sont traitées par différents nuanceurs tels que les nuanceurs de vertex et les nuanceurs de pixels.

Les nuanceurs de vertex traitent les sommets, effectuant généralement des opérations telles que les transformations, l’apparence et l’éclairage. Les nuanceurs de pixels permettent de riches techniques d’ombrage telles que l’éclairage par pixel et le post-traitement. Il combine des variables constantes, des données de texture, des valeurs interpolées par vertex et d’autres données pour produire des sorties par pixel.

Étapes du nuanceur

Une séquence de ces différents nuanceurs définis pour traiter ce flux de primitives est appelée étapes de nuanceur dans un pipeline de rendu. Les étapes réelles dépendent de la version de Direct3D, mais incluent généralement les étapes de vertex, de géométrie et de pixels. Il existe également d’autres étapes, telles que les nuanceurs de coque et de domaine pour le pavage, et le nuanceur de calcul. Toutes ces étapes sont entièrement programmables à l’aide de HLSL. Pour plus d’informations, consultez Pipeline graphique.

Différents formats de fichier de nuanceur

Voici les extensions de fichier de code du nuanceur.

  • Un fichier avec l’extension contient le .hlsl code source [HLSL])(#hlsl).
  • Un fichier avec l’extension .cso contient un objet de nuanceur compilé.
  • Un fichier avec l’extension .h est un fichier d’en-tête, mais dans un contexte de code de nuanceur, ce fichier d’en-tête définit un tableau d’octets qui contient des données de nuanceur.
  • Un fichier avec l’extension .hlsli contient le format des mémoires tampons constantes. Dans l’exemple de jeu, le fichier est Shaders>ConstantBuffers.hlsli.

Notes

Vous incorporez un nuanceur en chargeant un .cso fichier au moment de l’exécution ou en ajoutant un .h fichier dans votre code exécutable. Mais vous n’utiliserez pas les deux pour le même nuanceur.

Compréhension approfondie de DirectX

Direct3D 11 est un ensemble d’API qui peuvent nous aider à créer des graphiques pour des applications graphiques intensives telles que des jeux, où nous voulons disposer d’une bonne carte graphiques pour traiter des calculs intensifs. Cette section explique brièvement les concepts de programmation graphique Direct3D 11 : ressource, sous-ressource, appareil et contexte d’appareil.

Ressource

Vous pouvez considérer les ressources (également appelées ressources d’appareil) comme des informations sur le rendu d’un objet, comme la texture, la position ou la couleur. Les ressources fournissent des données au pipeline et définissent ce qui est rendu pendant votre scène. Les ressources peuvent être chargées à partir de votre média de jeu ou créées dynamiquement au moment de l’exécution.

Une ressource est en fait une zone de mémoire accessible par le pipeline Direct3D. Pour permettre un accès efficace du pipeline à la mémoire, les données fournies au pipeline (géométrie d’entrée, ressources des nuanceurs et textures) doivent être stockées dans une ressource. Il existe deux types de ressource dont dérivent l’ensemble des ressources Direct3D : la mémoire tampon et la texture. Jusqu’à 128 ressources peuvent être actives à chaque phase du pipeline. Pour plus d’informations, consultez Ressources.

Sous-ressource

Le terme sous-ressource fait référence à un sous-ensemble d’une ressource. Direct3D peut référencer une ressource entière ou référencer des sous-ensembles d’une ressource. Pour plus d’informations, consultez Sous-ressource.

Gabarit de profondeur

Une ressource de gabarit de profondeur contient le format et la mémoire tampon pour contenir les informations de profondeur et de gabarit. Il est créé à l’aide d’une ressource de texture. Pour plus d’informations sur la création d’une ressource de gabarit de profondeur, consultez Configuration des fonctionnalités Depth-Stencil. Nous accédons à la ressource de gabarit de profondeur via la vue de gabarit de profondeur implémentée à l’aide de l’interface ID3D11DepthStencilView .

Les informations de profondeur nous indiquent quelles zones de polygones se trouvent derrière d’autres, afin que nous puissions déterminer celles qui sont masquées. Les informations de gabarit nous indiquent quels pixels sont masqués. Il peut être utilisé pour produire des effets spéciaux, car il détermine si un pixel est dessiné ou non; définit le bit sur 1 ou 0.

Pour plus d’informations, consultez Vue de gabarit de profondeur, Mémoire tampon de profondeur et Mémoire tampon de gabarit.

Cible de rendu

Une cible de rendu est une ressource dans laquelle nous pouvons écrire à la fin d’une passe de rendu. Il est généralement créé à l’aide de la méthode ID3D11Device::CreateRenderTargetView en utilisant la mémoire tampon d’arrière de la chaîne d’échange (qui est également une ressource) comme paramètre d’entrée.

Chaque cible de rendu doit également avoir une vue de gabarit de profondeur correspondante, car lorsque nous utilisons OMSetRenderTargets pour définir la cible de rendu avant de l’utiliser, elle nécessite également une vue de gabarit de profondeur. Nous accédons à la ressource cible de rendu via la vue cible de rendu implémentée à l’aide de l’interface ID3D11RenderTargetView .

Appareil

Vous pouvez imaginer un appareil comme un moyen d’allouer et de détruire des objets, de restituer des primitives et de communiquer avec les graphiques carte via le pilote graphique.

Pour une explication plus précise, un appareil Direct3D est le composant de rendu de Direct3D. Un périphérique encapsule et stocke l’état de rendu, effectue des transformations et des opérations d’éclairage, et rastérise une image sur une surface. Pour plus d’informations, consultez Appareils.

Un appareil est représenté par l’interface ID3D11Device . En d’autres termes, l’interface ID3D11Device représente un adaptateur d’affichage virtuel et est utilisée pour créer des ressources appartenant à un appareil.

Il existe différentes versions de ID3D11Device. ID3D11Device5 est la dernière version et ajoute de nouvelles méthodes à celles dans ID3D11Device4. Pour plus d’informations sur la façon dont Direct3D communique avec le matériel sous-jacent, consultez Architecture wdDM (Windows Device Driver Model).

Chaque application doit avoir au moins un appareil ; la plupart des applications n’en créent qu’une seule. Créez un appareil pour l’un des pilotes matériels installés sur votre ordinateur en appelant D3D11CreateDevice ou D3D11CreateDeviceAndSwapChain et en spécifiant le type de pilote avec l’indicateur D3D_DRIVER_TYPE . Chaque appareil peut utiliser un ou plusieurs contextes d’appareil, en fonction de la fonctionnalité souhaitée. Pour plus d’informations, consultez Fonction D3D11CreateDevice.

Contexte d’appareil

Un contexte d’appareil est utilisé pour définir l’état du pipeline et générer des commandes de rendu à l’aide des ressources appartenant à un appareil.

Direct3D 11 implémente deux types de contextes d’appareil, l’un pour le rendu immédiat et l’autre pour le rendu différé ; les deux contextes sont représentés avec une interface ID3D11DeviceContext .

Les interfaces ID3D11DeviceContext ont des versions différentes ; ID3D11DeviceContext4 ajoute de nouvelles méthodes à celles dans ID3D11DeviceContext3.

ID3D11DeviceContext4 est introduit dans le Windows 10 Creators Update et est la dernière version de l’interface ID3D11DeviceContext. Les applications ciblant Windows 10 Creators Update et versions ultérieures doivent utiliser cette interface au lieu des versions antérieures. Pour plus d’informations, consultez ID3D11DeviceContext4.

DX::D eviceResources

La classe DX::D eviceResources se trouve dans les fichiers DeviceResources.cpp.h/ et contrôle toutes les ressources d’appareil DirectX.

Buffer

Une ressource de mémoire tampon est une collection de données entièrement typées regroupées en éléments. Vous pouvez utiliser des mémoires tampons pour stocker un large éventail de données, notamment des vecteurs de position, des vecteurs normaux, des coordonnées de texture dans une mémoire tampon de vertex, des index dans une mémoire tampon d’index ou l’état de l’appareil. Les éléments de mémoire tampon peuvent inclure des valeurs de données compressées (telles que des valeurs de surface R8G8B8A8 ), des entiers 8 bits uniques ou quatre valeurs à virgule flottante 32 bits.

Il existe trois types de mémoires tampons disponibles : mémoire tampon de vertex, mémoire tampon d’index et mémoire tampon constante.

Tampon de vertex

Contient les données de vertex utilisées pour définir votre géométrie. Les données de vertex incluent les coordonnées de position, les données de couleur, les données de coordonnées de texture, les données normales, etc.

Mémoire tampon d’index

Contient des décalages entiers dans des mémoires tampons de vertex et sont utilisés pour rendre les primitives plus efficacement. Une mémoire tampon d’index contient un ensemble séquentiel d’index 16 bits ou 32 bits ; chaque index est utilisé pour identifier un sommet dans une mémoire tampon de vertex.

Mémoire tampon constante ou mémoire tampon constante du nuanceur

Vous permet de fournir efficacement des données de nuanceur au pipeline. Vous pouvez utiliser des mémoires tampons constantes comme entrées pour les nuanceurs qui s’exécutent pour chaque primitive et stockent les résultats de l’étape de sortie de flux du pipeline de rendu. D’un point de vue conceptuel, une mémoire tampon constante ressemble à une mémoire tampon de vertex à élément unique.

Conception et implémentation de tampons

Vous pouvez concevoir des mémoires tampons basées sur le type de données, par exemple, comme dans notre exemple de jeu, une mémoire tampon est créée pour les données statiques, une autre pour les données constantes sur l’image et une autre pour des données spécifiques à une primitive.

Tous les types de mémoires tampons sont encapsulés par l’interface ID3D11Buffer et vous pouvez créer une ressource de mémoire tampon en appelant ID3D11Device::CreateBuffer. Toutefois, une mémoire tampon doit être liée au pipeline pour pouvoir y accéder. Les mémoires tampons peuvent être liées à plusieurs étapes de pipeline simultanément pour la lecture. Une mémoire tampon peut également être liée à une seule étape de pipeline pour l’écriture ; Toutefois, la même mémoire tampon ne peut pas être liée simultanément à la lecture et à l’écriture.

Vous pouvez lier des mémoires tampons de ces manières.

  • À l’étape d’assembleur d’entrée en appelant des méthodes ID3D11DeviceContext telles que ID3D11DeviceContext::IASetVertexBuffers et ID3D11DeviceContext::IASetIndexBuffer.
  • À l’étape de flux de sortie en appelant ID3D11DeviceContext::SOSetTargets.
  • À l’étape du nuanceur en appelant des méthodes de nuanceur, telles que ID3D11DeviceContext::VSSetConstantBuffers.

Pour plus d’informations, consultez Présentation des mémoires tampons dans Direct3D 11.

DXGI

Microsoft DirectX Graphics Infrastructure (DXGI) est un sous-système qui encapsule certaines des tâches de bas niveau requises par Direct3D. Une attention particulière doit être prise lors de l’utilisation de DXGI dans une application multithread pour s’assurer que les interblocages ne se produisent pas. Pour plus d’informations, consultez Multithreading et DXGI

Niveau de fonctionnalité

Le niveau de fonctionnalité est un concept introduit dans Direct3D 11 pour gérer la diversité des cartes vidéo dans les machines nouvelles et existantes. Un niveau de fonctionnalité est un ensemble bien défini de fonctionnalités d’unité de traitement graphique (GPU).

Chaque vidéo carte implémente un certain niveau de fonctionnalité DirectX en fonction des GPU installés. Dans les versions antérieures de Microsoft Direct3D, vous pouviez trouver la version de Direct3D, la vidéo carte implémentée, puis programmer votre application en conséquence.

Avec le niveau de fonctionnalité, lorsque vous créez un appareil, vous pouvez essayer de créer un appareil pour le niveau de fonctionnalité que vous souhaitez demander. Si la création de l’appareil fonctionne, ce niveau de fonctionnalité existe. Si ce n’est pas le cas, le matériel ne prend pas en charge ce niveau de fonctionnalité. Vous pouvez essayer de recréer un appareil à un niveau de fonctionnalité inférieur, ou vous pouvez choisir de quitter l’application. Pour instance, le niveau de fonctionnalité 12_0 nécessite Direct3D 11.3 ou Direct3D 12 et le modèle de nuanceur 5.1. Pour plus d’informations, consultez Niveaux de fonctionnalités Direct3D : Vue d’ensemble de chaque niveau de fonctionnalité.

À l’aide des niveaux de fonctionnalités, vous pouvez développer une application pour Direct3D 9, Microsoft Direct3D 10 ou Direct3D 11, puis l’exécuter sur du matériel 9, 10 ou 11 (avec quelques exceptions). Pour plus d’informations, consultez Niveaux de fonctionnalités Direct3D.

Rendu stéréo

Le rendu stéréo est utilisé pour améliorer l’illusion de profondeur. Il utilise deux images, l’une de l’œil gauche et l’autre de l’œil droit pour afficher une scène sur l’écran d’affichage.

Mathématiquement, nous appliquons une matrice de projection stéréo, qui est un léger décalage horizontal à droite et à gauche, de la matrice de projection mono normale pour y parvenir.

Nous avons effectué deux passes de rendu pour obtenir un rendu stéréo dans cet exemple de jeu.

  • Liez à la cible de rendu droite, appliquez une projection droite, puis dessinez l’objet primitif.
  • Liez à la cible de rendu gauche, appliquez une projection gauche, puis dessinez l’objet primitif.

Espace de la caméra et des coordonnées

Le jeu a le code en place pour mettre à jour le monde dans son propre système de coordonnées (parfois appelé espace du monde ou espace de scène). Tous les objets, y compris la caméra, sont positionnés et orientés dans cet espace. Pour plus d’informations, consultez Systèmes de coordonnées.

Un nuanceur de vertex effectue la lourde opération de conversion des coordonnées du modèle en coordonnées d’appareil avec l’algorithme suivant (où V est un vecteur et M une matrice).

V(device) = V(model) x M(model-to-world) x M(world-to-view) x M(view-to-device)

  • M(model-to-world) est une matrice de transformation pour les coordonnées de modèle en coordonnées de monde, également appelée matrice de transformation mondiale. Elle est fournie par la primitive.
  • M(world-to-view) est une matrice de transformation pour les coordonnées du monde afin d’afficher les coordonnées, également appelée matrice de transformation d’affichage.
    • Elle est fournie par la matrice globale de la caméra. Il est défini par la position de la caméra avec les vecteurs d’apparence (le vecteur d’regard qui pointe directement dans la scène à partir de la caméra et le vecteur de recherche vers le haut qui est perpendiculaire à celle-ci).
    • Dans l’exemple de jeu, m_viewMatrix est la matrice de transformation d’affichage et est calculé à l’aide de Camera::SetViewParams.
  • M(view-to-device) est une matrice de transformation pour les coordonnées de vue en coordonnées d’appareil, également appelée matrice de transformation de projection.
    • Elle est fournie par la projection de la caméra. Il fournit des informations sur la quantité de cet espace réellement visible dans la scène finale. Le champ de vision (FoV), les proportions et les plans de découpage définissent la matrice de transformation de projection.
    • Dans l’exemple de jeu, m_projectionMatrix définit la transformation en coordonnées de projection, calculée à l’aide de Camera::SetProjParams (Pour la projection stéréo, vous utilisez deux matrices de projection, une pour la vue de chaque œil).

Le code du nuanceur dans VertexShader.hlsl est chargé avec ces vecteurs et matrices à partir des mémoires tampons constantes, et effectue cette transformation pour chaque sommet.

Transformation de coordonnées

Direct3D utilise trois transformations pour modifier vos coordonnées de modèle 3D en coordonnées de pixels (espace d’écran). Ces transformations sont une transformation mondiale, une transformation de vue et une transformation de projection. Pour plus d’informations, consultez Vue d’ensemble de la transformation.

Matrice de transformation mondiale

Une transformation de monde change les coordonnées de l’espace de modèle, où les sommets sont définis par rapport à l’origine locale d’un modèle, à l’espace mondial, où les sommets sont définis par rapport à une origine commune à tous les objets d’une scène. En substance, la transformation du monde place un modèle dans le monde; d’où son nom. Pour plus d’informations, consultez Transformation mondiale.

Afficher la matrice de transformation

La transformation de vue localise la visionneuse dans l’espace du monde, transformant les sommets en espace de caméra. Dans l’espace de la caméra, la caméra, ou visionneuse, est à l’origine, regardant dans la direction z positive. Pour plus d’informations, accédez à Afficher la transformation.

Matrice de transformation de projection

La transformation de projection convertit le frustum d’affichage en forme de cuboïde. Un frustum d’affichage est un volume 3D dans une scène positionnée par rapport à la caméra de la fenêtre d’affichage. Une fenêtre d’affichage est un rectangle 2D dans lequel une scène 3D est projetée. Pour plus d’informations, consultez Fenêtres d’affichage et découpage

Étant donné que la fin proche du frustum d’affichage est plus petite que l’extrémité lointaine, cela a pour effet de développer des objets qui sont proches de la caméra; c’est ainsi que la perspective est appliquée à la scène. Ainsi, les objets qui sont plus proches du joueur apparaissent plus grands; Les objets qui sont plus éloignés semblent plus petits.

Mathématiquement, la transformation de projection est une matrice qui est généralement à la fois une échelle et une projection de perspective. Il fonctionne comme l’objectif d’un appareil photo. Pour plus d’informations, consultez Transformation de projection.

État de l’échantillonneur

L’état de l’échantillonneur détermine comment les données de texture sont échantillonnées à l’aide des modes d’adressage de texture, du filtrage et du niveau de détail. L’échantillonnage est effectué chaque fois qu’un pixel de texture (ou texel) est lu à partir d’une texture.

Une texture contient un tableau de texels. La position de chaque texel est indiquée par (u,v), où u est la largeur et v est la hauteur, et est mappée entre 0 et 1 en fonction de la largeur et de la hauteur de la texture. Les coordonnées de texture obtenues sont utilisées pour traiter un texel lors de l’échantillonnage d’une texture.

Lorsque les coordonnées de texture sont inférieures à 0 ou supérieures à 1, le mode d’adresse de texture définit la façon dont les coordonnées de texture répondent à un emplacement texel. Par exemple, lors de l’utilisation de TextureAddressMode.Clamp, toute coordonnée située en dehors de la plage 0-1 est limitée à une valeur maximale de 1 et à une valeur minimale de 0 avant l’échantillonnage.

Si la texture est trop grande ou trop petite pour le polygone, la texture est filtrée pour s’adapter à l’espace. Un filtre d’agrandissement agrandit une texture, un filtre de minimisation réduit la texture pour qu’elle s’adapte à une zone plus petite. L’agrandissement de texture répète l’exemple de texel pour une ou plusieurs adresses, ce qui génère une image plus floue. La réduction des textures est plus compliquée, car elle nécessite la combinaison de plusieurs valeurs de texel en une seule valeur. Cela peut entraîner des alias ou des arêtes dentelées en fonction des données de texture. L’approche la plus courante pour la minimisation consiste à utiliser un mipmap. Un mipmap est une texture à plusieurs niveaux. La taille de chaque niveau est une puissance de 2 plus petite que le niveau précédent jusqu’à une texture 1x1. Lorsque la minimisation est utilisée, un jeu choisit le niveau de mipmap le plus proche de la taille nécessaire au moment du rendu.

Classe BasicLoader

BasicLoader est une classe de chargeur simple qui prend en charge le chargement de nuanceurs, de textures et de maillages à partir de fichiers sur disque. Il fournit des méthodes synchrones et asynchrones. Dans cet exemple de jeu, les BasicLoader.h/.cpp fichiers se trouvent dans le dossier Utilitaires .

Pour plus d’informations, consultez Chargeur de base.