DirectX 11 - Tutoriel 4 : tampons, shaders et HLSL

RasterTek

Traducteur

Jérôme Marsaguet

Ce tutoriel introduit l'écriture des vertex et pixel shaders ainsi que l'utilisation des tampons de sommets et d'indices dans DirectX 11. Ce sont les concepts les plus fondamentaux que vous devez comprendre et utiliser pour effectuer un rendu 3D.

Commentez

- Titre : DirectX 11 - Tutoriel 4 : tampons, shaders et HLSL

- Auteur : RasterTek

- Traducteur : Jérôme Marsaguet

- Parution : 22 mai 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. Tampons de sommets

- III. Tampons d'indices

- IV. Vertex Shaders

- V. Pixel Shaders

- VI. HLSL

- VII. Mise à jour du cadre de travail

- VIII. Color.vs

- IX. Color.ps

- X. Modelclass.h

- XI. Modelclass.cpp

- XII. Colorshaderclass.h

- XIII. Colorshaderclass.cpp

- XIV. Cameraclass.h

- XV. Cameraclass.cpp

- XVI. Graphicsclass.h

- XVII. Graphicsclass.cpp

- XVIII. Résumé

- XIX. Exercices à faire

- XX. Code source

- XXI. Remerciements

- Navigation

Navigation

Tutoriel précédent : Initialisation de DirectX 11

Sommaire

Tutoriel suivant : application de textures

II - Tampons de sommets

Le premier élément à comprendre concerne les tampons de sommets. Pour illustrer ce concept, prenons l'exemple d'un modèle 3D d'une sphère :

Le modèle de la sphère 3D est en fait composé de centaines de triangles :

Chacun des triangles dans le modèle de la sphère possède trois points, que l'on appelle sommets. Donc, pour effectuer le rendu du modèle de la sphère, nous devons stocker tous les sommets qui forment la sphère, dans un tableau de données spécial que nous appelons un tampon de sommets. Une fois tous les points du modèle de la sphère dans le tampon de sommets, nous pouvons l'envoyer au GPU afin qu'il puisse réaliser le rendu du modèle.

III - Tampons d'indices

Les tampons d'indices sont liés aux tampons de sommets. Leur but est d'enregistrer la position de chaque sommet du tampon de sommets. Le GPU utilise alors le tampon d'indices pour retrouver rapidement un sommet. Le concept du tampon d'indices est similaire au concept de l'index dans un livre, il permet de trouver la rubrique que vous recherchez plus rapidement. La documentation du SDK de DirectX précise que l'utilisation de tampons d'indices peut également augmenter la possibilité de mettre en cache les données des sommets dans des emplacements plus rapides de la mémoire vidéo. Il est donc fortement conseillé de les utiliser, également pour des raisons de performances.

IV - Vertex Shaders

Les vertex shaders sont de petits programmes écrits principalement pour transformer les sommets du tampon de sommets vers l'espace 3D. D'autres calculs tels que celui des normales en chaque sommet peuvent être effectués. Le programme du vertex shader sera appelé par le GPU pour chaque sommet à traiter. Par exemple, un modèle de 5 000 polygones exécutera votre programme vertex shader 15 000 fois lors de chaque trame pour ce modèle unique. Donc, si vous verrouillez votre programme graphique à 60 fps, il appellera votre vertex shader 900 000 fois par seconde pour seulement dessiner 5 000 triangles. Comme vous pouvez le deviner, écrire des vertex shaders performants est important.

V - Pixel Shaders

Les pixel shaders sont de petits programmes écrits pour faire la coloration des polygones que nous dessinons. Ils sont exécutés par le GPU pour chaque pixel visible dessiné à l'écran. Le coloriage, l'application de textures, l'éclairage et la plupart des autres effets que vous pourrez appliquer aux faces de vos polygones sont gérés par ces programmes. Les pixel shaders doivent être écrits efficacement en raison du nombre de fois qu'ils seront appelés par le GPU.

VI - HLSL

HLSL est le langage que nous utilisons dans DirectX 11 pour coder les pixels et vertex shaders. La syntaxe est à peu près identique au langage C avec certains types pré-définis. Les fichiers HLSL sont composés de variables globales, définitions de types, vertex shaders, pixel shaders et geometry shaders. Comme ceci est le premier tutoriel HLSL, nous ferons pour commencer un programme HLSL très simple utilisant DirectX 11.

VII - Mise à jour du cadre de travail

Le cadre de travail a été mis à jour pour ce tutoriel. Dans la classe GraphicsClass, nous avons ajouté trois nouvelles classes appelées CameraClass, ModelClass et ColorShaderClass. La classe CameraClass s'occupera de la matrice de vue dont nous avons parlé précédemment. Elle se chargera de la position de la caméra dans le monde et de sa transmission aux shaders lorsqu'ils auront besoin de dessiner ou savoir d'où nous regardons la scène. La classe ModelClass se chargera de la géométrie de nos modèles 3D. Dans ce tutoriel, le modèle 3D se résumera à un seul triangle pour des raisons de simplicité. Et finalement ColorShaderClass sera responsable du rendu du modèle à l'écran, en invoquant notre shader HLSL.

Nous allons commencer le tutoriel en regardant le code des programmes de shaders HLSL.

VIII - Color.vs

Ceux-ci seront nos premiers programmes de shader. Les shaders sont de petits programmes qui font le rendu réel de modèles. Ces shaders sont écrits en HLSL et stockés dans les fichiers sources nommés color.vs et color.ps. J'ai placé les fichiers d'extension .cpp et .h dans le moteur pour le moment. Le but de ce shader est de juste dessiner des triangles de couleur, gardant les choses aussi simples que possible dans ce premier tutoriel HLSL. Premièrement, voici le code pour le vertex shader :

////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : color.vs
////////////////////////////////////////////////////////////////////////////////

