Astuces DotNet (Sébastien Courtois)

17/02/2013

[SharpDX] Direct2D/Direct3D 10/11 Interoperability

Filed under: 3D, DirectX, Intermediaire, Jeux vidéos — Étiquettes : , , , , , , , — sebastiencourtois @ 22:48
Aparté de l’auteur de ce blog

Plus d’un an de silence sur ce blog depuis mon dernier post. Cela s’explique par beaucoup de changement. Tout d’abord, l’abandon de XNA par Microsoft (désormais officiel) m’ont fait me poser beaucoup de questions sur les technologies managées liées aux jeu vidéos que j’allais maintenant blogguer. Heureusement des initiatives comme SlimDX et SharpDX ont permis aux orphelins de XNA comme moi de trouver un alternative pour continuer a faire du jeu vidéo en .NET. De plus, cela fait plusieurs mois que je ne suis pas satisfait de la façon de présenter l’information sur ce blog et je cherche a une nouvelle alternative pour diffuser mes tutoriaux/exemples de code (si vous avez des idées, n’hésitez pas). Enfin, j’ai aussi changé de travail et de pays. Apres un an et demi a San Francisco, je suis maintenant a Montréal travaillant pour Ubisoft Montréal (Assassin’s Creed, Far Cry, Watch Dogs…) en tant que programmeur outils.

 

[EDIT 19/02/13]

Code fonctionnant en DirectX 10/11 Windows 7/8

Les explications techniques se trouvent dans l’article ci-dessous. Dans les différences avec les explications qui suivent se situe sur les classes utilise. Le render target 2d correspond au device context de Direct2d. Cet exemple fonctionne avec les dll SharpDX fournis par NuGet.

Constructeur :

_factory2D = new SharpDX.Direct2D1.Factory(SharpDX.Direct2D1.FactoryType.SingleThreaded, DebugLevel.Information);
Surface surface = swapChain.GetBackBuffer<Surface>(0);
RenderTargetProperties renderTargetProperties = new RenderTargetProperties
    {
        Usage = RenderTargetUsage.None,
        DpiX = 96,
        DpiY = 96,
        PixelFormat = new PixelFormat(Format.B8G8R8A8_UNorm,AlphaMode.Premultiplied),
        MinLevel = FeatureLevel.Level_10,
        Type = RenderTargetType.Hardware
    };
_renderTarget2D = new RenderTarget(_factory2D, surface, renderTargetProperties);
_factoryDW = new SharpDX.DirectWrite.Factory(FactoryType.Isolated);

Dessin :

_renderTarget2D.BeginDraw();
_renderTarget2D.DrawText(s, s.Length, _format, new RectangleF(950, 50, 1280, 300), _colorBrushYellow, DrawTextOptions.None, MeasuringMode.GdiClassic);
_renderTarget2D.EndDraw();

Prérequis pour ce tutorial

Afin de fonctionner, vous devez avoir :

  • Une machine sous Windows 8
  • Visual Studio 2012
  • SharpDX   (ATTENTION : NE PAS UTILISER LE PACKAGE Nuget. Pour un raison que je ne connais pas, il ne fournit pas les DLL Windows8. Celle ci se trouve dans le répertoire ‘Bin\Win8Desktop-net40’ ou vous aurez installer SharpDX.)
  • Les références a ajouter sont :
    • SharpDX
    • SharpDX.Direct2D1
    • SharpDX.Direct3D11
    • SharpDX.DXGI
  • Avoir un pipeline de rendu 3d DirectX 11 fonctionnant déjà de façon autonome (si vous n’en avez pas, vous pouvez utiliser mon pipeline de test).

Introduction

Direct2D et Direct3D sont deux technologies de DirectX permettant l’affichage de dessins 2D et de modèles 3D. Le problème est que Microsoft n’a jamais donné de façon simple  de les faire communiquer depuis la version DirectX 11. En effet, alors que Direct3d 11 était sorti, Direct2D devait toujours dépendre de DirectX 10.1 pour fonctionner ce qui rendait complexe une interaction entre les deux. Certains (comme Alexandre MUTEL – Createur du SharpDX) ont parfois trouvé des solutions de contournement mais cela restait complexe et lourd a mettre en place dans un architecture de moteur 3d déjà existante.

DirectX 11.1 permet de faire un pont entre Direct3d 11 et Direct2d1 en utilisant DXGI. DXGI (DirectX Graphics Infrastructure) est un modèle d’abstraction du driver de la carte graphique et se trouve être la base pour l’ensemble des technologies graphiques DirectX.

dxgi

L’idée va être d’utiliser le Device et la SwapChain créés par Direct3D et les “convertir” en Device/SwapChain DXGI pour être utilisé par Direct2d. Le but étant de faire écrire Direct2d dans le même backbuffer que Direct3d. Tout cela en étant le moins intrusif possible dans le code Direct3d existant.

 

Modification du code Direct3d

Comme indiqué précédemment, nous partons d’un pipeline Direct3d existant et fonctionnant de façon autonome. Si vous avez téléchargé mon pipeline de test, il s’agit de la classe Renderer3d.cs. Si vous lancez le programme, cela vous donne le cube tournant suivant.

