Tutorial Series - Introduction au développement d'application Direct3D avec XAML pour Windows Phone 8


Introduction

Bienvenue dans ce tutoriel sur Direct3D pour Windows Phone 8 ! Dans le cadre de cet article, j'ai décidé de construire l'application Direct3D depuis un projet Windows Phone vide (au lieu d'utiliser les modèles proposés par Visual Studio). Nous allons donc ajouter la plomberie progressivement pour comprendre, étape par étape : 1/ comment la communication entre les composants C # et C ++ est effectuée et 2/ comment créer et manipuler les assets 3D à l’aide de Direct3D.

A la fin de cette série, vous serez en mesure d'afficher le vaisseau spatial Omega Crusher conçu par Michel en appliquant la texture et l’éclairage (comme affiché dans la vidéo). Pour ce premier post, nous allons créer la structure de notre solution et ajouter le code pour initialiser la surface avec superbe bleu. De fait, beaucoup de plomberie aujourd'hui, la partie plus amusante avec Direct3D débutera dans le prochain post :).

Quelques mots sur les modèles Direct3D fournis par Visual Studio

Je vais commencer avec un projet vide pour expliquer comment les composants interagissent entre eux. Quoi qu'il en soit, si vous débutez un nouveau développement, vous pouvez bien évidemment utiliser un de ces modèles pour gagner du temps. Pour référence, Windows Phone SDK 8.0 propose trois modèles pour créer des applications Direct3D.

Windows Phone Direct3D App (Natif uniquement)

Ce modèle se trouve dans le dossier Visual C++, c'est une application native qui ne prend pas en charge XAML. Si votre application a besoin de contrôles standard, tels que les zones de texte, des boutons ou des cases à cocher, vous aurez besoin de réécrire ces composants ‘from scratch’ ou utiliser des bibliothèques tierces.

Windows Phone Direct3D with XAML App

Ce modèle se trouve également dans le dossier Visual C++ mais fournit une structure à deux projets : un projet managé en C# et l'autre natif en C++. La partie managé est basée sur un contrôle DrawingSurfaceBackgroundGrid qui vous permet d'utiliser Direct3D pour afficher les graphismes sur l'ensemble du fond de votre application, le code Direct3D est mis en œuvre dans un composant Windows Phone séparé (la partie C++).

C'est la structure de projet que je vais mettre en œuvre dans ce tutoriel aujourd'hui.

Windows Phone XAML and Direct3D App

Ce modèle se trouve dans les dossiers Visual C# ou Visual Basic. La structure du projet est assez similaire à la précédente, mais l'interface XAML accueille un contrôle DrawingSurface au lieu de DrawingSurfaceBackgroundGrid.

Le contrôle DrawingSurface peut être placé n'importe où sur l'écran (comme tout contrôle XAML standard) contrairement à DrawingSurfaceBackgroundGrid qui doit être placé à la racine du code XAML et couvre toujours la totalité de l'écran. Le frame rate d'une application utilisant le contrôle DrawingSurfaceBackgroundGrid est légèrement meilleure qu’une application équivalente utilisant DrawingSurface.

Structure de la solution

1. Créez un nouveau projet Windows Phone App nommé OmegaCrusher comme décrit ci-dessous.

1_thumb3[1]

2. Modifiez le layout de MainPage.xaml pour placer un contrôle DrawingSurfaceBackgroundGrid à la racine de l’arborescence.

Les graphiques que vous dessinez à l'aide de ce contrôle couvrent tout l'écran du téléphone et sont affichés derrière tous les autres contrôles XAML que vous utilisez sur votre page. Pour dessiner des graphiques Direct3D sur une partie de l'écran uniquement, consultez DrawingSurface.

MainPage.xaml

<phone:PhoneApplicationPage
x:Class="OmegaCrusher.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True">

<!-- DrawingSurfaceBackgroundGrid -->
<DrawingSurfaceBackgroundGrid x:Name="DrawingSurfaceBackground" Loaded="DrawingSurfaceBackground_Loaded">
</DrawingSurfaceBackgroundGrid>