Les programmes shader débutent avec les variables globales. Ces variables globales peuvent être modifiées à l'extérieur de votre code C++. Vous pouvez utiliser de nombreux types de variables tels que int ou float, les définir à l'extérieur et les utiliser dans vos programmes de shader. En général, vous allez mettre la plupart de ces variables globales dans des objets tampons appelés « cbuffer » même s'il n'est question que d'une seule variable. L'organisation logique de ces tampons est importante pour l'exécution efficace des shaders ainsi que la façon dont la carte graphique va stocker ces tampons. Dans cet exemple, j'ai mis trois matrices dans le même tampon puisque je vais les mettre à jour en même temps à chaque trame.

////////////////////////
// VARIABLES GLOBALES //
////////////////////////
cbuffer MatrixBuffer
{
matrix worldMatrix;
matrix viewMatrix;
matrix projectionMatrix;
};

De même qu'en C, nous pouvons créer nos propres définitions de type. Nous allons utiliser différents types tels que float4 disponible en HLSL, qui rendent la programmation de shaders plus facile et lisible. Dans cet exemple, nous créons des types qui ont les vecteurs de position x, y, z, w et les couleurs : rouge, vert, bleu et alpha. POSITION, COLOR et SV_POSITION sont des sémantiques qui renseignent le GPU sur l'utilisation des variables. Je dois créer deux structures différentes ici car les sémantiques pour les vertex et pixel shaders sont différentes, même si leurs structures sont identiques au fond. POSITION fonctionne pour les vertex shaders et SV_POSITION fonctionne pour les pixel shaders ; COLOR fonctionne pour les deux. Si vous en voulez plusieurs de même type, vous devez ajouter un numéro à la fin tel que COLOR0, COLOR1, et ainsi de suite.

//////////////////////////
// DÉFINITION DES TYPES //
//////////////////////////
struct VertexInputType
{
float4 position : POSITION;
float4 color : COLOR;
};

struct PixelInputType
{
float4 position : SV_POSITION;
float4 color : COLOR;
};

Le vertex shader est appelé par le GPU lorsqu'il traite les données qui lui ont été envoyées par les tampons de sommets. Ce vertex shader que j'ai nommé ColorVertexShader sera appelé pour chaque sommet contenu dans le tampon de sommets. L'entrée du vertex shader doit correspondre au format de données du tampon de sommets ainsi qu'à la définition du type dans le fichier source du shader qui est ici VertexInputType. La sortie du vertex shader sera envoyée au pixel shader. Dans notre cas, le type de sortie se nomme PixelInputType tel que vous le voyez défini ci-dessus.

Avec cela à l'esprit, vous voyez que le vertex shader crée une variable de sortie de type PixelInputType. Il prend alors la position du sommet en entrée et la multiplie par les matrices monde, de vue et de projection. Ceci placera le sommet au bon emplacement pour le rendu dans l'espace 3D selon notre point de vue, puis sur l'écran 2D. Ensuite, la variable de sortie récupère une copie de la couleur d'entrée et est renvoyée en entrée du pixel shader. Notez également que je définis la valeur W de la position d'entrée à 1.0 car ne lisant que le vecteur XYZ, sa valeur n'est pas définie.

////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType ColorVertexShader(VertexInputType input)
{
PixelInputType output;

// Change la quatrième composante du vecteur pour avoir des calculs matriciels corrects.
input.position.w = 1.0f;

// Calcule la position du sommet selon les matrices monde, de vue et de projection.
output.position = mul(input.position, worldMatrix);
output.position = mul(output.position, viewMatrix);
output.position = mul(output.position, projectionMatrix);

// Stocke la couleur d'entrée pour le pixel shader.
output.color = input.color;

return output;
}

IX - Color.ps

Le pixel shader dessine chaque pixel sur les polygones qui seront affichés à l'écran. Ce pixel shader utilise un PixelInputType en entrée et renvoie un float4 (représentant la couleur finale du pixel) en sortie. Ce programme de pixel shader est très simple : nous lui demandons juste de recopier la couleur d'entrée. Notez que le pixel shader prend son entrée en sortie du vertex shader.

////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : color.ps
////////////////////////////////////////////////////////////////////////////////

//////////////////////////
// DÉFINITION DES TYPES //
//////////////////////////
struct PixelInputType
{
float4 position : SV_POSITION;
float4 color : COLOR;
};

////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 ColorPixelShader(PixelInputType input) : SV_TARGET
{
return input.color;
}

X - Modelclass.h

Comme indiqué précédemment, la classe ModelClass est responsable de l'encapsulation de la géométrie des modèles 3D. Dans ce tutoriel, nous allons définir manuellement les données pour un seul triangle vert. Nous allons également créer pour ce triangle les tampons de sommets et d'indices, de sorte qu'il puisse être affiché.

////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : modelclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_

////////////////
// INCLUSIONS //
////////////////
#include <d3d11.h>
#include <d3dx10math.h>

////////////////////////////////////////////////////////////////////////////////
// Nom de la classe : ModelClass
////////////////////////////////////////////////////////////////////////////////
class ModelClass
{
private:

Voici la définition de notre type de sommet qui sera utilisé avec le tampon de sommets dans cette classe ModelClass. Notez également que la disposition des éléments doit correspondre à celle de la classe ColorShaderClass que nous verrons plus tard dans le tutoriel.

struct VertexType
{
D3DXVECTOR3 position;
D3DXVECTOR4 color;
};

public:
ModelClass();
ModelClass(const ModelClass&);
~ModelClass();

Ces fonctions gèrent l'initialisation et la finalisation des tampons de sommets et d'indices du modèle. La fonction Render envoie la géométrie du modèle vers la carte vidéo afin de préparer son affichage qui sera effectué par le shader de couleur.

bool Initialize(ID3D11Device*);
void Shutdown();
void Render(ID3D11DeviceContext*);

int GetIndexCount();

private:
bool InitializeBuffers(ID3D11Device*);
void ShutdownBuffers();
void RenderBuffers(ID3D11DeviceContext*);

Les attributs privés dans la classe ModelClass sont les tampons de sommets et d'indices ainsi que deux entiers gardant la taille de chaque tampon. Notez que les tampons sous DirectX 11 utilisent principalement le type générique ID3D11Buffer et sont plus clairement identifiés par une description de tampon lors de leur création.

private:
ID3D11Buffer *m_vertexBuffer, *m_indexBuffer;
int m_vertexCount, m_indexCount;
};

