I. Créez votre projet "Managed DirectX" en C#▲
I-A. Prérequis▲
I-B. Préparation de votre projet▲
Téléchargez et installez le SDK de DirectX.
Ouvrez Visual Studio .NET ou Visual C# Express Edition et créez un nouveau projet "Application Windows" que vous pourrez appeler "D3D Tutoriel 1" par exemple.
Une fois le projet créé, faites un clic droit sur "Références" dans le panneau "Explorateur de Solution" puis cliquez sur "Ajouter une référence".
Dans la fenêtre nouvellement ouverte par l'IDE, faites défiler la liste des composants jusqu'à trouver "Microsoft.DirectX". Vous devriez avoir deux références de ce composant. Une en version 1.1 et l'autre en version 2.0. Sélectionnez le composant en version 1.1. En effet, bien qu'étant bien avancée la version 2.0 est encore un peu trop jeune et peut causer de nombreux petits soucis. Par mesure de sécurité donc nous utiliserons donc la version 1.1. Trouvez maintenant la référence au composant "Microsoft.DirectX.Direct3D" et "Microsoft.DirectX.Direct3DX "et sélectionnez-les également...
Cliquez sur "OK" : les références s'ajoutent à votre projet dans l'arbre des références du panneau d'explorateur de solution.
Pour finir, allez dans le code source de la Form utilisée par votre application et ajoutez les clauses "using" suivantes afin de référencer les composants que nous venons d'ajouter :
using
Microsoft.DirectX;
using
Microsoft.DirectX.Direct3D;
II. Présentation et Initialisation de Direct3D▲
II-A. La classe "Device"▲
La classe "Device" est la base de tout rendu dans l'environnement Direct3D. Voici donc la syntaxe du constructeur qui va nous intéresser dans ce tutoriel :
public
Device (
System.Int32 adapter,
Microsoft.DirectX.Direct3D.DeviceType deviceType,
System.Windows.Forms.Control renderWindow,
Microsoft.DirectX.Direct3D.CreateFlags behaviorFlags,
Microsoft.DirectX.Direct3D.PresentParameters presentationParameters)
Voyons les paramètres du constructeur :
Paramètres | Description |
---|---|
adapter | Définit l'entier représentant l'adaptateur (carte vidéo) que DirectX devra utiliser. En général on utilise l'adapter par défaut c'est-à-dire l'adapteur 0. |
deviceType | Le deviceType défini quel type de device on veut créer. En général on utilisera un device de type "Hardware" ( DeviceType.Hardware) mais on peut également utiliser le type "Rasterizer". Ce type sera utile notamment pour des phases de débogage ou pour tester des effets qui ne peuvent être gérés par l'adapter utilisé par le device Direct3D. Il va sans dire que ce mode est extrêmement lent et que son utilisation n'est pas conseillée dans d'autres cas que ceux cités précédemment. |
renderWindow | Définit le contrôle qui va "accueillir" Direct3D. Cela peut être un Form, un Panel, une PictureBox... À définir selon vos besoins. |
behaviorFlags | Définit le comportement de Direct3D une fois son initialisation effectuée. Il existe beaucoup de flags et je vous invite à lire la documentation de Direct3D à ce sujet. Disons simplement que les trois plus utiles vont être :
Dans le cas où on initialise le flag "SoftwareVertexProcessing" toutes les opérations de vertex seront réalisées par le CPU. C'est bien entendu plus lent que le "HardwareVertexProcessing" mais ce flag est compatible avec toutes les cartes vidéos. Pour le flag "PureDevice" nous verrons ça dans un tutoriel suivant. Pour information il est bien sur possible de combiner ces flags. |
presentationParameters | Enfin ce dernier paramètre va définir la présentation du device Direct3D à l'écran. Je détaille cette structure dans le chapitre suivant. |
II-B. Les "presentationParameters"▲
Afin de correctement initialiser notre device Direct3D il est indispensable de bien paramétrer les "presentationParameters". Tout d'abord, créons notre structure :
PresentParameters presentParams =
new
PresentParameters();
Nous allons voir dans ce chapitre le minimum requis pour initialiser un device Direct3D fonctionnel. En l'occurrence deux variables de "presentParams" nous intéressent ici :
- Windowed (booléen définissant si Direct3D doit se comporter en mode fenêtré ou non) ;
- SwapEffect (définit le comportement du BackBuffer. Pour l'instant on utilisera "SwapEffect.Discard" qui définit que l'on annule les données du BackBuffer si celui-ci n'est pas prêt à être présenté).
II-C. La procédure d'initialisation du Device Direct3D▲
Voici le code source utilisant ce que nous avons vu jusqu'à présent, à savoir, l'initialisation des "PresentParameters" de notre device et bien sûr l'initialisation du device Direct3D. Ce code source est bien entendu à utiliser dans le code source de la Form de notre projet :
private
Device device =
null;
// Nous initialisons notre device Direct3D ici
public
void
InitializeGraphics()
{
// Définition des PresentParameters
PresentParameters presentParams =
new
PresentParameters();
presentParams.Windowed =
true
;
presentParams.SwapEffect =
SwapEffect.Discard;
// Creation de notre device
device =
new
Device(0
, DeviceType.Hardware, this
, CreateFlags.SoftwareVertexProcessing, presentParams);
III. Utilisation de Direct3D▲
III-A. Les évènements▲
Nous allons voir maintenant très rapidement comment gérer les évènements que nous renvoie le device Direct3D. Je vais vous présenter ici quatre évènements qui vont nous être utiles afin de bien "manager" notre device.
III-A-1. L'évènement "DeviceReset"▲
Comme son nom l'indique, cet évènement est envoyé lors d'un reset du device Direct3D. Nous verrons comment gérer cet évènement plus en détail dans un prochain tutoriel. Je vous indique simplement comment intercepter cet évènement qui nous sera utile lors de nos prochaines leçons :
// Après l'initialisation du device Direct3D
device.DeviceReset +=
new
EventHandler(OnResetDevice);
// Fonction appelée lors de l'interception de l'évènement "DeviceReset"
private
void
OnResetDevice(object sender, EventArgs e)
{
// Si le device n'est pas initialisé ou a été libéré on sort directement
if
(device ==
null ||
device.Disposed)
return
;
// Traitement de l'évènement
}
III-A-2. L'évènement "DeviceLost"▲
Pour faire simple, disons que cet évènement est déclenché lorsque le container du device Direct3D (une Form par exemple) perd sa synchronisation avec le device.
// On ajoute une variable locale
private
bool
isDeviceLost =
false
;
// Après l'initialisation du device Direct3D
device.DeviceLost +=
new
EventHandler(OnLostDevice);
// Fonction appelée lors de l'interception de l'évènement "DeviceLost"
private
void
OnLostDevice(object sender, EventArgs e)
{
// On initialise la variable locale à TRUE
isDeviceLost =
true
;
// Traitement de l'évènement
}
III-A-3. L'évènement "Disposing"▲
Cet évènement est déclenché lorsque le device a reçu une demande de fermeture.
// On ajoute une variable locale
private
bool
isTerminated =
false
;
// Après l'initialisation du device Direct3D
device.Disposing +=
new
EventHandler(OnDisposingDevice);
// Fonction appelée lors de l'interception de l'évènement "Disposing"
private
void
OnDisposingDevice(object sender, EventArgs e)
{
// On initialise la variable locale à TRUE
isTerminated =
true
;
// Traitement de l'évènement
device =
null;
}
III-A-4. L'évènement "DeviceResizing"▲
Cet évènement est déclenché lorsque le device (en fait son container) est redimensionné.
// Après l'initialisation du device Direct3D
device.DeviceResizing +=
new
EventHandler(OnResizingDevice);
// Fonction appelée lors de l'interception de l'évènement "DeviceResizing"
private
void
OnResizingDevice(object sender, EventArgs e)
{
// Si le device n'est pas initialisé ou a été libéré on sort directement
if
(device ==
null ||
device.Disposed)
return
;
// Traitement de l'évènement
if
(device.CheckCooperativeLevel() ==
false
||
this
.WindowState ==
FormWindowState.Minimized ||
this
.Activate ==
false
)
{
e.Cancel =
true
;
}
// On définit comme 320x200 la résolution minimum
if
(this
.WindowState !=
FormWindowState.Minimized)
{
if
(this
.Size.Width <
320
||
this
.Size.Height <
200
)
this
.Size =
new
Size(320
, 200
);
}
}
III-B. La boucle de rendu▲
Nous allons maintenant apprendre à utiliser le device Direct3D que nous venons de créer. La première étape consiste à réaliser une boucle de rendu. Voyons de quelles fonctions se compose cette boucle :
- effacement du ou des buffers ;
- début de la scène ;
- dessin des éléments composant la scène ;
- fin de la scène ;
- affichage du buffer.
III-B-1. L'effacement▲
La première étape à réaliser dans notre boucle est donc l'effacement des différents buffers qu'utilise le device Direct3D. Pour cela nous appellerons la fonction "Clear" de la classe "Device". Voici le détail de cette fonction :
public
void
Clear(
ClearFlags flags,
Color color,
float
zdepth,
int
stencil
);
Détaillons les paramètres de cette fonction :
Paramètres | Description |
flags | Définit les buffers concernés par cet effacement. Pour le moment nous effacerons deux buffers : le "Target" (backbuffer primaire) et le "ZBuffer" (Buffer stockant en gros les informations de profondeur de la scène). |
color | Définit la couleur qui sera utilisée pour l'effacement. |
zdepth | Définit la valeur (de 0 à 1) qui sera placée dans le ZBuffer après effacement. |
stencil | Représente la valeur à stocker dans chaque Stencil Buffer. Je ne rentre pas dans les détails, c'est inutile pour l'instant. |
III-B-2. Rendu de la scène▲
Seconde étape, le rendu de la scène en elle-même. Tout d'abord nous devons indiquer au device Direct3D que nous allons rendre la scène. Pour ce faire il faut lancer la fonction "BeginScene".
Ensuite, nous lançons le rendu de nos éléments composant notre scène. Pour l'instant nous n'affichons rien...
Enfin nous indiquons au device Direct3D que le rendu de notre scène est terminé par l'appel de la fonction "EndScene" et nous présentons le résultat à l'écran en appelant la fonction "Present".
Concrètement voici l'appel de ces fonctions :
device.Clear(ClearFlags.Target |
ClearFlags.ZBuffer, 0
, 1.0
f, 0
);
device.BeginScene();
// TODO : Rendu des éléments 3D de notre scène
device.EndScene();
device.Present();
III-B-3. L'appel de la boucle de rendu▲
L'appel de la boucle de rendu ne peut être effectué dans une boucle simple dans la mesure où nous devons nous assurer avant que le rendu et la présentation peuvent être réalisés. Pour cela deux possibilités s'offrent à nous.
La première consiste à utiliser les évènements de la Form en l'occurrence, ici, l'évènement "Paint".
La seconde consiste à réaliser les tests nous-mêmes afin de définir si oui ou non l'application est apte à réaliser le rendu. Nous ne nous occuperons pas de réaliser cette fonction de test dans ce tutoriel dans la mesure où ces tests font appel à pas mal de fonctions que nous ne verrons que plus tard. Je traiterai ce point ultérieurement.
Nous allons donc utiliser l'évènement "Paint" pour lancer notre rendu. Dans un premier temps il convient d'attacher une fonction à l'appel de cet évènement :
// Dans le constructeur de la Form
this
.Paint +=
new
PaintEventHandler(this
.Render);
// La fonction réalisant le rendu de la scène Direct3D
private
void
Render(object sender, PaintEventArgs e)
{
device.Clear(ClearFlags.Target |
ClearFlags.ZBuffer, 0
, 1.0
f, 0
);
device.BeginScene();
// TODO : Rendu des éléments 3D de notre scène
device.EndScene();
device.Present();
}
III-C. Récapitulatif▲
Nous avons donc vu comment :
- initialiser un device Direct3D ;
- intercepter les évènements retournés par le device ;
- réaliser un appel à une boucle de rendu ;
- réaliser une boucle de rendu.
Voici, pour récapituler, le code source de ce tutoriel :
public
partial class
Form1 : Form
{
// Notre device
private
Device device =
null;
// Initialisation de la form
public
Form1()
{
InitializeComponent();
// Appel de la procédure d'initialisation du device Direct3D
InitializeGraphics();
// Evènement pour la boucle de rendu
this
.Paint +=
new
PaintEventHandler(this
.Render);
}
// Procédure d'initialisation du device Direct3D
public
void
InitializeGraphics()
{
// Définition des PresentParameters
PresentParameters presentParams =
new
PresentParameters();
presentParams.Windowed =
true
;
presentParams.SwapEffect =
SwapEffect.Discard;
// Création de notre device
device =
new
Device(0
, DeviceType.Hardware, this
, CreateFlags.SoftwareVertexProcessing, presentParams);
}
// La boucle de rendu
private
void
Render(object sender, PaintEventArgs e)
{
// Effacement de la scène
device.Clear(ClearFlags.Target, 0
, 1.0
f, 0
);
// Début de la scène
device.BeginScene();
// TODO : Rendu des éléments 3D de notre scène
// Fin de la scène
device.EndScene();
// Présentation de la scène à l'écran
device.Present();
}
}