Partager via


Ajouter un contenu visuel à l’échantillon Marble Maze

Ce document décrit comment le jeu Marble Maze utilise Direct3D et Direct2D dans l’environnement d’application plateforme Windows universelle (UWP) pour que vous puissiez apprendre les modèles, puis les adapter lorsque vous travaillez avec votre propre contenu de jeu. Pour savoir comment les composants de jeu visuel s’intègrent dans la structure d’application globale de Marble Maze, veuillez consulter la rubrique Structure de l’application Marble Maze.

Nous avons suivi ces étapes de base à mesure que nous avons développé les aspects visuels de Marble Maze :

  1. Créez une infrastructure de base qui initialise les environnements Direct3D et Direct2D.
  2. Utilisez des programmes de modification de modèles et d’image pour concevoir les éléments multimédias 2D et 3D qui apparaissent dans le jeu.
  3. Garantissez le chargement et l’affichage corrects des éléments 2D et 3D dans le jeu.
  4. Intégrez des nuanceurs de vertex et des nuanceurs de pixels qui améliorent la qualité visuelle des éléments multimédias du jeu.
  5. Intégrez la logique de jeu, telle que l’animation et l’entrée utilisateur.

Nous nous sommes tout d’abord concentrés sur l’ajout d’éléments multimédias 3D, puis d’éléments multimédias 2D. Par exemple, nous nous sommes concentrés sur la logique de jeu principale avant d’ajouter le système de menu et le minuteur.

Nous avons également besoin d’itérer certaines de ces étapes plusieurs fois pendant le processus de développement. Par exemple, lors de modifications des modèles de maillage et de bille, nous avons dû également modifier le code du nuanceur qui prend en charge ces modèles.

Remarque

L’échantillon de code qui correspond à ce document se trouve dans l’échantillon de jeu Marble Maze DirectX.

  Voici quelques-uns des points clés que ce document décrit lorsque vous travaillez avec le contenu DirectX et visuel du jeu, à savoir lorsque vous initialisez les bibliothèques graphiques DirectX, chargez les ressources de scène, mettez à jour, puis affichez la scène :

  • L’ajout de contenu de jeu implique généralement de nombreuses étapes. Ces étapes nécessitent également souvent une itération. Les développeurs de jeu se concentrent surtout d’abord sur l’ajout de contenu 3D, puis sur l’ajout de contenu 2D.
  • Atteignez davantage de clients, puis donnez-leur une grande expérience en prenant en charge la plus grande gamme de matériel graphique possible.
  • Séparez nettement les formats au moment de la conception et au moment de l’exécution. Structurez vos éléments multimédias au moment du design pour optimiser la flexibilité, puis activer des itérations rapides sur le contenu. Mettez en forme, puis compressez vos éléments multimédias pour charger et afficher aussi efficacement que possible lors du temps d’exécution.
  • Vous créez les appareils Direct3D et Direct2D dans une application UWP comme vous le faites dans une application de bureau Windows classique. Une différence importante est liée à la façon dont la chaîne d’échange est associée à la fenêtre de sortie.
  • Lorsque vous concevez votre jeu, vérifiez que le format de maillage que vous choisissez prend en charge vos scénarios clés. Par exemple, si votre jeu nécessite une collision, vérifiez que vous pouvez obtenir des données de collision depuis vos maillages.
  • Séparez la logique de jeu de la logique de rendu en mettant d’abord à jour tous les objets de scène avant de les afficher.
  • En règle générale, vous dessinez vos objets de scène 3D, puis les objets 2D qui s’affichent devant la scène.
  • Synchronisez le dessin sur le vide vertical pour veiller à ce que votre jeu ne passe pas de temps à dessiner des cadres qui n’apparaîtront jamais réellement sur l’affichage. Un vide vertical représente le temps entre la fin du dessin d’une image dans le moniteur et le début de l’image suivante.

Prise en main des graphiques DirectX

Quand nous avons commencé à imaginer le jeu de la plateforme Windows universelle (UWP) Merble Maze, notre choix s’est tourné vers C++ et Direct3D 11.1, tous deux excellents pour créer des jeux 3D qui nécessitent un contrôle optimal sur le rendu et pour obtenir un haut niveau de performance. DirectX 11.1 prend en charge le matériel de DirectX 9 vers DirectX 11 et peut donc vous aider à atteindre plus de clients plus efficacement, car vous n’avez pas besoin de réécrire de code pour chacune des versions antérieures de DirectX.

Marble Maze utilise Direct3D 11.1 pour le rendu des éléments 3D, c’est-à-dire la bille et le labyrinthe. Marble Maze utilise également Direct2D, DirectWrite, et le Composant Imagerie Windows (WIC) pour le dessin des éléments multimédias de jeu 2D, comme les menus et le minuteur.

Le développement de jeux nécessite une planification. S’il s’agit de votre première utilisation des graphiques DirectX, nous vous recommandons de lire la rubrique DirectX : prise en main pour vous familiariser avec les concepts de base liés à la création d’un jeu DirectX UWP. Quand vous lisez ce document et utilisez le code source de Marble Maze, vous pouvez consulter les ressources suivantes pour des informations détaillées sur les graphiques DirectX :

  • Graphismes Direct3D 11 : décrit Direct3D 11, une API graphique 3D à accélération matérielle puissante pour le rendu d’éléments géométriques 3D sur la plateforme Windows.
  • Direct2D : décrit Direct2D, une API graphique 2D à accélération matérielle qui fournit un niveau de performance élevé et un rendu de grande qualité pour les éléments géométriques 2D, les bitmaps et le texte.
  • DirectWrite : décrit DirectWrite, qui prend en charge un rendu de haute qualité du texte.
  • Composant Imagerie Windows : décrit WIC, une plateforme extensible qui fournit une API de bas niveau pour les images numériques.

Niveaux de fonctionnalité

Direct3D 11 propose un modèle de référence nommé niveaux de fonctionnalité. Un niveau de fonctionnalité est un ensemble bien défini de fonctionnalités GPU. Utilisez les niveaux de fonctionnalités pour cibler votre jeu pour qu’il s’exécute sur des versions antérieures du matériel Direct3D. Marble Maze prend en charge le niveau de fonctionnalité 9.1, car ce système ne nécessite aucune fonctionnalité avancée des niveaux supérieurs. Nous vous recommandons de prendre en charge la plus grande gamme de matériel possible, puis de mettre à l’échelle votre contenu de jeu pour que vos clients qui disposent d’ordinateurs haut de gamme ou bas de gamme aient une grande expérience. Si vous souhaitez en savoir plus sur les niveaux de fonctionnalités, veuillez consulter la rubrique Direct3D 11 sur le matériel de niveau inférieur.

Initialisation de Direct3D et Direct2D

Un appareil représente la carte vidéo. Vous créez les appareils Direct3D et Direct2D dans une application UWP comme vous le faites dans une application de bureau Windows classique. La principale différence est la façon dont vous connectez la chaîne d’échange Direct3D au système de fenêtrage.

La classe DeviceResources est une base pour gérer Direct3D et Direct2D. Elle gère l’infrastructure générale, et non les éléments multimédias spécifiques du jeu. Marble Maze définit la classe MarbleMaze pour gérer les élément multimédias intrinsèques du jeu. Il dispose ainsi d’une référence à un objet DeviceResources qui lui procure un accès à Direct3D et à Direct2D.