#endif

XI - Modelclass.cpp

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

Le constructeur de la classe initialise à NULLles pointeurs sur les tampons des sommets et d'indices.

ModelClass::ModelClass()
{
m_vertexBuffer = 0;
m_indexBuffer = 0;
}

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

ModelClass::~ModelClass()
{
}

La méthode Initialize va appeler les fonctions d'initialisation des tampons de sommets et d'indices.

bool ModelClass::Initialize(ID3D11Device* device)
{
bool result;

// Initialise les tampons d'indices et de sommets qui contiendront la géométrie du triangle.
result = InitializeBuffers(device);
if(!result)
{
return false;
}

return true;
}

La méthode Shutdown va appeler les fonctions de libération des tampons de sommets et d'indices.

void ModelClass::Shutdown()
{
// Libère les tampons d'indices et de sommets.
ShutdownBuffers();

return;
}

La méthode Render est appelée dans la méthode GraphicsClass::Render. Cette fonction appelle la fonction RenderBuffers pour placer les tampons de sommets et d'indices sur le pipeline graphique afin que le shader de couleur puisse effectuer leur rendu.

void ModelClass::Render(ID3D11DeviceContext* deviceContext)
{
// Place les tampons d'indices et de sommets sur le pipeline graphique afin de préparer leur rendu.
RenderBuffers(deviceContext);

return;
}

La fonction GetIndexCount retourne le nombre d'indices du modèle. Le shader de couleur aura besoin de cette information pour le dessiner.

int ModelClass::GetIndexCount()
{
return m_indexCount;
}

La fonction InitializeBuffers sert à la création des tampons de sommets et d'indices. Généralement, vous effectuerez la lecture d'un modèle depuis un fichier et créerez les tampons à partir des données stockées. Ne dessinant qu'un triangle dans ce tutoriel, nous écrirons manuellement les sommets et indices.

bool ModelClass::InitializeBuffers(ID3D11Device* device)
{
VertexType* vertices;
unsigned long* indices;
D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
D3D11_SUBRESOURCE_DATA vertexData, indexData;
HRESULT result;

Nous créons tout d'abord deux tableaux temporaires pour stocker les sommets et indices qui vont nous aider à remplir les tampons définitifs.

// Fixe la taille du tableau de sommets.
m_vertexCount = 3;

// Fixe la taille du tableau d'indices.
m_indexCount = 3;

// Crée le tableau de sommets.
vertices = new VertexType[m_vertexCount];
if(!vertices)
{
return false;
}

// Crée le tableau d'indices.
indices = new unsigned long[m_indexCount];
if(!indices)
{
return false;
}

Puis, remplissons à la fois le sommet et le tableau d'indices avec les trois points du triangle, ainsi que l'indice de chacun de ces points. Notez que je crée les points pour les dessiner dans le sens horaire. Si vous faites cela dans le sens antihoraire, le résultat sera un triangle orienté en sens inverse et qui ne sera pas dessiné à cause de la suppression des faces arrières (back face culling). Toujours se rappeler que l'ordre dans lequel vous envoyez vos sommets au GPU est très important. La couleur est aussi définie ici, car elle fait partie de la description du sommet. J'ai mis la couleur verte.

// Charge les données dans le tableau de sommets.
vertices[0].position = D3DXVECTOR3(-1.0f, -1.0f, 0.0f);  // En bas à gauche.
vertices[0].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f);

vertices[1].position = D3DXVECTOR3(0.0f, 1.0f, 0.0f);  // En haut au milieu
vertices[1].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f);

vertices[2].position = D3DXVECTOR3(1.0f, -1.0f, 0.0f);  // En bas à droite.
vertices[2].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f);

// Charge les données dans le tableau d'indices.
indices[0] = 0;  // En bas à gauche.
indices[1] = 1;  // En haut au milieu
indices[2] = 2;  // En bas à droite.

Une fois les tableaux de sommets et d'indices remplis, nous pouvons les utiliser pour créer les tampons. Pour cela, nous réutilisons la même méthode. Premièrement, le remplissage de la description du tampon, et vous devez vous assurer que les deux champs ByteWidth (taille du tampon) et BindFlags (type de tampon) soient remplis correctement. Une fois la description renseignée, vous devez également renseigner un pointeur de sous-ressource qui ira pointer sur l'un des deux tampons de sommets ou d'indices créés. Vous pouvez alors appeler CreateBuffer en utilisant l'objet D3D qui retournera un pointeur vers votre nouveau tampon.

// Met en place la description du tampon statique de sommets.
vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vertexBufferDesc.CPUAccessFlags = 0;
vertexBufferDesc.MiscFlags = 0;
vertexBufferDesc.StructureByteStride = 0;

// Donne à la structure de sous-ressources un pointeur vers le tableau des sommets.
vertexData.pSysMem = vertices;
vertexData.SysMemPitch = 0;
vertexData.SysMemSlicePitch = 0;

// Crée le tampon des sommets.
result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer);
if(FAILED(result))
{
return false;
}

