Ajout de contenu visuel à l’exemple Marble Maze

Ce document décrit comment le jeu Marble Maze utilise Direct3D et Direct2D dans l’environnement d’application de plateforme Windows universelle (UWP) afin que vous puissiez apprendre les modèles et 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, consultez structure d’application Marble Maze.

Nous avons suivi ces étapes de base au fur et à 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 d’édition d’images et de modèles pour concevoir les ressources 2D et 3D qui apparaissent dans le jeu.
  3. Assurez-vous que les ressources 2D et 3D se chargent correctement et apparaissent dans le jeu.
  4. Intégrez des nuanceurs de vertex et de pixels qui améliorent la qualité visuelle des ressources du jeu.
  5. Intégrez la logique de jeu, telle que l’animation et l’entrée utilisateur.

Nous nous sommes également concentrés sur l’ajout de ressources 3D, puis sur les ressources 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, à mesure que nous avons apporté des modifications aux modèles de maillage et de marbre, nous avons également dû modifier certains du code du nuanceur qui prend en charge ces modèles.

Remarque

L’exemple de code correspondant à ce document se trouve dans l’exemple de jeu DirectX Marble Maze .

  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 et mettez à jour et 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 jeux se concentrent souvent d’abord sur l’ajout de contenu de jeu 3D, puis sur l’ajout de contenu 2D.
  • Atteignez davantage de clients et donnez-leur une grande expérience en prenant en charge la plus grande gamme de matériel graphique possible.
  • Séparez clairement les formats au moment de la conception et au moment de l’exécution. Structurez vos ressources au moment du design pour optimiser la flexibilité et activer des itérations rapides sur le contenu. Mettez en forme et compressez vos ressources pour charger et afficher aussi efficacement que possible au moment de l’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 la façon dont la chaîne d’échange est associée à la fenêtre de sortie.
  • Lorsque vous concevez votre jeu, assurez-vous 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, assurez-vous que vous pouvez obtenir des données de collision à partir de 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 restituer.
  • Vous dessinez généralement vos objets de scène 3D, puis tous les objets 2D qui apparaissent devant la scène.
  • Synchronisez le dessin sur le vide vertical pour vous assurer que votre jeu ne passe pas de temps à dessiner des cadres qui ne seront jamais réellement affichés sur l’affichage. Une vide verticale est le temps entre le moment où un cadre termine le dessin sur le moniteur et que le cadre suivant commence.

Prise en main des graphiques DirectX

Lorsque nous avons planifié le jeu de la plateforme Windows universelle Marble Maze (UWP), nous avons choisi C++ et Direct3D 11.1, car ils sont d’excellents choix pour créer des jeux 3D qui nécessitent un contrôle maximal sur le rendu et les performances élevées. 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 du code pour chacune des versions antérieures de DirectX.

Marble Maze utilise Direct3D 11.1 pour restituer les ressources de jeu 3D, à savoir la bille et le labyrinthe. Marble Maze utilise également Direct2D, DirectWrite et windows Imaging Component (WIC) pour dessiner les ressources de jeu 2D, telles que les menus et le minuteur.

Le développement de jeux nécessite une planification. Si vous débutez avec les graphismes DirectX, nous vous recommandons de lire DirectX : prise en main pour vous familiariser avec les concepts de base de la création d’un jeu DirectX UWP. Lorsque vous lisez ce document et que vous parcourez le code source Marble Maze, vous pouvez consulter les ressources suivantes pour obtenir des informations plus détaillées sur les graphiques DirectX :

  • Graphiques Direct3D 11: décrit Direct3D 11, une API graphique 3D puissante et accélérée par le matériel pour le rendu de la géométrie 3D sur la plateforme Windows.
  • direct2D: décrit Direct2D, une API graphique 2D accélérée par le matériel qui fournit des performances élevées et un rendu de haute qualité pour la géométrie 2D, les bitmaps et le texte.
  • DirectWrite: décrit DirectWrite, qui prend en charge le rendu de texte de haute qualité.
  • composant d’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 introduit un concept dénommé niveaux de fonctionnalités. 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 il 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 et de mettre à l’échelle votre contenu de jeu afin que vos clients qui disposent d’ordinateurs haut de gamme ou bas de gamme aient une grande expérience. Pour plus d’informations sur les niveaux de fonctionnalités, consultez Direct3D 11 sur le matériel de niveau inférieur.