</phone:PhoneApplicationPage>

3. Nettoyer le code-behind pour supprimer les commentaires et ajouter le gestionnaire d'événements DrawingSurfaceBackgroundGrid_Loaded.

MainPage.xaml.cs

using System;
using System.Windows;
using Microsoft.Phone.Controls;
using OmegaCrusher.Resources;

namespace OmegaCrusher
{
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
}

private void DrawingSurfaceBackground_Loaded(object sender, RoutedEventArgs e)
{

}
}
}

4. Ajouter un nouveau projet Windows Phone Runtime Component nommé OmegaCrusher.PhoneComponent depuis les modèles Visual C++ .

2_thumb3

5. Renommer WindowsPhoneRuntimeComponent en Direct3DBackground afin d’obtenir le résultat suivant.

OmegaCrusher.PhoneComponent.h
#pragma once

namespace OmegaCrusher_PhoneComponent
{
public ref class Direct3DBackground sealed
{
public:
Direct3DBackground();
};
}

OmegaCrusher.PhoneComponent.cpp

// OmegaCrusher.PhoneComponent.cpp
#include "pch.h"
#include "OmegaCrusher.PhoneComponent.h"

using namespace OmegaCrusher_PhoneComponent;
using namespace Platform;

Direct3DBackground::Direct3DBackground()
{
}

6. Ajoutez une référence depuis le projet OmegaCrusher pour inclure le composant OmegaCrusher.PhoneComponent.

image_thumb2

7. Mettez ensuite à jour MainPage.xaml.cs pour instancier le composant Direct3DBackground comme déclaré ci-dessous.

using System;
using System.Windows;
using OmegaCrusher.Resources;
using Microsoft.Phone.Controls;
using OmegaCrusher_PhoneComponent;

namespace OmegaCrusher
{
public partial class MainPage : PhoneApplicationPage
{
Direct3DBackground m_d3dBackground = null;

// Constructor
public MainPage()
{
InitializeComponent();
}

private void DrawingSurfaceBackground_Loaded(object sender, RoutedEventArgs e)
{
if (m_d3dBackground == null)
{
m_d3dBackground = new Direct3DBackground();
}
}
}
}

Félicitations. Vous avez la structure de base suivante présente dans votre Explorateur de Solutions. Vous devriez être capable de compiler le code et de lancer l'application sans aucune erreur (qui affiche actuellement un contenu vide).

3_thumb2

 

Plomberie pour fournir un contenu Direct3D au contrôle DrawingSurfaceBackgroundGrid

Pour le moment, nous avons une structure de solution avec deux projets. Nous devons maintenant informer le code managé que le contenu Direct3D affiché dans le contrôle est fourni par notre composant C++ Windows Phone Runtime. Les lignes suivantes proposent quelques détails de bas niveau, mais vous pouvez aller directement à l'étape 8 et revenir plus tard si vous préférez occulter cette partie de la plomberie dans l’immédiat :).

En fait, DrawingSurfaceBackgroundGrid est plutôt sympa : il s'occupe pour nous de l'initialisation de Direct3D en créant un device Direct3D et une swap chain pour Windows Phone (je détaillerai ce point dans le prochain billet). Pour consommer les interfaces natives Direct3D et les API associés en C++, ce contrôle doit cibler un objet qui va effectuer les opérations de rendu Direct3D en utilisant la méthode SetBackgroundContentProvider.

Cependant, l'objet visé par SetBackgroundContentProvider doit d'abord mettre en œuvre les deux interfaces suivantes:

  • IDrawingSurfaceBackgroundContentProvider
  • IDrawingSurfaceBackgroundContentProviderNative


Une solution (utilisé dans les modèles Visual Studio) consiste à ajouter une helper class nommée Direct3DContentProvider qui implémente ces interfaces. Cette helper class est une classe Windows Phone Runtime. Si vous êtes familier avec COM et le langage ATL en C++, nous allons utiliser le même type de programmation via Windows Runtime Template Library pour implémenter notre classe Direct3DContentProvider en dérivant de la classe RuntimeClass.

