II-10. Tutoriel 10 : Aller plus loin avec DXUT▲
II-10-a. Résumé▲
Ce tutoriel couvre les concepts avancés de DXUT. La plupart des fonctionnalités montrées dans ce tutoriel sont facultatives. Toutefois, elles peuvent servir à améliorer votre application à moindre coût. DXUT fournit un simple système GUI à base de sprites et une boîte de dialogue de réglage de device. De plus, il fournit quelques types de classes de caméra.
Dans ce tutoriel, vous créez un GUI pleinement fonctionnel pour modifier les réglages en utilisant le device et la scène. Il y aura des boutons, des barres de défilement et du texte pour démontrer ces capacités.
II-10-b. Source▲
(SDK root)\Samples\C++\Direct3D10\Tutorials\Tutorial10
II-10-c. Caméra DXUT▲
La classe CModelViewerCamera dans DXUT est fournie pour simplifier la gestion des transformations de vue et de projection. Elle fournit aussi des fonctionnalités GUI.
CModelViewerCamera g_Camera;
La première fonction que fournit la caméra est la création des matrices de vue et de projection. Avec la caméra, vous n'avez pas à vous soucier de ces matrices. Au lieu de cela, spécifiez l'emplacement de l'observateur, la vue elle-même et la taille de la fenêtre. Ensuite, passez ces paramètres à l'objet caméra, ce qui crée les matrices derrière la scène.
L'exemple suivant règle la portion de vue de la caméra. Cela inclut l'emplacement et la vue.
// Initialise la caméra
D3DXVECTOR3 Eye( 0.0
f, 0.0
f, -
800.0
f );
D3DXVECTOR3 At( 0.0
f, 0.0
f, 0.0
f );
g_Camera.SetViewParams( &
Eye, &
At );
Ensuite, spécifiez la portion de projection de la caméra. On a besoin de fournir l'angle de vue, le rapport hauteur/longueur et les plans de coupe avant et arrière de la pyramide tronquée de visualisation. Ce sont les mêmes informations dont on avait besoin dans les tutoriels précédents. Toutefois, dans ce tutoriel, on n'a pas besoin de se soucier de créer les matrices elles-mêmes.
// Initialisation des paramètres de projection de la caméra
float
fAspectRatio =
pBackBufferSurfaceDesc->
Width /
(FLOAT)pBackBufferSurfaceDesc->
Height;
g_Camera.SetProjParams( D3DX_PI/
4
, fAspectRatio, 0.1
f, 5000.0
f );
g_Camera.SetWindow( pBackBufferSurfaceDesc->
Width, pBackBufferSurfaceDesc->
Height );
La caméra crée aussi des masques pour le feedback simple de la souris. Ici, on spécifie trois boutons de souris pour l'utilisation des opérations à la souris qui sont fournies - rotation du modèle, zoom et rotation de la caméra. Essayez de compiler le projet et de jouer avec chaque bouton pour comprendre ce que fait chaque opération.
g_Camera.SetButtonMasks( MOUSE_LEFT_BUTTON, MOUSE_WHEEL, MOUSE_MIDDLE_BUTTON );
Lorsque les boutons sont configurés, la caméra surveilles les entrées à la souris et agit en conséquence. Pour réagir aux entrées de l'utilisateur, ajoutez une fonction de surveillance à la fonction de rappel MsgProc. C'est la fonction vers laquelle DXUT dirige les messages.
// Transmet tous les messages de fenêtre restants à la caméra pour qu'elle puisse réagir aux entrées d'utilisateur
g_Camera.HandleMessages( hWnd, uMsg, wParam, lParam );
Finalement, après que toutes les données nécessaires aient été entrées dans la caméra, on extrait les matrices effectives pour les transformations. On saisit la matrice de projection et la matrice de vue ensemble avec les fonctions associées. L'objet caméra est responsable du calcul des matrices elles-mêmes.
g_pProjectionVariable->
SetMatrix( (float
*
)g_Camera.GetProjMatrix() );
g_pViewVariable->
SetMatrix( (float
*
)g_Camera.GetViewMatrix() );
II-10-d. Boîtes de dialogue de DXUT▲
L'interaction avec l'utilisateur peut être réalisée grâce à la classe CDXUTDialog. Elle contient des commandes dans une boîte de dialogue qui accepte les entrées d'utilisateur et les transmet à l'application à traiter. D'abord, on instancie la classe de boîte de dialogue. Ensuite, on peut ajouter des commandes individuelles.
II-10-d-1. Déclarations▲
Dans ce tutoriel, deux boîtes de dialogue sont ajoutées. L'une s'appelle g_HUD, et contient le même code que les exemples Direct3D 10. L'autre s'appelle g_SampleUI et illustre des fonctions qui sont spécifiques à ce tutoriel. La seconde boîte de dialogue sert à contrôler le caractère "bouffi" du modèle. Elle règle une variable qui est transmise aux shaders.
CDXUTDialog g_HUD; // gère la 3D UI
CDXUTDialog g_SampleUI; // boîte de dialogue pour les commandes spécifiques de l'exemple
Les boîtes de dialogue sont contrôlées par une classe appelée CDXUTDialogResourceManager. Elle transfère les messages et gère des ressources qui sont partagées par les boîtes de dialogue.
CDXUTDialogResourceManager g_DialogResourceManager; // gestionnaire des ressources partagées par les boîtes de dialogue
Finalement, une nouvelle fonction de rappel est associée aux évènements qui sont traités par le GUI. Cette fonction sert à gérer l'interaction entre les commandes.
void
CALLBACK OnGUIEvent( UINT nEvent, int
nControlID, CDXUTControl*
pControl, void
*
pUserContext );
II-10-d-2. Initialisation des boîtes de dialogue▲
Parce que davantage d'usages ont été introduits et doivent être initialisés, ce tutoriel reporte l'initialisation de ces modules vers une fonction séparée appelée InitApp().
Les commandes pour chaque boîte de dialogue sont initialisées dans cette fonction. Chaque boîte de dialogue appelle sa fonction Init et passe au gestionnaire de ressources pour spécifier où les commandes doivent être placées. Elle règle aussi la fonction de rappel pour traiter les réponses GUI. Dans ce cas, la fonction de rappel associée est OnGUIEvent.
g_HUD.Init( &
g_DialogResourceManager );
g_SampleUI.Init( &
g_DialogResourceManager );
g_HUD.SetCallback( OnGUIEvent );
g_SampleUI.SetCallback( OnGUIEvent );
Après que chaque boîte de dialogue ait été initialisée, elle peut insérer les commandes à utiliser. HUD ajoute trois boutons à la fonctionnalité de base : touche plein écran, touche rendu de référence (logiciel), et modifier le device.
Pour ajouter un bouton, spécifiez l'identifiant IDC à utiliser, une chaîne à afficher, les coordonnées, la largeur et la longueur, et facultativement un raccourci clavier à associer au bouton.
Notez que les coordonnées sont relatives à l'ancre de la boîte de dialogue.
int
iY =
10
;
g_HUD.AddButton( IDC_TOGGLEFULLSCREEN, L"Toggle full screen"
, 35
, iY, 125
, 22
);
g_HUD.AddButton( IDC_TOGGLEREF, L"Toggle REF (F3)"
, 35
, iY +=
24
, 125
, 22
);
g_HUD.AddButton( IDC_CHANGEDEVICE, L"Change device (F2)"
, 35
, iY +=
24
, 125
, 22
, VK_F2 );
De façon similaire, pour l'interface utilisateur de l'exemple, trois commandes sont ajoutées - un texte statique, une barre de défilement et une case à cocher.
Les paramètres du texte statique sont l'identifiant IDC, la chaîne, les coordonnées, la largeur et la hauteur.
Les paramètres de la barre de défilement sont l'identifiant IDC, les coordonnées, la largeur et la hauteur, puis les valeurs min et max de la barre de défilement, enfin la variable où stocker le résultat.
Les paramètres de la case à cocher sont l'identifiant IDC, une étiquette de chaîne, les coordonnées, la largeur et la hauteur, et la variable booléenne où stocker le résultat.
iY =
10
;
WCHAR sz[100
];
iY +=
24
;
StringCchPrintf( sz, 100
, L"Puffiness: %0.2f"
, g_fModelPuffiness );
g_SampleUI.AddStatic( IDC_PUFF_STATIC, sz, 35
, iY +=
24
, 125
, 22
);
g_SampleUI.AddSlider( IDC_PUFF_SCALE, 50
, iY +=
24
, 100
, 22
, 0
, 2000
, (int
)(g_fModelPuffiness*
100.0
f) );
iY +=
24
;
g_SampleUI.AddCheckBox( IDC_TOGGLESPIN, L"Toggle Spinning"
, 35
, iY +=
24
, 125
, 22
, g_bSpinning );
Lorsque les boîtes de dialogue ont été initialisées, il faut les placer à l'écran. Cela se fait en appelant OnD3D10ResizedSwapChain, parce que les coordonnées d'écran peuvent changer chaque fois que la swap chain est recréée - par exemple si l'on redimensionne la fenêtre.
g_HUD.SetLocation( pBackBufferSurfaceDesc->
Width-
170
, 0
);
g_HUD.SetSize( 170
, 170
);
g_SampleUI.SetLocation( pBackBufferSurfaceDesc->
Width-
170
, pBackBufferSurfaceDesc->
Height-
300
);
g_SampleUI.SetSize( 170
, 300
);
Finalement, les boîtes de dialogue doivent être identifiées dans la fonction OnD3D10FrameRender. Cela permet qu'elles soient dessinées afin que l'utilisateur puisse effectivement les voir.
//
// Rendu de l'interface utilisateur
//
g_HUD.OnRender( fElapsedTime );
g_SampleUI.OnRender( fElapsedTime );
II-10-e. Initialisation du Gestionnaire de Ressources▲
Le gestionnaire de ressources doit être initialisé à chaque rappel associé à une initialisation et une destruction. Ceci parce que le GUI doit être recréé chaque fois qu'un device est créé ou qu'une swap chain est recréée. La classe CDXUTDialogResourceManager contient des fonctions qui correspondent à chaque rappel. Chaque fonction a le même nom que son rappel correspondant. Tout ce qu'on doit faire, c'est insérer le code pour appeler chacun d'entre eux aux bons endroits.
V_RETURN( g_DialogResourceManager.OnD3D10CreateDevice( pd3dDevice ) );
V_RETURN( g_DialogResourceManager.OnD3D10ResizedSwapChain( pd3dDevice, pBackBufferSurfaceDesc ) );
g_DialogResourceManager.OnD3D10ReleasingSwapChain();
g_DialogResourceManager.OnD3D10DestroyDevice();
II-10-e-1. ▲
Après que tout ait été initialisé, on peut écrire du code pour traiter les interactions GUI. Pendant l'initialisation des boîtes de dialogue, on règle la fonction de rappel sur OnGUIEvent. On peut maintenant créer la fonction OnGUIEvent qui surveille les évènements liés au GUI puis les traite (le GUI est invoqué par l'environnement cadre).
OnGUIEvent est une fonction simple qui contient une instruction de cas pour chaque identifiant IDC détecté quand les boîtes de dialogue ont été créées. Chaque instruction de cas contient le code de gestion d'évènement, en présumant que l'utilisateur interagisse avec la commande. Le code ici est très similaire au code Win32 qui gère les commandes.
Les commandes liées aux fonctions d'appel HUD sont incorporées dans DXUT. Il existe une fonction DXUT pour alterner entre les modes plein écran et fenêtré, pour activer et désactiver le moteur de rendu du logiciel de référence et pour modifier les réglages du device.
La boîte de dialogue SampleUI contient un code personnalisé pour manipuler les variables associées à la barre de défilement. Elle collecte la valeur, actualise le texte associé et transfère la valeur au shader.
void
CALLBACK OnGUIEvent( UINT nEvent, int
nControlID, CDXUTControl*
pControl, void
*
pUserContext )
{
switch
( nControlID )
{
case
IDC_TOGGLEFULLSCREEN: DXUTToggleFullScreen(); break
;
case
IDC_TOGGLEREF: DXUTToggleREF(); break
;
case
IDC_CHANGEDEVICE: g_D3DSettingsDlg.SetActive( !
g_D3DSettingsDlg.IsActive() ); break
;
case
IDC_TOGGLESPIN:
{
g_bSpinning =
g_SampleUI.GetCheckBox( IDC_TOGGLESPIN )->
GetChecked();
break
;
}
case
IDC_PUFF_SCALE:
{
g_fModelPuffiness =
(float
) (g_SampleUI.GetSlider( IDC_PUFF_SCALE )->
GetValue() *
0.01
f);
WCHAR sz[100
];
StringCchPrintf( sz, 100
, L"Puffiness: %0.2f"
, g_fModelPuffiness );
g_SampleUI.GetStatic( IDC_PUFF_STATIC )->
SetText( sz );
g_pPuffiness->
SetFloat( g_fModelPuffiness );
break
;
}
}
}
II-10-e-2. Actualisation du Traitement de Message▲
A présent, nous avons des messages de boîte de dialogue et des interactions d'utilisateur. Les messages qui sont transmis à l'application doivent être traités par les boîtes de dialogue. Le code correspondant est traité dans la fonction de rappel MsgProc qui est fournie par DXUT. Dans les tutoriels précédents, cette section est vide parce qu'il n'y a pas de message à traiter. A présent, on doit s'assurer que les messages destinés au gestionnaire de ressources et aux boîtes de dialogue sont convenablement acheminés.
Aucun code de traitement de message spécial n'est requis. On se contente d'appeler les MsgProcs pour chaque boîte de dialogue pour s'assurer que le message est traité. On le fait en appelant la fonction MsgProc qui correspond à chaque classe. Notez que la fonction fournit un registre pour notifier à l'environnement cadre qu'aucun autre traitement du message n'est nécessaire, et donc que l'environnement cadre peut se fermer.
LRESULT CALLBACK MsgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, bool
*
pbNoFurtherProcessing, void
*
pUserContext )
{
// Toujours permettre aux appels du gestionnaire de ressources de boîte de dialogue de gérer les messages globaux
// ainsi l'état GUI sera actualisé correctement
*
pbNoFurtherProcessing =
g_DialogResourceManager.MsgProc( hWnd, uMsg, wParam, lParam );
if
( *
pbNoFurtherProcessing )
return
0
;
if
( g_D3DSettingsDlg.IsActive() )
{
g_D3DSettingsDlg.MsgProc( hWnd, uMsg, wParam, lParam );
return
0
;
}
// Permettez aux boîtes de dialogue de traiter le message en premier
*
pbNoFurtherProcessing =
g_HUD.MsgProc( hWnd, uMsg, wParam, lParam );
if
( *
pbNoFurtherProcessing )
return
0
;
*
pbNoFurtherProcessing =
g_SampleUI.MsgProc( hWnd, uMsg, wParam, lParam );
if
( *
pbNoFurtherProcessing )
return
0
;
if
( uMsg ==
WM_CHAR &&
wParam ==
'1'
)
DXUTToggleFullScreen();
return
0
;
}
II-10-f. Boîte de Dialogue 3D Settings▲
Il existe une boîte de dialogue spéciale incorporée qui contrôle les réglages du device Direct3D. Cette boîte de dialogue est fournie par DXUT sous le nom CD3DSettingsDlg. Elle fonctionne comme une boîte de dialogue personnalisée, mais elle fournit toutes les options dont les utilisateurs ont besoin pour modifier les réglages.
CD3DSettingsDlg g_D3DSettingsDlg; // Boîte de dialogue de réglage du device
L'initialisation ressemble beaucoup à celle d'autres boîtes de dialogue. Il suffit d'appeler la fonction Init. Toutefois, chaque fois que Direct3D modifie sa swap chain ou son device, la boîte de dialogue aussi doit être actualisée. Par conséquent, il faut un appel convenablement nommé dans OnD3D10CreateDevice et OnD3D10ResizedSwapChain. De même, les modifications aux objets détruits doivent aussi être notifiées. Par conséquent, on a besoin des appels appropriés dans OnD3D10DestroyDevice.
g_D3DSettingsDlg.Init( &
g_DialogResourceManager );
V_RETURN( g_D3DSettingsDlg.OnD3D10CreateDevice( pd3dDevice ) );
V_RETURN( g_D3DSettingsDlg.OnD3D10ResizedSwapChain( pd3dDevice, pBackBufferSurfaceDesc ) );
g_D3DSettingsDlg.OnD3D10DestroyDevice();
Pour ce qui est du rendu, pour faire alterner l'état d'apparence de la boîte de dialogue, on utilise un registre appelé IsActive(). Si ce registre est réglé sur false, le panneau n'est pas rendu. L'alternance d'affichage du panneau est gérée par la boîte de dialogue HUD. Le IDC_CHANGEDEVICE qui est associé au HUD contrôle ce registre.
if
( g_D3DSettingsDlg.IsActive() )
{
g_D3DSettingsDlg.MsgProc( hWnd, uMsg, wParam, lParam );
return
0
;
}
Lorsque les étapes d'initialisation sont terminées, vous pouvez inclure la boîte de dialogue dans votre application. Essayez de compiler le tutoriel et d'interagir avec le panneau Change Settings pour voir ses effets. La reconstruction du device Direct3D ou de la swap chain est faite en interne par DXUT.
II-10-g. Rendu de Texte▲
Une application n'est pas très intéressante si l'utilisateur n'a aucune idée de ce qu'il doit faire. DXUT inclut une classe utilitaire pour dessiner du texte 2D à l'écran, pour le retour d'action de l'utilisateur. Cette classe, CDXUTD3D10TextHelper, vous permet de tracer des lignes de texte n'importe où à l'écran en utilisant de simples entrées de chaînes. D'abord, on instancie la classe. Puisque le rendu de texte peut être isolé de la plupart des procédures d'initialisation, on garde l'essentiel du code dans RenderText10.
CDXUTD3D10TextHelper txtHelper( g_pFont, g_pSprite, 15
);
II-10-g-1. Initialisation▲
Le premier paramètre transmis est la fonte à tracer. Cette fonte est du type ID3DXFont qui est fourni par D3DX. Pour initialiser la fonte, appelez D3DX10CreateFont et transférez le device, la hauteur, la largeur, la nuance, les niveaux mipmap (généralement 1), l'italique, le jeu de caractères, la précision, la qualité, la chasse et la famille, le nom de la fonte et le pointeur de l'objet. Seuls les quatre premiers et les deux derniers éléments de cette liste sont vraiment importants.
V_RETURN( D3DX10CreateFont( pd3dDevice, 15
, 0
, FW_BOLD, 1
, FALSE, DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH |
FF_DONTCARE,
L"Arial"
, &
g_pFont ) );
Le deuxième paramètre exige que l'on initialise une classe ID3DXSprite. Pour le faire, on appelle D3DX10CreateSprite. Les seules choses que la fonction exige comme paramètres sont le device, le nombre maximum de sprites dessinés sur une trame et le pointeur de l'objet.
// Initialise le sprite
V_RETURN( D3DX10CreateSprite( pd3dDevice, MAX_SPRITES, &
g_pSprite ) );
Comme pour tout autre objet, la fonte et le sprite doivent être détruits après que l'on en ait fini avec eux. On peut le faire en utilisant la macro SAFE_RELEASE.
SAFE_RELEASE( g_pFont );
SAFE_RELEASE( g_pSprite );
II-10-g-2. Rendu▲
Le texte dans cet exemple inclut des statistiques sur le rendu. Il y a aussi une section d'aide qui explique comment manipuler le modèle en utilisant la souris.
Les appels de rendu doivent être faits dans OnD3D10FrameRender. Ici, on appelle RenderText10 dans le cadre de l'appel de rendu de trame.
La première section est toujours rendue la première. Le premier appel à rendu de texte est Begin(). Cela notifie à l'appareil de commencer à envoyer du texte à l'écran. Ensuite, on règle la position du curseur et la couleur du texte. A présent, on peut tracer.
La sortie de la chaîne de texte s'effectue en appelant DrawTextLine. La transmission de la chaîne et la sortie qui correspond à la chaîne sont assurées à la position en cours. Le curseur est incrémenté pendant l'écriture du texte. Par exemple, si la chaîne contient "\n", le curseur est automatiquement déplacé à la ligne suivante.
// Statistiques de sortie
txtHelper.Begin();
txtHelper.SetInsertionPos( 2
, 0
);
txtHelper.SetForegroundColor( D3DXCOLOR( 1.0
f, 1.0
f, 0.0
f, 1.0
f ) );
txtHelper.DrawTextLine( DXUTGetFrameStats() );
txtHelper.DrawTextLine( DXUTGetDeviceStats() );
Il existe une autre méthode pour assurer la sortie de texte, qui est similaire à printf. Formatez la chaîne en utilisant des caractères spéciaux puis insérez des variables dans la chaîne. Utilisez DrawFormattedTextLine à cette fin.
txtHelper.SetForegroundColor( D3DXCOLOR( 1.0
f, 1.0
f, 1.0
f, 1.0
f ) );
txtHelper.DrawFormattedTextLine( L"fTime: %0.1f sin(fTime): %0.4f"
, fTime, sin(fTime) );
Puisque le texte d'aide est tracé de la même manière, on n'a pas besoin de passer en revue son code.
Vous pouvez repositionnez le pointeur à tout moment en appelant SetInsertionPos.
Quand vous êtes satisfait de la sortie de texte, appelez End() pour en notifier le device.