Plus....

Beginning Game Development

Partie II – Introduction à DirectX

Traduit par Valentin BILLOTTE

Consultez cet article en anglais  

Cet article n’a pas été traduit par Microsoft. Il n’a pas été relu ou vérifié par Microsoft.

Sur cette page

Introduction Introduction
Vue générale de DirectX Vue générale de DirectX
Mon GPU est plus gros que le tient Mon GPU est plus gros que le tient
Adapters et Devices Adapters et Devices
Glossaires de termes 3D Glossaires de termes 3D
Maintenance du code Maintenance du code
Résumé Résumé

Introduction

Bienvenue dans le second article sur l’initiation au développement de jeu. Dans cet article nous allons couvrir les bases du développement avec DirectX.

DirectX est une API multimédia qui fourni une interface standardisée pour interagir avec les cartes graphiques, les cartes sonores, avec les devices d’entrées (clavier, souris, joystick) d’un PC et bien plus encore … Sans ces interfaces le développeur de jeu aurait à écrire un code différent pour chaque type de carte graphique, chaque type de carte son … DirectX permet une abstraction du matériel et traduit les instructions du développeur en un ensemble de commandes spécifique au matériel présent sur la machine.

Comme chaque nouvel outil, DirectX possède un ensemble de termes nouveaux et de définitions qu’il convient de comprendre pour pouvoir l’exploiter. Une révision des mathématiques du niveau seconde et première sont aussi nécessaires. Connaître les mathématiques sur le bout des doigts n’est pas ce que nous demandons ici. Il est préférable pour ce que nous allons faire de bien assimiler les bases de la trigonométrie et de la géométrie dans l’espace. La plupart des calculs que nous effectuerons utiliseront des formules prédéfinies que nous aurons juste à appliquer.

Haut de page Haut de page

Vue générale de DirectX

DirectX a été créé en 1995 et se nommait à l’époque GameSDK. Il s’agissait d’une librairie d’abord destiné aux développeurs C et C++. C’est la première release du MDX 9.0 (Managed DirectX) en décembre 2002 qui a permit l’utilisation des langages C# et VB.Net avec DirectX (en fait aujourd’hui n’importe quel langage .Net peut être utilisé).

Tout a déjà été dit au sujet des performances du managed DirectX comparé à la version non manage. Aujourd’hui on trouve sur le marché nombre de jeux développé avec MDX tendant à prouver que la version managé n’a rien à prouver face à la version non managé. Pourtant, il est clair que pour un jeu ou une application requérant un besoin de performance énorme, la version non manage sera préférée. L’avantage de la version managée vient de l’utilisation de langages puissants comme C# et de la syntaxe de l’API beaucoup plus simple qui permet aux développeurs d’être plus productif en écrivant plus, dans un temps moindre, avec un code plus sûr et maintenable. La plupart du temps nous verrons qu’un jeu en MDX fait souvent appel à des portions de code non managé.

Après l’installation du SDK Direct, vous devriez avoir sur votre machine un répertoire situé à C:\WINDOWS\Microsoft.NET\Managed DirectX avec un sous répertoire pour chacune des versions du SDK. Pour ma part j’ai 4 versions de DirectX installées sur ma machine, j’ai donc quatre répertoire. Chacun de ces sous répertoire contient neuf DLLs et neuf fichiers Xml. .Net nous permettant d’avoir plusieurs versions d’une même DLL sur une machine sans poser de problèmes du type DLL-Hell, nous pouvons faire cohabiter plusieurs versions de DirectX sans soucis. Le développeur DirectX peut donc facilement changer de versions du SDK pour ses programmes.

Les neufs DLL correspondent respectivement à neufs namespaces. Lorsque nous allons créer notre jeu, nous allons utiliser plusieurs d’entre eux pour profiter de leurs fonctionnalités sur les devices d’entrées utilisateur, sur le son, sur le réseau et bien entendu … sur la 3D.