// Met en place la description du tampon statique des indices.
indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount;
indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
indexBufferDesc.CPUAccessFlags = 0;
indexBufferDesc.MiscFlags = 0;
indexBufferDesc.StructureByteStride = 0;

// Donne à la structure de sous-ressources un pointeur vers le tableau des indices.
indexData.pSysMem = indices;
indexData.SysMemPitch = 0;
indexData.SysMemSlicePitch = 0;

// Crée le tampon des indices.
result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer);
if(FAILED(result))
{
return false;
}

Les deux tampons créés, vous pouvez libérer les tableaux qui ne nous serviront plus, puisque les données ont été copiées dans les tampons.

// Libère les tableaux, maintenant que les tampons ont été créés et remplis.
delete [] vertices;
vertices = 0;

delete [] indices;
indices = 0;

return true;
}

La méthode ShutdownBuffers libère juste les deux tampons ainsi créés dans la méthode InitializeBuffers.

void ModelClass::ShutdownBuffers()
{
// Libère le tampon d'indices.
if(m_indexBuffer)
{
m_indexBuffer->Release();
m_indexBuffer = 0;
}

//  Libère le tampon de sommets.
if(m_vertexBuffer)
{
m_vertexBuffer->Release();
m_vertexBuffer = 0;
}

return;
}

La méthode RenderBuffers est appelée dans la méthode Render. Son but est d'activer les tampons de sommets et d'indices sur l'assembleur d'entrée (input assembler) dans le GPU. Une fois que le GPU a un tampon de sommets actif, il peut dès lors utiliser le shader pour effectuer son rendu. Cette fonction définit également la façon dont ces tampons doivent être dessinés : en triangles, lignes, éventails, etc. Dans ce tutoriel, nous avons activés les tampons de sommets et d'indices sur l'assembleur d'entrée et signalé au GPU qu'ils doivent être dessinés à l'aide de triangles avec la fonction IASetPrimitiveTopology de DirectX.

void ModelClass::RenderBuffers(ID3D11DeviceContext* deviceContext)
{
unsigned int stride;
unsigned int offset;

// Règle le pas et l'offset du tampon.
stride = sizeof(VertexType);
offset = 0;

// Active le tampon des sommets dans l'assembleur d'entrée pour effectuer son rendu.
deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset);

// Active le tampon des indices dans  l'assembleur d'entrée pour effectuer son rendu.
deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);

// Définit le type de primitive qui doit être rendue depuis ce tampon de sommets, ici un triangle.
deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

return;
}

XII - Colorshaderclass.h

Nous utilisons la classe ColorShaderClass pour invoquer nos shaders HLSL afin de dessiner nos modèles 3D dans le GPU.

////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : colorshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _COLORSHADERCLASS_H_
#define _COLORSHADERCLASS_H_

////////////////
// INCLUSIONS //
////////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <d3dx11async.h>
#include <fstream>
using namespace std;

////////////////////////////////////////////////////////////////////////////////
// Nom de la classe : ColorShaderClass
////////////////////////////////////////////////////////////////////////////////
class ColorShaderClass
{
private:

Voici la définition du type cBuffer qui sera utilisé avec le vertex shader. Cette définition doit être exactement la même que celle du vertex shader, car les données du modèle doivent correspondre aux définitions des types du shader pour un rendu correct.

struct MatrixBufferType
{
D3DXMATRIX world;
D3DXMATRIX view;
D3DXMATRIX projection;
};

public:
ColorShaderClass();
ColorShaderClass(const ColorShaderClass&);
~ColorShaderClass();

Les méthodes ci-dessous gèrent l'initialisation et l'arrêt du shader. La méthode Render définit les paramètres du shader puis dessine les sommets des modèles préparés.

bool Initialize(ID3D11Device*, HWND);
void Shutdown();
bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX);

private:
bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*);
void ShutdownShader();
void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*);

bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX);
void RenderShader(ID3D11DeviceContext*, int);

private:
ID3D11VertexShader* m_vertexShader;
ID3D11PixelShader* m_pixelShader;
ID3D11InputLayout* m_layout;
ID3D11Buffer* m_matrixBuffer;
};

#endif

XIII - Colorshaderclass.cpp

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

Comme d'habitude, nous initialisons tous les pointeurs private à NULL.

ColorShaderClass::ColorShaderClass()
{
m_vertexShader = 0;
m_pixelShader = 0;
m_layout = 0;
m_matrixBuffer = 0;
}

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

ColorShaderClass::~ColorShaderClass()
{
}

La méthode Initialize appelle la fonction d'initialisation des shaders. Nous lui passons le nom des fichiers de shaders HLSL ; dans ce tutoriel, leur nom sont color.vs et color.ps.

bool ColorShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
{
bool result;

// Initialise les vertex et pixel shaders.
result = InitializeShader(device, hwnd, L"../Engine/color.vs", L"../Engine/color.ps");
if(!result)
{
return false;
}

return true;
}

La méthode Shutdown appelle la fonction de fermeture des shaders.

void ColorShaderClass::Shutdown()
{
// Ferme les vertex et pixel shaders ainsi que les objets associés.
ShutdownShader();

return;
}

La méthode Render va d'abord définir les paramètres à l'intérieur du shader à l'aide la fonction SetShaderParameters. Ensuite, elle appelle la fonction RenderShader, pour dessiner le triangle de couleur verte en utilisant le shader HLSL.

bool ColorShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix,
D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix)
{
bool result;

// Définit les paramètres du shader qui seront utilisés pour le rendu.
result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix);
if(!result)
{
return false;
}

// Effectue le rendu des tampons avec le shader.
RenderShader(deviceContext, indexCount);

return true;
}