Lors de l’initialisation, le constructeur DeviceResources crée des ressources indépendantes de l’appareil ainsi que des appareils Direct3D et Direct2D.

// Initialize the Direct3D resources required to run. 
DX::DeviceResources::DeviceResources() :
    m_screenViewport(),
    m_d3dFeatureLevel(D3D_FEATURE_LEVEL_9_1),
    m_d3dRenderTargetSize(),
    m_outputSize(),
    m_logicalSize(),
    m_nativeOrientation(DisplayOrientations::None),
    m_currentOrientation(DisplayOrientations::None),
    m_dpi(-1.0f),
    m_deviceNotify(nullptr)
{
    CreateDeviceIndependentResources();
    CreateDeviceResources();
}

La classe DeviceResources sépare cette fonctionnalité, si bien qu’elle peut répondre plus facilement lorsque l’environnement change. Par exemple, elle appelle la méthode CreateWindowSizeDependentResources lorsque la taille de la fenêtre change.

Initialisation des usines Direct2D, DirectWrite et WIC

La méthode DeviceResources::CreateDeviceIndependentResources crée les fabriques pour Direct2D, DirectWrite et WIC. Dans les graphiques DirectX, les usines sont les points de départ de la création de ressources graphiques. Marble Maze spécifie D2D1\_FACTORY\_TYPE\_SINGLE\_THREADED, car il effectue le dessin sur le thread principal.

// These are the resources required independent of hardware. 
void DX::DeviceResources::CreateDeviceIndependentResources()
{
    // Initialize Direct2D resources.
    D2D1_FACTORY_OPTIONS options;
    ZeroMemory(&options, sizeof(D2D1_FACTORY_OPTIONS));

#if defined(_DEBUG)
    // If the project is in a debug build, enable Direct2D debugging via SDK Layers.
    options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif

    // Initialize the Direct2D Factory.
    DX::ThrowIfFailed(
        D2D1CreateFactory(
            D2D1_FACTORY_TYPE_SINGLE_THREADED,
            __uuidof(ID2D1Factory2),
            &options,
            &m_d2dFactory
            )
        );

    // Initialize the DirectWrite Factory.
    DX::ThrowIfFailed(
        DWriteCreateFactory(
            DWRITE_FACTORY_TYPE_SHARED,
            __uuidof(IDWriteFactory2),
            &m_dwriteFactory
            )
        );

    // Initialize the Windows Imaging Component (WIC) Factory.
    DX::ThrowIfFailed(
        CoCreateInstance(
            CLSID_WICImagingFactory2,
            nullptr,
            CLSCTX_INPROC_SERVER,
            IID_PPV_ARGS(&m_wicFactory)
            )
        );
}

Création des appareils Direct3D et Direct2D

La méthode DeviceResources::CreateDeviceResources appelle D3D11CreateDevice pour créer l’objet d’appareil qui représente l’adaptateur d’affichage Direct3D. Dans la mesure où Marble Maze prend en charge le niveau de fonctionnalité 9.1 et supérieur, la méthode DeviceResources::CreateDeviceResources indique les niveaux 9.1 à 11.1 dans le tableau featureLevels. Direct3D parcourt la liste dans l’ordre, puis donne à l’application le premier niveau de fonctionnalité disponible. Par conséquent, les entrées du tableau D3D_FEATURE_LEVEL sont répertoriées en ordre décroissant, pour que l’application puisse obtenir le niveau de fonctionnalité le plus élevé disponible. La méthode DeviceResources::CreateDeviceResources obtient l’appareil Direct3D 11.1 en interrogeant l’appareil Direct3D 11 retourné par D3D11CreateDevice.

// This flag adds support for surfaces with a different color channel ordering
// than the API default. It is required for compatibility with Direct2D.
UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;

#if defined(_DEBUG)
    if (DX::SdkLayersAvailable())
    {
        // If the project is in a debug build, enable debugging via SDK Layers 
        // with this flag.
        creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
    }
#endif

// This array defines the set of DirectX hardware feature levels this app will support.
// Note the ordering should be preserved.
// Don't forget to declare your application's minimum required feature level in its
// description.  All applications are assumed to support 9.1 unless otherwise stated.
D3D_FEATURE_LEVEL featureLevels[] =
{
    D3D_FEATURE_LEVEL_11_1,
    D3D_FEATURE_LEVEL_11_0,
    D3D_FEATURE_LEVEL_10_1,
    D3D_FEATURE_LEVEL_10_0,
    D3D_FEATURE_LEVEL_9_3,
    D3D_FEATURE_LEVEL_9_2,
    D3D_FEATURE_LEVEL_9_1
};

// Create the Direct3D 11 API device object and a corresponding context.
ComPtr<ID3D11Device> device;
ComPtr<ID3D11DeviceContext> context;

HRESULT hr = D3D11CreateDevice(
    nullptr,                    // Specify nullptr to use the default adapter.
    D3D_DRIVER_TYPE_HARDWARE,   // Create a device using the hardware graphics driver.
    0,                          // Should be 0 unless the driver is D3D_DRIVER_TYPE_SOFTWARE.
    creationFlags,              // Set debug and Direct2D compatibility flags.
    featureLevels,              // List of feature levels this app can support.
    ARRAYSIZE(featureLevels),   // Size of the list above.
    D3D11_SDK_VERSION,          // Always set this to D3D11_SDK_VERSION for UWP apps.
    &device,                    // Returns the Direct3D device created.
    &m_d3dFeatureLevel,         // Returns feature level of device created.
    &context                    // Returns the device immediate context.
    );

if (FAILED(hr))
{
    // If the initialization fails, fall back to the WARP device.
    // For more information on WARP, see:
    // https://go.microsoft.com/fwlink/?LinkId=286690
    DX::ThrowIfFailed(
        D3D11CreateDevice(
            nullptr,
            D3D_DRIVER_TYPE_WARP, // Create a WARP device instead of a hardware device.
            0,
            creationFlags,
            featureLevels,
            ARRAYSIZE(featureLevels),
            D3D11_SDK_VERSION,
            &device,
            &m_d3dFeatureLevel,
            &context
            )
        );
}

// Store pointers to the Direct3D 11.1 API device and immediate context.
DX::ThrowIfFailed(
    device.As(&m_d3dDevice)
    );

DX::ThrowIfFailed(
    context.As(&m_d3dContext)
    );

La méthode DeviceResources::CreateDeviceResources crée ensuite l’appareil Direct2D. Direct2D utilise Microsoft DirectX Graphics Infrastructure (DXGI) pour interagir avec Direct3D. DXGI permet de partager des surfaces de mémoire vidéo entre des runtimes graphiques. Marble Maze utilise l’appareil DXGI sous-jacent depuis l’appareil Direct3D pour créer l’appareil Direct2D depuis l’usine Direct2D.

// Create the Direct2D device object and a corresponding context.
ComPtr<IDXGIDevice3> dxgiDevice;
DX::ThrowIfFailed(
    m_d3dDevice.As(&dxgiDevice)
    );

DX::ThrowIfFailed(
    m_d2dFactory->CreateDevice(dxgiDevice.Get(), &m_d2dDevice)
    );

