DirectX 11 - Tutoriel 3 : initialisation de DirectX 11

RasterTek

Traducteur

Jérôme Marsaguet

Dans ce tutoriel, nous allons commencer à travailler avec DirectX 11. Nous initialiserons et fermerons Direct3D et effectuerons le rendu dans une fenêtre.

Commentez

- Titre : DirectX 11 - Tutoriel 3 : initialisation de DirectX 11

- Auteur : RasterTek

- Traducteur : Jérôme Marsaguet

- Parution : 25 avril 2014

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2014 RasterTek. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- Navigation

- II. Liste des composants mis à jour

- III. Graphicsclass.h

- IV. Graphicsclass.cpp

- V. D3dclass.h

- VI. D3dclass.cpp

- VII. Résumé

- VII-A. Exercices à faire

- VIII. Code source

- IX. Remerciements

- Navigation

Navigation

Tutoriel précédent : Création du cadre de travail et d'une fenêtre

Sommaire

Tutoriel suivant : Tampons, shaders et HLSL

II - Liste des composants mis à jour

Nous allons ajouter à notre base une autre classe, que nous appellerons D3DClass, qui gérera toutes les fonctions du système Direct3D. Voici le nouveau diagramme :

Comme vous pouvez le voir, la classe D3DClass sera contenue dans la classe GraphicsClass. Le tutoriel précédent mentionnait que toutes les classes touchant les graphiques seraient situées dans la classe GraphicsClass, c'est donc la meilleure place pour notre nouvelle classe D3DClass. Intéressons-nous maintenant aux changements effectués dans la classe GraphicsClass.

III - Graphicsclass.h

////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_

Voici le premier changement. Nous avons enlevé le fichier d'en-tête windows.h et inclus d3dclass.h à la place.

///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"

/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = false;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;

////////////////////////////////////////////////////////////////////////////////
// Nom de la classe : GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
GraphicsClass();
GraphicsClass(const GraphicsClass&);
~GraphicsClass();

bool Initialize(int, int, HWND);
void Shutdown();
bool Frame();

private:
bool Render();

private:

Et le deuxième changement se situe au niveau du pointeur vers la classe D3DClass que nous avons nommé m_D3D. Si vous vous le demandez, j'utilise le préfixe m_ pour tous les attributs de classe. Je peux ainsi aisément me rappeler les variables qui sont membres de ma classe et celles qui ne le sont pas.

D3DClass* m_D3D;
};

#endif

IV - Graphicsclass.cpp

Si vous vous rappelez le tutoriel précédent, cette classe ne contenait aucun code. Maintenant que nous avons la classe membre D3DClass, nous allons commencer à la remplir par l'initialisation et la libération de notre objet D3DClass. Nous allons aussi rajouter des appels à BeginScene et EndScene dans la fonction Render afin de dessiner notre fenêtre en utilisant Direct3D.

Le tout premier changement se situe dans le constructeur. Nous initialisons le pointeur à zéro pour des raisons de sécurité, comme nous le faisons pour tous les pointeurs de classe.

GraphicsClass::GraphicsClass()
{
m_D3D = 0;
}

Le deuxième changement est dans la fonction Initialize. Nous créons ici l'objet D3DClass et appelons sa fonction Initialize. Nous envoyons à cette fonction les largeur et hauteur de l'écran, l'identifiant de la fenêtre, et les quatre variables globales du fichier Graphicsclass.h. La classe D3DClass va utiliser toutes ces variables pour mettre en place le système Direct3D. Nous y reviendrons plus en détails lorsque nous étudierons le fichier d3dclass.cpp.

bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
bool result;

// Crée l'objet Direct3D.
m_D3D = new D3DClass;
if(!m_D3D)
{
return false;
}

// Initialise l'objet Direct3D.
result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
if(!result)
{
MessageBox(hwnd, L"Could not initialize Direct3D", L"Error", MB_OK);
return false;
}

return true;
}

La nouvelle modification se trouve dans la fonction ShutDown de la classe GraphicsClass. Tous les objets graphiques sont détruits ici, l'appel de la méthode Shutdown de la classe D3DClass s'effectue donc ici. Remarquez que je vérifie avant si le pointeur est initialisé ou non. S'il ne l'est pas, nous pouvons supposer qu'il n'a jamais été construit, et n'a donc pas besoin d'être détruit. C'est pourquoi il est très important d'assigner à tous les pointeurs la valeur NULL dans le constructeur. S'il décèle que le pointeur a été initialisé, il tentera de finaliser l'objet D3DClass puis de libérer le pointeur.