cube

Première chose a faire est d’autoriser le support du device Direct3d avec le modèle BRGA utilisé par Direct2d. Pour cela, il suffit de rajouter le DeviceCreationFlags ‘BgraSupport’ lors de la création du Device 3d.

SharpDX.Direct3D11.Device.CreateWithSwapChain(driverType,
                            DeviceCreationFlags.Debug | DeviceCreationFlags.BgraSupport,
                            levels,
                            desc,
                            out _device,
                            out _swapChain);

Ensuite, il est nécessaire de créer les DXGI Device et Swapchain depuis ceux de Direct3d. Cela se réalise en faisant un QueryInterface<>().

_dxgiDevice = _device.QueryInterface<SharpDX.DXGI.Device>();
_dxgiSwapChain = _swapChain.QueryInterface<SharpDX.DXGI.SwapChain>();

Avec ces informations, nous pouvons maintenant initialiser un device Direct2d a partir du pipeline Direct3d.

 

Initialisation de Direct2d

Comme Direct3d 11, Direct2d utilise un device et un device context pour communiquer avec la carte graphique. Nous allons ajouter en plus un lien entre la Surface Direct3d et le bitmap sur lequel nous allons écrire en Direct2d.

_device = new SharpDX.Direct2D1.Device(dxgiDevice);
_deviceContext = new DeviceContext(_device, DeviceContextOptions.None);
Surface surface = swapChain.GetBackBuffer<Surface>(0);
BitmapProperties1 bitmapProp = new BitmapProperties1(new PixelFormat(Format.B8G8R8A8_UNorm, AlphaMode.Premultiplied), 96, 96, BitmapOptions.Target | BitmapOptions.CannotDraw);
_target = new Bitmap1(_deviceContext, surface, bitmapProp);

Nous récupérons ainsi la Surface d’écriture du DXGI SwapChain de Direct3d (i.e le back buffer ou va écrire Direct3d). Cela nous permet de créer un Bitmap Direct2d qui va écrire directement dans le back buffer Direct3d.

Attention : Le BitmapProperties1 PixelFormat doit correspondre au PixelFormat utilisé lors de la création du Device/SwapChain Direct3d (SwapChainDescription.ModelDescription). Si ce n’est pas le cas, vous aurez un exception “The parameter is incorrect.” lors de la creation du bitmap.

 

Dessin de Direct2d

A partir du moment ou Direct2d a été initialisé, il suffit d’indiquer au Device Context Direct2d ou l’on souhaite dessiner avec la propriété Target. La suite reste du code Direct2d classique.

_deviceContext.Target = _target;
_deviceContext.BeginDraw();
_deviceContext.DrawText(s, s.Length, _format, new RectangleF(950, 50, 1280, 300), _colorBrushYellow, DrawTextOptions.None, MeasuringMode.GdiClassic);
_deviceContext.EndDraw();

Remarque : Le dessin Direct2D se fera par dessus le dessin présent dans le backbuffer.  Si vous souhaitez faire un HUD devant la scène 3d, il est nécessaire de dessiner la 2d après la 3d. Dans le cas contraire, votre dessin 2d sera caché par le dessin 3d par superposition. La méthode Present() de Direct3d (qui envoie le backbuffer a l’écran) doit être réalisé après lorsque l’ensemble des dessins sont terminés.

Si vous investissez un peu de temps, vous pouvez avoir une HUD de diagnostic comme dans la démo que j’ai réalisé pour ce tutorial.

cubefps

Vous pouvez telecharger le code du tutorial ici.

17/09/2010

[XNA 4] Tutoriel 3 : Affichage de texte

Filed under: .NET, C# 4, Débutant, Jeux vidéos, XBOX 360, XNA — Étiquettes : , , , , , , , — sebastiencourtois @ 13:39

Après avoir appris à afficher et déplacer des images, nous allons apprendre à écrire du texte à l’écran. Pour ce tutoriel, nous partirons de ce projet (Solution des exercices du Tutoriel 2).

  • Création d’une police de caractère (font)

Afin de pouvoir afficher du texte, il faut indiquer quel police de caractères (aussi appelé font), on souhaite utiliser. Pour cela, on ajoute un nouvel élément au projet Content (Les fonts sont généralement stockés dans le projet Content comme l’ensemble des ressources XNA).

xna_tuto3_1

On sélectionne un élément de type “SpriteFont” en indiquant un nom et on clique sur OK.

xna_tuto3_2

A l’instar des images, les fonts ont aussi un assetName visible et modifiable par la fenêtre Propriétés. 

Si l’on ouvre le fichier contenant la police de caractères, on obtient le fichier XML suivant :

<?xml version="1.0" encoding="utf-8"?>
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
  <Asset Type="Graphics:FontDescription">
    <FontName>Kootenay</FontName>
    <Size>14</Size>
    <Spacing>0</Spacing>
    <UseKerning>true</UseKerning>
    <Style>Regular</Style>
    <CharacterRegions>
      <CharacterRegion>
        <Start>32</Start>
        <End>126</End>
      </CharacterRegion>
    </CharacterRegions>
  </Asset>