Namespace Description
Microsoft.DirectX Classe communes, et structures mathématiques
Microsoft.DirectX.Direct3D Bibliothèque de fonctionnalités 3D et de classes d’utilitaires.
Microsoft.DirectX.DirectDraw API Direct Draw.
Microsoft.DirectX.DirectPlay API réseau pour les jeux multi-joueurs.
Microsoft.DirectX.DirectSound Support du son.
Microsoft.DirectX.DirectInput Super des devices d’entrées utilisateurs (souris, clavier …)
Microsoft.DirectX.AudioVideoPlayback Manipulation de vidéo et de sons (lecture d’un DVD par exemple)
Microsoft.DirectX.Diagnostics Dépannage.
Microsoft.DirectX.Security Gestion d’accès sécurisés.
Microsoft.DirectX.Security.Permissions Permissions sur les accès.


Figure 1. Liste des namespaces DirectX 9.0.

Avant d’aller plus loin nous devons terminer le travail commencé dans l’article précédent. Après l’ajout de la classe FrameworkTimer, notre solution ne pouvait plus compiler du fait de l’absence de référence vers DirectX. Corrigeons ce point.

Haut de page Haut de page

Mon GPU est plus gros que le tient

Avant d’entrer plus en details dans l’API DirectX, revenons sur le but initial de ce pour quoi nous sommes ici. Pour créer un jeu rapide, nous devons utiliser un nouveau type d’architecture de processeur pour calculer chaque images qui sera affichée sur notre moniteur. Aucun d’entre nous ne possède de moniteur 3D, l’image affichée sera donc en 2D. Il nous faut donc utiliser des formules mathématiques pour transformer pour chaque frame, tous les modèles 3D présents en images 2D. Utiliser le processeur central de la machine (CPU) pour ce genre de calcul rendrait notre jeu lent dans le mesure où celui-ci est déjà affecté aux calculs des autres données du jeu comme la gestion des scores, des potions des joueurs, de l’IA, des entrées utilisateur …. Mais aussi du bon fonctionnement du système d’exploitation. Heureusement pour nous, les cartes graphiques modernes possèdent leur propre processeur : Graphics Processing Unit ou GPU.

Les GPUs sont des processeurs spécialisés dans ce genre de calculs fastidieux qu’ils accomplissent avec une très grande vélocité. Ils permettent donc au CPU de garder toute sa puissance sur des traitements plus nobles. Le GPU possède sa propre mémoire qui permet une gestion des données graphiques beaucoup plus rapide. On se trouve donc en quelque sort avec un ordinateur à l’intérieur d’un ordinateur. La vitesse d’un jeu aujourd’hui dépend plus de la puissance du GPU que celle du CPU.

Haut de page Haut de page

Adapters et Devices

La plupart des cartes graphiques ne peuvent être connectées qu’un à moniteur. Il est possible d’avoir plusieurs cartes graphiques sur son PC pour avoir un espace d’affichage réparti sur plusieurs écrans. Chaque carte graphique reconnue est associée à un Adapter. Un adapter est en quelque sort un handle (ou lien) vers la carte graphique. Les adapters possèdent un identifiant. Le premier adapteur (qu’on appelle l’adapteur par défaut) possède l’identifiant « 0 », le second « 1 » et ainsi de suite. Avec DirectX on n’interagit pas directement avec l’adapter. C’est le Device qui va nous permettre de communiquer avec lui.

Un Device représente une connexion vers un adapter spécifique. Chaque adapter possède de multiples devices connectés à lui. DirectX support trois types de devices : Hardware, References and Software. Nous utiliserons le device Hardware qui est le plus véloce.

Voyons maintenant comment créer un device dans notre jeu. Ajoutez la ligne suivante au code du constructeur juste après le code déjà présent. Nous effectuons ceci à l’intérieur du constructeur pour être sur que ce code sera exécuté avant tout autre traitement.

  • Ajoutez le code suivant au constructeur de la classe GameEngine (juste après l’instruction affectant la propertie this.SetStyle)

Visual C#