Nous allons maintenant commencer par l'une des plus importantes fonctions de ce tutoriel : InitializeShader. Cette fonction a pour but de charger les fichiers du shader et de les rendre utilisables par DirectX et le GPU. Vous pouvez également voir la configuration de la disposition du shader et la façon dont les données du tampon de sommets serviront à la lecture sur le pipeline graphique dans le GPU. La disposition du shader devra correspondre au VertexType situé dans le fichier modelclass.h ainsi qu'à celui défini dans le fichier color.vs.

bool ColorShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename)
{
HRESULT result;
ID3D10Blob* errorMessage;
ID3D10Blob* vertexShaderBuffer;
ID3D10Blob* pixelShaderBuffer;
D3D11_INPUT_ELEMENT_DESC polygonLayout[2];
unsigned int numElements;
D3D11_BUFFER_DESC matrixBufferDesc;

// Initialise à zéro les pointeurs que la méthode va utiliser.
errorMessage = 0;
vertexShaderBuffer = 0;
pixelShaderBuffer = 0;

C'est ici que nous compilons les programmes shaders au sein des tampons. Nous fournissons le nom du fichier de shader, le nom du shader, sa version (5.0 pour DirectX 11), et le tampon où sera compilé le shader. Si la compilation échoue, le shader écrira un message d'erreur dans la chaîne errorMessage, que nous envoyons à une autre fonction afin d'écrire l'erreur. Si elle échoue mais qu'il n'y a pas de message dans errorMessage, cela signifie qu'il n'a pas pu trouver le fichier de shader, nous affichons alors une boîte de dialogue pour le notifier.

// Compile le code du vertex shader.
result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "ColorVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL,
&vertexShaderBuffer, &errorMessage, NULL);
if(FAILED(result))
{
// Si la compilation a échoué, le shader doit avoir écrit un message d'erreur.
if(errorMessage)
{
OutputShaderErrorMessage(errorMessage, hwnd, vsFilename);
}
// Si le message d'erreur a été laissé vide, c'est qu'il n'a pas pu trouver le fichier.
else
{
MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK);
}

return false;
}

// Compile le code du pixel shader.
result = D3DX11CompileFromFile(psFilename, NULL, NULL, "ColorPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL,
&pixelShaderBuffer, &errorMessage, NULL);
if(FAILED(result))
{
// Si la compilation a échoué, le shader doit avoir écrit un message d'erreur.
if(errorMessage)
{
OutputShaderErrorMessage(errorMessage, hwnd, psFilename);
}
// Si le message d'erreur a été laissé vide, c'est qu'il n'a pas pu trouver le fichier.
else
{
MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK);
}

return false;
}

Une fois que le code du vertex shader et pixel shader a été compilé avec succès dans les tampons, nous utilisons ces derniers pour créer les objets shader eux-mêmes. Nous utiliserons ces pointeurs comme interface avec les vertex shader et pixel shader à partir de ce point.

// Crée le vertex shader à partir du tampon.
result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader);
if(FAILED(result))
{
return false;
}

// Crée le pixel shader à partir du tampon.
result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader);
if(FAILED(result))
{
return false;
}

L'étape suivante consiste à créer la disposition des données représentant les sommets qui seront traités par le shader. Comme ce shader utilise un vecteur de positions et de couleurs, nous devons les créer tous les deux dans cette disposition, en spécifiant leur taille. Le nom sémantique est le premier élément à mettre dans la disposition du shader, ce qui permet au shader de déterminer son utilisation. Comme nous avons deux éléments distincts, nous utilisons POSITION pour le premier et COLOR pour le second. La prochaine partie importante de la disposition est le format. Nous utilisons DXGI_FORMAT_R32G32B32_FLOAT pour le vecteur de positions et DXGI_FORMAT_R32G32B32A32_FLOAT pour la couleur. Pour finir, vous devez faire attention au champ AlignedByteOffset qui indique comment les données sont espacées dans le tampon. Pour cette disposition, nous disons que les douze premiers octets sont pour la position et les seize octets suivants sont pour la couleur. AlignedByteOffset désigne où chaque élément commence. Vous pouvez utiliser D3D11_APPEND_ALIGNED_ELEMENT au lieu de vos propres valeurs pour qu'il vous calcule automatiquement l'espacement. J'ai laissé les autres paramètres à leur valeur par défaut pour le moment, car nous ne nous en servons pas dans ce tutoriel.

// Met en place la disposition des données transmises au shader.
// Cette disposition doit être la même que celle de la structure VextexType du shader.
polygonLayout[0].SemanticName = "POSITION";
polygonLayout[0].SemanticIndex = 0;
polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
polygonLayout[0].InputSlot = 0;
polygonLayout[0].AlignedByteOffset = 0;
polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
polygonLayout[0].InstanceDataStepRate = 0;

polygonLayout[1].SemanticName = "COLOR";
polygonLayout[1].SemanticIndex = 0;
polygonLayout[1].Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
polygonLayout[1].InputSlot = 0;
polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
polygonLayout[1].InstanceDataStepRate = 0;

Une fois la description de la disposition des données effectuée, nous pouvons obtenir sa taille, puis la créer en utilisant l'objet D3D. Nous libérerons également les tampons des vertex shader et pixel shader car nous n'en avons plus besoin une fois la disposition créée.

// Obtient le nombre d'éléments de la disposition des données.
numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

// Crée la disposition d'entrée du vertex.
result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(),
vertexShaderBuffer->GetBufferSize(), &m_layout);
if(FAILED(result))
{
return false;
}

// Libère les vertex et pixel shaders car nous n'en avons plus besoin.
vertexShaderBuffer->Release();
vertexShaderBuffer = 0;

pixelShaderBuffer->Release();
pixelShaderBuffer = 0;

