Ceci est le début d’un ensemble d’articles concernant l’utilisation de DirectX 11 pour le rendu de jeux vidéo 3D ou même 2D.
Dans cet article en particulier nous allons voir les fondements de notre programme avec la création d’une fenêtre simple avec Win32. C’est dans celle-ci qu’évoluera pas la suite notre contexte Direct3D (qui correspond au module 3D de DirectX) et donc notre rendu.
Tout d’abord, nous allons voir en introduction à quoi correspond DirectX, ce qu’il permet de faire.
DirectX quésaco ?
Description
Description Wikipédia :
Microsoft DirectX est une collection de bibliothèques destinées à la programmation d’applications multimédia, plus particulièrement de jeux ou de programmes faisant intervenir de la vidéo, sur les plates-formes Microsoft (Xbox, systèmes d’exploitation Windows).
Souvent par abus de langage l’on cite DirectX pour le rendu graphique uniquement et parfois même comparé à OpenGL/Vulkan. Ceci est faux, car DirectX est un framework, c’est-à-dire un cadre de travail composé d’une multitude de module, chacun spécifique à un besoin.
Nous allons voir une liste des modules DirectX que vous pouvez utiliser, je ne citerais ici que les modules non obsolète, c’est-à-dire encore maintenu par Microsoft.
- Direct3D (D3D) : permets le rendu graphique en 3 dimensions ainsi qu’en 2 dimensions. Ceci est le module le plus conséquent sur lequel vous passerez le plus clair de votre temps.
- Direct2D : destiné aux graphismes en 2 dimensions, simplifie grandement le code par rapport à direct3D. Nous ne l’utiliserons pas dans les articles concernant DirectX.
- DirectWrite : permets l’affichage de police (fonts) native sur un contexte Direct3D/Direct2D.
- XInput : gère les périphériques de type controller, c’est-à-dire manette de jeu Xbox One, 360 et constructeur respectant les normes (dualshock de la PlayStation 4 par exemple).
- XAudio2 : remplace DirectSound, permets de jouer du son/musique et propose le mixage complexe avec différents canaux.
Je ne cite pas dans cette liste Direct Input, car même si très connu avec beaucoup de tutoriel le concernant il n’a pas été mis à jour depuis plusieurs années et Microsoft déconseille son utilisation. Nous verrons que Win32 nous propose déjà tout ce qu’il faut.
Installation
Pour commencer il nous faut un IDE (Environnement de développement) digne de ce nom. Personnellement je vais utiliser Visual Studio Community, qui est gratuit, très complet et propose une très bonne intégration de DirectX ainsi que de Win32.
Téléchargement de Visual Studio Community
Après de longues heures d’attente concernant l’installation de Visual Studio (cela peut dépendre de votre connexion) il vous faut maintenant le SDK DirectX contenant les DLL/Lib et .h de DirectX.
Comme je suis d’humeur sympathique voilà le lien : DirectX Software Development Kit
Hein ? Mais il date de 2010 ton Kit !
Alors oui, il faut savoir que depuis l’arrivée de Windows 8 et les applications de type “metro” et maintenant sur Windows 10 avec les applications Universelles, Microsoft ne met plus à jour le kit DirectX et intègre cela directement dans le SDK Windows. Malheureusement pour nous cela ne fonctionne pas pour les applications Win32, par conséquent nous devons installer l’ancien Kit. Cependant ne vous inquiétez pas, celui-ci ne contient juste pas DirectX 11.1/11.2 et DirectX 12 qui sont dédiés à Windows 8/8.1 et Windows 10. De plus avec le Kit vous retrouverez des tutoriels très spécifiques en anglais ainsi que des outils supplémentaires comme pour le mixage, la compression d’image.
Fondement du projet : système de fenêtrage
Création du projet sur Visual Studio
Nous allons devoir créer un projet du type “Win32 Project”. J’utilise personnellement Visual Studio Community en anglais, par conséquent la démarche que je vais vous décrire le sera de même. Cependant, la manipulation est la même, il suffit en général de traduire bêtement mot par mot.
Sur la page d’accueil de Visual Studio :
- “New Project”
- Une nouvelle fenêtre fait son apparition, sélectionner “Visual C++” puis “Win32”.
- Choisissez “Win32 Project”. Ajouté un nom ainsi que la localisation de votre projet puis appuyer sur “ok”.
Configurer votre projet comme sur l’image ci-dessous :
Le code
Maintenant il nous faut définir le point d’entrée de notre programme. Certains d’entre vous serez tenté de faire un fichier main.cpp. Et bien non, Win32 propose/impose une autre approche concernant le point d’entrée. Notre fichier se nommera donc WinMain.cpp. Je vous laisse créer le fichier puis glisser y ce code (je vais vous l’expliquer en détail par la suite).
/////////////////////////////INCLUDE/////////////////////////
#include <Windows.h>
#include <tchar.h>
#include <iostream>
/////////////////////////////////////////////////////////////
HWND hWnd;
LRESULT CALLBACK wndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_KEYDOWN:
if (wParam == VK_ESCAPE)
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
break;
}
}
bool InitWindow(HINSTANCE hInstance, int nCmdShow)
{
//Window Size
int windowHeight = 600;
int windowWidth = 600;
//Init Window
WNDCLASSEX wndStruct;
ZeroMemory(&wndStruct, sizeof(WNDCLASSEX));
wndStruct.cbSize = sizeof(WNDCLASSEX);
wndStruct.style = CS_HREDRAW | CS_VREDRAW;
wndStruct.lpfnWndProc = (WNDPROC)wndProc;
wndStruct.hInstance = hInstance;
wndStruct.hCursor = LoadCursor(NULL, IDC_ARROW);
wndStruct.hbrBackground = (HBRUSH)COLOR_WINDOW;
wndStruct.lpszClassName = TEXT("Window");
RegisterClassEx(&wndStruct);
hWnd = CreateWindow(_T("Window"),
_T("Nom de votre fenêtre"),
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
windowWidth,
windowHeight,
NULL,
NULL,
hInstance,
NULL);
if (!hWnd)
return false;
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return true;
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
//Init Win32 Window
if (!InitWindow(hInstance, nCmdShow))
return 0;
bool run = true;
MSG msg = { 0 };
while (WM_QUIT != msg.message && run)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
//DirectX Render
}
}
return (int)msg.wParam;
}
Si vous compilez vous verrez apparaître une fenêtre Windows simple et redimensionnement. Bravo ! Voilà votre première fenêtre Win32 ! Maintenant je vais vous expliquer le code en détail, pour cela je vais le faire de façon analytique, c’est-à-dire de haut en bas.
Je ne vais pas détailler plus que cela les include. Je vais juste faire un petit point sur HWND hWnd;
. Il correspond, en quelque sorte, à un lien avec notre fenêtre. Il est donc important d’y avoir accès simplement dans notre WinMain pour cela que je déclare donc ici.
wndProc
Cette fonction est très simple. Elle est appelée automatiquement lorsqu’un événement a lieu. C’est-à-dire lorsque Windows détecte la pression d’une touche du clavier par exemple. Je ne la détaille pas plus car nous nous en servirons plus tard dans notre programme. Lors de la gestion des entrées utilisateurs.
InitWindow
Comme son nom l’indique c’est ici que nous allons initialiser notre fenêtre et lui donner des paramètres qui vont régir son comportement par la suite. La fonction retourne true
si tout s’est bien passé et false
dans le cas contraire, jusqu’ici rien d’extraordinaire.
//Init Window
WNDCLASSEX wndStruct;
ZeroMemory(&wndStruct, sizeof(WNDCLASSEX));
wndStruct.cbSize = sizeof(WNDCLASSEX);
wndStruct.style = CS_HREDRAW | CS_VREDRAW;
wndStruct.lpfnWndProc = (WNDPROC)wndProc;
wndStruct.hInstance = hInstance;
wndStruct.hCursor = LoadCursor(NULL, IDC_ARROW);
wndStruct.hbrBackground = (HBRUSH)COLOR_WINDOW;
wndStruct.lpszClassName = TEXT("Window");
RegisterClassEx(&wndStruct);
WNDCLASSEX wndStruct;
Ceci est une structure qui contient les différents paramètres de notre fenêtre, ce qui régira son comportement par la suite. Le EX indique ici que nous utilisons la version étendue, de l’anglais “extended”. Cela permet de simplifier la structure et donc la création de notre fenêtre.
ZeroMemory(&wndStruct, sizeof(WNDCLASSEX));
Vous retrouverez régulièrement cette ligne de code. Elle permet simplement de mettre à NULL un espace de mémoire d’une certaine taille. Le premier paramètre est un pointeur vers l’objet et le second correspond à sa taille dans la mémoire.
wndStruct.cbSize = sizeof(WNDCLASSEX);
Rien d’extraordinaire, l’on donne juste la taille de notre structure WNDCLASSEX
via un sizeof.
wndStruct.style = CS_HREDRAW | CS_VREDRAW;
Ici nous allons stocker le style de notre fenêtre. Vous retrouvez une liste complète ici. CS_HREDRAW
permet d’indiquer à Windows qu’il doit réafficher le contenu de la fenêtre après un mouvement ou changement de taille de la fenêtre en largeur. De même pour CS_VREDRAW
mais pour la hauteur cette fois-ci.
wndStruct.lpfnWndProc = (WNDPROC)wndProc;
Cette ligne de code indique que les événements Windows seront envoyés à notre fonction wndProc
pour être ensuite traité par notre programme.
wndStruct.hInstance = hInstance;
Ici nous copions l’en-tête de notre application, notre instance.
wndStruct.hCursor = LoadCursor(NULL, IDC_ARROW);
Nous définissions un type pour représenter la souris de l’utilisateur. Dans le cas actuel nous donnons simplement la forme par défaut. Il faut savoir que cela s’applique uniquement lorsque la souris de l’utilisateur est dans la fenêtre. En dehors c’est le système qui s’en occupe pour nous.
wndStruct.hbrBackground = (HBRUSH)COLOR_WINDOW;
Nous définissons un background, une couleur de fond pour notre fenêtre, ici par défaut se sera blanc.
wndStruct.lpszClassName = TEXT(“Window”);
Ici nous donnons un ID à notre fenêtre, nous l’utiliserons plus tard dans notre programme. La ligne que nous allons voir après générera donc en quelque sorte une classe qui possédera le nom que nous avons indiqué.
RegisterClassEx(&wndStruct);
Comme le nom l’indique nous enregistrons donc une classe avec nos paramètres définis ultérieurement.
Maintenant nous allons devoir créer notre fenêtre pour qu’elle s’affiche, voilà le code correspondant.
hWnd = CreateWindow(_T("Window"), //Nom de la classe de la fenêtre, nous l'avons défini précédemment
_T("Nom de votre fenêtre"), //Nom visible de la fenêtre
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_OVERLAPPEDWINDOW, //Style de la fenêtre
CW_USEDEFAULT, //Position x de la fenêtre sur l'écran, ici je laisse Windows s'en charger
CW_USEDEFAULT, //Position y de la fenêtre sur l'écran
windowWidth, //Largeur de la fenêtre
windowHeight, //Hauteur de la fenêtre
NULL, //Fenêtre parent, ici il n'y en a pas
NULL, //Ajout d'un menu, ici nous ne l'utilisons pas
hInstance, //En-tête de l'instance de l’application
NULL); //Si utilisation de plusieurs fenêtres
if (!hWnd)
return false;
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
J’ai fait une description des paramètres dans le code, cela est plus simple et plus agréable pour vous. Je vais juste revenir sur le style de la fenêtre, cela permet de définir son comportement (redimensionnement ou non, les boutons accessibles comme la croix, plein écran, etc…), son style (bordure, etc…). Vous retrouverez une liste ici.
Par la suite nous indiquons simplement que nous souhaitons que la fenêtre s’affiche ShowWindow(hWnd, nCmdShow);
, pour cela nous donnons simplement en paramètre les informations nécessaires.
####WinMain Maintenant nous allons voir le WinMain, c’est par ici que démarrera notre programme lorsqu’il sera exécuté. Les paramètres d’appel de la fonction sont automatiquement donnés par Windows, comme c’est le cas avec une main
traditionnel.
//Init Win32 Window
if (!InitWindow(hInstance, nCmdShow))
return 0;
bool run = true;
MSG msg = { 0 };
Pour commencer nous faisons appel à la fonction InitWindow que nous avons vue précédemment. Celle-ci va initialiser les paramètres de notre fenêtre et l’afficher. Si tous se passent bien nous déclarons un booléen qui pourra être modifiable dans le futur pour annoncer la fin du programme. De plus nous créons une variable du type MSG qui se chargera de récupérer les messages provenant de Windows.
while (WM_QUIT != msg.message && run)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
//DirectX Render
}
}
return (int)msg.wParam;
Nous retrouvons ici une boucle qui s’exécutera indéfiniment durant l’exécution de notre jeu et de notre fenêtre. Nous indiquons aussi que l’action sur la croix de fermeture de la fenêtre ferme bien le programme. Dans cette boucle nous récupérons le message s’il y en existe un, le translate le traduit dans le bon format et le dispatch s’occupe de remonter l’événement à notre fonction wndProc.
Et bien voilà pour cette première partie ! Vous savez maintenant comment créer une fenêtre Win32 pour votre futur contexte Direct3D. Si vous compilez vous devez normalement avoir cela.
Merci d’avoir suivi cet article ! Je rappelle que le code source ainsi que l’article en lui-même est libre de droits, selon les indications ci-dessous.
Téléchargement du projet Visual Studio Community
Téléchargement du code source (sans projet)