Initialisation de Direct3D et Direct2D

Un appareil représente l’adaptateur d’affichage. 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. Cette classe gère l’infrastructure générale, et non les ressources spécifiques au jeu. Marble Maze définit la classe MarbleMazeMain pour gérer les ressources spécifiques au jeu, qui a une référence à un objet DeviceResources pour lui donner accès à Direct3D et Direct2D.

Lors de l’initialisation, le constructeur du DeviceResources crée des ressources indépendantes du périphérique ainsi que les périphériques 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 DeviceResources classe sépare cette fonctionnalité afin qu’elle puisse 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 factories pour Direct2D, DirectWrite et WIC. Dans les graphiques DirectX, les fabriques 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 tous les dessins 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. Étant donné que Marble Maze prend en charge le niveau de fonctionnalité 9.1 et versions ultérieures, la méthode DeviceResources ::CreateDeviceResources spécifie les niveaux 9.1 à 11.1 dans le tableau featureLevels. Direct3D guide la liste dans l’ordre et donne à l’application le premier niveau de fonctionnalité disponible. Par conséquent, les entrées de tableau D3D_FEATURE_LEVEL sont répertoriées de la plus haute à la plus basse afin que l’application obtienne 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 les runtimes graphiques. Marble Maze utilise le périphérique DXGI sous-jacent du périphérique Direct3D pour créer le périphérique Direct2D à partir de 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
        )
    );

Pour plus d’informations sur DXGI et l’interopérabilité entre Direct2D et Direct3D, consultez Vue d’ensemble de DXGI et Vue d’ensemble de l’interopérabilité Direct2D et Direct3D.

Association de Direct3D à la vue

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 façon importante qu’une application UWP DirectX diffère d’une application de bureau est 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 sur laquelle l’appareil s’affiche sur le moniteur. structure d’application Marble Maze décrit comment le système de fenêtrage d’une application UWP diffère d’une application de bureau. Étant donné qu’une application UWP ne fonctionne pas avec objets HWND, Marble Maze doit utiliser la méthode IDXGIFactory2 ::CreateSwapChainForCoreWindow pour associer la sortie de l’appareil à la vue. L’exemple suivant montre la partie 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 vous assurer que le jeu est rendu 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 dans 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 vue a une signification différente dans Windows Runtime que dans Direct3D. Dans Windows Runtime, une vue fait référence à la collection de paramètres d’interface utilisateur pour 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 spécifiez la configuration et les paramètres dont vous avez besoin lorsque vous créez une vue. Le processus de configuration de la vue d’application est décrit dans structure d’application Marble Maze. Dans Direct3D, le terme "vue" a plusieurs significations. Une vue de ressource définit les sous-ressources auxquelles une ressource peut accéder. Par exemple, lorsqu’un objet de texture est associé à une vue de ressource de nuanceur, ce nuanceur peut accéder ultérieurement à la texture. L’un des avantages d’une vue des ressources est que vous pouvez interpréter les données de différentes manières à différentes étapes du pipeline de rendu. Pour plus d’informations sur les vues de ressources, consultez Vues de ressources. Lorsqu’elle est utilisée dans le contexte d’une transformation d’affichage ou d’une matrice de transformation de vue, la vue fait référence à l’emplacement et à l’orientation de la caméra. Une transformation de vue déplace les objets dans le monde autour de la position et de l’orientation de la caméra. Pour plus d’informations sur les transformations d’affichage, consultez Vue Transform (Direct3D 9). La façon dont Marble Maze utilise des vues de ressources et de matrices est décrite plus en détail dans cette rubrique.

 

Chargement des ressources de scène

Marble Maze utilise la classe BasicLoader , déclarée dans BasicLoader.h, pour charger des textures et des nuanceurs. Marble Maze utilise la classe SDKMesh pour charger les maillages tridimensionnels 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. Pendant que les ressources 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 Chargement des ressources 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 exemple 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 dans le cadre des exemples Direct3D, vous pouvez adapter ce code pour afficher toute image qui apparaît devant votre scène.