void GraphicsClass::Shutdown()
{
if(m_D3D)
{
m_D3D->Shutdown();
delete m_D3D;
m_D3D = 0;
}

return;
}

La méthode Frame a été mise à jour pour maintenant appeler la fonction Render à chaque trame.

bool GraphicsClass::Frame()
{
bool result;

// Effectue le rendu de la scène.
result = Render();
if(!result)
{
return false;
}

return true;
}

Le dernier changement dans cette fonction a lieu dans la fonction Render. Nous utilisons l'objet D3DClass pour remplir l'écran de gris. Puis appelons EndScene afin que la couleur apparaisse dans la fenêtre.

bool GraphicsClass::Render()
{
// Efface les tampons de la scène.
m_D3D->BeginScene(0.5f, 0.5f, 0.5f, 1.0f);

// Affiche la scène rendue à l'écran.
m_D3D->EndScene();

return true;
}

Jetons maintenant un coup d'œil au nouveau fichier d'en-tête de la classe D3DClass :

V - D3dclass.h

////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : d3dclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _D3DCLASS_H_
#define _D3DCLASS_H_

Première chose, nous précisons dans l'en-tête les bibliothèques à lier lors de l'utilisation de ce module. Ces bibliothèques contiennent toutes les fonctionnalités de Direct3D pour la mise en place et le dessin des graphiques 3D dans DirectX ainsi que des outils d'interface avec le matériel de l'ordinateur permettant d'obtenir des informations sur la fréquence de rafraîchissement de l'écran, la carte vidéo utilisée, et ainsi de suite. Vous remarquerez que certaines bibliothèques DirectX 10 sont encore utilisées, c'est parce qu'elles n'ont jamais été mises à jour pour DirectX 11, leur fonctionnalité n'ayant pas besoin de changer.

/////////////
// LINKING //
/////////////
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dx11.lib")
#pragma comment(lib, "d3dx10.lib")

Ensuite, nous incluons les en-têtes de ces mêmes bibliothèques que nous venons de lier, ainsi que ceux contenant les définitions de types DirectX et autres.

//////////////
// INCLUDES //
//////////////
#include <dxgi.h>
#include <d3dcommon.h>
#include <d3d11.h>
#include <d3dx10math.h>

La définition de la classe D3DClass est aussi simple que possible ici. Elle possède le constructeur par défaut, le constructeur de copie et le destructeur. Plus important, les méthodes Initialize et Shutdown. C'est ce sur quoi nous allons principalement nous concentrer dans ce tutoriel. En dehors de cela, des fonctions auxiliaires sans importance ici et un certain nombre d'attributs privés que nous examinerons en même temps que le fichier d3dclass.cpp. Pour l'instant, concentrez-vous juste sur les méthodes Initialize et Shutdown.

////////////////////////////////////////////////////////////////////////////////
// Nom de la classe : D3DClass
////////////////////////////////////////////////////////////////////////////////
class D3DClass
{
public:
D3DClass();
D3DClass(const D3DClass&);
~D3DClass();

bool Initialize(int, int, bool, HWND, bool, float, float);
void Shutdown();

void BeginScene(float, float, float, float);
void EndScene();

ID3D11Device* GetDevice();
ID3D11DeviceContext* GetDeviceContext();

void GetProjectionMatrix(D3DXMATRIX&);
void GetWorldMatrix(D3DXMATRIX&);
void GetOrthoMatrix(D3DXMATRIX&);

void GetVideoCardInfo(char*, int&);

private:
bool m_vsync_enabled;
int m_videoCardMemory;
char m_videoCardDescription[128];
IDXGISwapChain* m_swapChain;
ID3D11Device* m_device;
ID3D11DeviceContext* m_deviceContext;
ID3D11RenderTargetView* m_renderTargetView;
ID3D11Texture2D* m_depthStencilBuffer;
ID3D11DepthStencilState* m_depthStencilState;
ID3D11DepthStencilView* m_depthStencilView;
ID3D11RasterizerState* m_rasterState;
D3DXMATRIX m_projectionMatrix;
D3DXMATRIX m_worldMatrix;
D3DXMATRIX m_orthoMatrix;
};

#endif

Pour ceux qui connaissent déjà Direct3D, vous remarquerez peut-être que cette classe ne contient pas de matrice de vue. Celle-ci sera utilisée dans une classe Camera que verrons dans un prochain tutoriel.

VI - D3dclass.cpp

////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : d3dclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "d3dclass.h"

Comme la plupart des classes, nous commençons par initialiser tous les pointeurs membres à la valeur nulle dans le constructeur. Tous les pointeurs du fichier d'en-tête ont été pris en compte ici.