</XnaContent>

La balise FontName permet de définir le nom de la police utilisée. Les fonts disponibles sont uniquement des fonts de type TrueType (Liste). La communauté a créé des générateur de font personnalisé (Exemple : FontBuilder). Les plus utilisés sont souvent Arial ou Verdana.

La balise Size indique la taille du texte comme cela se fait dans les suites bureautiques.

La balise Spacing permet d’indiquer si l’on souhaite un espace entre les lettres (valeur en pixel).

La balise UseKerning permet de définir si l’on autorise le kerning. Pour faire simple, le kerning est le fait que deux lettres puissent “se superposer” (voir l’article et cette image qui décrit le kerning).

La balise Style indique si l’on souhaite mettre en gras/italique. Valeurs possibles : Regular, Bold, Italic ou Bold Italic.

La balise CharacterRegions défini les plages de valeurs ascii utilisable par la police (ici de 32 à 126 ce qui correspond au caractères de ‘ ‘ à ‘~’ en incluant les lettres en minuscules et majuscules, les chiffres ainsi que certains signes de ponctuations (cf tableau ASCII). Nous verrons qu’il est possible de rajouter d’autres plages.

  • Chargement de la police de caractères

Une fois défini, il est nécessaire de charger la police de caractères lors du lancement de l’application. Pour cela, on utilise le même principe que pour les textures.

SpriteFont textFont;

protected override void LoadContent()
{
    [...]
    this.textFont = Content.Load<SpriteFont>("MyFont");
}

Rappel : On utilise toujours le AssetName du fichier avec la méthode Load(). On doit donner aussi le type de sortie (SpriteFont pour les polices de caractères).

  • Affichage de texte

L’affichage du texte se déroule dans la méthode Draw() en utilisant le spriteBatch comme pour les textures.

Pour l’exemple, nous allons réutiliser l’exemple du chapitre précédent pour afficher en temps réel les coordonnées du tank, son angle de rotation ainsi que l’angle de rotation de son canon.

protected override void Draw(GameTime gameTime)
{
   GraphicsDevice.Clear(Color.CornflowerBlue);
   spriteBatch.Begin();

   string text1 = string.Format("Position du tank : X = {0}   Y = {1}",this.TankPosition.X,this.TankPosition.Y);
   spriteBatch.DrawString(this.textFont, text1, Vector2.Zero, Color.Red);
   string text2 = string.Format("Rotation du tank : {0}", TankRotation);
   spriteBatch.DrawString(this.textFont, text2, new Vector2(0,20), Color.DarkGreen);
   string text3 = string.Format("Rotation du canon : {0}", CanonRotation);
   spriteBatch.DrawString(this.textFont, text3, new Vector2(0, 40), Color.Yellow);

   spriteBatch.Draw(this.tank, TankPosition, null, Color.White, MathHelper.ToRadians(TankRotation), TankOriginPoint, 1.0f, SpriteEffects.None, 0);            
   spriteBatch.Draw(this.canon, TankPosition, null, Color.White, MathHelper.ToRadians(CanonRotation), new Vector2(canon.Width / 2 - 4.0f, canon.Height / 2), 1.0f, SpriteEffects.None, 0);
            
   spriteBatch.End();
   base.Draw(gameTime);
} 

On utilise la méthode DrawString () de spriteBatch. Cette méthode prend 3 paramètres :

  1. Police de caractères (SpriteFont)
  2. Position (en pixel à l’écran)
  3. Couleur du texte.

Le résultat en image :

xna_tuto3_3

  • Si je veux rajouter le ° à coté de l’angle.

Si vous tentez de faire le changement suivant :

string text2 = string.Format("Rotation du tank : {0} °", TankRotation);
spriteBatch.DrawString(this.textFont, text2, new Vector2(0,20), Color.DarkGreen);

Vous allez rencontre l’erreur : Argument Exception : The character ‘°’ (0x00b0) is not available in this SpriteFont. If applicable, adjust the font’s start and end CharacterRegions to include this character. Parameter name: character

Cela est dû au fait que la balise CharacterRegions ne contient pas ce  caractère dans sa liste de caractère disponible. (‘°’ a le code hexa 0xb0 => 176 en décimal). Il suffit de rajouter une range dans le fichier xml pour ajouter le caractère.

<CharacterRegions>
      <CharacterRegion>
        <Start>32</Start>
        <End>126</End>
      </CharacterRegion>
      <CharacterRegion>
        <Start>176</Start>
        <End>176</End>
      </CharacterRegion>
 </CharacterRegions>

Si vous recompilez et lancez l’application, le symbole apparait.

xna_tuto3_4

  • Conclusion

Vous pouvez maintenant écrire du texte à l’écran. Dans le prochain article, nous verrons l’utilisation du clavier/souris/manette XBOX 360 et Touch sur Windows Phone.

Code de ce tutorial

Propulsé par WordPress.com.