DX::ThrowIfFailed(
    m_d2dDevice->CreateDeviceContext(
        D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
        &m_d2dContext
        )
    );

Si vous souhaitez en savoir plus sur DXGI et l’interopérabilité entre Direct2D et Direct3D, veuillez consulter les rubriques Vue d’ensemble de DXGI et Vue d’ensemble de l’interopérabilité Direct2D et Direct3D.

Association de Direct3D à l’affichage

La méthode DeviceResources::CreateWindowSizeDependentResources crée les ressources graphiques qui dépendent d’une taille de fenêtre donnée, telle que la chaîne d’échange et les cibles de rendu Direct3D et Direct2D. Une différence importante entre une application UWP DirectX et une application de bureau réside dans la façon dont la chaîne d’échange est associée à la fenêtre de sortie. Une chaîne d’échange est chargée d’afficher la mémoire tampon où l’appareil s’affiche sur le moniteur. Le document Structure de l’application Marble Maze décrit les différences entre le système de fenêtrage d’une application UWP et celui d’une application de bureau. Dans la mesure où les applications UWP n’utilisent pas d’objets HWND, Marble Maze doit utiliser la méthode IDXGIFactory2::CreateSwapChainForCoreWindow pour associer la sortie de l’appareil à l’affichage. L’exemple suivant montre le composant de la méthode DeviceResources::CreateWindowSizeDependentResources qui crée la chaîne d’échange.

// Obtain the final swap chain for this window from the DXGI factory.
DX::ThrowIfFailed(
    dxgiFactory->CreateSwapChainForCoreWindow(
        m_d3dDevice.Get(),
        reinterpret_cast<IUnknown*>(m_window.Get()),
        &swapChainDesc,
        nullptr,
        &m_swapChain
        )
    );

Pour réduire la consommation d’alimentation, ce qui est important pour les appareils à batterie tels que les ordinateurs portables et les tablettes, la méthode DeviceResources::CreateWindowSizeDependentResources appelle la méthode IDXGIDevice1::SetMaximumFrameLatency pour veiller à ce que le jeu s’affiche uniquement après le vide vertical. La synchronisation avec le vide vertical est décrite plus en détail dans la section Présentation de la scène, de ce document.

// Ensure that DXGI does not queue more than one frame at a time. This both 
// reduces latency and ensures that the application will only render after each
// VSync, minimizing power consumption.
DX::ThrowIfFailed(
    dxgiDevice->SetMaximumFrameLatency(1)
    );

La méthode DeviceResources::CreateWindowSizeDependentResources initialise les ressources graphiques d’une manière qui fonctionne pour la plupart des jeux.

Remarque

Le terme affichage a une signification différente dans Windows Runtime et Direct3D. Dans Windows Runtime, un affichage fait référence à la collection de paramètres d’interface utilisateur d’une application, y compris la zone d’affichage et les comportements d’entrée, ainsi que le thread qu’il utilise pour le traitement. Vous devez spécifier la configuration et les paramètres dont vous avez besoin lorsque vous créez un affichage. Le processus de configuration de l’affichage d’application est décrit dans la structure de l’application Marble Maze. Dans Direct3D, le terme « affichage » a plusieurs significations. Un affichage de ressource définit les sous-ressources auxquelles cette ressource peut accéder. Par exemple, lorsqu’un objet de texture est associé à un affichage de ressource de nuanceur, ce nuanceur peut accéder ultérieurement à la texture. L’un des avantages d’un affichage de ressource est que vous pouvez interpréter les données de différentes manières à différentes étapes du pipeline de rendu. Si vous souhaitez en savoir plus sur la création d’affichages de source de données, veuillez consulter la rubrique Vues de ressource. Lors de l’utilisation dans le contexte d’une transformation d’affichage ou d’une matrice de transformation d’affichage, l’affichage fait référence à l’emplacement et à l’orientation de la caméra. Une transformation d’affichage déplace des objets dans le monde autour de la position et de l’orientation de la caméra. Si vous souhaitez en savoir plus sur les transformations d’affichage, veuillez consulter la rubrique Transformation d’affichage (Direct3D 9). L’utilisation d’affichages de ressources et de matrice par Marble Maze est décrite plus en détail dans cette rubrique.

 

Chargement des ressources de scène

Marble Maze utilise la classe BasicLoader, qui est déclarée dans BasicLoader.h, pour charger les textures et les nuanceurs. Marble Maze utilise la classe SDKMesh pour charger les maillages 3D du labyrinthe et de la bille.

Pour garantir une application réactive, Marble Maze charge les ressources de scène de manière asynchrone ou en arrière-plan. À mesure que des éléments multimédias se chargent en arrière-plan, votre jeu peut répondre aux événements de fenêtre. Ce processus est expliqué plus en détail dans la rubrique Chargement des éléments multimédias de jeu en arrière-plan dans ce guide.

Chargement de la superposition 2D et de l’interface utilisateur

Dans Marble Maze, la superposition est l’image qui apparaît en haut de l’écran. La superposition apparaît toujours devant la scène. Dans Marble Maze, la superposition contient le logo Windows et la chaîne de texte Échantillon de jeu DirectX Marble Maze. La gestion de la superposition est effectuée par la classe SampleOverlay, qui est définie dans SampleOverlay.h. Bien que nous utilisions la superposition comme composant des échantillons Direct3D, vous pouvez adapter ce code pour afficher toute image qui apparaît devant votre scène.

Voici un aspect important de la superposition : étant donné que son contenu ne change pas, la classe SampleOverlay dessine ou met en cache son contenu sur un objet ID2D1Bitmap1 pendant l’initialisation. Au moment du dessin, la classe SampleOverlay doit uniquement dessiner la bitmap à l’écran. De cette façon, l’exécution des routines coûteuses telles que le dessin de texte n’est pas nécessaire pour chaque image.

L’interface utilisateur (UI) se compose de composants 2D, tels que des menus et des affichages tête haute (HUD), qui apparaissent sur le devant de la scène. Marble Maze définit les éléments d’IU suivants :

  • Éléments de menu qui permettent à l’utilisateur de démarrer le jeu ou d’afficher des scores élevés.
  • Minuteur qui compte à rebours pendant trois secondes avant le début du jeu.
  • Minuteur qui suit le temps de jeu écoulé.
  • Tableau qui répertorie les temps de fin les plus rapides.
  • Texte Interrompu quand le jeu est interrompu.

Marble Maze définit des éléments d’interface spécifiques au jeu dans UserInterface.h. Marble Maze définit la classe ElementBase comme type de base pour tous les éléments d’IU. La classe ElementBase définit des attributs tels que la taille, la position, l’alignement et la visibilité d’un élément d’IU. Elle contrôle également la façon de mettre à jour et d’afficher les éléments.

class ElementBase
{
public:
    virtual void Initialize() { }
    virtual void Update(float timeTotal, float timeDelta) { }
    virtual void Render() { }

    void SetAlignment(AlignType horizontal, AlignType vertical);
    virtual void SetContainer(const D2D1_RECT_F& container);
    void SetVisible(bool visible);

    D2D1_RECT_F GetBounds();

    bool IsVisible() const { return m_visible; }

protected:
    ElementBase();