D3DClass::D3DClass()
{
m_swapChain = 0;
m_device = 0;
m_deviceContext = 0;
m_renderTargetView = 0;
m_depthStencilBuffer = 0;
m_depthStencilState = 0;
m_depthStencilView = 0;
m_rasterState = 0;
}

D3DClass::D3DClass(const D3DClass& other)
{
}

D3DClass::~D3DClass()
{
}

La fonction d'initialisation se charge de toute la configuration de Direct3D pour DirectX 11. J'ai mis tout le code nécessaire ici, ainsi que quelques éléments supplémentaires qui nous aideront dans les tutoriels à venir. J'aurais pu effectuer des simplifications et supprimer certains points, mais il est préférable de couvrir tout cela dans un unique tutoriel dédié.

Les variables ScreenWidth et screenHeight passées à cette fonction sont la largeur et la hauteur de la fenêtre que nous avons créée dans la classe SystemClass. Direct3D va les utiliser pour s'initialiser et utiliser ces mêmes dimensions pour la fenêtre. La variable hwnd est l'identifiant de la fenêtre ; Direct3D en aura besoin pour accéder à la fenêtre précédemment créée. La variable fullscreen sert à savoir si nous sommes en mode fenêtré ou plein écran. Direct3D a aussi besoin de cette information afin de créer la fenêtre avec les bons paramètres. Les variables screendepth et screenNear servent au réglage de la profondeur de notre environnement 3D qui sera rendu dans la fenêtre. La variable vsync indique si nous voulons que Direct3D effectue le rendu à la fréquence de rafraîchissement de l'écran de l'utilisateur ou le plus vite possible.

bool D3DClass::Initialize(int screenWidth, int screenHeight, bool vsync, HWND hwnd, bool fullscreen,
float screenDepth, float screenNear)
{
HRESULT result;
IDXGIFactory* factory;
IDXGIAdapter* adapter;
IDXGIOutput* adapterOutput;
unsigned int numModes, i, numerator, denominator, stringLength;
DXGI_MODE_DESC* displayModeList;
DXGI_ADAPTER_DESC adapterDesc;
int error;
DXGI_SWAP_CHAIN_DESC swapChainDesc;
D3D_FEATURE_LEVEL featureLevel;
ID3D11Texture2D* backBufferPtr;
D3D11_TEXTURE2D_DESC depthBufferDesc;
D3D11_DEPTH_STENCIL_DESC depthStencilDesc;
D3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc;
D3D11_RASTERIZER_DESC rasterDesc;
D3D11_VIEWPORT viewport;
float fieldOfView, screenAspect;

// Stocke le réglage de la synchronisation verticale
m_vsync_enabled = vsync;

Avant que nous puissions initialiser Direct3D, nous devons obtenir le taux de rafraîchissement de la carte graphique/du moniteur, informations légèrement différentes selon chaque ordinateur ; cette information est donc nécessaire. Nous acquérons les valeurs du numérateur et du dénominateur que nous passons ensuite lors de l'initialisation à DirectX, qui va calculer le taux de rafraîchissement approprié. Si nous ne le faisons pas et demandons une valeur de fréquence de rafraîchissement par défaut, celle-ci n'existant pas sur tous les ordinateurs, DirectX effectuera un blit à la place d'un échange de tampons qui dégradera les performances et nous donnera des erreurs gênantes à la sortie de débogage.

// Crée une interface de fabrique graphique DirectX.
result = CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&factory);
if(FAILED(result))
{
return false;
}

// Utilise la fabrique pour créer un adaptateur pour l'interface graphique principale (carte graphique).
result = factory->EnumAdapters(0, &adapter);
if(FAILED(result))
{
return false;
}

// Énumère la sortie de l'adaptateur principal (écran).
result = adapter->EnumOutputs(0, &adapterOutput);
if(FAILED(result))
{
return false;
}

// Obtient le nombre de modes correspondant au format d'affichage DXGI_FORMAT_R8G8B8A8_UNORM pour l'adaptateur principal (écran).
result = adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, NULL);
if(FAILED(result))
{
return false;
}

// Crée une liste pour stocker tous les modes d'affichage possibles pour cette combinaison écran/carte graphique.
displayModeList = new DXGI_MODE_DESC[numModes];
if(!displayModeList)
{
return false;
}

// Remplit les structures de la liste des modes.
result = adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, displayModeList);
if(FAILED(result))
{
return false;
}