// Get the ordinal for the default adapter
int adapterOrdinal = Manager.Adapters.Default.Adapter;

// Get our device capabilities so we can check them to set up the
// CreateFlags Caps
caps = Manager.GetDeviceCaps(adapterOrdinal, DeviceType.Hardware);
CreateFlags createFlags;

// Check the capabilities of the graphcis card is capable of
// performing the vertex-processing operations
// The HardwareVertexProcessing choice is the best
if (caps.DeviceCaps.SupportsHardwareTransformAndLight)
 {
 createFlags = CreateFlags.HardwareVertexProcessing;
 }
 else
 {
 createFlags = CreateFlags.SoftwareVertexProcessing;
 }

// If the graphics card supports vertex processing check if the device
// can do rasterization, matrix transformations, and lighting and
//shading operations
// This combination provides the fastest game experience
if (caps.DeviceCaps.SupportsPureDevice && createFlags ==
CreateFlags.HardwareVertexProcessing)
 {
 createFlags |= CreateFlags.PureDevice;
 }

// Set up the PresentParameters which determine how the device
// behaves PresentParameters
presentParams = new PresentParameters();
presentParams.SwapEffect = SwapEffect.Discard;

// Make sure we are in windowed mode when we are debugging
#if DEBUG presentParams.Windowed = true;
#endif

// Now create the device
device = new Device(adapterOrdinal,DeviceType.Hardware,this,
createFlags,presentParams);

Visual Basic

' Get the ordinal for the default adapter
Dim adapterOrdinal As Integer = Manager.Adapters.Default.Adapter

' Get our device capabilities so we can check them to set up the
' CreateFlags
Dim caps As Caps = Manager.GetDeviceCaps(adapterOrdinal, DeviceType.Hardware)
Dim createFlags As CreateFlags

' Check the capabilities of the graphcis card is capable of
' performing the vertex-processing operations
' The HardwareVertexProcessing choice is the best
If caps.DeviceCaps.SupportsHardwareTransformAndLight
Then createFlags = createFlags.HardwareVertexProcessing
Else createFlags = createFlags.SoftwareVertexProcessing
End If

' If the graphics card supports vertex processing check if the device
' can
' do rasterization, matrix transformations, and lighting and shading
' operations
' This combination provides the fastest game experience
If caps.DeviceCaps.SupportsPureDevice
AndAlso createFlags = createFlags.HardwareVertexProcessing
Then createFlags = createFlags Or createFlags.PureDevice
End If

' Set up the PresentParameters which determine how the device behaves
Dim presentParams As New PresentParameters() presentParams.SwapEffect =
SwapEffect.Discard
 
' Make sure we are in windowed mode when we are debugging
#If DEBUG
Then presentParams.Windowed = True
#End If
 
' Now create the device
device = New Device(adapterOrdinal, DeviceType.Hardware, Me,
createFlags, presentParams)
  • A la fin du code de la fenêtre, juste après la declaration de la variable deltaTime , ajoutez le code suivant :

Visual C#

private Device device;

Visual Basic

Private device As Device