    virtual void CalculateSize() { }

    Alignment       m_alignment;
    D2D1_RECT_F     m_container;
    D2D1_SIZE_F     m_size;
    bool            m_visible;
};

En fournissant une classe de base commune pour les éléments d’IU, la classe UserInterface, qui gère l’interface utilisateur, n’a besoin que d’une collection d’objets ElementBase, ce qui simplifie la gestion de l’IU et fournit un manager d’interface utilisateur réutilisable. Marble Maze définit les types qui dérivent d’ElementBase et qui implémentent des comportements spécifiques au jeu. Par exemple, HighScoreTable définit le comportement de la table de score élevé. Si vous souhaitez en savoir plus sur ces types, veuillez consulter le code source.

Remarque

Dans la mesure où XAML vous permet de créer plus facilement des interfaces utilisateur complexes, comme celles des jeux de simulation ou de stratégie, nous vous recommandons d’utiliser XAML pour définir votre IU. Si vous souhaitez en savoir plus sur le développement d’une interface utilisateur en langage XAML dans un jeu UWP DirectX, veuillez consulter la rubrique Étendre l’échantillon de jeu. Cette rubrique fait référence à l’échantillon de jeu de tir DirectX 3D.

 

Chargement de nuanceurs

Marble Maze utilise la méthode BasicLoader::LoadShader pour charger un nuanceur depuis un fichier.

Les nuanceurs constituent l’unité fondamentale de programmation GPU dans les jeux aujourd’hui. Les nuanceurs traitent la quasi-totalité des graphiques 3D, qu’il s’agisse de transformation de modèle ou d’éclairage de scène ou de traitement géométrique plus complexe, de l’apparence des personnages au pavage. Si vous souhaitez en savoir plus sur le modèle de programmation de nuanceur, veuillez consulter la rubrique HLSL.

Marble Maze utilise des nuanceurs de vertex et de pixels. Un nuanceur de vertex fonctionne toujours sur un vertex d’entrée et produit un seul vertex en tant que sortie. Un nuanceur de pixels prend des valeurs numériques, des données de texture, des valeurs par vertex interpolées et d’autres données pour produire une couleur de pixel comme sortie. Étant donné qu’un nuanceur transforme un seul élément à la fois, le matériel graphique qui fournit plusieurs pipelines de nuanceur peut traiter des ensembles d’éléments en parallèle. Le nombre de pipelines parallèles disponibles pour l’unité centrale graphique (GPU) peut être largement supérieur au nombre disponible pour l’UC. Par conséquent, même les nuanceurs de base peuvent améliorer considérablement le débit.

La méthode MarbleMazeMain::LoadDeferredResources charge un seul nuanceur de vertex et un seul nuanceur de pixels après le chargement de la superposition. Les versions au moment de la conception de ces nuanceurs sont définies respectivement dans BasicVertexShader.hlsl et BasicPixelShader.hlsl. Marble Maze applique ces nuanceurs à la fois à la bille et au labyrinthe pendant la phase de rendu.

Le projet Marble Maze inclut les deux versions .hlsl (format au moment du design) et .cso (format d’exécution) des fichiers de nuanceur. Au moment de la génération, Visual Studio utilise le compilateur fxc.exe effect-compiler pour compiler votre fichier source .hlsl dans un nuanceur binaire .cso. Si vous souhaitez en savoir plus sur l’outil effect-compiler, veuillez consulter la rubrique Outil Effect-Compiler.

Le nuanceur de vertex utilise les matrices de modèle, d’affichage et de projection fournies pour transformer la géométrie d’entrée. Les données de position de la géométrie d’entrée sont transformées et sorties deux fois : une fois dans l’espace d’écran, ce qui est nécessaire pour le rendu, et de nouveau dans l’espace mondial pour permettre au nuanceur de pixels d’effectuer des calculs d’éclairage. Le vecteur normal de surface est transformé en espace mondial, qui est également utilisé par le nuanceur de pixels pour l’éclairage. Les coordonnées de texture sont transmises par le nuanceur de pixels inchangé.

sPSInput main(sVSInput input)
{
    sPSInput output;
    float4 temp = float4(input.pos, 1.0f);
    temp = mul(temp, model);
    output.worldPos = temp.xyz / temp.w;
    temp = mul(temp, view);
    temp = mul(temp, projection);
    output.pos = temp;
    output.tex = input.tex;
    output.norm = mul(float4(input.norm, 0.0f), model).xyz;
    return output;
}

Le nuanceur de pixels reçoit la sortie du nuanceur de vertex comme entrée. Ce nuanceur effectue des calculs d’éclairage pour imiter un projecteur à bord doux qui passe sur le labyrinthe et est aligné sur la position de la bille. L’éclairage est le plus fort pour les surfaces qui pointent directement vers la lumière. Le composant diffus descend à zéro à mesure que la normale de la surface devient perpendiculaire à la lumière, et le terme ambiant diminue à mesure que la normale s’éloigne de la lumière. Les points plus proches de la bille (et donc plus près du centre du projecteur) sont plus fortement éclairés. Toutefois, l’éclairage est modulé pour les points sous la bille pour simuler une ombre douce. Dans un environnement réel, un objet comme la bille blanche reflète de façon diffuse le projecteur sur d’autres objets de la scène. Cela est approximatif pour les surfaces qui sont en vue de la moitié brillante de la bille. Les facteurs d’éclairage supplémentaires sont à un angle et à une distance relatifs de la bille. La couleur de pixel résultante est une composition de la texture échantillonnée avec le résultat des calculs d’éclairage.

float4 main(sPSInput input) : SV_TARGET
{
    float3 lightDirection = float3(0, 0, -1);
    float3 ambientColor = float3(0.43, 0.31, 0.24);
    float3 lightColor = 1 - ambientColor;
    float spotRadius = 50;

    // Basic ambient (Ka) and diffuse (Kd) lighting from above.
    float3 N = normalize(input.norm);
    float NdotL = dot(N, lightDirection);
    float Ka = saturate(NdotL + 1);
    float Kd = saturate(NdotL);

    // Spotlight.
    float3 vec = input.worldPos - marblePosition;
    float dist2D = sqrt(dot(vec.xy, vec.xy));
    Kd = Kd * saturate(spotRadius / dist2D);

    // Shadowing from ball.
    if (input.worldPos.z > marblePosition.z)
        Kd = Kd * saturate(dist2D / (marbleRadius * 1.5));

    // Diffuse reflection of light off ball.
    float dist3D = sqrt(dot(vec, vec));
    float3 V = normalize(vec);
    Kd += saturate(dot(-V, N)) * saturate(dot(V, lightDirection))
        * saturate(marbleRadius / dist3D);

    // Final composite.
    float4 diffuseTexture = Texture.Sample(Sampler, input.tex);
    float3 color = diffuseTexture.rgb * ((ambientColor * Ka) + (lightColor * Kd));
    return float4(color * lightStrength, diffuseTexture.a);
}

Avertissement

Le nuanceur de pixels compilé contient 32 instructions arithmétiques et 1 instruction de texture. Ce nuanceur donne de bons résultats sur les ordinateurs de bureau ou sur les tablettes hautes performances. Cependant, certains ordinateurs ne seront sans doute pas en mesure de traiter ce nuanceur, mais de fournir quand même une fréquence d’images interactive. Examinez le matériel classique de votre public cible, puis concevez vos nuanceurs pour répondre aux fonctionnalités de ce matériel.

 