IDrawingSurfaceBackgroundContentProviderNative déclare quatre méthodes qui doivent être mises en œuvre par le composant tel que défini ci-dessous. Vous pouvez voir que la méthode Draw fournit des pointeurs sur ID3D11Device1, ID3D11DeviceContext1 et ID3D11RenderTargetView. C’est notre point d’entrée pour consommer les API Direct3D.

MIDL_INTERFACE("262C1892-975A-45AA-8FE7-F25C2C4088E3")
IDrawingSurfaceBackgroundContentProviderNative : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE Connect(
/* [annotation][in] */
_In_ IDrawingSurfaceRuntimeHostNative *host,
/* [annotation][in] */
_In_ ID3D11Device1 *hostDevice) = 0;

virtual void STDMETHODCALLTYPE Disconnect( void) = 0;

virtual HRESULT STDMETHODCALLTYPE PrepareResources(
/* [annotation][in] */
_In_ const LARGE_INTEGER *presentTargetTime,
/* [annotation][in][out] */
_Inout_ DrawingSurfaceSizeF *desiredRenderTargetSize) = 0;

virtual HRESULT STDMETHODCALLTYPE Draw(
/* [annotation][in] */
_In_ ID3D11Device1 *hostDevice,
/* [annotation][in] */
_In_ ID3D11DeviceContext1 *hostDeviceContext,
/* [annotation][in] */
_In_ ID3D11RenderTargetView *hostRenderTargetView) = 0;

};
 
  • Connect est appelée dès que l’appel à SetContentProvider(Object) est finalisé. Dans le prochain billet, nous utiliserons cette méthode pour créer certains éléments de notre scène 3D comme les vertex & pixel shaders, et les index & vertex buffers de notre mesh, …
  • Disconnect is appelée quand le contrôle DrawingSurfaceBackgroundGrid quitte l’arborescence visuelle XAML. Nous pourrons libérer certaines ressources lors de cet appel.
  • Draw est appelée pour mettre à jour le périphérique graphique (device), le contexte (device context) et la cible de rendu (render target view). Nous allons effectuer le rendu de notre vaisseau Omega Crusher dans cette méthode.
  • PrepareResources est appelée par le moteur XAML pour chaque trame. Cette méthode permet au composant C++ de définir la taille requise pour la cible de rendu (permet d’optimiser le temps de traitement dans certains scénarios).
 
Notre helper class va rediriger l’implémentation de ces méthodes vers notre composant principal Direct3DBackground (nommé m_controller).

8. Ajoutez une classe C++ nommée Direct3DContentProvider comme indiqué ci-dessous. C’est notre helper class qui implemente les interfaces IDrawingSurfaceBackgroundContentProvider et IDrawingSurfaceBackgroundContentProviderNative.

4_thumb2

9. Mettez à jour le code de la classe Direct3DContentProvider avec les lignes suivantes.

Direct3DContentProvider.h

#pragma once

#include "pch.h"
#include <wrl/module.h>
#include <Windows.Phone.Graphics.Interop.h>
#include <DrawingSurfaceNative.h>

#include "OmegaCrusher.PhoneComponent.h"

class Direct3DContentProvider : public Microsoft::WRL::RuntimeClass <
Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::WinRtClassicComMix>,
ABI::Windows::Phone::Graphics::Interop::IDrawingSurfaceBackgroundContentProvider,
IDrawingSurfaceBackgroundContentProviderNative >
{
public:
Direct3DContentProvider(OmegaCrusher_PhoneComponent::Direct3DBackground^ controller);

// IDrawingSurfaceContentProviderNative
HRESULT STDMETHODCALLTYPE Connect(_In_ IDrawingSurfaceRuntimeHostNative* host, _In_ ID3D11Device1* device);
void STDMETHODCALLTYPE Disconnect();

HRESULT STDMETHODCALLTYPE PrepareResources(_In_ const LARGE_INTEGER* presentTargetTime, _Inout_ DrawingSurfaceSizeF* desiredRenderTargetSize);
HRESULT STDMETHODCALLTYPE Draw(_In_ ID3D11Device1* device, _In_ ID3D11DeviceContext1* context, _In_ ID3D11RenderTargetView* renderTargetView);

private:
OmegaCrusher_PhoneComponent::Direct3DBackground^ m_controller;
Microsoft::WRL::ComPtr<IDrawingSurfaceRuntimeHostNative> m_host;
};