// Parcourt tous les modes d'affichage, et trouve celui qui correspond à la taille de l'écran.
// Quand une correspondance est trouvée, stocke le numérateur et dénominateur du taux de rafraîchissement de l'écran.
for(i=0; i<numModes; i++)
{
if(displayModeList[i].Width == (unsigned int)screenWidth)
{
if(displayModeList[i].Height == (unsigned int)screenHeight)
{
numerator = displayModeList[i].RefreshRate.Numerator;
denominator = displayModeList[i].RefreshRate.Denominator;
}
}
}

Nous possédons le numérateur et le dénominateur du taux de rafraîchissement. La dernière chose à récupérer en utilisant l'adaptateur est le nom de la carte vidéo et sa quantité de mémoire.

// Obtient la description de l'adaptateur (carte graphique).
result = adapter->GetDesc(&adapterDesc);
if(FAILED(result))
{
return false;
}

// Stocke la valeur de la mémoire graphique dédiée en mégaoctets.
m_videoCardMemory = (int)(adapterDesc.DedicatedVideoMemory / 1024 / 1024);

// Convertit le nom de la carte graphique en tableau de caractères et le stocke.
error = wcstombs_s(&stringLength, m_videoCardDescription, 128, adapterDesc.Description, 128);
if(error != 0)
{
return false;
}

Maintenant que nous avons stocké le numérateur et le dénominateur du taux de rafraîchissement, et les informations sur la carte vidéo, nous pouvons libérer les structures et interfaces utilisées pour les obtenir.

// Libère la liste des modes d'affichage.
delete [] displayModeList;
displayModeList = 0;

// Libère la sortie de l'adaptateur.
adapterOutput->Release();
adapterOutput = 0;

// Libère l'adaptateur.
adapter->Release();
adapter = 0;

// Libère la fabrique.
factory->Release();
factory = 0;

Le taux de rafraîchissement du système obtenu, nous pouvons débuter l'initialisation de DirectX. La première chose que nous allons faire est de définir la chaîne de permutation. Elle se compose des tampons avant et arrière sur lesquels seront dessinés les graphismes. En général nous utilisons un unique tampon arrière, y dessinons tout, puis l'échangeons avec le tampon avant qui s'affiche alors sur l'écran de l'utilisateur. C'est pourquoi nous l'appelons une « chaîne de permutation ».

// Initialise la chaîne de permutation.
ZeroMemory(&swapChainDesc, sizeof(swapChainDesc));

// Utilise un unique tampon arrière.
swapChainDesc.BufferCount = 1;

// Règle la largeur et la hauteur du tampon arrière.
swapChainDesc.BufferDesc.Width = screenWidth;
swapChainDesc.BufferDesc.Height = screenHeight;

// Fixe un format 32 bits pour le tampon arrière.
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;

La partie suivante de la définition de la chaîne de permutation est le taux de rafraîchissement, qui désigne combien de fois par seconde le tampon arrière est copié dans le tampon avant. Si vsync est mis à true dans notre en-tête graphicsclass.h, il suivra le taux d'actualisation des réglages système (par exemple 60 Hz). Cela signifie que l'écran sera redessiné 60 fois par seconde (ou plus si le taux de rafraîchissement système est supérieur à 60). Maintenant, si nous mettons vsync à false, il redessinera l'écran autant de fois par seconde qu'il le peut, ceci pouvant provoquer des artefacts visuels.

// Règle le taux de rafraîchissement du tampon arrière.
if(m_vsync_enabled)
{
swapChainDesc.BufferDesc.RefreshRate.Numerator = numerator;
swapChainDesc.BufferDesc.RefreshRate.Denominator = denominator;
}
else
{
swapChainDesc.BufferDesc.RefreshRate.Numerator = 0;
swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
}

// Définit le rôle du tampon arrière.
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;

// Définit l'identifiant de la fenêtre de rendu.
swapChainDesc.OutputWindow = hwnd;

// Désactive le multi-échantillonnage.
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;

// Détermine le mode plein écran ou fenêtré.
if(fullscreen)
{
swapChainDesc.Windowed = false;
}
else
{
swapChainDesc.Windowed = true;
}

// Définit l'ordre de balayage des lignes et le redimensionnement à non-spécifié.
swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

// Supprime le contenu du tampon arrière après affichage.
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

// Aucun flag avancé.
swapChainDesc.Flags = 0;

La description de la chaîne de permutation faite, nous avons aussi besoin de configurer une variable appelée « niveau de fonctionnalité ». Celle-ci indique quelle version de DirectX nous souhaitons utiliser. Nous fixons ici le niveau à 11.0, soit DirectX 11. Vous pouvez le mettre à 10 ou 9 pour utiliser une version de DirectX antérieure si vous prévoyez de prendre en charge plusieurs versions ou d'exécuter votre code sur une ancienne machine.