Il s’agit d’une portion de code assez conséquente pour la création et la configuration d’un Device, mais n’oubliez pas que le Device est notre lien vers la carte graphique, il nous faut donc s’assurer qu’il est configure parfaitement. Le meilleur moyen de bien comprendre le bloc de code que nous venons d’insérer à notre programme est sans doute de le découper en 4 parties distinctes.

  1. La première ligne de code récupère tout simplement le nom de l’adapter par défaut (qui est normalement « 0 »). Nous pourrions spécifier directement « 0 », mais il convient pour plus de sécuriser de toujours passer par les fonctionnalités DirectX. Notre jeu ne plantera pas si à tout hasard l’adapteur par défaut a pour identifiant “2”.
  2. La portion de code suivante est utilisée pour expliciter la ou les valeurs de l’énumération CreateFlags qui vont être passées au constructeur de la classe Device et qui vont impacter profondément la manière dont ce dernier va fonctionner. Nous utilisons encore un Manager pour obtenir la liste des possibilités (représenté par la classe Caps) que nous permet l’adapter courant. Nous listons ces possibilités pour savoir si nous devons utiliser un traitement de Vertex hardware, plus rapide ou software, plus lent mais assuré de toujours fonctionner quelque soit le matériel (le terme de vertex est explicité plus loin). SoftwareVertexProcessing indique que nous allons utiliser le CPU pour les traitements, tandis que HardwareVertexProcessing utilisera le GPU. Un autre test est effectué juste après pour déterminer si notre adapter peut supporter un device « pur ». A savoir si notre carte peut effectuer de la rasterization, de la transformation de matrices, du lightning et du traitement de shader. Si cela est possible, nous ajoutons le flag PureDevice. La combinaison des flags HardwareVertexProcessing et PureDevice fournit au jeu le maximum de performances possible.
  3. La dernier paramètre demandé pour la création d’un Device est l’objet PresentParameters. Cet objet indique comment le device affiche les données graphiques à l’écran. Nous commençons par l’énumération SwapEffect qui indique comment le buffer et le device sont liés. Le flag Discard indique ici que nous n’utiliserons pas de back buffer et écrirons directement sur le front Buffer. A l’intérieur de la clause If nous déterminons si nous sommes en mode Debug. Dans ce cas, le jeu ne tournera pas en mode plein écran pour faciliter le débuggage. Utilise ce moyen est plus sûr que de hard coder l’information pour l’oublier lors de l’édition de la release.
  4. La dernière étape créé le device. Nous passons l’identifiant de l’adapter, les valeurs CreateFlags, et PresentParameters.

Cette façon de procéder nous assure un device valide créé de manière sécurisée. Modifions maintenant notre code pour utiliser ce device.

  • Ajoutez le code suivant dans la méthode OnPaint method juste après l’instruction FrameworkTimer.Start().

Visual C#

device.Clear(ClearFlags.Target, Color.DarkBlue, 1.0f, 0); device.Present();

Visual Basic

device.Clear(ClearFlags.Target, Color.DarkBlue, 1.0F, 0) device.Present()

La première ligne efface le contenu de la fenêtre avec la couleur spécifiée en second paramètre. Les deux derniers paramètres spécifient le z-depth and stencil, notions qui ne sont pas importantes ici.

La méthode Present du device force le device à afficher le contenu du backbuffer à l’écran. L’écran est appelé le front buffer. La façon dont ces buffer interagissent est spécifié par le paramètre de type SwapEffect que nous avons vu précédemment.

Si nous exécutons notre projet ainsi modifié nous obtenons un magnifique écran bleu. Pas très impressionnant certes…mais nous avons maintenant intégré parfaitement DirectX à notre programme.

Haut de page Haut de page

Glossaires de termes 3D

Avant de continuer plus en amont et afficher un terrain et des unités, nous devons nous allons faire une pause de quelques minutes et afin de se familiariser avec les principes et définitions utilisés dans le monde de la programmation de jeux en 3D. Ceci n’est pas fait par gentillesse ou pour remplir des pages de caractères mais simplement parce qu’il est obligatoire de les connaître.