Direct3DContentProvider.cpp

#include "pch.h"
#include "Direct3DContentProvider.h"

using namespace OmegaCrusher_PhoneComponent;

Direct3DContentProvider::Direct3DContentProvider(Direct3DBackground^ controller) :
m_controller(controller)
{
m_controller->RequestAdditionalFrame += ref new RequestAdditionalFrameHandler([=]()
{
if (m_host)
{
m_host->RequestAdditionalFrame();
}
});
}

// IDrawingSurfaceContentProviderNative interface
HRESULT Direct3DContentProvider::Connect(_In_ IDrawingSurfaceRuntimeHostNative* host, _In_ ID3D11Device1* device)
{
m_host = host;
return m_controller->Connect(host, device);
}

void Direct3DContentProvider::Disconnect()
{
m_controller->Disconnect();
m_host = nullptr;
}

HRESULT Direct3DContentProvider::PrepareResources(_In_ const LARGE_INTEGER* presentTargetTime, _Inout_ DrawingSurfaceSizeF* desiredRenderTargetSize)
{
return m_controller->PrepareResources(presentTargetTime, desiredRenderTargetSize);
}

HRESULT Direct3DContentProvider::Draw(_In_ ID3D11Device1* device, _In_ ID3D11DeviceContext1* context, _In_ ID3D11RenderTargetView* renderTargetView)
{
return m_controller->Draw(device, context, renderTargetView);
}

>

A quoi sert l'événement RequestAdditionalFrame dans ce code précédent?

Nous devons déclencher cet événement pour demander au moteur XAML de redessiner l'interface utilisateur dès que possible après la frame actuelle. L’appel à RequestAdditionalFrame peut être effectuée depuis n'importe où dans le composant (une fois l’appel à Connect effectuée et avant Disconnect)

10. Comme nous redirigeons les appels de Connect, Disconnect, PrepareResources et Draw vers Direct3DBackground (et également l’event delegate RequestAdditionalFrame), nous devons mettre à jour notre classe avec le code suivant.

OmegaCrusher.PhoneComponent.h

#pragma once

#include "pch.h"
#include <DrawingSurfaceNative.h>

namespace OmegaCrusher_PhoneComponent
{
public delegate void RequestAdditionalFrameHandler();

public ref class Direct3DBackground sealed
{
public:
Direct3DBackground();

Windows::Phone::Graphics::Interop::IDrawingSurfaceBackgroundContentProvider^ CreateContentProvider();
event RequestAdditionalFrameHandler^ RequestAdditionalFrame;

internal:
HRESULT Connect(_In_ IDrawingSurfaceRuntimeHostNative* host, _In_ ID3D11Device1* device);
void Disconnect();

HRESULT PrepareResources(_In_ const LARGE_INTEGER* presentTargetTime, _Inout_ DrawingSurfaceSizeF* desiredRenderTargetSize);
HRESULT Draw(_In_ ID3D11Device1* device, _In_ ID3D11DeviceContext1* context, _In_ ID3D11RenderTargetView* renderTargetView);
};
}

OmegaCrusher.PhoneComponent.cpp

// OmegaCrusher.PhoneComponent.cpp
#include "pch.h"
#include "OmegaCrusher.PhoneComponent.h"
#include "Direct3DContentProvider.h"

using namespace OmegaCrusher_PhoneComponent;
using namespace Platform;
using namespace Microsoft::WRL;
using namespace Windows::Phone::Graphics::Interop;

Direct3DBackground::Direct3DBackground()
{
}

IDrawingSurfaceBackgroundContentProvider^ Direct3DBackground::CreateContentProvider()
{
ComPtr<Direct3DContentProvider> provider = Make<Direct3DContentProvider>(this);
return reinterpret_cast<IDrawingSurfaceBackgroundContentProvider^>(provider.Get());
}