L’un des aspects importants de la superposition est que, étant donné que son contenu ne change pas, la classe SampleOverlay dessine ou met en cache son contenu dans un objet ID2D1Bitmap1 lors de l’initialisation. Au moment du dessin, la classe SampleOverlay doit uniquement dessiner la bitmap à l’écran. De cette façon, les routines coûteuses telles que le dessin de texte n’ont pas besoin d’être effectuées pour chaque cadre.

L’interface utilisateur se compose de composants 2D, tels que les menus et les affichages tête-haut (HUD), qui apparaissent devant votre scène. Marble Maze définit les éléments d’interface utilisateur suivants :

  • Éléments de menu qui permettent à l’utilisateur de démarrer le jeu ou d’afficher des scores élevés.
  • Minuteur qui compte 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 indiquant Suspendu lorsque le jeu est suspendu.

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

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’interface utilisateur, 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’interface utilisateur et fournit un gestionnaire d’interface utilisateur réutilisable. Marble Maze définit les types qui dérivent de ElementBase qui implémentent des comportements spécifiques au jeu. Par exemple, HighScoreTable définit le comportement de la table de score élevé. Pour plus d’informations sur ces types, reportez-vous au code source.

Remarque

Étant donné que XAML vous permet de créer plus facilement des interfaces utilisateur complexes, comme celles trouvées dans les jeux de simulation et de stratégie, déterminez s’il faut utiliser XAML pour définir votre interface utilisateur. Pour plus d’informations sur le développement d’une interface utilisateur en XAML dans un jeu UWP DirectX, consultez la section Étendre l’exemple de jeu, qui fait référence à l’exemple de jeu de tir DirectX 3D.

 

Chargement des shaders

Marble Maze utilise la méthode BasicLoader ::LoadShader pour charger un nuanceur à partir d’un fichier.

Les nuanceurs sont l’unité fondamentale de programmation GPU dans les jeux aujourd’hui. Presque tous les traitements graphiques 3D sont pilotés par les shaders, soit qu’il s’agisse de la transformation de modèle et de l’éclairage de scène, ou d’un traitement géométrique plus complexe, de l'animation de personnages à la tessellation. Pour plus d’informations sur le modèle de programmation du nuanceur, consultez HLSL.

Marble Maze utilise des nuanceurs de vertex et de pixels. Un shader de sommet fonctionne toujours sur un sommet d'entrée et produit un sommet en tant que sortie. Un nuanceur de pixels prend des valeurs numériques, des données de texture, des valeurs interpolées par vertex et d’autres données pour produire une couleur de pixel comme sortie. Étant donné qu’un nuanceur transforme un é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 le GPU peut être largement supérieur au nombre disponible pour l’UC. Par conséquent, même les shaders de base peuvent améliorer considérablement le débit.

La méthode MarbleMazeMain ::LoadDeferredResources charge un nuanceur de vertex et un nuanceur de pixels après avoir chargé la superposition. Les versions au moment du design de ces nuanceurs sont définies dans BasicVertexShader.hlsl et BasicPixelShader.hlsl, respectivement. Marble Maze applique ces shaders à la fois à la balle et au labyrinthe pendant la phase de rendu.

Le projet Marble Maze inclut les 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 d’effets fxc.exe pour compiler votre fichier source .hlsl en un shader binaire .cso. Pour plus d’informations sur l’outil de compilation des effets, consultez l’outil Effect-Compiler.

Le nuanceur de vertex utilise les matrices de modèle, de vue 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 passées inchangées par le nuanceur de pixels.

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 shader effectue des calculs d'éclairage pour imiter un projecteur à bords doux qui flotte au-dessus du labyrinthe et s'aligne 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 diminue jusqu'à disparaître lorsque la normale de la surface devient perpendiculaire à la lumière, et le terme ambiant s'atténue à mesure que la normale s'éloigne de la lumière. Les points plus proches du marbre (et donc plus près du centre de la lumière) sont plus fortement allumés. Toutefois, l’éclairage est modulé pour les points sous la marbre afin de simuler une ombre douce. Dans un environnement réel, un objet comme le marbre blanc reflète de façon diffuse la lumière sur d’autres objets de la scène. Cela est approximatif pour les surfaces visibles de la moitié brillante du marbre. Les facteurs d’éclairage supplémentaires sont à l’angle relatif et à la distance du marbre. La couleur de pixel est une composition de la texture échantillonnée et du 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 shader doit bien fonctionner sur les ordinateurs de bureau ou les tablettes plus puissantes. Cependant, certains ordinateurs peuvent ne pas être capables de traiter ce shader tout en fournissant une fréquence d'images interactive. Considérez le matériel classique de votre public cible et 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 shaders. L’exemple suivant charge le shader de sommet. Le format d’exécution de 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 mise en page de l'entrée encapsule l'état d'entrée de l'étape d'assembleur d'entrée (IA). L’un des travaux de la phase 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 les primitives ou sommets qui n’ont pas déjà été traités.