La méthode MarbleMazeMain::LoadDeferredResources utilise la méthode BasicLoader::LoadShader pour charger les nuanceurs. L’exemple suivant charge le nuanceur de vertex. Le format d’exécution pour ce nuanceur est BasicVertexShader.cso. La variable membre m_vertexShader est un objet ID3D11VertexShader.

BasicLoader^ loader = ref new BasicLoader(m_deviceResources->GetD3DDevice());

D3D11_INPUT_ELEMENT_DESC layoutDesc [] =
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 32, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
m_vertexStride = 44; // must set this to match the size of layoutDesc above

Platform::String^ vertexShaderName = L"BasicVertexShader.cso";
loader->LoadShader(
    vertexShaderName,
    layoutDesc,
    ARRAYSIZE(layoutDesc),
    &m_vertexShader,
    &m_inputLayout
    );

La variable membre m_inputLayout est un objet ID3D11InputLayout. L’objet de disposition d’entrée input-layout encapsule l’état d’entrée de l’étape d’assembleur d’entrée (IA). L’un des travaux de l’étape IA consiste à rendre les nuanceurs plus efficaces à l’aide de valeurs générées par le système, également appelées sémantiques, pour traiter uniquement ces éléments primitifs ou vertex qui n’ont pas déjà été traités.

Utilisez la méthode ID3D11Device::CreateInputLayout pour créer une disposition d’entrée input-layout depuis un tableau de descriptions input-element. Le tableau contient un ou plusieurs éléments d’entrée ; chaque élément d’entrée décrit un élément vertex-data depuis un tampon vertex. L’ensemble des descriptions input-element décrit tous les éléments vertex-data de tous les tampons vertex qui seront liés à l’étape IA.

layoutDesc dans l’extrait de code ci-dessus montre la description de la disposition, description utilisée par Marble Maze. La description de la disposition décrit un tampon vertex qui contient quatre éléments vertex-data. Les parties importantes de chaque entrée du tableau sont le nom sémantique, le format de données et le décalage d’octets. Par exemple, l’élément POSITION spécifie la position de vertex dans l’espace objet. Il démarre au décalage d’octet 0 et contient trois composants à virgule flottante (pour un total de 12 octets). L’élément NORMAL spécifie le vecteur normal. Il démarre au décalage d’octet 12, car il apparaît directement après POSITION dans la disposition, ce qui nécessite 12 octets. L’élément NORMAL contient un entier non signé de quatre composants, 32 bits.

Comparez la disposition d’entrée à la structure sVSInput définie par le nuanceur de vertex, comme illustré dans l’exemple suivant. La structure sVSInput définit les éléments POSITION, NORMAL et TEXCOORD0. Le runtime DirectX mappe chaque élément de la disposition à la structure d’entrée définie par le nuanceur.

struct sVSInput
{
    float3 pos : POSITION;
    float3 norm : NORMAL;
    float2 tex : TEXCOORD0;
};

struct sPSInput
{
    float4 pos : SV_POSITION;
    float3 norm : NORMAL;
    float2 tex : TEXCOORD0;
    float3 worldPos : TEXCOORD1;
};

sPSInput main(sVSInput input)
{
    sPSInput output;
    float4 temp = float4(input.pos, 1.0f);
    temp = mul(temp, model);
    output.worldPos = temp.xyz / temp.w;
    temp = mul(temp, view);
    temp = mul(temp, projection);
    output.pos = temp;
    output.tex = input.tex;
    output.norm = mul(float4(input.norm, 0.0f), model).xyz;
    return output;
}

Le document Sémantiques décrit plus en détail chacune des sémantiques disponibles.

Remarque

Dans une disposition, vous pouvez indiquer des composants supplémentaires qui ne sont pas utilisés pour permettre à plusieurs nuanceurs de partager la même disposition. Par exemple, l’élément TANGENT n’est pas utilisé par le nuanceur. Vous pouvez utiliser l’élément TANGENT si vous souhaitez faire l’expérience de techniques telles que le mappage normal. En utilisant le mappage normal, également appelé mappage de relief, vous pouvez créer l’effet des reliefs sur les surfaces des objets. Si vous souhaitez en savoir plus sur le mappage de relief, veuillez consulter la rubrique Mappage de relief (Direct3D 9).

 

Si vous souhaitez en savoir plus sur l’état de l’aperçu d’assembly d’entrée, veuillez consulter les rubriques Aperçu Input-Assembler et Prise en main de l’aperçu Input-Assembler.

Le processus d’utilisation des nuanceurs de vertex et de pixels pour afficher la scène est décrit dans la section Rendu de la scène plus loin dans ce document.

Création de la mémoire tampon constante

La mémoire tampon Direct3D regroupe une collection de données. Une mémoire tampon constante est un type de mémoire tampon que vous pouvez utiliser pour transmettre des données aux nuanceurs. Marble Maze utilise une mémoire tampon constante pour contenir l’affichage du modèle (ou du monde) et les matrices de projection pour l’objet de scène actif.

L’exemple suivant montre comment la méthode MarbleMazeMain::LoadDeferredResources crée une mémoire tampon constante qui contiendra ultérieurement les données de la matrice. L’exemple crée une structure D3D11_BUFFER_DESC qui utilise l’indicateur D3D11_BIND_CONSTANT_BUFFER pour spécifier l’utilisation en tant que mémoire tampon constante. Cet exemple transmet ensuite cette structure à la méthode ID3D11Device::CreateBuffer. La variable m_constantBuffer est un objet ID3D11Buffer.

// Create the constant buffer for updating model and camera data.
D3D11_BUFFER_DESC constantBufferDesc = {0};

// Multiple of 16 bytes
constantBufferDesc.ByteWidth = ((sizeof(ConstantBuffer) + 15) / 16) * 16;

constantBufferDesc.Usage               = D3D11_USAGE_DEFAULT;
constantBufferDesc.BindFlags           = D3D11_BIND_CONSTANT_BUFFER;
constantBufferDesc.CPUAccessFlags      = 0;
constantBufferDesc.MiscFlags           = 0;

// This will not be used as a structured buffer, so this parameter is ignored.
constantBufferDesc.StructureByteStride = 0;

DX::ThrowIfFailed(
    m_deviceResources->GetD3DDevice()->CreateBuffer(
        &constantBufferDesc,
        nullptr,    // leave the buffer uninitialized
        &m_constantBuffer
        )
    );

La méthode MarbleMazeMain::Update met ensuite à jour les objets ConstantBuffer, un pour le labyrinthe et un pour la bille. La méthode MarbleMazeMain::Render lie chaque objet ConstantBuffer à la mémoire tampon constante avant d’afficher chaque objet. L’exemple suivant montre la structure ConstantBuffer, qui se trouve dans MarbleMaze.h.

// Describes the constant buffer that draws the meshes.
struct ConstantBuffer
{
    XMFLOAT4X4 model;
    XMFLOAT4X4 view;
    XMFLOAT4X4 projection;

    XMFLOAT3 marblePosition;
    float marbleRadius;
    float lightStrength;
};