// Interface With Direct3DContentProvider
HRESULT Direct3DBackground::Connect(_In_ IDrawingSurfaceRuntimeHostNative* host, _In_ ID3D11Device1* device)
{
return S_OK;
}

void Direct3DBackground::Disconnect()
{
}

HRESULT Direct3DBackground::PrepareResources(_In_ const LARGE_INTEGER* presentTargetTime, _Inout_ DrawingSurfaceSizeF* desiredRenderTargetSize)
{
return S_OK;
}

HRESULT Direct3DBackground::Draw(_In_ ID3D11Device1* device, _In_ ID3D11DeviceContext1* context, _In_ ID3D11RenderTargetView* renderTargetView)
{
return S_OK;
}

La plomberie est presque terminée!! Il faut simplement mettre à jour notre code managé pour appeler la méthode SetBackgroundContentProvider comme ci-desous (dans MainPage.xaml.cs).

private void DrawingSurfaceBackground_Loaded(object sender, RoutedEventArgs e)
{
if (m_d3dBackground == null)
{
m_d3dBackground = new Direct3DBackground();

// Hook-up native component to DrawingSurfaceBackgroundGrid
DrawingSurfaceBackground.SetBackgroundContentProvider(m_d3dBackground.CreateContentProvider());
}
}

    
Nos premières lignes de code Direct3D

Maintenant que vous avez réussi à atteindre ce point ;), vous pouvez initialiser la vue cible (RenderTargetView) avec un magnifique bleu nuit 🙂 Mettez à jour la méthode Draw dans OmegaCrusher.PhoneComponent.cpp avec le code suivant.

HRESULT Direct3DBackground::Draw(_In_ ID3D11Device1* device, _In_ ID3D11DeviceContext1* context, _In_ ID3D11RenderTargetView* renderTargetView)
{
// Clear the render target and depth stencil to default values.
const float midnightBlue [] = { 0.098f, 0.098f, 0.439f, 1.000f };
context->ClearRenderTargetView(
renderTargetView,
midnightBlue
);

return S_OK;
}

Un peu long pour afficher un simple fond d’écran tout bleu 😉 mais on peut désormais jouer avec les API Direc3D !
 

5

Téléchargez le Code

Comments (3)

  1. Yannick says:

    Bonjour,

    Et merci pour ce tuto qui m'intéresse au plus haut point !

    En effet, je cherche depuis un bon moment à savoir s'il est possible de charger dans une application 3D des modèles issues de logiciels CAO en passant par les formats d'échanges usuels comme IGES ou STEP.

    Savez-vous s'il existe des bibliothèques permettant de faire cela STEP to Direct3D par exemple ?

    Je travaille avec le logiciel SolidWorks qui permet d'exporter une pièce 3D au format Microsoft XAML. Cependant, je ne sais pas comment exploiter ces données dans une application Direct3D.

    Auriez-vous des infos sur ces points ?

    Encore merci pour ces tutos...

  2. Merci Yannick. Le plus simple est probablement de trouver un moyen de convertir ton fichier STEP au format .FBX. Celui-ci à le mérite d'être interprétable directement par Visual Studio 2012/2013 et DirectX. Maintenant, si tu connais le format de ce fichier (liste des vertices, des indices, des coordonnées de textures ...) tu peux t'en sortir en remplissant manuellement les Index et Vertices buffers. Je prépare la suite des articles, j'espère éclairer ce point  😉

  3. Yannick says:

    Ok,

    merci pour les informations ...

    Disons que j'aimerais me passer d'une conversion de IGES ou STEP vers FBX car cela nécessite un logiciel comme 3DS Max ...

    J'aimerais bien lire directement des fichiers IGES et les convertir directement depuis mon application W8 ou WP8

    J'ai regardé pour utiliser Unity3D ou WaveEngine mais ils sont trop orienté jeu et surtout il manque le côté XAML ..

    Ce qui était vraiment bien avec XNA et Silverlight.

    Donc je suis de près vos tutoriels 😉

Skip to main content