// Règle le niveau de fonctionnalité de DirectX 11.
featureLevel = D3D_FEATURE_LEVEL_11_0;

Maintenant que la description de la chaîne de permutation et le niveau de fonctionnalité ont été remplis, nous pouvons créer la chaîne de permutation, le dispositif Direct3D, et le contexte de périphérique Direct3D. Ces deux derniers sont très importants, ils sont l'interface de toutes les fonctions Direct3D. Nous les utiliserons pour presque tout par la suite.

Ceux d'entre vous qui connaissent les versions précédentes de DirectX reconnaîtront le dispositif Direct3D, mais découvriront le nouveau contexte de périphérique Direct3D. Fondamentalement, ils ont pris les fonctionnalités du dispositif Direct3D et les ont scindées en deux périphériques différents. Vous aurez donc besoin d'utiliser les deux.

Notez que si l'utilisateur ne dispose pas d'une carte vidéo DirectX 11, l'appel de cette fonction ne parviendra pas à créer le dispositif et son contexte. Aussi, si vous souhaitez tout de même tester les fonctionnalités DirectX 11, vous pouvez remplacer D3D_DRIVER_TYPE_HARDWARE par D3D_DRIVER_TYPE_REFERENCE. DirectX utilisera votre CPU au lieu de la carte vidéo pour dessiner. Notez que cela prend 1/1000 de la vitesse, mais c'est pratique pour les gens qui n'ont pas encore de carte vidéo DirectX 11 sur toutes leurs machines.

// Crée la chaîne de permutation, le périphérique Direct3D et le contexte de périphérique Direct3D.
result = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, &featureLevel, 1,
D3D11_SDK_VERSION, &swapChainDesc, &m_swapChain, &m_device, NULL, &m_deviceContext);
if(FAILED(result))
{
return false;
}

Parfois, cet appel servant à créer le dispositif échouera si la carte vidéo principale n'est pas compatible avec DirectX 11. Certaines machines peuvent avoir la carte vidéo principale en DirectX 10 et la secondaire en DirectX 11. Certaines cartes graphiques hybrides fonctionnent de cette façon : avec la carte Intel principale ayant une plus faible puissance et la secondaire Nvidia plus puissante. Pour contourner ce problème, vous ne devrez pas utiliser le périphérique par défaut, mais énumérer toutes les cartes vidéo de la machine et demander à l'utilisateur laquelle il souhaite utiliser, puis spécifier cette carte lors de la création du dispositif.
Maintenant que nous avons une chaîne de permutation, nous devons obtenir un pointeur vers le tampon arrière et le lier avec elle en utilisant la fonction CreateRenderTargetView.

// Obtient le pointeur vers le tampon arrière.
result = m_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBufferPtr);
if(FAILED(result))
{
return false;
}

// Crée le rendu de la cible à partir du tampon arrière.
result = m_device->CreateRenderTargetView(backBufferPtr, NULL, &m_renderTargetView);
if(FAILED(result))
{
return false;
}

// Libère le pointeur vers le tampon arrière, car nous n'en avons plus besoin.
backBufferPtr->Release();
backBufferPtr = 0;

Nous aurons également besoin de mettre en place la description du tampon de profondeur, que nous utiliserons pour avoir un rendu 3D correct de nos polygones. En même temps, nous allons lui lier un tampon stencil. Le tampon stencil peut être utilisé pour obtenir des effets de flou de mouvement, d'ombres volumétriques et autres.

// Initialise la description du tampon de profondeur.
ZeroMemory(&depthBufferDesc, sizeof(depthBufferDesc));

// Définit la description du tampon de profondeur.
depthBufferDesc.Width = screenWidth;
depthBufferDesc.Height = screenHeight;
depthBufferDesc.MipLevels = 1;
depthBufferDesc.ArraySize = 1;
depthBufferDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
depthBufferDesc.SampleDesc.Count = 1;
depthBufferDesc.SampleDesc.Quality = 0;
depthBufferDesc.Usage = D3D11_USAGE_DEFAULT;
depthBufferDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
depthBufferDesc.CPUAccessFlags = 0;
depthBufferDesc.MiscFlags = 0;