Pour mieux comprendre comment les mémoires tampons constantes sont mappées au code du nuanceur, comparez la structure ConstantBuffer dans MarbleMazeMain.h à la mémoire tampon constante SimpleConstantBuffer définie par le nuanceur de vertex dans BasicVertexShader.hlsl :

cbuffer ConstantBuffer : register(b0)
{
    matrix model;
    matrix view;
    matrix projection;
    float3 marblePosition;
    float marbleRadius;
    float lightStrength;
};

La disposition de la structure ConstantBuffer correspond à l’objet cbuffer. La variable cbuffer spécifie le registre b0, ce qui signifie que les données de mémoire tampon constante sont stockées dans le registre 0. La méthode MarbleMazeMain::Render spécifie le registre 0 quand elle active la mémoire tampon constante. Ce processus est décrit plus en détail ultérieurement dans ce document.

Si vous souhaitez en savoir plus sur les mémoires tampons constantes, veuillez consulter la rubrique Présentation des mémoires tampons dans Direct3D 11. Si vous souhaitez en savoir plus sur le mot clé register, veuillez consulter la rubrique register.

Chargement de maillages

Marble Maze utilise SDK-Mesh comme format d’exécution, car ce format offre un moyen de base de charger des données de maillage pour des exemples d’applications. Pour une utilisation en production, vous devez utiliser un format de maillage qui répond aux exigences spécifiques de votre jeu.

La méthode MarbleMazeMain::LoadDeferredResources charge les données de maillage après avoir chargé les nuanceurs de vertex et de pixels. Un maillage est une collection de données de vertex qui incluent souvent des informations telles que des positions, des données normales, des couleurs, des matériaux et des coordonnées de texture. Les maillages sont créés dans un logiciel de création 3D et tenus à jour dans des fichiers séparés du code de l’application. La bille et le labyrinthe sont deux exemples de maillages que le jeu utilise.

Marble Maze utilise la classe SDKMesh pour gérer les maillages. Cette classe est déclarée dans SDKMesh.h. SDKMesh fournit des méthodes pour charger, afficher et détruire des données de maillage.

Important

Marble Maze utilise le format SDK-Mesh et fournit la classe SDKMesh à des fins d’illustration uniquement. Bien que le format SDK-Mesh soit utile pour l’apprentissage et pour la création de prototypes, c’est un format très basique qui peut ne pas répondre aux exigences de la plupart des développements de jeux. Nous vous recommandons d’utiliser un format de maillage qui répond aux exigences spécifiques de votre jeu.

 

L’exemple suivant montre comment la méthode MarbleMazeMain::LoadDeferredResources utilise la méthode SDKMesh::Create pour charger les données de maillage pour le labyrinthe et la bille.

// Load the meshes.
DX::ThrowIfFailed(
    m_mazeMesh.Create(
        m_deviceResources->GetD3DDevice(),
        L"Media\\Models\\maze1.sdkmesh",
        false
        )
    );

DX::ThrowIfFailed(
    m_marbleMesh.Create(
        m_deviceResources->GetD3DDevice(),
        L"Media\\Models\\marble2.sdkmesh",
        false
        )
    );

Chargement de données de collision

Bien que cette section ne se concentre pas sur la façon dont Marble Maze implémente la simulation physique entre la bille et le labyrinthe, notez que la géométrie de maillage pour le système physique est lue lorsque les maillages sont chargés.

// Extract mesh geometry for the physics system.
DX::ThrowIfFailed(
    ExtractTrianglesFromMesh(
        m_mazeMesh,
        "Mesh_walls",
        m_collision.m_wallTriList
        )
    );

DX::ThrowIfFailed(
    ExtractTrianglesFromMesh(
        m_mazeMesh,
        "Mesh_Floor",
        m_collision.m_groundTriList
        )
    );

DX::ThrowIfFailed(
    ExtractTrianglesFromMesh(
        m_mazeMesh,
        "Mesh_floorSides",
        m_collision.m_floorTriList
        )
    );

m_physics.SetCollision(&m_collision);
float radius = m_marbleMesh.GetMeshBoundingBoxExtents(0).x / 2;
m_physics.SetRadius(radius);

La façon dont vous chargez les données de collision dépend en grande partie du format d’exécution que vous utilisez. Si vous souhaitez en savoir plus sur la façon dont Marble Maze charge la géométrie de collision depuis un fichier SDK-Mesh, veuillez vous reportez à la méthode MarbleMazeMain::ExtractTrianglesFromMesh dans le code source.

Mise à jour de l’état du jeu

Marble Maze séparer la logique de jeu de la logique de rendu en mettant d’abord à jour tous les objets de scène avant de les afficher.

Le document Structure de l’application Marble Maze décrit la boucle du jeu principale. La mise à jour de la scène, qui fait partie de la boucle de jeu, se produit après le traitement des événements, puis des entrées Windows et avant le rendu de la scène. La méthode MarbleMazeMain::Update gère la mise à jour de l’IU et du jeu.

Mise à jour de l’interface utilisateur

La méthode MarbleMazeMain::Update appelle la méthode UserInterface::Update pour mettre à jour l’état de l’IU.

UserInterface::GetInstance().Update(
    static_cast<float>(m_timer.GetTotalSeconds()), 
    static_cast<float>(m_timer.GetElapsedSeconds()));

La méthode UserInterface::Update met à jour chaque élément de la collection d’IU.

void UserInterface::Update(float timeTotal, float timeDelta)
{
    for (auto iter = m_elements.begin(); iter != m_elements.end(); ++iter)
    {
        (*iter)->Update(timeTotal, timeDelta);
    }
}

Les classes dérivées d’ElementBase (défini dans UserInterface.h) implémentent la méthode Update pour effectuer des comportements spécifiques. Par exemple, la méthode StopwatchTimer::Update met à jour le temps écoulé par la quantité fournie, puis met à jour le texte qu’elle affiche ultérieurement.

void StopwatchTimer::Update(float timeTotal, float timeDelta)
{
    if (m_active)
    {
        m_elapsedTime += timeDelta;

        WCHAR buffer[16];
        GetFormattedTime(buffer);
        SetText(buffer);
    }

    TextElement::Update(timeTotal, timeDelta);
}

Mise à jour de la scène

La méthode MarbleMazeMain::Update met à jour le jeu en fonction de l’état actuel de la machine d’état (GameState, stocké dans m_gameState). Quand le jeu est à l’état actif (GameState::InGameActive), Marble Maze met à jour la caméra pour qu’elle suive la bille, met à jour la partie de matrice d’affichage des mémoires tampon constante, puis met à jour la simulation physique.

L’exemple suivant montre comment la méthode MarbleMazeMain::Update met à jour la position de la caméra. Marble Maze utilise la variable m_resetCamera pour marquer que la caméra doit être réinitialisée pour se trouver directement au-dessus de la bille. La caméra est réinitialisée lorsque le jeu démarre ou que la bille tombe dans le labyrinthe. Lorsque l’écran d’affichage du menu principal ou du score élevé est actif, l’appareil photo est défini à un emplacement constant. Dans le cas contraire, Marble Maze utilise le paramètre timeDelta pour interpoler la position de la caméra entre ses positions actuelles et cibles. La position cible est légèrement au-dessus et devant la bille. L’utilisation du temps d’image écoulé permet à la caméra de suivre progressivement, ou de chasser, la bille.