Utilisez la méthode ID3D11Device ::CreateInputLayout pour créer une disposition d’entrée à partir d’un tableau de descriptions d’élément d’entrée. Le tableau contient un ou plusieurs éléments d’entrée ; chaque élément d’entrée décrit un élément de données de vertex à partir d’une mémoire tampon de vertex. Les descriptions des éléments d'entrée décrivent tous les éléments de données de sommets de tous les tampons de sommets qui seront liés à la phase IA.

layoutDesc dans l'extrait de code ci-dessus indique la description du layout utilisée par Marble Maze. La description de la disposition décrit une mémoire tampon de vertex qui contient quatre éléments de données de vertex. 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 commence 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 commence 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 avec 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, NORMALet TEXCOORD0. L'environnement d'exécution de DirectX associe chaque élément de la mise en page à 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émantique décrit plus en détail chacune des sémantiques disponibles.

Remarque

Dans une disposition, vous pouvez spécifier 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 expérimenter des techniques telles que le mappage normal. En utilisant le mappage normal, également appelé mappage de bosses, vous pouvez créer l’effet des bosses sur les surfaces des objets. Pour plus d’informations sur le mappage de bosses, consultez mappage de bosses (Direct3D 9).

 

Pour plus d'informations sur la phase d'assemblage d'entrée, consultez Input-Assembler Étape et Prise en main de la Phase 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 la vue 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 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 ensuite chaque objet ConstantBuffer à la mémoire tampon constante avant le rendu de chaque objet. L’exemple suivant montre la structure ConstantBuffer, qui se trouve dans MarbleMazeMain.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 correspondent au code du nuanceur, comparez la structure ConstantBuffer dans MarbleMazeMain.h à la mémoire tampon constante constante ConstantBuffer 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 lors de l'activation de la mémoire tampon constante. Ce processus est décrit plus en détail plus loin dans ce document.

Pour plus d’informations sur les mémoires tampons constantes, consultez Présentation des mémoires tampons dans Direct3D 11. Pour plus d'informations sur le mot clé register, consultez 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 généralement créés dans des logiciels de création 3D et conservés dans des fichiers distincts du code d’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 de chargement, de rendu et de destruction des données de maillage.

Important

Marble Maze utilise le format SDK-Mesh et fournit la classe SDKMesh uniquement pour l’illustration. Bien que le format SDK-Mesh soit utile pour l’apprentissage et pour la création de prototypes, il s’agit d’un format très basique qui ne répond pas 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 des données de maillage pour le labyrinthe et pour la balle.

// 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 des 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 le marbre 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 des données de collision dépend en grande partie du format d’exécution que vous utilisez. Pour plus d’informations sur la façon dont Marble Maze charge la géométrie de collision à partir d’un fichier SDK-Mesh, consultez la méthode MarbleMazeMain ::ExtractTrianglesFromMesh dans le code source.

Mise à jour de l’état du jeu

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