Maintenant, nous créons les tampons de profondeur/stencil en utilisant cette description. Vous remarquerez que nous utilisons la fonction CreateTexture2D pour créer les tampons, de ce fait le tampon est juste une texture 2D. La raison en est que, une fois que vos polygones sont triés puis pixelisés, ils finissent par être des pixels de couleur dans ce tampon 2D. Ce tampon 2D est alors dessiné à l'écran.

// Crée la texture pour le tampon de profondeur en utilisant la description précédemment définie.
result = m_device->CreateTexture2D(&depthBufferDesc, NULL, &m_depthStencilBuffer);
if(FAILED(result))
{
return false;
}

Maintenant, nous devons configurer la description du stencil de profondeur. Cela nous permet de contrôler le type de test de profondeur qu'effectuera Direct3D pour chaque pixel.

// Initialise la description de l'état stencil.
ZeroMemory(&depthStencilDesc, sizeof(depthStencilDesc));

// Configure la description de l'état stencil.
depthStencilDesc.DepthEnable = true;
depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
depthStencilDesc.DepthFunc = D3D11_COMPARISON_LESS;

depthStencilDesc.StencilEnable = true;
depthStencilDesc.StencilReadMask = 0xFF;
depthStencilDesc.StencilWriteMask = 0xFF;

// Opérations sur le tampon stencil si le pixel est de face.
depthStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
depthStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR;
depthStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
depthStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

// Opérations sur le tampon stencil si le pixel est situé à l'arrière.
depthStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
depthStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR;
depthStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
depthStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

La description renseignée, nous pouvons créer un état de tampon profondeur/stencil.

// Crée l'état du tampon de profondeur/stencil.
result = m_device->CreateDepthStencilState(&depthStencilDesc, &m_depthStencilState);
if(FAILED(result))
{
return false;
}

Avec l'état du tampon de profondeur/stencil créé, nous pouvons le régler pour qu'il prenne effet. Remarquez que nous utilisons le contexte de périphérique pour cela.

// Définit l'état du tampon profondeur/stencil.
m_deviceContext->OMSetDepthStencilState(m_depthStencilState, 1);

Nous avons ensuite besoin de créer la description de la vue du tampon profondeur/stencil. Nous faisons cela afin que Direct3D utilise le tampon de profondeur comme une texture de profondeur et de stencil. Après avoir renseigné la description, nous appelons la fonction CreateDepthStencilView pour créer notre vue.

// Initialise la vue du tampon profondeur/stencil.
ZeroMemory(&depthStencilViewDesc, sizeof(depthStencilViewDesc));

// Configure la description de la vue du tampon profondeur/Stencil.
depthStencilViewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
depthStencilViewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
depthStencilViewDesc.Texture2D.MipSlice = 0;

// Crée la vue du tampon profondeur/stencil.
result = m_device->CreateDepthStencilView(m_depthStencilBuffer, &depthStencilViewDesc, &m_depthStencilView);
if(FAILED(result))
{
return false;
}

Celle-ci créée, nous pouvons maintenant appeler OMSetRenderTargets qui va lier la vue cible de rendu et le tampon profondeur/stencil au pipeline de rendu de sortie. De cette façon, les graphismes rendus par le pipeline seront dessinés dans le tampon arrière que nous avons créé précédemment. Les graphiques écrits dans le tampon arrière, nous pouvons échanger ce dernier avec le tampon avant et afficher nos graphiques sur l'écran de l'utilisateur.

// Lie la vue cible de rendu et le tampon de profondeur/stencil au pipeline de rendu de sortie.
m_deviceContext->OMSetRenderTargets(1, &m_renderTargetView, m_depthStencilView);

Maintenant que les cibles de rendu sont configurées, nous pouvons considérer quelques fonctions supplémentaires qui nous donneront plus de contrôle sur nos scènes dans les tutoriels à venir. Premièrement, nous allons créer un état de rastérisation qui nous donnera le contrôle sur le rendu des polygones. Par exemple, faire que nos scènes soient rendues en mode filaire ou faire dessiner à DirectX les deux faces avant et arrière des polygones. Par défaut, DirectX a déjà un état de rastérisation mis en place qui permet de faire exactement ce que fait le code qui suit, mais sans la possibilité de le modifier, vous devez pour cela le configurer vous-même.

// Configure la description de trame qui va déterminer de quelle façon et quels polygones seront dessinés.
rasterDesc.AntialiasedLineEnable = false;
rasterDesc.CullMode = D3D11_CULL_BACK;
rasterDesc.DepthBias = 0;
rasterDesc.DepthBiasClamp = 0.0f;
rasterDesc.DepthClipEnable = true;
rasterDesc.FillMode = D3D11_FILL_SOLID;
rasterDesc.FrontCounterClockwise = false;
rasterDesc.MultisampleEnable = false;
rasterDesc.ScissorEnable = false;
rasterDesc.SlopeScaledDepthBias = 0.0f;