La dernière chose à configurer pour utiliser le shader est un tampon constant. Comme vous l'avez vu pour le vertex shader, nous avons juste un tampon constant ; nous avons donc besoin d'en configurer un ici afin que nous puissions l'interfacer avec le shader. L'utilisation du tampon sera dynamique puisque nous le mettrons à jour à chaque trame. Les drapeaux de liaison indiquent que ce tampon sera constant. Les drapeaux d'accès au processeur doivent correspondre à l'utilisation, nous leur assignons donc D3D11_CPU_ACCESS_WRITE. Une fois que la description est remplie, nous pouvons créer l'interface du tampon constant puis l'utiliser pour accéder aux variables internes du shader en utilisant la fonction SetShaderParameters.

// Met en place la description du tampon de matrice dynamique constant situé dans le vertex shader.
matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType);
matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
matrixBufferDesc.MiscFlags = 0;
matrixBufferDesc.StructureByteStride = 0;

// Crée le pointeur vers le tampon constant afin de pouvoir y accéder depuis cette classe.
result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer);
if(FAILED(result))
{
return false;
}

return true;
}

La méthode ShutdownShader libère les quatre interfaces configurées dans la méthode InitializeShader.

void ColorShaderClass::ShutdownShader()
{
// Libère le tampon de matrice constant.
if(m_matrixBuffer)
{
m_matrixBuffer->Release();
m_matrixBuffer = 0;
}

// Libère la disposition des données.
if(m_layout)
{
m_layout->Release();
m_layout = 0;
}

// Libère le pixel shader.
if(m_pixelShader)
{
m_pixelShader->Release();
m_pixelShader = 0;
}

// Libère le vertex shader.
if(m_vertexShader)
{
m_vertexShader->Release();
m_vertexShader = 0;
}

return;
}

La méthode OutputShaderErrorMessage écrit les messages d'erreur générés lors de la compilation des vertex shaders ou des pixel shaders.

void ColorShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename)
{
char* compileErrors;
unsigned long bufferSize, i;
ofstream fout;

// Récupère le pointeur vers le message d'erreur du tampon.
compileErrors = (char*)(errorMessage->GetBufferPointer());

// Obtient la longueur du message.
bufferSize = errorMessage->GetBufferSize();

// Ouvre un fichier pour y écrire le message d'erreur.
fout.open("shader-error.txt");

// Écrit le message d'erreur.
for(i=0; i<bufferSize; i++)
{
fout << compileErrors[i];
}

// Ferme le fichier.
fout.close();

// Libère le message d'erreur.
errorMessage->Release();
errorMessage = 0;

// Affiche un message à l'écran pour avertir l'utilisateur qu'il doit vérifier le contenu du fichier pour y trouver les erreurs de compilation.
MessageBox(hwnd, L"Error compiling shader.  Check shader-error.txt for message.", shaderFilename, MB_OK);

return;
}

La méthode SetShaderVariables facilite le réglage des variables globales dans le shader. Les matrices utilisées sont d'abord créées dans la classe GraphicsClass, puis envoyées par cette méthode dans le vertex shader lors de l'appel à la méthode Render.

bool ColorShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix,
D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix)
{
HRESULT result;
D3D11_MAPPED_SUBRESOURCE mappedResource;
MatrixBufferType* dataPtr;
unsigned int bufferNumber;

Assurez-vous de transposer vos matrices avant de les envoyer dans le shader, il s'agit d'une contrainte de DirectX 11.

// Transpose les matrices afin de les préparer pour le shader.
D3DXMatrixTranspose(&worldMatrix, &worldMatrix);
D3DXMatrixTranspose(&viewMatrix, &viewMatrix);
D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);

Verrouillez le tampon m_matrixBuffer, définissez les nouvelles matrices en son sein, puis déverrouillez-le.

// Pose un verrou sur le tampon constant afin que l'on puise écrire dedans.
result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
if(FAILED(result))
{
return false;
}

// Récupère le pointeur des données dans le tampon constant.
dataPtr = (MatrixBufferType*)mappedResource.pData;

// Copie la matrice dans le tampon constant.
dataPtr->world = worldMatrix;
dataPtr->view = viewMatrix;
dataPtr->projection = projectionMatrix;

// Déverrouille le tampon constant.
deviceContext->Unmap(m_matrixBuffer, 0);

Maintenant, placez le tampon de matrices mis à jour dans le vertex shader HLSL.

// Définit la position du tampon constant dans le vertex shader.
bufferNumber = 0;

// Fixe finalement le tampon constant contenant les données à jour dans le vertex shader.
deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);

return true;
}

RenderShader est la deuxième méthode appelée dans Render. SetShaderParameters est appelée avant pour s'assurer que les paramètres de shader sont correctement configurés.

Cette fonction commence par activer la disposition des données en entrée dans l'assembleur d'entrée. Cela permet au GPU de connaître le format des données dans le tampon des sommets. La deuxième étape consiste à définir les vertex et pixel shaders que nous allons utiliser pour le rendu du tampon des sommets. Une fois que les shaders sont fixés, nous effectuons le rendu du triangle en appelant la fonction DrawIndexed de DirectX 11 qui utilise le contexte de périphérique D3D. Le triangle vert sera affiché une fois la fonction appelée.

void ColorShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount)
{
// Définit la disposition des données en entrée du vertex.
deviceContext->IASetInputLayout(m_layout);

// Définit les vertex et pixel shaders qui seront utilisés pour le rendu du triangle.
deviceContext->VSSetShader(m_vertexShader, NULL, 0);
deviceContext->PSSetShader(m_pixelShader, NULL, 0);

// Effectue le rendu du triangle.
deviceContext->DrawIndexed(indexCount, 0, 0);

return;
}

XIV - Cameraclass.h