static float eyeDistance = 200.0f;
static XMFLOAT3A eyePosition = XMFLOAT3A(0, 0, 0);

// Gradually move the camera above the marble.
XMFLOAT3A targetEyePosition;
XMStoreFloat3A(
    &targetEyePosition, 
    XMLoadFloat3A(&marblePosition) - (XMLoadFloat3A(&g) * eyeDistance));

if (m_resetCamera)
{
    eyePosition = targetEyePosition;
    m_resetCamera = false;
}
else
{
    XMStoreFloat3A(
        &eyePosition, 
        XMLoadFloat3A(&eyePosition) 
            + ((XMLoadFloat3A(&targetEyePosition) - XMLoadFloat3A(&eyePosition)) 
                * min(1, static_cast<float>(m_timer.GetElapsedSeconds()) * 8)
            )
    );
}

// Look at the marble. 
if ((m_gameState == GameState::MainMenu) || (m_gameState == GameState::HighScoreDisplay))
{
    // Override camera position for menus.
    XMStoreFloat3A(
        &eyePosition, 
        XMLoadFloat3A(&marblePosition) + XMVectorSet(75.0f, -150.0f, -75.0f, 0.0f));

    m_camera->SetViewParameters(
        eyePosition, 
        marblePosition, 
        XMFLOAT3(0.0f, 0.0f, -1.0f));
}
else
{
    m_camera->SetViewParameters(eyePosition, marblePosition, XMFLOAT3(0.0f, 1.0f, 0.0f));
}

L’exemple suivant montre comment la méthode MarbleMazeMain::Update met à jour les mémoires tampons constantes pour la bille et le labyrinthe. La matrice de modèle, ou matrice mondiale, du labyrinthe reste toujours la matrice d’identité. À l’exception de la diagonale principale, dont les éléments sont tous des uns, la matrice d’identité est une matrice carrée composée de zéros. La matrice de modèle de la bille est basée sur sa matrice de position multipliée par sa matrice de rotation.

// Update the model matrices based on the simulation.
XMStoreFloat4x4(&m_mazeConstantBufferData.model, XMMatrixIdentity());

XMStoreFloat4x4(
    &m_marbleConstantBufferData.model, 
    XMMatrixTranspose(
        XMMatrixMultiply(
            marbleRotationMatrix, 
            XMMatrixTranslationFromVector(XMLoadFloat3A(&marblePosition))
        )
    )
);

// Update the view matrix based on the camera.
XMFLOAT4X4 view;
m_camera->GetViewMatrix(&view);
m_mazeConstantBufferData.view = view;
m_marbleConstantBufferData.view = view;

Si vous souhaitez en savoir plus sur la façon dont la méthode MarbleMazeMain::Update lit les entrées utilisateur et simule le mouvement de la bille, veuillez consulter la rubrique Ajout d’entrées et d’interactivité à l’échantillon Marble Maze.

Rendu de la scène

Lorsqu’une scène est rendue, ces étapes sont généralement incluses.

  1. Définissez le tampon stencil buffer de profondeur de la cible de rendu actuelle.
  2. Effacez les affichages de rendu et de stencil.
  3. Préparez les nuanceurs de vertex et de pixels pour le dessin.
  4. Affichez les objets 3D dans la scène.
  5. Affichez les objets 2D qui doivent apparaître sur le devant de la scène.
  6. Présenter l’image rendue au moniteur.

La méthode MarbleMazeMain::Render lie les affichages de stencil de profondeur et de cible de rendu, efface ces affichages, dessine la scène, puis la superposition.

Préparation des cibles de rendu

Avant d’afficher votre scène, vous devez définir le tampon stencil buffer de profondeur de la cible de rendu actuelle. Si le dessin de votre scène sur chaque pixel de l’écran n’est pas garanti, effacez également les affichages de rendu et de stencil. Marble Maze efface les vues de rendu et de stencil sur chaque image pour veiller à ce qu’il n’y ait pas d’artefacts visibles de l’image précédente.

L’exemple suivant montre comment la méthode MarbleMazeMain::Render appelle la méthode ID3D11DeviceContext::OMSetRenderTargets pour définir comme actives la cible de rendu et le tampon stencil buffer de profondeur.

auto context = m_deviceResources->GetD3DDeviceContext();

// Reset the viewport to target the whole screen.
auto viewport = m_deviceResources->GetScreenViewport();
context->RSSetViewports(1, &viewport);

// Reset render targets to the screen.
ID3D11RenderTargetView *const targets[1] = 
    { m_deviceResources->GetBackBufferRenderTargetView() };

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

// Clear the back buffer and depth stencil view.
context->ClearRenderTargetView(
    m_deviceResources->GetBackBufferRenderTargetView(), 
    DirectX::Colors::Black);

context->ClearDepthStencilView(
    m_deviceResources->GetDepthStencilView(), 
    D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 
    1.0f, 
    0);

Les interfaces ID3D11RenderTargetView et ID3D11DepthStencilView prennent en charge le mécanisme d’affichage de texture fourni par Direct3D 10 et versions ultérieures. Si vous souhaitez en savoir plus sur les affichages de texture, veuillez consulter la rubrique Affichages de texture (Direct3D 10). La méthode OMSetRenderTargets prépare l’aperçu output-merger du pipeline Direct3D. Si vous souhaitez en savoir plus sur l’aperçu output-merger, veuillez consulter la rubrique Aperçu output-merger.

Préparation des nuanceurs de vertex et de pixels

Avant d’afficher les objets de scène, procédez comme suit pour préparer les nuanceurs de vertex et de pixels pour le dessin :

  1. Définissez la disposition d’entrée du nuanceur comme disposition actuelle.
  2. Définissez les nuanceurs de vertex et de pixels comme nuanceurs actuels.
  3. Mettez à jour toutes les mémoires tampons constantes avec les données que vous devez transmettre aux nuanceurs.

Important

Marble Maze utilise une paire de nuanceurs de vertex et de pixels pour tous les objets 3D. Si votre jeu utilise plusieurs paires de nuanceurs, vous devez effectuer ces étapes chaque fois que vous dessinez des objets qui utilisent des nuanceurs différents. Pour réduire le traitement associé à la modification de l’état du nuanceur, nous vous recommandons de regrouper les appels de rendu pour tous les objets qui utilisent les mêmes nuanceurs.

 

La section Chargement de nuanceurs dans ce document décrit le mode de création de la disposition d’entrée lors de la création du nuanceur de vertex. L’exemple suivant montre comment la méthode MarbleMazeMain::Render utilise la méthode ID3D11DeviceContext::IASetInputLayout pour définir cette disposition comme active.

m_deviceResources->GetD3DDeviceContext()->IASetInputLayout(m_inputLayout.Get());

L’exemple suivant montre comment la méthode MarbleMazeMain::Render utilise les méthodes ID3D11DeviceContext::VSSetShader et ID3D11DeviceContext::PSSetShader pour définir les nuanceurs de vertex et de pixels comme nuanceurs actuels, respectivement.