// Crée l'état de rastérisation selon la description précédente.
result = m_device->CreateRasterizerState(&rasterDesc, &m_rasterState);
if(FAILED(result))
{
return false;
}

// Définit l'état de rasterisation.
m_deviceContext->RSSetState(m_rasterState);

Le viewport doit aussi être configuré afin que Direct3D puisse lier l'espace du clip à l'espace cible de rendu. Réglez-le à la taille totale de la fenêtre.

// Configure le viewport pour le rendu.
viewport.Width = (float)screenWidth;
viewport.Height = (float)screenHeight;
viewport.MinDepth = 0.0f;
viewport.MaxDepth = 1.0f;
viewport.TopLeftX = 0.0f;
viewport.TopLeftY = 0.0f;

// Crée le viewport.
m_deviceContext->RSSetViewports(1, &viewport);

Maintenant, nous allons créer la matrice de projection utilisée pour transformer la scène 3D dans le viewport 2D que nous avons créé précédemment. Nous aurons besoin de conserver une copie de cette matrice afin que nous puissions la transmettre à nos shaders utilisés pour le rendu de nos scènes.

// Configure la matrice de projection.
fieldOfView = (float)D3DX_PI / 4.0f;
screenAspect = (float)screenWidth / (float)screenHeight;

// Crée la matrice de projection pour le rendu 3D.
D3DXMatrixPerspectiveFovLH(&m_projectionMatrix, fieldOfView, screenAspect, screenNear, screenDepth);

Nous allons créer une autre matrice appelée matrice monde, utilisée pour convertir les sommets de nos objets en sommets dans la scène 3D. Cette matrice sera également utilisée pour la rotation, la translation et le redimensionnement de nos objets dans l'espace 3D. Au début, nous allons juste l'initialiser à la matrice identité et conserver une copie dans notre instance. Nous aurons besoin de transmettre cette copie aux shaders, également pour le rendu.

// Initialise la matrice monde à la matrice identité.
D3DXMatrixIdentity(&m_worldMatrix);

Généralement, c'est là que vous créez une matrice de vue utilisée pour calculer la position de l'endroit d'où nous observons la scène. Vous pouvez voir cela comme une scène que l'on regarde à travers un appareil photo. Pour cela, je créerai dans les tutoriels à venir une classe Camera logiquement utilisée ici. Nous sautons cette étape pour l'instant.

Dernière chose à mettre en place dans la fonction Initialize : une matrice de projection orthographique. Cette matrice est utilisée pour le rendu d'éléments, telles des interfaces utilisateur, en 2D sur l'écran qui nous permettent de sauter le rendu 3D. Elle sera utilisée dans les futurs tutoriels lorsque nous aborderons le rendu des graphiques 2D et des polices à l'écran.

//Crée une matrice de projection orthographique pour le rendu 2D.
D3DXMatrixOrthoLH(&m_orthoMatrix, (float)screenWidth, (float)screenHeight, screenNear, screenDepth);

return true;
}

La fonction Shutdown va libérer et nettoyer tous les pointeurs utilisés dans la fonction Initialize, c'est plutôt direct. Mais avant cela, j'effectue un appel pour forcer la chaîne de permutation à passer en mode fenêtré avant de libérer un seul pointeur. Si vous ne faites pas cela et que vous essayez de libérer la chaîne de permutation en mode plein écran, des exceptions seront lancées. Pour éviter que cela ne se produise, nous forçons toujours le mode fenêtré avant d'arrêter Direct3D.

void D3DClass::Shutdown()
{
// Avant l'arrêt, on met en mode fenêtré sinon lors de la libération de la chaîne de permutation, une exception sera lancée.
if(m_swapChain)
{
m_swapChain->SetFullscreenState(false, NULL);
}

if(m_rasterState)
{
m_rasterState->Release();
m_rasterState = 0;
}

if(m_depthStencilView)
{
m_depthStencilView->Release();
m_depthStencilView = 0;
}

if(m_depthStencilState)
{
m_depthStencilState->Release();
m_depthStencilState = 0;
}

if(m_depthStencilBuffer)
{
m_depthStencilBuffer->Release();
m_depthStencilBuffer = 0;
}

if(m_renderTargetView)
{
m_renderTargetView->Release();
m_renderTargetView = 0;
}

if(m_deviceContext)
{
m_deviceContext->Release();
m_deviceContext = 0;
}

if(m_device)
{
m_device->Release();
m_device = 0;
}

if(m_swapChain)
{
m_swapChain->Release();
m_swapChain = 0;
}

return;
}