Nous avons étudié la façon de coder des shaders HLSL, la configuration des tampons d'indices et de sommets, et l'appel des shaders HLSL visant à dessiner ces tampons en utilisant la classe ColorShaderClass. Il ne nous manque qu'une chose : le point de vue duquel nous les affichons. Pour cela, nous aurons besoin d'une classe Camera qui renseignera DirectX 11 sur la façon dont nous voyons la scène. Cette classe gardera la trace de la position et de l'angle de vue de la caméra. Elle utilisera ces informations pour générer une matrice de vue qui sera transmise au shader HLSL pour le rendu.

////////////////////////////////////////////////////////////////////////////////
// Nom du fichier : cameraclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _CAMERACLASS_H_
#define _CAMERACLASS_H_

////////////////
// INCLUSIONS //
////////////////
#include <d3dx10math.h>

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

void SetPosition(float, float, float);
void SetRotation(float, float, float);

D3DXVECTOR3 GetPosition();
D3DXVECTOR3 GetRotation();

void Render();
void GetViewMatrix(D3DXMATRIX&);

private:
float m_positionX, m_positionY, m_positionZ;
float m_rotationX, m_rotationY, m_rotationZ;
D3DXMATRIX m_viewMatrix;
};

#endif

L'en-tête de la classe CameraClass est assez simple avec seulement quatre méthodes. SetPosition et SetRotation seront utilisées pour définir la position et l'angle de la caméra. Render sera utilisée pour créer la matrice de vue à partir de la position et l'angle de la caméra. Enfin GetViewMatrix sera utilisée pour récupérer la matrice de vue de la caméra de sorte que les shaders puissent l'utiliser pour le rendu.

XV - Cameraclass.cpp

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

Le constructeur de la classe va initialiser la position et l'angle de la caméra pour se situer à l'origine de la scène.

CameraClass::CameraClass()
{
m_positionX = 0.0f;
m_positionY = 0.0f;
m_positionZ = 0.0f;

m_rotationX = 0.0f;
m_rotationY = 0.0f;
m_rotationZ = 0.0f;
}

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

CameraClass::~CameraClass()
{
}

Les méthodes SetPosition et SetRotation sont utilisées pour régler la position et l'angle de la caméra.

void CameraClass::SetPosition(float x, float y, float z)
{
m_positionX = x;
m_positionY = y;
m_positionZ = z;
return;
}

void CameraClass::SetRotation(float x, float y, float z)
{
m_rotationX = x;
m_rotationY = y;
m_rotationZ = z;
return;
}

Les fonctions GetPosition et GetRotation retournent la position et l'angle de la caméra aux fonctions appelantes.

D3DXVECTOR3 CameraClass::GetPosition()
{
return D3DXVECTOR3(m_positionX, m_positionY, m_positionZ);
}

D3DXVECTOR3 CameraClass::GetRotation()
{
return D3DXVECTOR3(m_rotationX, m_rotationY, m_rotationZ);
}

La méthode Render utilise la position et l'angle de la caméra pour construire et mettre à jour la matrice de vue. Nous définissons d'abord les variables up, position, rotation, et ainsi de suite. Puis, depuis l'origine de la scène, on fait tourner la caméra selon ses axes x, y et z. Ensuite, nous déplaçons la caméra à sa position dans l'espace 3D. Avec les bonnes valeurs dans les variables position, lookAt, et up, nous pouvons utiliser la fonction D3DXMatrixLookAtLH pour créer la matrice de vue représentant l'angle actuel de la caméra et son déplacement.

void CameraClass::Render()
{
D3DXVECTOR3 up, position, lookAt;
float yaw, pitch, roll;
D3DXMATRIX rotationMatrix;

// Définit un vecteur dirigé vers le haut.
up.x = 0.0f;
up.y = 1.0f;
up.z = 0.0f;

// Définit la position de la caméra dans le monde.
position.x = m_positionX;
position.y = m_positionY;
position.z = m_positionZ;

// Définit la direction de la caméra par défaut.
lookAt.x = 0.0f;
lookAt.y = 0.0f;
lookAt.z = 1.0f;

// Définit l'embardée (yaw, selon l'axe des Y), le tangage (pitch, axe des X) et le roulement (roll, axe des Z) en radians.
pitch = m_rotationX * 0.0174532925f;
yaw   = m_rotationY * 0.0174532925f;
roll  = m_rotationZ * 0.0174532925f;

// Crée la matrice de rotation à partir des valeurs de yaw, pitch et roll.
D3DXMatrixRotationYawPitchRoll(&rotationMatrix, yaw, pitch, roll);

// Transforme les vecteurs lookAt et up à l'aide de la matrice de rotation afin qu'ils soient correctement tournés au début.
D3DXVec3TransformCoord(&lookAt, &lookAt, &rotationMatrix);
D3DXVec3TransformCoord(&up, &up, &rotationMatrix);

// Déplace la caméra tournée à la position du spectateur.
lookAt = position + lookAt;

// Crée finalement la matrice de vue à partir des vecteurs mis à jour.
D3DXMatrixLookAtLH(&m_viewMatrix, &position, &lookAt, &up);

return;
}

Suite à l'appel de la méthode Render mettant à jour la matrice de vue, nous pouvons fournir cette dernière aux fonctions appelantes par le biais de la méthode GetViewMatrix. La matrice de vue sera l'une des trois principales matrices utilisées dans le shader vertex HLSL.

void CameraClass::GetViewMatrix(D3DXMATRIX& viewMatrix)
{
viewMatrix = m_viewMatrix;
return;
}

XVI - Graphicsclass.h

La classe GraphicsClass possède maintenant trois nouvelles classes : CameraClass, ModelClass et ColorShaderClass dont nous avons ici ajouté les en-têtes, ainsi que les attributs privés. Rappelez-vous que GraphicsClass est la classe principale qui est utilisée pour effectuer le rendu de la scène, ceci en invoquant toutes les instances de classe nécessaires au projet.

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

/////////////////////////////
// INCLUSIONS DE MA CLASSE //
/////////////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "modelclass.h"
#include "colorshaderclass.h"