Le jeu auquel je joue en ce moment se nomme Brothers in Arms: Road to Hill 30 (www.brothersinarmsgame.com). C’est là aussi un jeu de tir avec vue à la première personne qui se déroule en Normandie en 1942. Tous les terrains, les constructions, les routes, les rivières etc. dans le jeu sont les exactes répliques du terrain réel de l’époque. Les auteurs du jeu on utilisés des photographies aérienne, des cartes et se sont déplacés sur place pour reproduire la géographie à l’identique. Pour situer un élément sur terre, nous utilisons un système de coordonnées nommé coordonnées géographiques (http://en.wikipedia.org/wiki/Geographic_coordinate_system). Dans ce système chaque point possède une latitude et une longitude unique.

Les auteurs de ce jeu n’ont pas pu garder ce système de coordonnées parce qu’un PC n’a aucune idée de ce qu’une latitude ou longitude représente. Pour être capable de placer un objet dans un espace en 3 dimensions nous utilisons un système de coordonnées que l’ordinateur comprend en transformant les coordonnées d’un système vers un autre. Le système utilisé par DirectX est le Cartesien dit « main gauche ».

SYSTEME DE COORDONNEES CARTESIEN

Pour placer efficacement n’importe quel objet dans un espace en trios dimension nous devons savoir où les placer et comment définir chaque point de manière clair et non ambigüe. C’est ce que permet le système cartésien. Il comprend trois axes perpendiculaires les uns des autres avec un point d’intersection commun nommé origine (vous avez sans doute du voir plusieurs fois le système similaire a deux axes x et y en seconde et première). Deux propriétés supplémentaires définissent ce système: l’agencement des axes et l’orientation.

AGENCEMENT

Il y’a deux moyens de représentent un système cartésien a trios dimensions : représentation en main gauche et représentation en main droite (http://en.wikipedia.org/wiki/Cartesian_coordinate_system). DirectX utilise la représentation en main gauche. Dans cette représentation plus la valeur Z augmente, plus la distance s’accrois, en main droite la valeur de Z diminue dans la distance augmente.

ORIENTATION

Un système de coordonnées cartésien a trois dimensions peut avoir différentes orientations. L’orientation du système dépend de celle de son axe Z.

Figure 2: Système de coordonnées cartésien a trois dimensions

VECTEUR

Chaque point dans un système de coordonnées cartésien en trois dimensions est défini par trois valeurs, X, Y et Z. Pour nous simplifier la tâche l’API directX possède une structure nommée Vector3 qui nous permet de manipuler simplement ces coordonnées. On trouvera dans l’API les structures Vector2 et Vector4.

VERTEX

Nous avons employé ce terme plus en aval lors de la création d’un device pour déterminer si nous devions avoir un traitement des vertex hardware ou software. Mais qu’est ce qu’un vertex ? Un vertex est un point qui fait partie d’un objet en 3 dimensions. Il consiste en un vecteur et en un ensemble de valeurs qui spécifient la manière dont un texte s’affiche sur lui, comment il reflète la lumière etc. Il ne s’agit donc pas simplement d’un point.

TEXTURE

Une texture est tout simplement un bitmap 2D qui est “collé” sur un objet 3D pour lui donner un habillage.

MESH

Nous ne parlerons pas de Mesh dans cet article. Cette notion est très liée à celle d’un vertex. Un mesh est l’ensemble des données qui décrit une forme 3D. Ces données incluent une liste de vertex qui définissent la forme de l’objet mais aussi la manière dont ces vertex sont interconnectés et diverses informations comme les textures qui le couvrent. Pour parler simplement il s’agit d’un plan pour afficher un objet 3D à l’écran.

A partir de maintenant quand vous entendrez la notion de vecteur, pensez à “point”. Quand vous entendez « vertex » pendez à « point associé a des données ». Quand vous entendez « mesh », pendez a “ensemble de point et ensemble de données”.

TRIANGLES

Nous savons maintenant ce qu’est un vertex : un point dans l’espace, nous devons comprendre maintenant comment ces points sont relies pour former des objets. Chaque objet en DirectX est un assemblage de triangles. Pourquoi des triangles? La raison est simple: il s’agit du plus petit polygone coplanaire 3D possible (coplanaire signifie que tous les points sont sur le même plan). Ceci simplifie les calculs mathématiques de rendu.

Nous venons de voir plusieurs termes techniques qu’il est important de bien assimiler avant que nous abordions la notion de matrices. Nous aborderons cette notion dans le prochain article avec le principe de camera (au sens DirectX du terme) et les principes de transformations. Nous verrons de même plus en détail les meshes et le mapping de texture (texturage).

UN COMPTEUR DE FPS

Nous l’avions indiqué dans l’article précédent, il est maintenant temps d’ajouter un compteur de frames à notre jeu. Connaître les FPS de son jeu est important parce que cela nous permet de déterminer la rapidité de notre application.

  • Ajoutez une nouvelle classe au projet nommée FrameRate.
  • Ajoutez le code suivant à l’intérieur de la déclaration de la classe.

Visual C#

public static int CalculateFrameRate() {
if (System.Environment.TickCount - lastTick >= 1000) {
lastFrameRate = frameRate;
frameRate = 0;
lastTick = System.Environment.TickCount;
}
frameRate++;
return lastFrameRate;
}
private static int lastTick;
private static int lastFrameRate;
private static int frameRate

Visual Basic

Public Shared Function CalculateFrameRate() As Integer
If System.Environment.TickCount - lastTick >= 1000
Then lastFrameRate = frameRate
frameRate = 0
lastTick = System.Environment.TickCount
End If
frameRate += 1
Return lastFrameRate
End Function

'CalculateFrameRate
Private Shared lastTick As Integer
Private Shared lastFrameRate As Integer
Private Shared frameRate As Integer
  • A l’intérieur de la méthode OnPaint, ajoutez la ligne de code suivante juste après l’affectation de la variable deltaTime.

Visual C#

this.Text = string.Format("The framerate is {0}",
FrameRate.CalculateFrameRate());

Visual Basic

Me.Text = String.Format("The framerate is {0}",
FrameRate.CalculateFrameRate())

Ce compteur de Frame utilise la propriété TickCount qui est en fait un wrapper vers la méthode GetTickCount de l’API Win32. La précision n’est pas énorme, de l’ordre de 15 millisecondes mais pour calculer une fréquence d’actualisation, c’est suffisant.

Haut de page Haut de page

Maintenance du code

Avant de terminer, j’ai effectué deux modifications dans mon code. Tout d’abord j’ai supprimé l’instruction Application.EnablRTLMirroring() qui est maintenant dépréciée. J’ai de même encapsulé la création de mon objet GameEngine à l’intérieur d’un bloc using afin d’être sur que celui-ci sera bien ramassé par le garbage lorsque l’application se terminera.

Haut de page Haut de page

Résumé

A la fin de ce second article, vous vous demandez sans doute si nous allons un jour passer aux choses sérieuses et réellement commencer à travailler sur notre jeu. Vous devez comprendre que l’une des plus grande difficulté dans l’apprentissage de la programmation de jeu est de bien comprendre comment créer de bonne fondations pour nos applications afin de supporter de grande idées. Il est aussi important de bien se familiariser avec le monde de la 3D. Après cela, votre concentration pourra se porter exclusivement sur le jeu.

Dans cet article nous avons fait nos premiers pas avec DirectX, l’API que nous utiliserons dans BattleTank 2005. Nous avons présenté ce qu’est un GPU et pourquoi il est indispensable dans le bon fonctionnement des jeux aujourd’hui. Nous avons découvert l’adapter, lien privilégié vers la carte, le device pour la manipulation d’un adapter. Nous avons appris comment configurer un device. Nous avons explicités plusieurs termes et définitions lies au monde du développement 3D. Enfin nous avons terminé sur la création d’un compteur de frame pour suivre les performances de notre jeu.

Dans le prochain article nous découvrirons les matrices et les transformations, nous verrons comment placer une camera dans notre monde 3D et les notions de clipping et de culling. Pour terminer, nous ajouterons un terrain à notre jeu.

J’espère que ce premier contact avec le monde de DirectX ne vous a pas trop découragé. Les termes présentés ici peuvent paraître difficiles à assimiler mais il s vous seront plus familiers avec le temps. Je ne peux que vous conseiller de vous amuser avec le programme en changeant les différentes propriétés que nous avons explicitées (dans la création du device ou dans l’affichage de l’écran). De même profitez de la richesse du SDK pour découvrir les nombreux exemples proposés.

Haut de page Haut de page Précédent 2 sur 7 Suivant