Dans la classe D3DClass, j'ai quelques fonctions d'assistance. Les deux premières sont BeginScene et EndScene. BeginScene sera appelée chaque fois que nous dessinerons une nouvelle scène 3D au début de chaque trame. Tout ce qu'elle fait est initialiser les tampons de sorte qu'ils soient vides et prêts à être dessinés. L'autre fonction, EndScene, demande à la chaîne de permutation d'afficher notre scène 3D une fois que le dessin est terminé, à la fin de chaque trame.

void D3DClass::BeginScene(float red, float green, float blue, float alpha)
{
float color[4];

// Fixe la couleur d'effacement du tampon.
color[0] = red;
color[1] = green;
color[2] = blue;
color[3] = alpha;

// Efface le tampon arrière.
m_deviceContext->ClearRenderTargetView(m_renderTargetView, color);

// Efface le tampon de profondeur.
m_deviceContext->ClearDepthStencilView(m_depthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0);

return;
}

void D3DClass::EndScene()
{
// Affiche le tampon arrière à l'écran dès que le rendu est terminé.
if(m_vsync_enabled)
{
// Synchronise à la fréquence de rafraîchissement de l'écran.
m_swapChain->Present(1, 0);
}
else
{
// Affichage le plus rapidement possible.
m_swapChain->Present(0, 0);
}

return;
}

Les fonctions qui suivent récupèrent simplement des pointeurs vers le dispositif et le contexte de périphérique de Direct3D. Elles seront souvent appelées par le cadre de travail.

ID3D11Device* D3DClass::GetDevice()
{
return m_device;
}

ID3D11DeviceContext* D3DClass::GetDeviceContext()
{
return m_deviceContext;
}

Les trois fonctions suivantes donnent des copies des matrices de projection, monde, et orthographique aux fonctions appelantes. La plupart des shaders auront besoin de ces matrices pour effectuer le rendu, les objets extérieurs ont donc besoin d'un moyen facile d'obtenir une copie. Nous n'appellerons pas ces fonctions dans ce tutoriel, j'explique juste leur présence dans le code.

void D3DClass::GetProjectionMatrix(D3DXMATRIX& projectionMatrix)
{
projectionMatrix = m_projectionMatrix;
return;
}

void D3DClass::GetWorldMatrix(D3DXMATRIX& worldMatrix)
{
worldMatrix = m_worldMatrix;
return;
}

void D3DClass::GetOrthoMatrix(D3DXMATRIX& orthoMatrix)
{
orthoMatrix = m_orthoMatrix;
return;
}

Les dernières fonctions retournent par référence le nom de la carte vidéo et sa quantité de mémoire dédiée. Connaître ces informations peut aider au débogage sur des configurations différentes.

void D3DClass::GetVideoCardInfo(char* cardName, int& memory)
{
strcpy_s(cardName, 128, m_videoCardDescription);
memory = m_videoCardMemory;
return;
}

VII - Résumé

Nous sommes enfin en mesure d'initialiser et stopper Direct3D ainsi que d'effectuer le rendu d'une couleur dans la fenêtre. Compiler et exécuter le code va produire la même fenêtre que le dernier tutoriel, mais Direct3D est ici initialisé et la fenêtre est effacée à la couleur grise. Il vous indiquera également si votre compilateur est correctement configuré et peut voir les en-têtes et les fichiers de bibliothèque du SDK de DirectX.

VII-A - Exercices à faire

1. Recompiler le code et exécuter le programme pour s'assurer du fonctionnement de DirectX, sinon regarder les étapes du premier tutoriel. Appuyer sur la touche Echap pour quitter le programme après l'affichage de la fenêtre ;

2. Modifier la variable globale dans le fichier graphicsclass.h pour activer le plein écran et recompiler/exécuter ;

3. Remplacer la couleur d'effacement dans la méthode GraphicsClass::Rendu par du jaune ;

4. Écrire le nom de la carte vidéo et sa mémoire dédiée dans un fichier texte.

VIII - Code source

- Projet Visual Studio 2010 : dx11tut03.zip

- Source seule : dx11src03.zip

- Exécutable seul : dx11exe03.zip

IX - Remerciements

Cet article est une traduction autorisée de l'article paru sur RasterTek

Navigation

Tutoriel précédent : Création du cadre de travail et d'une fenêtre

Sommaire

Tutoriel suivant : Tampons, shaders et HLSL