// Set the vertex shader stage state.
m_deviceResources->GetD3DDeviceContext()->VSSetShader(
    m_vertexShader.Get(),   // use this vertex shader
    nullptr,                // don't use shader linkage
    0);                     // don't use shader linkage

m_deviceResources->GetD3DDeviceContext()->PSSetShader(
    m_pixelShader.Get(),    // use this pixel shader
    nullptr,                // don't use shader linkage
    0);                     // don't use shader linkage

m_deviceResources->GetD3DDeviceContext()->PSSetSamplers(
    0,                          // starting at the first sampler slot
    1,                          // set one sampler binding
    m_sampler.GetAddressOf());  // to use this sampler

Une fois que la méthode MarbleMazeMain::Render a défini les nuanceurs et leur disposition d’entrée, elle utilise la méthode ID3D11DeviceContext::UpdateSubresource pour mettre à jour la mémoire tampon constante avec les matrices de modèle, d’affichage et de projection pour le labyrinthe. La méthode UpdateSubresource copie les données de matrice de la mémoire de l’UC vers la mémoire de l’unité centrale graphique (GPU). N’oubliez pas que les composants de modèle et d’affichage de la structure ConstantBuffer sont mis à jour dans la méthode MarbleMazeMain::Update. La méthode MarbleMazeMain::Render appelle ensuite les méthodes ID3D11DeviceContext::VSSetConstantBuffers et ID3D11DeviceContext::PSSetConstantBuffers pour définir cette mémoire tampon constante comme active.

// Update the constant buffer with the new data.
m_deviceResources->GetD3DDeviceContext()->UpdateSubresource(
    m_constantBuffer.Get(),
    0,
    nullptr,
    &m_mazeConstantBufferData,
    0,
    0);

m_deviceResources->GetD3DDeviceContext()->VSSetConstantBuffers(
    0,                                  // starting at the first constant buffer slot
    1,                                  // set one constant buffer binding
    m_constantBuffer.GetAddressOf());   // to use this buffer

m_deviceResources->GetD3DDeviceContext()->PSSetConstantBuffers(
    0,                                  // starting at the first constant buffer slot
    1,                                  // set one constant buffer binding
    m_constantBuffer.GetAddressOf());   // to use this buffer

La méthode MarbleMazeMain::Render effectue des étapes similaires pour préparer le rendu de la bille.

Rendu du labyrinthe et de la bille

Après avoir activé les nuanceurs actuels, vous pouvez dessiner vos objets de scène. La méthode MarbleMazeMain::Render appelle la méthode SDKMesh::Render pour afficher le maillage du labyrinthe.

m_mazeMesh.Render(
    m_deviceResources->GetD3DDeviceContext(), 
    0, 
    INVALID_SAMPLER_SLOT, 
    INVALID_SAMPLER_SLOT);

La méthode MarbleMazeMain::Render effectue des étapes similaires pour afficher la bille.

Comme mentionné précédemment dans ce document, la classe SDKMesh est fournie à des fins de démonstration, mais nous ne la recommandons pas pour une utilisation dans un jeu de qualité de production. Toutefois, notez que la méthode SDKMesh ::RenderMesh, appelée par SDKMesh ::Render, utilise les méthodes ID3D11DeviceContext::IASetVertexBuffers et ID3D11DeviceContext::IASetIndexBuffer pour définir les tampons de vertex et d’index actuels qui définissent le maillage, et la méthode ID3D11DeviceContext::D rawIndexed pour dessiner les mémoires tampons. Si vous souhaitez en savoir plus sur l’utilisation des mémoires tampons de vertex et d’index, veuillez consulter la rubrique Présentation des mémoires tampons dans Direct3D 11.

Dessin de l’interface utilisateur et de la superposition

Après avoir dessiné les objets de scène 3D, Marble Maze dessine les éléments d’IU 2D qui apparaissent sur le devant de la scène.

La méthode MarbleMazeMain::Render se termine en dessinant l’interface utilisateur et la superposition.

// Draw the user interface and the overlay.
UserInterface::GetInstance().Render(m_deviceResources->GetOrientationTransform2D());

m_deviceResources->GetD3DDeviceContext()->BeginEventInt(L"Render Overlay", 0);
m_sampleOverlay->Render();
m_deviceResources->GetD3DDeviceContext()->EndEvent();

La méthode UserInterface::Render utilise un objet ID2D1DeviceContext pour dessiner les éléments de l’IU. Cette méthode définit l’état du dessin, dessine tous les éléments d’IU actifs, puis restaure l’état du dessin précédent.

void UserInterface::Render(D2D1::Matrix3x2F orientation2D)
{
    m_d2dContext->SaveDrawingState(m_stateBlock.Get());
    m_d2dContext->BeginDraw();
    m_d2dContext->SetTransform(orientation2D);

    m_d2dContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);

    for (auto iter = m_elements.begin(); iter != m_elements.end(); ++iter)
    {
        if ((*iter)->IsVisible())
            (*iter)->Render();
    }

    // We ignore D2DERR_RECREATE_TARGET here. This error indicates that the device
    // is lost. It will be handled during the next call to Present.
    HRESULT hr = m_d2dContext->EndDraw();
    if (hr != D2DERR_RECREATE_TARGET)
    {
        DX::ThrowIfFailed(hr);
    }

    m_d2dContext->RestoreDrawingState(m_stateBlock.Get());
}

La méthode SampleOverlay::Render utilise une technique similaire pour dessiner le bitmap de superposition.

Présentation de la scène

Une fois les objets de scène 2D et 3D dessinés, Marble Maze présente l’image rendue au moniteur. Cela synchronise le dessin sur le vide vertical pour veiller à ce que le système ne passe pas de temps à dessiner des images qui n’apparaîtront jamais réellement sur l’affichage. Marble Maze gère également les changements d’appareil lorsqu’il présente la scène.

Une fois la méthode MarbleMazeMain::Render renvoyée, la boucle de jeu appelle la méthode DX::DeviceResources::Present pour envoyer l’image rendue au moniteur ou à l’affichage. La méthode DX::DeviceResources::Present appelle IDXGISwapChain::Present pour effectuer l’opération actuelle, comme illustré dans l’exemple suivant :

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

Dans cet exemple, m_swapChain est un objet IDXGISwapChain1. L’initialisation de cet objet est décrite dans la section Initialisation de Direct3D et Direct2D dans ce document.

Le premier paramètre pour IDXGISwapChain::Present, SyncInterval, spécifie le nombre de vides verticaux à attendre avant de présenter l’image. Marble Maze spécifie 1 si bien qu’il attend jusqu’au vide vertical suivant.

La méthode IDXGISwapChain::Present retourne un code d’erreur qui indique que l’appareil a été supprimé ou qu’il a subi un échec. Dans ce cas, Marble Maze réinitialise l’appareil.

// 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)
{
    HandleDeviceLost();
}
else
{
    DX::ThrowIfFailed(hr);
}

Étapes suivantes

Si vous souhaitez en savoir plus sur les pratiques clés à garder à l’esprit lors de l’utilisation de périphériques d’entrée, veuillez consulter la rubrique Ajout d’entrées et d’interactivité à l’échantillon Marble Maze. Ce document explique comment Marble Maze prend en charge l’interaction tactile, l’accéléromètre, les contrôleurs de jeu et l’entrée de souris.