structure d’application Marble Maze décrit la boucle de 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 et des entrées Windows et avant le rendu de la scène. La méthode MarbleMazeMain ::Update gère la mise à jour de l’interface utilisateur 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’interface utilisateur.

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’interface utilisateur.

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 de ElementBase (définies 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 et met à jour le texte qu’il 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 l’ordinateur d’état (GameState, stocké dans m_gameState). Lorsque le jeu est dans l’état actif (GameState ::InGameActive), Marble Maze met à jour la caméra pour suivre la bille, met à jour la partie matrice d’affichage des mémoires tampons constantes et 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 indiquer que la caméra doit être réinitialisée 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 le menu principal ou le tableau des scores est actif, la caméra est définie à 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 écoulé permet à l’appareil photo de suivre progressivement, ou de poursuivre, 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. Le modèle du labyrinthe, ou le monde, reste toujours la matrice d’identité. À l’exception de la diagonale principale, dont les éléments sont tous des uns, la matrice 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;

Pour plus d’informations sur la façon dont la méthode MarbleMazeMain ::Update lit l’entrée utilisateur et simule le mouvement de la bille, consultez Ajout d’entrée et d’interactivité à l’exemple Marble Maze.

Rendu de la scène

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

  1. Définissez la mémoire tampon de profondeur de la cible de rendu actuelle.
  2. Effacez le rendu et les vues 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 tout objet 2D que vous souhaitez afficher devant la scène.
  6. Présenter l’image rendue au moniteur.

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

Préparation des cibles de rendu

Avant de restituer votre scène, vous devez configurer le tampon de profondeur-stencil de la cible de rendu actuelle. Si votre scène n’est pas garantie de dessiner sur chaque pixel sur l’écran, effacez également le rendu et les vues de gabarit. Marble Maze efface le rendu et les vues de gabarit sur chaque image pour s’assurer qu’il n’y a pas d’artefacts visibles du cadre précédent.

L’exemple suivant montre comment la méthode MarbleMazeMain::Render appelle la méthode ID3D11DeviceContext::OMSetRenderTargets pour définir la cible de rendu et le tampon de profondeur-stencil en tant que ceux actuellement utilisés.

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 de vue de texture fourni par Direct3D 10 et versions ultérieures. Pour plus d’informations sur les vues de texture, consultez vues de texture (Direct3D 10). La méthode OMSetRenderTargets prépare l’étape de fusion de sortie du pipeline Direct3D. Pour plus d'informations sur l'étape de fusion de sortie, consultez Output-Merger étape.

Préparation des shaders de vertex et de pixels

Avant de restituer 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 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 la surcharge associée à 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 des nuanceurs dans ce document décrit comment la disposition d’entrée est créé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 disposition actuelle.

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 ::P SSetShader pour définir les nuanceurs de vertex et de pixels en tant que 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

Après que MarbleMazeMain::Render a défini les nuanceurs et leur disposition d’entrée, il utilise la méthode ID3D11DeviceContext::UpdateSubresource pour mettre à jour le tampon de constante avec les matrices de modèle, de vue et de projection pour le labyrinthe. La méthode UpdateSubresource copie les données de matrice de la mémoire processeur vers la mémoire GPU. Rappelez-vous 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 ce tampon constant comme le tampon actuel.

// 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 la bille à être rendue.

Rendre le labyrinthe et le marbre

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 maze.

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

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

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

Dessin de l’interface utilisateur et superposition

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

La méthode MarbleMazeMain ::Render se termine par dessiner 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’interface utilisateur. Cette méthode définit l’état du dessin, dessine tous les éléments d’interface utilisateur actifs, puis restaure l’état de 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 la bitmap de superposition.

Présentation de la scène

Après avoir dessiné tous les objets de scène 2D et 3D, Marble Maze présente l’image rendue au moniteur. Il synchronise le dessin sur le blanc vertical pour s'assurer que le temps n'est pas passé à dessiner des images qui ne seront jamais affichées sur l'écran. Marble Maze gère également les changements d’appareil lorsqu’il présente la scène.

Une fois la méthode MarbleMazeMain::Render retournée, la boucle de jeu appelle la méthode DX::DeviceResources::Present pour envoyer l’image rendue à l’écran. La méthode DX::DeviceResources::Present appelle IDXGISwapChain::Present pour effectuer l'opération de présentation, 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 à IDXGISwapChain ::P resent, SyncInterval, spécifie le nombre de vides verticaux à attendre avant de présenter le cadre. Marble Maze spécifie 1 afin qu’il attende jusqu’à ce que la valeur verticale suivante soit vide.

La méthode IDXGISwapChain::Present retourne un code d’erreur qui indique que le périphérique a été supprimé ou a rencontré une erreur. 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

Lisez Ajouter de l'entrée et de l'interactivité à l'exemple Marble Maze pour plus d'informations sur certaines des pratiques clés à garder à l'esprit lorsque vous travaillez avec des périphériques d'entrée. 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 la souris.