////////////////////////
// VARIABLES GLOBALES //
////////////////////////
const bool FULL_SCREEN = true;
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:
D3DClass* m_D3D;
CameraClass* m_Camera;
ModelClass* m_Model;
ColorShaderClass* m_ColorShader;
};

#endif

XVII - Graphicsclass.cpp

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

Le premier changement de la classe GraphicsClass est l'initialisation à 0 de la caméra, du modèle et du shader de couleur dans son constructeur.

GraphicsClass::GraphicsClass()
{
m_D3D = 0;
m_Camera = 0;
m_Model = 0;
m_ColorShader = 0;
}

La méthode Initialize a également été mise à jour pour créer et initialiser les trois nouveaux objets.

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

// Crée l'objet Camera.
m_Camera = new CameraClass;
if(!m_Camera)
{
return false;
}

// Fixe la position initiale de la caméra.
m_Camera->SetPosition(0.0f, 0.0f, -10.0f);

// Crée l'objet Model.
m_Model = new ModelClass;
if(!m_Model)
{
return false;
}

// Initialise l'objet Model.
result = m_Model->Initialize(m_D3D->GetDevice());
if(!result)
{
MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
return false;
}

// Crée l'objet Shader de couleur.
m_ColorShader = new ColorShaderClass;
if(!m_ColorShader)
{
return false;
}

// Initialise l'objet Shader de couleur.
result = m_ColorShader->Initialize(m_D3D->GetDevice(), hwnd);
if(!result)
{
MessageBox(hwnd, L"Could not initialize the color shader object.", L"Error", MB_OK);
return false;
}

return true;
}

Également la méthode Shutdown afin de les libérer.

void GraphicsClass::Shutdown()
{
// Libère l'objet Shader de couleurs.
if(m_ColorShader)
{
m_ColorShader->Shutdown();
delete m_ColorShader;
m_ColorShader = 0;
}

// Libère l'objet Model.
if(m_Model)
{
m_Model->Shutdown();
delete m_Model;
m_Model = 0;
}

// Libère l'objet Caméra.
if(m_Camera)
{
delete m_Camera;
m_Camera = 0;
}

// Libère l'objet Direct3D.
if(m_D3D)
{
m_D3D->Shutdown();
delete m_D3D;
m_D3D = 0;
}

return;
}

La méthode Frame est restée la même que lors du précédent tutoriel.

bool GraphicsClass::Frame()
{
bool result;

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

return true;
}

Comme on peut s'y attendre, c'est la méthode Render qui possède le plus de changements. Elle débute toujours avec l'effacement de la scène, excepté que nous l'effaçons avec la couleur noire. Après cela, elle appelle la méthode Render de la caméra pour créer une matrice de vue basée sur sa position définie dans la méthode Initialize. Une fois que la matrice de vue est créée, nous en récupérons une copie. Nous recueillons également des copies des matrices monde et de projection depuis l'objet D3DClass. Nous appelons ensuite la méthode ModelClass::Render afin de passer la géométrie du modèle du triangle vert sur le pipeline graphique. Les sommets ainsi préparés, nous appelons le shader de couleur pour dessiner les sommets en utilisant les informations du modèle et les trois matrices pour positionner chaque sommet. Le triangle vert est maintenant établi dans le tampon arrière. Avec ceci, la scène est terminée, nous appelons EndScene pour l'afficher à l'écran.

bool GraphicsClass::Render()
{
D3DXMATRIX viewMatrix, projectionMatrix, worldMatrix;
bool result;

// Efface les tampons pour débuter la scène.
m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

// Génère la matrice de vue à partir de la position de la caméra.
m_Camera->Render();

// Récupère les matrices monde, de vue et de projection depuis les objets Caméra et D3D.
m_Camera->GetViewMatrix(viewMatrix);
m_D3D->GetWorldMatrix(worldMatrix);
m_D3D->GetProjectionMatrix(projectionMatrix);

// Place les tampons des sommets et d'indices du modèle dans le pipeline graphique pour les préparer à être rendus.
m_Model->Render(m_D3D->GetDeviceContext());

// Effectue le rendu du modèle en utilisant le shader de couleurs.
result = m_ColorShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix);
if(!result)
{
return false;
}

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

return true;
}

XVIII - Résumé

En résumé, vous devriez avoir appris les rudiments du fonctionnement des tampons d'indices et de sommets, ainsi que les bases des vertex et pixel shaders et comment les écrire en utilisant le HLSL. Enfin, vous devriez comprendre comment nous avons intégré ces nouveaux concepts dans notre structure pour afficher un triangle vert à l'écran. Je tiens également à mentionner que je me rends compte que le code est plutôt long pour juste dessiner un triangle alors qu'il aurait très bien pu tenir dans une unique fonction main().Je l'ai cependant fait de cette façon dans une structure appropriée afin que les tutoriels à venir ne nécessitent que très peu de changements pour faire des graphiques beaucoup plus complexes.

XIX - Exercices à faire

1. Compilez et exécutez le tutoriel. Assurez-vous qu'il dessine un triangle vert à l'écran. Appuyez sur « Echap » pour quitter le programme une fois fait.

2. Changez la couleur du triangle en rouge.

3. Modifiez le triangle en carré.

4. Déplacez la caméra de dix unités en arrière.

5. Modifiez le pixel shader pour qu'il ne donne que la moitié de la luminosité (énorme indice : multiplier quelque chose dans ColorPixelShader par 0.5f).

XX - Code source

- Projet Visual Studio 2010 : dx11tut04.zip

- Source seule : dx11src04.zip

- Exécutable seul : dx11exe04.zip

XXI - Remerciements

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

Navigation

Tutoriel précédent : Initialisation de DirectX 11

Sommaire

Tutoriel suivant : application de textures