Les principales étapes de rendu de DirectX 11

Intro :

Les étapes de la chaîne de rendu de DirectX 11 sont les mêmes que celles de DirectX 10.1, sauf qu’il y a été ajouté le support des nouvelles fonctionnalités de tessellation.

Prérequis :

– Connaître les étapes de la chaîne de rendu de DirectX 10.1

Explications :

Voici un schéma qui les résume :

pipeline

Seuls les étapes Hull Shader et Domain Shader sont programmables ; l’étape Tessellator est rigidement fixée par le GPU.

Étape Hull Shader :

 

Étape Tessellator Shader :

Une étape de la chaîne du rendu graphique non-programmable qui s’occupe de subdiviser un « domain » (un une face, un triangle, une ligne) en morceaux plus petits.

Étape Domain Shader :

Un programme shader qui calcule la position du vertex d’un point subdivisé par « l’output patch ».

Résumé :

Nous avons présenté les nouvelles étapes de la « pipeline » du rendu graphique de DirectX 11 concernant la tessellation.

Références :

– DirectX SDK 2010

La tessellation – Théorie (DirectX 11)

tessellation

Intro :

Le principe de la tessellation est simple : on transforme un polygone et on le décompose en morceaux plus petits.

Prérequis :

– Savoir comment fonctionne un programme HLSL

– Savoir comment utiliser DirectX 11

Explications :

Si on prend un carré et qu’on le coupe en deux le long de sa diagonale, on opère une « tessellation » en deux triangles.

Dans DirectX 11, la tessellation est opérée directement par le GPU (c’est-à-dire par le processeur de la carte graphique).

Elle peut être utilisée dans le rendu en faisant du « Displacement Mapping ». Au lieu de précharger, dans la mémoire vidéo, un modèle très détaillé : on peut augmenter ses détails par le GPU ; en effet le Displacement Mapping va de paire avec la tessellation.

Par exemple au lieu d’implémenter une grille plate très détaillée, à la place, on peut augmenter les détails d’un polygone carré par le procédé de tessellation.

En conséquence et d’autre part, il devient possible de faire varier progressivement le niveau de détail géométrique des modèles.

Les avantages :

– Réduit la mémoire et la bande passante utilisées

– Permet de complexifier des modèles basse résolution en des modèles très détaillés et ceci à la volée

– Déplace le calcul classique de modification géométrique effectué par le CPU vers un calcul effectué par le GPU

– Facilite le LOD (Level of Detail) des objets distants. En effet la tessellation permet de faire varier dynamiquement le niveau de détail d’un modèle

– On peut effectuer le calcul physique de collision ou d’animation avec un modèle non détaillé en mémoire centrale tout en affichant un modèle très détaillé au rendu

– Possibilité d’affichage de meilleurs modèles détaillés

Trois nouvelles étapes de shader :

dx11_pipeline

DirectX 11 implémente la tessellation par 3 nouvelles étapes de chaîne de rendu : l’étape Hull Shader, l’étape Tessellator et l’étape Domain.

Ces trois étapes suive l’étape Vertex Shader et précède l’étape Geometry Shader.

Avant tout, définissons quelques concepts liés à la tessellation par DirectX 11 :

Patch :
Control Point :
Tessellation factor :
Input primitive :

Lorsque ces deux étapes (Hull Shader & Domain Shader) sont définis dans un fichier shader « .fx », l’étape Vertex Shader ne renvoie pas spécialement un vertex mais renvoie ce qu’on appel un « control point » qui représente le vertex d’un triangle ou d’une face. Ce sera la dernière étape de la « Pipeline » qui se chargera de renvoyer le vertex au Pixel Shader.

Hull Shader :

Est invoqué une fois par « control point » et renvoie des « tessellator factors ». Cette étape est programmable. Cette étape de shader permet de faire savoir à l’étape suivante Tesselator comment opérer la tesselation sur les données d’entrées.

TesselatorLevel

Tessellator Stage :

Génère de nouvelles vertices. Plus les « tessellator factors » reçues sont élevés plus il y a de triangles créés. Cette étape est non-programmable.

Domain Shader :

Cette étape est programmable. Est exécutée une fois par vertex. Convertis et fait correspondre les données (u, v) vers les données d’attributs (x, y, z, w).

Exemples :

1) Un carré subissant une « tessellation »

tesselator

 2) Puis utilisation d’une « displacement map »

tesselator_2

3) Enfin utilisation d’une texture et bump mapping

Tessellation3

Résumé :

Références :

– http://www.nvidia.fr/object/tessellation_fr.html

– https://msdn.microsoft.com/en-us/library/windows/desktop/ff476340%28v=vs.85%29.aspx

– http://www.rastertek.com/dx11tut38.html

– http://richardssoftware.net/Home/Post/28

– 3D Game Programming with DirectX 11 – Franck D. Luna

Le langage shader HLSL

shader

Intro :

Le HLSL signifie : High Level Shading Language. C’est un langage (qui a la même syntaxe que le C) qui permet de programmer le processeur de la carte graphique afin d’effectuer des algorithmes de rendus spécifiques.

Prérequis :

– Savoir configurer DirectX 10

Explications :

Le HLSL permet de générer la diffusion de la lumière, la texture à utiliser, les réflexions et réfractions, l’ombrage, le déplacement de primitives et les effets post-traitement.

C’est un outil très puissant qui permet de créer une vaste gamme d’effets graphiques et esthétiques pour les jeux modernes.

Avant l’existence du langage HLSL il fallait écrire les instructions en langage d’assembleur pour la carte graphique.

Un programme HLSL (en DirectX 11) est constitué par 5 étapes de chaîne rendu :

– Le vertex shader
– Le compute shader
– Le geometry shader
– Le tessellation shader
– Le domain shader
– Le pixel shader

Les vertex et pixel shaders doivent être obligatoirement programmés ; les autres ne doivent pas être nécessairement être programmés.

Le vertex shader :

Le vertex shader opère les transformations de repère, le skinning (l’animation squelettale), le déplacement de vertex et le calcul d’attributs de vertex pour les materials.

Le pixel shader :

Le pixel shader opère le blending de texturage, le calcul lumineux, etc…

Exemple :

Voici quelques fonctions intrinsèques utilisables dans le HLSL :

abs(x) : valeur absolue
ceil(x) : retourne le plus petit entier qui est égale ou supérieur à x
clamp(x, min, max) : retourne x dans l’intervalle [min, max]
cos(x) : retourne le cosinus de x
clip(x) : ignore le pixel en cours si x est inférieur à 0
cross(x, y) : retourne le produit vectoriel de deux vecteurs 3D
dot(x, y) : retourne le produit scalaire de deux vecteurs
length(v) : retourne la longueur du vecteur v
lerp(x, y, s) : retourne x + s(y – x)
normalize(x) : retourne un vecteur normalisé
max(x, y) : retourne le plus grand entier entre x et y
min(x, y) : retourne le plus petit entier entre x et y
mul(x, y) : retourne la multiplication des matrices x et y
reflect(i, n) : retourne un vecteur de reflection en utilisant le rayon d’insidence et la normale à la surface
refract(i, n, η) : retourne un vecteur de refraction en utilisant un rayon d’entrée, la normale à la surface, et un index de refraction
saturate(x) : clamp entre les valeurs 0 et 1
sqrt(x) : retourne la racine carrée de x
transpose(x) : retourne la matrice transposée de x
tex2D(s, t) : retourne une texture samplée

 


 

Les variables globales se déclarent et se définissent ainsi :

static const float2 bodyTexSize = {512, 512};
static const float2 faceLowerTexSize = {256, 128};
static const float2 faceUpperTexSize = {256, 64};

 

Voici un petit exemple d’un programme écrit en HLSL qui créé un effet « bois » procédural en tant que texture.

matrix WorldViewProjMatrix;
matrix TextureMatrix;

struct VS_OUTPUT
{
	float4 Pos : POSITION;
	float3 Pshade : TEXCOORD0;
};

VS_OUTPUT main (float4 vPosition : POSITION)
{
	VS_OUTPUT Out = (VS_OUTPUT) 0; 

	// Transforme la position du vertex vers l'espace 2D de l'écran
	Out.Pos = mul (WorldViewProjectionMatrix, vPosition);

	// Transforme les coordonnées de la texture
	Out.Pshade = mul (texture_matrix0, vPosition);

	return Out;
}

Comment créer un effet shader à parti d’un fichier .fx :

HRESULT D3DX10CreateEffectFromFile(
  _In_        LPCTSTR            pFileName, // Nom du fichier shader .fx
  _In_  const D3D10_SHADER_MACRO *pDefines, // Liste de macros  (peut être mis à nullptr)
  _In_        ID3D10Include      *pInclude, // Inteface d'include (peut être mis à nullptr)
  _In_        LPCSTR             pProfile, // Spécifie le profile ; ex : "fx_4_0"
  _In_        UINT               HLSLFlags, // Options du compilateur
  _In_        UINT               FXFlags, // Options de l'effet shader
  _In_        ID3D10Device       *pDevice, // Le device de DirectX 10.1
  _In_        ID3D10EffectPool   *pEffectPool,
  _In_        ID3DX10ThreadPump  *pPump,
  _Out_       ID3D10Effect       **ppEffect, // Pointeur de retour vers l'effet créé
  _Out_       ID3D10Blob         **ppErrors, // Pointeur de retour vers un objet représentant les erreurs
  _Out_       HRESULT            *pHResult
);

 

 

Résumé :

Le langage shader HLSL est un outil très puissant pour le rendu graphique des jeux vidéo.

Tous les effets graphiques dans les jeux DirectX d’aujourd’hui sont construits à partir de fichier script shader HLSL.

Références :

– http://www.neatware.com/lbstudio/web/hlsl.html

– https://msdn.microsoft.com/fr-fr/library/ms810449.aspx

La normale (vecteur)

normale

Intro :

La normale est un vecteur perpendiculaire à un triangle ou une face quelconque.

Prérequis :

– Savoir ce qu’est un vecteur et les opérations algébriques associées.

Explications :

La normale décrit dans quelle direction une face est dirigée. La normale est un vecteur d’unité 1, puisqu’il a été normalisé. Elle est perpendiculaire à tous les points de la face.

Comment calculer la normale ?

On cherche à trouver deux vecteurs d’une face donnée en prenant le produit vectoriel de ces deux là.

Pour trouver la normale d’un triangle \Delta\vec{p_0}\vec{p_1}\vec{p_2} , on calcule ces deux vecteurs d’arrêtes (côtés) du triangle.

\vec{u} = \vec{p_1} - \vec{p_2}
\vec{v} = \vec{p_2} - \vec{p_0}

\vec{n} = (\vec{u}\times\vec{v}) / \|\vec{u}\times\vec{v}\|

 

Voici l’algorithme en C++ :

#include <d3dx10math.h>

void ComputeNormal(D3DXVECTOR3* p0, D3DXVECTOR3* p1, D3DXVECTOR3* p2,
                   D3DXVECTOR3* out)
{
    D3DXVECTOR3 u = *p1 - *p0;
    D3DXVECTOR3 v = *p2 - *p0;

    D3DXVec3Cross(out, &u, &v);

    D3DXVec3Normalize(out, out);
}

 

On peut la calculer autrement :

n_{x} = u_{y}v_{z} - u_{z}v_{y}

 

n_{y} = u_{z}v_{x} - u_{x}v_{z}

 

n_{z} = u_{x}v_{y} - u_{y}v_{x}

 

Comment la normaliser ?

{\vec{v}}_{norm}=\frac{{\vec{n}}}{\|{\vec{n}}\|}

 

Voici l’algorithme en C++ :

#include <d3dx10math.h>

void Normalize(D3DXVECTOR3& v)
{
    unsigned int iLength = sqrt((v.x * v.x) + (v.y * v.y) + (v.z * v.z));

    v.x /= iLength;
    v.y /= iLength;
    v.z /= iLength;
}

 

A quoi sert la normale ?

La normale est utilisée dans le calcul de la nuance de lumière d’une face en utilisant la loi de Lambert. De cette façon nous pouvons déterminer l’angle à laquelle la lumière frappe un vertex.

Elle est aussi utilisée dans le rendu du « Bump Mapping ».

Transformation de la normale par une mise à l’échelle non uniforme :

On doit faire attention aux vecteurs normales lorsqu’on fait une opération d’agrandissement (de mise à l’échelle / de scaling) autre que d’unité 1 ou non-uniforme.

[schéma / dessin]

La normale est correctement transformée avec la matrice de transformation d’agrandissement inversée et transposée.

Résumé :

Nous avons présenté le vecteur normal ; entité mathématique qui sert notamment à effectuer le calcul lumineux d’un modèle.

Références :

– https://www.opengl.org/wiki/Calculating_a_Surface_Normal

Les matrices

matrice

Intro :

Une matrice est un tableau de nombres ordonnés en lignes et en colonnes entourés par des parenthèses ou des crochets permettant d’effectuer des transformations géométriques dans un repère cartésien (un espace). Ces transformations peuvent être des agrandissements, des rotations ou des translations.

En effet, elles sont utilisées lors de l’affichage du rendu 3D à l’écran, de façon à ce que l’espace 3D du jeu soit transformée dans l’espace 2D de l’écran.

Les matrices servent à transformer un espace de départ vers un espace d’arrivé. Elles permettent de changer les coordonnées d’un point ou d’un vecteur depuis un espace à un autre.

Voir aussi cet article traitant les transformations.

Prérequis :

– Savoir faire des opérations sur les vecteurs.

– Savoir appliquer le produit scalaire.

Explications :

On peut réaliser sur les matrices des opérations proches de celles que l’on applique sur les nombres réels comme l’addition, la soustraction, la multiplication ou l’inversion.

Les nombres dans une matrice sont appelés coefficients. On représente un coefficient d’une matrice par : \textbf{M}_{i, j} .
Le premier indice i correspond à la ligne et le second indice j correspond à la colonne.

Une matrice avec M lignes et N colonnes est appelée une matrice MxN.

Multiplication de deux matrices :

Il est nécessaire pour multiplier deux matrices \textbf{A} et \textbf{B} que le nombre de colonnes de \textbf{A} vaut le nombre de lignes de \textbf{B} .

Si \textbf{A} est une matrice MxN et \textbf{B} une matrice NxP, alors le produit \textbf{AB} est possible et est une matrice \textbf{C} de dimension MxP.

Le coefficient \textbf{C}_{i, j} est donné en faisant le produit scalaire de la {i}_{eme} ligne de \textbf{A} par la {j}_{eme} colonne de \textbf{B} .

Voici la formule : \textbf{C}_{i, j} = \textbf{A}_{i, *} \cdot \textbf{B}_{*, j} .
Le symbole de l’étoile représente tous les coefficients de la matrice.

Rappel du produit scalaire :  {v}\cdot{w}={v}_{x}{w}_{x}+{v}_{y}{w}_{y}+{v}_{z}{w}_{z}

Voici un exemple :

\begin{bmatrix} -1 & 2 & 2 \\ 4 & 6 & 3 \end{bmatrix} \begin{bmatrix} 6 & 7 & 8 \\ 2 & 1 & 1 \\ 1 & 4 & 8 \end{bmatrix} = \begin{bmatrix} (-1, 2, 2) \cdot (6, 2, 1) & (-1, 2, 2) \cdot (7, 1, 4) & (-1, 2, 2) \cdot (8, 1, 8) \\ (4, 6, 3) \cdot (6, 2, 1) & (4, 6, 3) \cdot (7, 1, 4) & (4, 6, 3) \cdot (8, 1, 8) \end{bmatrix} = \newline\newline\begin{bmatrix} (-1 \times 6) + (2 \times 2) + (2 \times 1) & (-1 \times 7) + (2 \times 1) + (2 \times 4) & (-1 \times 8) + (2 \times 1) + (-1 \times 8) \\ (4 \times 6) + (6 \times 2) + (3 \times 1) & (4 \times 7) + (6 \times 1) + (3 \times 4) & (4 \times 8) + (6 \times 1) + (3 \times 8) \end{bmatrix} = \newline\newline\begin{bmatrix} 0 & 3 & -14 \\ 39 & 46 & 62 \end{bmatrix}


Matrice carrée :

C’est une matrice ayant le même nombres de colonnes et de lignes.

Voici une matrice carrée :

\begin{pmatrix} 2 & 3 \\ 4 & 5 \end{pmatrix}

 

Ordre d’une matrice :

L’ordre d’une matrice est l’autre dénomination de la taille d’une matrice. Une matrice à M lignes et N colonnes est dites d’ordre MxN.

Matrice identité :

C’est une matrice carrée dont tout les coefficients en diagonale valent 1 ; tandis que les autres valent 0.

Si nous multiplions un vecteur par une matrice identité, le vecteur ne sera absolument pas changé ; de même pour une matrice.

Voici une matrice identité d’ordre 3 :

\begin{pmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{pmatrix}

 

Multiplication d’un vecteur par une matrice :

On procède comme la multiplication de deux matrices. Sauf que le vecteur est considéré comme une matrice 1xN.

La multiplication est possible que si le nombre de colonnes de la matrice est égal au nombre de coordonnées du vecteur. On fait l’opération de cet ordre : \textbf{v}\textbf{M} ; d’abord le vecteur \textbf{v} à l’opérande gauche ensuite la matrice \textbf{M} à l’opérande droite.

La multiplication de matrices n’est pas commutative, c’est-à-dire que M1 x M2 ne sera pas forcément égal à M2 x M1.

Transposée d’une matrice :

La transposée d’une matrice \textbf{M} , notée \textbf{M}^{T} , est la matrice générée par l’inversion des éléments de la matrice d’origine par rapport à la diagonale.

Une matrice MxN devient une matrice NxM.

Exemple :

\textbf{A} = \begin{pmatrix} 2 & 3 & 1 \\ 5 & 8 & 4 \end{pmatrix}    donne \textbf{A}^T = \begin{pmatrix} 2 & 5 \\ 3 & 8 \\ 1 & 4 \end{pmatrix}

Propriétés remarquables :

\textbf{(A + B)}^T = \textbf{A}^T + \textbf{B}^T

 

(c\textbf{A})^T=c{\textbf{A}}^T

 

(\textbf{AB})^T=\textbf{B}^T\textbf{A}^T

 

(\textbf{A}^T)^T=\textbf{A}

 

(\textbf{A}^{-1})^T=(\textbf{A}^T)^{-1}

 

Ajouter deux matrices :

On ajoute deux matrices de même dimension en ajoutant un à un leur coefficient correspondant.

Soustraire deux matrices :

On procède comme suit : \textbf{A} - \textbf{B} = \textbf{A} + (-1 \cdot \textbf{B}) = \textbf{A} + (-\textbf{B})

Multiplier une matrice par un réel :

On multiplie le réel par tous les coefficients de la matrice.

Inverse d’une matrice :

Seul les matrices carrées sont inversibles.

Noté \textbf{M}^{-1} , la matrice inverse vérifie cette expression : \textbf{MM}^{-1} = \textbf{M}^{-1} \textbf{M} = \textbf{I} .
\textbf{I} étant la matrice identité.

Une matrice est inversible si et seulement si son déterminant n’est pas nul.

L’inverse d’une matrice permet de retrouver l’espace précédent suite à une transformation de repère.

Voici un schéma résumant ce principe :

img008

Déterminant d’une matrice :

Associativité :

\textbf{A}(\textbf{B } {+} \textbf{ C}) = \textbf{AB} + \textbf{AC}

 

(\textbf{AB})\textbf{C} = \textbf{A}(\textbf{BC})

 

Résumé :

Nous avons vu les opérations courantes concernant l’algèbre matricielle.

L’utilisation des matrices (afin d’effectuer des transformations) est fondamentale dans le rendu 3D de jeu vidéo.

Une classe AlphaPass pour activer la transparence (façon RAII)

alpha

Intro :

Parfois on veut afficher certaines modèles tout en activant la transparence.

Ici je présente une façon simple de gérer cela avec le principe du RAII.

Prérequis :

– Comprendre le principe du RAII

– Savoir initialiser DirectX 10 à travers la classe D3D10Renderer

Explications :

Voici le code du fichier AlphaPass.h :

#ifndef ALPHA_PASS_H
#define ALPHA_PASS_H

#include <d3dx10math.h>

class AlphaPass
{
public:
    AlphaPass();
    virtual ~AlphaPass();

protected:
    ID3D10BlendState* m_pOldBlendState;
    float m_OldBlendFactor[4];
    unsigned int m_OldSampleMask;
        
    ID3D10BlendState* m_pCurrentBlendState;
};

#endif

 

Voici le fichier AlphaPass.cpp :

#include "AlphaPass.h"

AlphaPass::AlphaPass() :
m_pOldBlendState(nullptr),
m_pCurrentBlendState(nullptr)
{
    D3D10_RENDERER->GetDevice()->OMGetBlendState(&m_pOldBlendState, m_OldBlendFactor, &m_OldSampleMask);
    m_pCurrentBlendState = nullptr;

    D3D10_BLEND_DESC BlendState;
    ZeroMemory(&BlendState, sizeof(D3D10_BLEND_DESC));

    BlendState.BlendEnable[0] = TRUE;
    BlendState.SrcBlend = D3D10_BLEND_SRC_ALPHA;
    BlendState.DestBlend = D3D10_BLEND_INV_SRC_ALPHA;
    BlendState.BlendOp = D3D10_BLEND_OP_ADD;
    BlendState.SrcBlendAlpha = D3D10_BLEND_ZERO;
    BlendState.DestBlendAlpha = D3D10_BLEND_ZERO;
    BlendState.BlendOpAlpha = D3D10_BLEND_OP_ADD;
    BlendState.RenderTargetWriteMask[0] = D3D10_COLOR_WRITE_ENABLE_ALL;

    D3D10_RENDERER->GetDevice()->CreateBlendState(&BlendState, &m_pCurrentBlendState);
    D3D10_RENDERER->GetDevice()->OMSetBlendState(m_pCurrentBlendState, 0, 0xffffffff);
}

AlphaPass::~AlphaPass()
{
    D3D10_RENDERER->GetDevice()->OMSetBlendState(m_pOldBlendState, m_OldBlendFactor, m_OldSampleMask);

    SAFE_RELEASE(m_pCurrentBlendState);
    SAFE_RELEASE(m_pOldBlendState);
}

 

warning

Vos entités transparentes doivent être affichées en dernier lors de le boucle de rendu et tout en désactivant le Z-Buffer.

Résumé :

Nous avons établi un objet AlphaPass qui permet d’activer, lors de sa création sur la pile, la transparence dans le rendu. La transparence est restauré lors de la destruction de l’objet en sortant du bloc de code.

Créer une icône de barre des tâches pour votre programme

taskbar

Intro :

Au lieu de garder l’icône représentative par défaut de votre programme, vous pouvez la remplacer en mettant une image de votre choix. Cette dernière image devra être du format .ico.

Vous pouvez en trouver à travers le web.

Prérequis :

– Savoir lire du C++

– Savoir initialiser DirectX 10 à travers la classe System

Explications :

– Télécharger une icône. Par exemple : Icone.ico

– Créer un fichier « Res.rc »

ICON ICON "Icone.ico"

Mettez ces fichiers dans le dossier des fichiers de ressources :

ressource

 

Le compilateur de Visual Studio C++ reconnaîtra automatiquement l’icône à lier dans votre programme et la chargera au lancement de votre programme.

Résumé :

Nous avons présenté une méthode permettant de remplacer l’icône classique de votre programme.

Créer un curseur de souris customisé pour votre jeu

wow_cursors

Intro :

Dans un jeu, il peut être plus joli de changer l’image du curseur de la souris en fonction de ce que pointe la souris.

Prérequis :

– Savoir lire du C++

– Savoir initialiser DirectX 10 à travers la classe System

Explications :

– Téléchargez un curseur (par exemple WoW.cur)

– Créez un fichier « Res.rc » :

#include "Ressource.h"

IDC_CURSOR_ARROW CURSOR DISCARDABLE "WoW.cur"

 

– Créez un fichier « Ressource.h » :

#define IDC_CURSOR_ARROW 4000

Mettez ces fichiers dans le dossier des fichiers de ressources :

ressource

Voici la fonction pour fixer le curseur de la souris sur la fenêtre de rendu :

#include "Ressource.h"

void LoadCustomCursor()
{
    HINSTANCE hInst = (HINSTANCE)GetModuleHandle(NULL);
    HWND hwnd = SYSTEM->GetHwnd();

    HCURSOR WoWCursor = LoadCursor(hInst, MAKEINTRESOURCE(IDC_CURSOR_ARROW));

    SetClassLong(hwnd, GCL_HCURSOR, (LONG)WoWCursor);
}

 

Il vaut mieux utiliser l’appel à la fonction SetClassLong que l’appel à la fonction SetCursor ;
en effet le programme gardera constamment l’image de la souris même si le pointeur de souris sort de la fenêtre.

Résumé :

Nous avons présenté une méthode permettant de changer l’image du curseur de la souris.

Une classe pour afficher des lignes en 3D avec une certaine couleur

3D_lines

Intro :

Il peut être utile d’afficher des lignes dans la surface de rendu ; par exemple pour représenter des normales, des trajectoires, un repère, etc…

Prérequis :

– Savoir lire du C++

– Savoir utiliser la classe MeshSceneNode

– Savoir utiliser la classe ShaderTechnique et les déclarations de vertices.

Explications :

Voici le fichier Vertex3DLine.h :

#ifndef MESH_3D_LINE_H
#define MESH_3D_LINE_H

class Vertex3DLine : public MeshSceneNode
{
public:
    Vertex3DLine(unsigned int bufferBytesSize);
    virtual ~Vertex3DLine();

    bool Initialize();

    void AddLine(D3DXVECTOR3 startPoint, D3DXVECTOR3 endPoint, D3DXCOLOR color);

private:
    void ResizeVertexBuffer(unsigned int iNewBytesSize);

private:
    std::vector<PCVertex> m_vertices;

    unsigned int m_bufferBytesSize;
};

#endif

 

Voici le fichier Vertex3DLine.cpp :

#include "Vertex3DLine.h"

Vertex3DLine::Vertex3DLine(unsigned int bufferBytesSize) :
m_bufferBytesSize(bufferBytesSize)
{
    SetVertexType(VertexLayoutType::PC_VERTEX);
    SetDrawMethod(DrawMethod::DRAW_NORMAL);
    SetTopology(D3D10_PRIMITIVE_TOPOLOGY_LINELIST);

    SetShaderTechnique( SHADER_MANAGER->GetShader("Vertex3DLine") );
}

Vertex3DLine::~Vertex3DLine()
{
    m_vertices.clear();
}

bool Vertex3DLine::Initialize()
{
    HRESULT hr;

    D3D10_BUFFER_DESC bd;
    bd.Usage = D3D10_USAGE_DYNAMIC;
    bd.ByteWidth = sizeof(PCVertex) * m_bufferBytesSize;
    bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
    bd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
    bd.MiscFlags = 0;

    hr = D3D10_RENDERER->GetDevice()->CreateBuffer(&bd, 0, &m_pVertexBuffer);

    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    return true;
}

void Vertex3DLine::AddLine(D3DXVECTOR3 startPoint, D3DXVECTOR3 endPoint, D3DXCOLOR color)
{
    if (!m_pVertexBuffer)
    {
        return;
    }

    D3D10_BUFFER_DESC bd;
    m_pVertexBuffer->GetDesc(&bd);

    unsigned int iActualBufferBytesSize = bd.ByteWidth;
    unsigned int iNewBufferBytesSize = (m_vertices.size() + 2) * sizeof(PCVertex);

    // Si la taille du buffer est trop petite pour ajouter de nouveaux éléments
    // => on redimensionne
    if (iNewBufferBytesSize > iActualBufferBytesSize)
    {
        ResizeVertexBuffer(iActualBufferBytesSize + 2);
    }

    PCVertex* data = nullptr;

    PCVertex vert1;
    PCVertex vert2;

    vert1.position = startPoint;
    vert1.color = color;

    vert2.position = endPoint;
    vert2.color = color;

    m_vertices.push_back(vert1);
    m_vertices.push_back(vert2);

    m_pVertexBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, (void**)&data);

    memcpy(data, m_vertices.data(), sizeof(PCVertex) * m_vertices.size());

    m_pVertexBuffer->Unmap();

    m_iVerticesCount = m_vertices.size();
}

void Vertex3DLine::ResizeVertexBuffer(unsigned int iNewBytesSize)
{
    SAFE_RELEASE(m_pVertexBuffer);

    HRESULT hr;

    D3D10_BUFFER_DESC bd;
    bd.Usage = D3D10_USAGE_DYNAMIC;
    bd.ByteWidth = sizeof(PCVertex) * iNewBytesSize;
    bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
    bd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
    bd.MiscFlags = 0;
    hr = D3D10_RENDERER->GetDevice()->CreateBuffer(&bd, nullptr, &m_pVertexBuffer);

    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
    }
}

 

Voici le fichier ShaderTechnique_Vertex3DLine.h :

//------------------------------------------------------
// Shader d'affichage de lignes 3D
//------------------------------------------------------
class Vertex3DShaderTechnique : public ShaderTechnique
{
public:
    Vertex3DShaderTechnique();
    virtual ~Vertex3DShaderTechnique();

    virtual bool Initialize();

    virtual void SetupShaderVariables();

    virtual void Update(float fTimeSinceLastFrame);

};

 

Voici le fichier ShaderTechnique_Vertex3DLine.cpp :

#include "ShaderTechnique_Vertex3DLine.h"

Vertex3DShaderTechnique::Vertex3DShaderTechnique() :
ShaderTechnique("3DLine.fx", "Render", VertexLayoutType::PC_VERTEX)
{
}

Vertex3DShaderTechnique::~Vertex3DShaderTechnique()
{
}

bool Vertex3DShaderTechnique::Initialize()
{
    ShaderTechnique::Initialize();

    SetupShaderVariables();

    return true;
}

void Vertex3DShaderTechnique::SetupShaderVariables()
{
    RegisterMatrixVariable("World", ShaderVariableType::WORLD);
    RegisterMatrixVariable("View", ShaderVariableType::VIEW);
    RegisterMatrixVariable("Projection", ShaderVariableType::PROJECTION);
}

void Vertex3DShaderTechnique::Update(float fTimeSinceLastFrame)
{
    SetAutoMatrix(ShaderVariableType::WORLD);
    SetAutoMatrix(ShaderVariableType::VIEW);
    SetAutoMatrix(ShaderVariableType::PROJECTION);
}

 

Voici le fichier 3DLine.fx correspondant :

matrix World;
matrix View;
matrix Projection;

struct VS_INPUT
{
    float4 Pos : POSITION;
    float4 Color : COLOR0;
};

struct PS_INPUT
{
    float4 Pos : SV_POSITION;
    float4 Color : COLOR0;
};

PS_INPUT VS( VS_INPUT input )
{
    PS_INPUT output = (PS_INPUT)0;
    
    output.Pos = mul( input.Pos, World );
    output.Pos = mul( output.Pos, View );
    output.Pos = mul( output.Pos, Projection );
    
    output.Color = input.Color;
    
    return output;
}

float4 PS( PS_INPUT input) : SV_Target
{
    return input.Color;
}

technique10 Render
{
    pass P0
    {
        SetVertexShader( CompileShader( vs_4_0, VS() ) );
        SetGeometryShader( NULL );
        SetPixelShader( CompileShader( ps_4_0, PS() ) );
    }
}

 

Résumé :

Nous avons présenté un système permettant d’afficher des lignes dans la surface de rendu tout en pouvant spécifier leur couleur.

Une classe ShaderManager pour gérer vos fichier shaders

AMD_6990

Intro :

Prérequis :

– Savoir utiliser un peu DirectX 10

– Savoir utiliser la classe Singleton. Voir cet article.

– Savoir ce qu’est un fichier shader.

– Savoir utiliser la classe ShaderTechnique.

Explications :

Voici le fichier ShaderManager.h :

#ifndef SHADERS_MANAGER_H
#define SHADERS_MANAGER_H 

class ShaderManager : public Singleton<ShaderManager>
{
public:
     ShaderManager();
     virtual ~ShaderManager();

     void RegisterShader(ShaderTechnique* pShader, const std::string& sName);
     void UnRegisterShader(std::string sName);

     ShaderTechnique* GetShader(std::string sName);

private:
    std::map<std::string, ShaderTechnique*> m_shaders;
};

#endif

 

Voici le fichier ShaderManager.cpp :

#include "ShaderManager.h"

template<> ShaderManager* Singleton<ShaderManager>::ms_instance = nullptr;

ShaderManager::ShaderManager()
{
}

ShaderManager::~ShaderManager()
{
}

void ShaderManager::RegisterShader(ShaderTechnique* pShader, const std::string& sName)
{
    Assert(pShader);

    m_shaders[sName] = pShader;
}

void ShaderManager::UnRegisterShader(std::string sName)
{
    Assert(m_shaders.count(sName) > 0);

    delete m_shaders[sName];

    m_shaders.erase(sName);

}

ShaderTechnique* ShaderManager::GetShader(std::string sName)
{
    Assert(m_shaders.count(sName) > 0);

    return m_shaders[sName];
}

 

Résumé :

Voici une classe toute simple qui permettra de rassembler tous vos shaders encapsulés dans une collection std::map.

Une classe Skybox pour afficher un ciel autour de votre caméra – 1ère approche

skybox

Intro :

Pour que votre scène soit réaliste, il peut être utile d’afficher un décor de ciel de grande taille autour de la caméra.

Ceci constitue une première approche à l’affichage d’une Skybox.

Prérequis :

– Savoir utiliser la classe ShaderTechnique. Voir cet article.

– Savoir utiliser la classe MeshSceneNode. Voir cet article.

– Savoir un peu utiliser DirectX 10.

Explications :

Tout d’abord la classe dérivant ShaderTechnique :

//------------------------------------------------------
// Une simple effet affichant une SkyBox
//------------------------------------------------------
class ShaderTechnique_SkyBox : public ShaderTechnique
{
public:
    friend class SkyBoxSceneNode;

    ShaderTechnique_SkyBox();
    virtual ~ShaderTechnique_SkyBox();

    virtual bool Initialize();

    virtual void SetupShaderVariables();

    virtual void Update(float fTimeSinceLastFrame);
};

 

Voici ensuite le fichier ShaderTechnique_SkyBox.cpp :

ShaderTechnique_SkyBox::ShaderTechnique_SkyBox() :
ShaderTechnique("SkyBox.fx", "Render", VertexLayoutType::PTN_VERTEX)
{
}

ShaderTechnique_SkyBox::~ShaderTechnique_SkyBox()
{
}

bool ShaderTechnique_SkyBox::Initialize()
{
    ShaderTechnique::Initialize();

    SetupShaderVariables();

    return true;
}

void ShaderTechnique_SkyBox::SetupShaderVariables()
{
    RegisterMatrixVariable("World", ShaderVariableType::WORLD);
    RegisterMatrixVariable("View", ShaderVariableType::VIEW);
    RegisterMatrixVariable("Projection", ShaderVariableType::PROJECTION);

    RegisterTextureVariable("txFace");
}

void ShaderTechnique_SkyBox::Update(float fTimeSinceLastFrame)
{
    SetAutoMatrix(ShaderVariableType::WORLD);
    SetAutoMatrix(ShaderVariableType::VIEW);
    SetAutoMatrix(ShaderVariableType::PROJECTION);
}

 

Voici le fichier SkyBoxSceneNode.h :

#ifndef SKY_BOX_SCENE_NODE_H
#define SKY_BOX_SCENE_NODE_H

class SkyBoxSceneNode : public MeshSceneNode
{
public:
    SkyBoxSceneNode(ID3D10ShaderResourceView* pTop, ID3D10ShaderResourceView* pBottom,
        ID3D10ShaderResourceView* pLeft, ID3D10ShaderResourceView* pRight,
        ID3D10ShaderResourceView* pFront, ID3D10ShaderResourceView* pBack);

    virtual ~SkyBoxSceneNode();

    virtual bool Initialize();
    virtual void Render(float fTimeSinceLastFrame);

protected:
    virtual void OnPreRender(float fTimeSinceLastFrame);
    virtual void OnPostRender(float fTimeSinceLastFrame);

private:
    PTNVertex SetVertex(D3DXVECTOR3 p, D3DXVECTOR2 t, D3DXVECTOR3 n);

private:
    ID3D10ShaderResourceView* m_pFrontTex;
    ID3D10ShaderResourceView* m_pLeftTex;
    ID3D10ShaderResourceView* m_pBackTex;
    ID3D10ShaderResourceView* m_pRightTex;
    ID3D10ShaderResourceView* m_pTopTex;
    ID3D10ShaderResourceView* m_pBottomTex;

    ID3D10Buffer* m_pFrontVB;
    ID3D10Buffer* m_pLeftVB;
    ID3D10Buffer* m_pBackVB;
    ID3D10Buffer* m_pRightVB;
    ID3D10Buffer* m_pTopVB;
    ID3D10Buffer* m_pBottomVB;
};

#endif

 

Voici le fichier SkyBoxSceneNode.cpp :

#include "SkyBoxSceneNode.h"

SkyBoxSceneNode::SkyBoxSceneNode(ID3D10ShaderResourceView* pTop, ID3D10ShaderResourceView* pBottom,
        ID3D10ShaderResourceView* pLeft, ID3D10ShaderResourceView* pRight,
        ID3D10ShaderResourceView* pFront, ID3D10ShaderResourceView* pBack) :
m_pFrontVB(nullptr),
m_pLeftVB(nullptr),
m_pBackVB(nullptr),
m_pRightVB(nullptr),
m_pTopVB(nullptr),
m_pBottomVB(nullptr),
m_pFrontTex(pFront),
m_pLeftTex(pLeft),
m_pBackTex(pBack),
m_pRightTex(pRight),
m_pTopTex(pTop),
m_pBottomTex(pBottom)
{
    SetVertexType(VertexLayoutType::PTN_VERTEX);
    SetDrawMethod(DrawMethod::DRAW_INDEXED);
    SetTopology(D3D_PRIMITIVE_TOPOLOGY::D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    SetShaderTechnique( SHADER_MANAGER->GetShader("SkyBox") );
}

SkyBoxSceneNode::~SkyBoxSceneNode()
{
}

PTNVertex SkyBoxSceneNode::SetVertex(D3DXVECTOR3 p, D3DXVECTOR2 t, D3DXVECTOR3 n)
{
    PTNVertex vertex;
    
    vertex.position = p;
    vertex.texture = t;
    vertex.normal = n;

    return vertex;
}

bool SkyBoxSceneNode::Initialize()
{
    std::vector<unsigned short> indices;

    indices.push_back(3);
    indices.push_back(1);
    indices.push_back(0);
    indices.push_back(2);
    indices.push_back(1);
    indices.push_back(3);

    BuildIB(indices);

    // On redecoupe les bords des textures pour éviter
    // d'afficher les arrêtes du cube
    float fTextureWidth = GetShaderTextureViewSize(m_pBottomTex).x;

    float onepixel = 1.0f / (fTextureWidth * 1.5f);
    float t = 1.0f - onepixel;
    float o = 0.0f + onepixel;

    std::vector<PTNVertex> vertices;

    // Front side
    vertices.push_back( SetVertex(D3DXVECTOR3(-1,-1,-1), D3DXVECTOR2(t, t), D3DXVECTOR3(0, 0, 1.0f)));
    vertices.push_back( SetVertex(D3DXVECTOR3(1,-1,-1),  D3DXVECTOR2(o, t), D3DXVECTOR3(0, 0, 1.0f)));
    vertices.push_back( SetVertex(D3DXVECTOR3(1, 1,-1),  D3DXVECTOR2(o, o), D3DXVECTOR3(0, 0, 1.0f)));
    vertices.push_back( SetVertex(D3DXVECTOR3(-1, 1,-1), D3DXVECTOR2(t, o), D3DXVECTOR3(0, 0, 1.0f)));

    BuildVB(vertices);

    m_pFrontVB = GetVertexBuffer();

    // Left side
    vertices.clear();

    vertices.push_back( SetVertex(D3DXVECTOR3(1,-1,-1), D3DXVECTOR2(t, t), D3DXVECTOR3(-1, 0, 0)));
    vertices.push_back( SetVertex(D3DXVECTOR3(1,-1, 1), D3DXVECTOR2(o, t), D3DXVECTOR3(-1, 0, 0)));
    vertices.push_back( SetVertex(D3DXVECTOR3(1, 1, 1), D3DXVECTOR2(o, o), D3DXVECTOR3(-1, 0, 0)));
    vertices.push_back( SetVertex(D3DXVECTOR3(1, 1,-1), D3DXVECTOR2(t, o), D3DXVECTOR3(-1, 0, 0)));

    BuildVB(vertices);

    m_pLeftVB = GetVertexBuffer();

    // Back side
    vertices.clear();

    vertices.push_back( SetVertex(D3DXVECTOR3( 1,-1, 1), D3DXVECTOR2(t, t), D3DXVECTOR3(0, 0, -1.0f)));
    vertices.push_back( SetVertex(D3DXVECTOR3(-1,-1, 1), D3DXVECTOR2(o, t), D3DXVECTOR3(0, 0, -1.0f)));
    vertices.push_back( SetVertex(D3DXVECTOR3(-1, 1, 1), D3DXVECTOR2(o, o), D3DXVECTOR3(0, 0, -1.0f)));
    vertices.push_back( SetVertex(D3DXVECTOR3( 1, 1, 1), D3DXVECTOR2(t, o), D3DXVECTOR3(0, 0, -1.0f)));

    BuildVB(vertices);

    m_pBackVB = GetVertexBuffer();

    // Right side
    vertices.clear();

    vertices.push_back( SetVertex(D3DXVECTOR3(-1,-1, 1), D3DXVECTOR2(t, t), D3DXVECTOR3(1, 0, 0)));
    vertices.push_back( SetVertex(D3DXVECTOR3(-1,-1,-1), D3DXVECTOR2(o, t), D3DXVECTOR3(1, 0, 0)));
    vertices.push_back( SetVertex(D3DXVECTOR3(-1, 1,-1), D3DXVECTOR2(o, o), D3DXVECTOR3(1, 0, 0)));
    vertices.push_back( SetVertex(D3DXVECTOR3(-1, 1, 1), D3DXVECTOR2(t, o), D3DXVECTOR3(1, 0, 0)));

    BuildVB(vertices);

    m_pRightVB = GetVertexBuffer();

    // Top side
    vertices.clear();

    vertices.push_back( SetVertex(D3DXVECTOR3( 1, 1,-1), D3DXVECTOR2(t, t), D3DXVECTOR3(0, -1, 0)));
    vertices.push_back( SetVertex(D3DXVECTOR3( 1, 1, 1), D3DXVECTOR2(o, t), D3DXVECTOR3(0, -1, 0)));
    vertices.push_back( SetVertex(D3DXVECTOR3(-1, 1, 1), D3DXVECTOR2(o, o), D3DXVECTOR3(0, -1, 0)));
    vertices.push_back( SetVertex(D3DXVECTOR3(-1, 1,-1), D3DXVECTOR2(t, o), D3DXVECTOR3(0, -1, 0)));

    BuildVB(vertices);

    m_pTopVB = GetVertexBuffer();

    // Bottom side
    vertices.clear();

    vertices.push_back( SetVertex(D3DXVECTOR3( 1,-1, 1), D3DXVECTOR2(t, t), D3DXVECTOR3(0, 1, 0)));
    vertices.push_back( SetVertex(D3DXVECTOR3( 1,-1,-1), D3DXVECTOR2(o, t), D3DXVECTOR3(0, 1, 0)));
    vertices.push_back( SetVertex(D3DXVECTOR3(-1,-1,-1), D3DXVECTOR2(o, o), D3DXVECTOR3(0, 1, 0)));
    vertices.push_back( SetVertex(D3DXVECTOR3(-1,-1, 1), D3DXVECTOR2(t, o), D3DXVECTOR3(0, 1, 0)));

    BuildVB(vertices);

    m_pBottomVB = GetVertexBuffer();

    return true;
}

void SkyBoxSceneNode::Render(float fTimeSinceLastFrame)
{
    D3D10_RENDERER->EnableZBuffer(false);

    SetVertexBuffer(m_pFrontVB);
    GetShaderTechnique()->SetTextureRV("txFace", m_pFrontTex);
    Renderable::Render(fTimeSinceLastFrame);

    SetVertexBuffer(m_pLeftVB);
    GetShaderTechnique()->SetTextureRV("txFace", m_pLeftTex);
    Renderable::Render(fTimeSinceLastFrame);

    SetVertexBuffer(m_pBackVB);
    GetShaderTechnique()->SetTextureRV("txFace", m_pBackTex);
    Renderable::Render(fTimeSinceLastFrame);

    SetVertexBuffer(m_pRightVB);
    GetShaderTechnique()->SetTextureRV("txFace", m_pRightTex);
    Renderable::Render(fTimeSinceLastFrame);

    SetVertexBuffer(m_pTopVB);
    GetShaderTechnique()->SetTextureRV("txFace", m_pTopTex);
    Renderable::Render(fTimeSinceLastFrame);

    SetVertexBuffer(m_pBottomVB);
    GetShaderTechnique()->SetTextureRV("txFace", m_pBottomTex);
    Renderable::Render(fTimeSinceLastFrame);

    D3D10_RENDERER->EnableZBuffer(true);
}

void SkyBoxSceneNode::OnPreRender(float fTimeSinceLastFrame)
{
    Camera* pCamera = SCENE_MANAGER->GetActiveCamera();

    D3DXMATRIX finalMatrix;

    D3DXMATRIX translate;

    D3DXVECTOR3 cameraPos = pCamera->GetPosition();

    // On centre toujours la skybox autour de la position de la caméra
    D3DXMatrixTranslation(&translate, cameraPos.x, cameraPos.y, cameraPos.z);

    float viewDistance = (pCamera->GerNearValue() + pCamera->GetFarValue()) * 0.5f;

    // On agrandie la skybox pour qu'elle est une bonne taille
    D3DXMATRIX scale;
    D3DXMatrixScaling(&scale, viewDistance, viewDistance, viewDistance);

    finalMatrix = scale * translate;

    SCENE_MANAGER->PushAndSetMatrix(finalMatrix);
}

void SkyBoxSceneNode::OnPostRender(float fTimeSinceLastFrame)
{    
    SCENE_MANAGER->PopMatrix();
}

 

Voici le fichier SkyBox.fx :

matrix World;
matrix View;
matrix Projection;

Texture2D txFace;

SamplerState samLinear
{
    Filter = MIN_MAG_MIP_LINEAR;
    AddressU = Wrap;
    AddressV = Wrap;
};

struct VS_INPUT
{
    float4 Pos : POSITION;
    float2 Tex : TEXCOORD;
    float3 Norm : NORMAL;
};

struct PS_INPUT
{
    float4 Pos : SV_POSITION;
    float2 Tex : TEXCOORD0;
    float3 Norm : NORMAL;
};

PS_INPUT VS( VS_INPUT input )
{
    PS_INPUT output = (PS_INPUT)0;
    
    output.Pos = mul( input.Pos, World );
    output.Pos = mul( output.Pos, View );
    output.Pos = mul( output.Pos, Projection );

    output.Tex = input.Tex;
    
    output.Norm = mul( input.Norm, World );
    
    return output;
}

float4 PS( PS_INPUT input) : SV_Target
{
    float4 textureColor = txFace.Sample(samLinear, input.Tex);
    
    return textureColor;
}

technique10 Render
{
    pass P0
    {
        SetVertexShader( CompileShader( vs_4_0, VS() ) );
        SetGeometryShader( NULL );
        SetPixelShader( CompileShader( ps_4_0, PS() ) );
    }
}

 

Résumé :

Nous avons présenté un énorme cube autour de la caméra qui servira de ciel ou de décor.

Une classe MeshSceneNode pour représenter des meshes dans votre scène

mesh node

Intro :

Pour afficher vos entités / mesh dans la scène 3D, je vous présente une classe qui permettra de les initialiser rapidement.

Elle pourra construire facilement du contenu 3D à partir des méthodes suivantes :

// Construit et remplit le vertex buffer à partir d'un tableau de vertices
bool BuildVB(std::vector<T>& vertices)

// Construit et remplit l'index buffer à partir d'une tableau d'indices
bool BuildIB(std::vector<T>& indices)

// Construit un mesh à partir des tableaux de vertices et d'indices
bool Build(std::vector<T>& vertices, std::vector<unsigned short>& indices)

 

Prérequis :

– Savoir utiliser la classe SceneNode. Voir cet article.

– Savoir utiliser la classe Renderable. Voir cet article.

– Savoir un peu utiliser DirectX 10

– Comprendre la classe SceneNode

 

Explications :

Voici le fichier MeshSceneNode.h :

#ifndef MESH_SCENE_NODE_H
#define MESH_SCENE_NODE_H

class MeshSceneNode : public SceneNode, public Renderable
{
public:
    MeshSceneNode(SceneNode* pParent = nullptr,
        const D3DXVECTOR3& position = D3DXVECTOR3(0, 0, 0),
        const D3DXVECTOR3& rotation = D3DXVECTOR3(0, 0, 0),
        const D3DXVECTOR3& scale = D3DXVECTOR3(0, 0, 0));

    virtual ~MeshSceneNode();

    virtual bool Initialize() { return true; }

    template<class T>
    bool BuildVB(std::vector<T>& vertices)
    {
        unsigned int iVertexSize = sizeof(T);

        D3D10_BUFFER_DESC vertexBufferDesc;
        D3D10_SUBRESOURCE_DATA vertexData;
 
        m_iVerticesCount = vertices.size();

        vertexBufferDesc.Usage = D3D10_USAGE_DYNAMIC;

        vertexBufferDesc.ByteWidth = iVertexSize * m_iVerticesCount;

        vertexBufferDesc.BindFlags = D3D10_BIND_VERTEX_BUFFER;
        vertexBufferDesc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
        vertexBufferDesc.MiscFlags = 0;

        vertexData.pSysMem = vertices.data();

        // Créé le vertex buffer
        HRESULT hr = D3D10_RENDERER->GetDevice()->CreateBuffer(&vertexBufferDesc, &vertexData, &m_pVertexBuffer);
 
        if (FAILED(hr))
        {
            ShowMessageBoxDXError(hr);

            return false;
        }

        return true;
    }

    template<class T>
    bool BuildIB(std::vector<T>& indices)
    {
        D3D10_BUFFER_DESC indexBufferDesc;
        D3D10_SUBRESOURCE_DATA indexData;

        HRESULT hr;
 
        m_iIndicesCount = indices.size();

        indexBufferDesc.Usage = D3D10_USAGE_DEFAULT;
        indexBufferDesc.ByteWidth = sizeof(T) * m_iIndicesCount;
        indexBufferDesc.BindFlags = D3D10_BIND_INDEX_BUFFER;
        indexBufferDesc.CPUAccessFlags = 0;
        indexBufferDesc.MiscFlags = 0;

        indexData.pSysMem = indices.data();
    
        // Créé l'index buffer
        hr = D3D10_RENDERER->GetDevice()->CreateBuffer(&indexBufferDesc, &indexData, &m_pIndexBuffer);
 
        if (FAILED(hr))
        {
            ShowMessageBoxDXError(hr);

            return false;
        }

        return true;
    }

    template<class T>
    bool Build(std::vector<T>& vertices, std::vector<unsigned short>& indices)
    {
        unsigned int iVertexSize = sizeof(T);

        D3D10_BUFFER_DESC vertexBufferDesc;
        D3D10_BUFFER_DESC indexBufferDesc;
 
        D3D10_SUBRESOURCE_DATA vertexData;
        D3D10_SUBRESOURCE_DATA indexData;
 
        HRESULT hr;
 
        m_iVerticesCount = vertices.size();
        m_iIndicesCount = indices.size();

        vertexBufferDesc.Usage = D3D10_USAGE_DYNAMIC;

        vertexBufferDesc.ByteWidth = iVertexSize * m_iVerticesCount;

        vertexBufferDesc.BindFlags = D3D10_BIND_VERTEX_BUFFER;
        vertexBufferDesc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
        vertexBufferDesc.MiscFlags = 0;

        vertexData.pSysMem = vertices.data();

        // Créé le vertex buffer
        hr = D3D10_RENDERER->GetDevice()->CreateBuffer(&vertexBufferDesc, &vertexData, &m_pVertexBuffer);
 
        if (FAILED(hr))
        {
            ShowMessageBoxDXError(hr);

            return false;
        }
 
        indexBufferDesc.Usage = D3D10_USAGE_DEFAULT;
        indexBufferDesc.ByteWidth = sizeof(unsigned short) * m_iIndicesCount;
        indexBufferDesc.BindFlags = D3D10_BIND_INDEX_BUFFER;
        indexBufferDesc.CPUAccessFlags = 0;
        indexBufferDesc.MiscFlags = 0;

        indexData.pSysMem = indices.data();
    
        // Créé l'index buffer
        hr = D3D10_RENDERER->GetDevice()->CreateBuffer(&indexBufferDesc, &indexData, &m_pIndexBuffer);
 
        if (FAILED(hr))
        {
            ShowMessageBoxDXError(hr);

            return false;
        }

        return true;
    }

    template<class T>
    void UpdateVertices(const std::vector<T> vertices)
    {
        T* data = nullptr;

        m_pVertexBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, (void**)&data);

        memcpy(data, vertices.data(), sizeof(T) * vertices.size());

        m_pVertexBuffer->Unmap();
    }
};

#endif

 

Resumé :

Nous avons présentée une classe qui permettra d’afficher du contenu 3D dans vos scène d’entités (SceneNode).

Une classe Renderable pour afficher vos contenus 3D

test_remeshing

Intro :

Pour afficher rapidement du contenu 3D, il est laborieux de déclarer à chaque fois les vertex et index buffers, le shader associé, le Vertex Input Layout, la méthode d’affichage, etc…

Cette classe suivante vous permettra de restreindre ces difficultés.

Prérequis :

– Savoir utiliser la classe ShaderTechnique. Voir cet article.

– Savoir un peu utiliser DirectX 10.

Explications :

Voici le fichier Renderable.h :

#ifndef RENDERABLE_H
#define RENDERABLE_H

class ShaderTechnique;

class Renderable
{
public:
    enum DrawMethod
    {
        DRAW_NORMAL,
        DRAW_INDEXED
    };

    Renderable();
    virtual ~Renderable();

    virtual void Render(float fTimeSinceLastFrame);

    void SetShaderTechnique(ShaderTechnique* pShader);
    ShaderTechnique* GetShaderTechnique();

    void SetVertexType(VertexLayoutType vertexType);
    void SetDrawMethod(DrawMethod drawMethod);
    void SetTopology(D3D_PRIMITIVE_TOPOLOGY topology);

    void SetRenderableName(const std::string& sName);
    std::string GetRenderableName();

protected:
    void SetVertexBuffer(ID3D10Buffer* pBuffer);
    void SetIndexBuffer(ID3D10Buffer* pBuffer);

    ID3D10Buffer* GetVertexBuffer();
    ID3D10Buffer* GetIndexBuffer();

protected:
    unsigned int m_iVerticesCount;
    unsigned int m_iIndicesCount;

    ID3D10Buffer* m_pVertexBuffer;
    ID3D10Buffer* m_pIndexBuffer;

private:
    void Draw();
    void InitVertexBuffer();
    void InitIndexBuffer();

private:
    VertexLayoutType m_vertexType;
    DrawMethod m_drawMethod;
    D3D_PRIMITIVE_TOPOLOGY m_topology;

    ShaderTechnique* m_pShader;

    std::string m_sName;
};

#endif

 

Voici le fichier Renderale.cpp :

#include "Renderable.h"

Renderable::Renderable() :
m_pShader(nullptr),
m_vertexType(VertexLayoutType::PTN_VERTEX),
m_drawMethod(DrawMethod::DRAW_NORMAL),
m_topology(D3D_PRIMITIVE_TOPOLOGY::D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST),
m_iIndicesCount(0),
m_iVerticesCount(0),
m_pVertexBuffer(nullptr),
m_pIndexBuffer(nullptr)
{
}

Renderable::~Renderable()
{
    SAFE_RELEASE(m_pIndexBuffer);
    SAFE_RELEASE(m_pVertexBuffer);
}

void Renderable::SetShaderTechnique(ShaderTechnique* pShader)
{
    Assert(pShader);

    bool bSucess = pShader->Initialize();

    if (bSucess)
    {
        m_pShader = pShader;
    }
}

ShaderTechnique* Renderable::GetShaderTechnique()
{
    return m_pShader;
}

void Renderable::Render(float fTimeSinceLastFrame)
{
    if (m_pShader == nullptr)
    {
        return;
    }

    D3D10_RENDERER->GetDevice()->IASetInputLayout(m_pShader->GetVertexLayout());
    D3D10_RENDERER->GetDevice()->IASetPrimitiveTopology(m_topology);

    InitIndexBuffer();
    InitVertexBuffer();

    m_pShader->Update(fTimeSinceLastFrame);

    D3D10_TECHNIQUE_DESC techDesc;
    ID3D10EffectTechnique* pTechnique = m_pShader->GetShaderTechnique();

    pTechnique->GetDesc(&techDesc);

    for (unsigned int p = 0; p < techDesc.Passes; p++)
    {
        pTechnique->GetPassByIndex(p)->Apply(0);
        Draw();
    }
}

void Renderable::Draw()
{
    if (m_drawMethod == DrawMethod::DRAW_NORMAL)
    {
        D3D10_RENDERER->GetDevice()->Draw(m_iVerticesCount, 0);
    }
    else if (m_drawMethod == DrawMethod::DRAW_INDEXED)
    {
        D3D10_RENDERER->GetDevice()->DrawIndexed(m_iIndicesCount, 0, 0);
    }
}

void Renderable::InitVertexBuffer()
{
    if (m_pVertexBuffer)
    {
        UINT stride = D3D10_RENDERER->GetVertexBytesSize(m_vertexType);
        UINT offset = 0;

        D3D10_RENDERER->GetDevice()->IASetVertexBuffers(0, 1, &m_pVertexBuffer, &stride, &offset);
    }
}

void Renderable::InitIndexBuffer()
{
    if (m_pIndexBuffer)
    {
        D3D10_RENDERER->GetDevice()->IASetIndexBuffer(m_pIndexBuffer, DXGI_FORMAT_R16_UINT, 0);
    }
}

void Renderable::SetDrawMethod(DrawMethod drawMethod)
{
    m_drawMethod = drawMethod;
}

void Renderable::SetTopology(D3D_PRIMITIVE_TOPOLOGY topology)
{
    m_topology = topology;
}

void Renderable::SetVertexType(VertexLayoutType vertexType)
{
    m_vertexType = vertexType;
}

void Renderable::SetRenderableName(const std::string& sName)
{
    m_sName = sName;
}

std::string Renderable::GetRenderableName()
{
    return m_sName;
}

void Renderable::SetVertexBuffer(ID3D10Buffer* pBuffer)
{
    m_pVertexBuffer = pBuffer;
}

void Renderable::SetIndexBuffer(ID3D10Buffer* pBuffer)
{
    m_pIndexBuffer = pBuffer;
}

ID3D10Buffer* Renderable::GetVertexBuffer()
{
    return m_pVertexBuffer;
}

ID3D10Buffer* Renderable::GetIndexBuffer()
{
    return m_pIndexBuffer;
}

 

Résumé :

Cette classe pourra servir à être dérivée pour afficher vos contenus 3D : image, quad de post-effect, rendu 3D, etc..

Différents types de déclarations de vertex pour DirectX 10

nvidia-quadro-graphics-card

Intro :

Dans nos programmes de rendu 3D nous avons besoin d’utiliser différents types de configurations de vertex.

Prérequis :

– Savoir utiliser les Vertex Input Layouts. Voir cet article.

Explications :

Les voici :

struct PTNVertex
{
    D3DXVECTOR3 position;
    D3DXVECTOR2 texture;
    D3DXVECTOR3 normal;
};

struct PTVertex
{
    D3DXVECTOR3 pos;
    D3DXVECTOR2 texture;
};

struct PNVertex
{
    D3DXVECTOR3 pos;
    D3DXVECTOR3 normal;
};

struct PCVertex
{
    D3DXVECTOR3 position;
    D3DXCOLOR color;
};

struct PTCVertex
{
    D3DXVECTOR3 pos;
    D3DXVECTOR2 texture;
    D3DXCOLOR color;
};

struct PTNTBVertex
{
    D3DXVECTOR3 position;
    D3DXVECTOR2 texture;
    D3DXVECTOR3 normal;
    D3DXVECTOR3 tangent;
    D3DXVECTOR3 binormal;
};

static D3D10_INPUT_ELEMENT_DESC layoutPTN[] =
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
    { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0 },
    { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 20, D3D10_INPUT_PER_VERTEX_DATA, 0 },
};

static D3D10_INPUT_ELEMENT_DESC layoutPN[] =
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
    { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0 },
};

static D3D10_INPUT_ELEMENT_DESC layoutPC[] =
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
    { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0 },
};

static D3D10_INPUT_ELEMENT_DESC layoutPTNTB[] =
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
    { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0 },
    { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 20, D3D10_INPUT_PER_VERTEX_DATA, 0 },
    { "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 32, D3D10_INPUT_PER_VERTEX_DATA, 0 },
    { "BINORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 44, D3D10_INPUT_PER_VERTEX_DATA, 0 },
};

static D3D10_INPUT_ELEMENT_DESC layoutWater[] =
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
    { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0 },
    { "TEXCOORD", 1, DXGI_FORMAT_R32G32_FLOAT, 0, 20, D3D10_INPUT_PER_VERTEX_DATA, 0 },
};

static D3D10_INPUT_ELEMENT_DESC layoutPT[] =
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
    { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0 },
};

static D3D10_INPUT_ELEMENT_DESC layoutPTC[] =
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
    { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0 },
    { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 20, D3D10_INPUT_PER_VERTEX_DATA, 0 },
};

enum VertexLayoutType
{
    PTN_VERTEX, // Position, texture, normal
    PT_VERTEX,
    PN_VERTEX,  // Position, normal
    PC_VERTEX, // Position, couleur
    PTC_VERTEX,
    PTNTB_VERTEX // Position, texture, normal, bitangente et binormale
};

 

Résumé :

Nous avons présenté différents types de configurations de vertex dont vous pouvez vous inspirer.

Une classe gestionnaire de textures pour DirectX 10

texture

Intro :

Pour gérer nos fichiers textures au sein d’un moteur 3D, nous allons utiliser un « TextureManager ».

Prérequis :

– Savoir utiliser la classe Singleton. Voir cet article

Explications :

Voici le fichier TextureManager.h :

#ifndef TEXTURE_MANAGER_H
#define TEXTURE_MANAGER_H

class TextureManager : public Singleton<TextureManager>
{
public:
    TextureManager();
    virtual ~TextureManager();

    ID3D10ShaderResourceView* AddTexture(const std::string& sTextureFileName, const std::string& sTextureAliasName);

    bool HasTexture(const std::string& sTextureAliasName);

    ID3D10ShaderResourceView* GetTexture(const std::string& sTextureAliasName);

    // Charge la texture dans la mémoire et si elle est déjà chargée, elle renvoit le pointeur de la ressource
    ID3D10ShaderResourceView* LoadTexture(const std::string& sTextureFilename);

    void RemoveTexture(const std::string& sTextureAliasName);
    void RemoveTexture(ID3D10ShaderResourceView* pTextureRV);

private:
    std::map<std::string, ID3D10ShaderResourceView*> m_texturesAlias;
};

#endif

 

Voici le fichier TextureManager.cpp :

#include "TextureManager.h"

template<> TextureManager* Singleton<TextureManager>::ms_instance = nullptr;

TextureManager::TextureManager()
{
}

TextureManager::~TextureManager()
{
    std::map<std::string, ID3D10ShaderResourceView*>::iterator it;

    for (it = m_texturesAlias.begin(); it != m_texturesAlias.end(); it++)
    {
        ID3D10ShaderResourceView* pTextureRV = (it->second);
        SAFE_RELEASE(pTextureRV);
    }
}

ID3D10ShaderResourceView* TextureManager::AddTexture(const std::string& sTextureFileName, const std::string& sTextureAliasName)
{
    HRESULT hr;

    // On charge la texture
    ID3D10ShaderResourceView* pTextureRV = nullptr;

    hr = D3DX10CreateShaderResourceViewFromFileA(D3D10_RENDERER->GetDevice(), sTextureFileName.c_str(), nullptr, nullptr, &pTextureRV, nullptr);

    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return nullptr;
    }

    if (m_texturesAlias.count(sTextureAliasName) == 0)
    {
        m_texturesAlias[sTextureAliasName] = pTextureRV;
    }
    else
    {
        std::cout << Formater(L"Texture déjà enregistrée !") << std::endl;
    }

    return pTextureRV;
}

bool TextureManager::HasTexture(const std::string& sTextureAliasName)
{
    return m_texturesAlias.count(sTextureAliasName) != 0;
}

ID3D10ShaderResourceView* TextureManager::GetTexture(const std::string& sTextureAliasName)
{
    if (m_texturesAlias.count(sTextureAliasName) != 0)
    {
        return m_texturesAlias[sTextureAliasName];
    }
    else
    {
        return nullptr;
    }
}

void TextureManager::RemoveTexture(const std::string& sTextureAliasName)
{
    Assert(m_texturesAlias.count(sTextureAliasName) > 0);

    ID3D10ShaderResourceView* pRV = m_texturesAlias[sTextureAliasName];

    SAFE_RELEASE(pRV);

    m_texturesAlias.erase(sTextureAliasName);
}

void TextureManager::RemoveTexture(ID3D10ShaderResourceView* pTextureRV)
{
    std::string sTextureAliasName = "";

    for (auto it = m_texturesAlias.begin(); it != m_texturesAlias.end(); it++)
    {        
        sTextureAliasName = it->first;

        ID3D10ShaderResourceView* pRV = it->second;

        if (pRV == pTextureRV)
        {
            SAFE_RELEASE(pRV);
        }
    }
        
    m_texturesAlias.erase(sTextureAliasName);
}

ID3D10ShaderResourceView* TextureManager::LoadTexture(const std::string& sTextureFilename)
{
    if (HasTexture(sTextureFilename))
    {
        return GetTexture(sTextureFilename);
    }
    else
    {
        return AddTexture(sTextureFilename, sTextureFilename);
    }
}

 

Résumé :

Nous avons présenté une classe gestionnaire de chargement de textures.

Obtenir la taille d’une texture ressource shader

equerre

Intro :

Parfois nous avons besoin d’obtenir la taille d’une ressource shader de texture rapidement.

Explications :

Voici comment l’obtenir :

D3DXVECTOR2 GetShaderTextureViewSize(ID3D10ShaderResourceView* texRV)
{
    Assert(texRV);

    ID3D10Texture2D* tex = nullptr;
    texRV->GetResource((ID3D10Resource**)&tex);

    D3D10_TEXTURE2D_DESC texDesc;

    tex->GetDesc(&texDesc);

    return D3DXVECTOR2((float)texDesc.Width, (float)texDesc.Height);
}

 

Résumé :

Nous avons vu comment obtenir les dimensions d’un ressource shader de texture.

Une classe ShaderTechnique pour représenter facilement vos effets shaders

shader_tech

todo : changer en m_pLayoutDesc

todo : enlever le prefixe des enums !

Intro :

Voici une classe qui vous aidera à déclarer et à définir vos fichiers shaders facilement.

Cette classe gestionnaire vous permettra de définir les variables shaders utilisées et même de paramétrer automatiquement certaines types de variables.

Explications :

Voici le fichier ShaderTechnique.h :

#ifndef SHADER_TECHNIQUE_H
#define SHADER_TECHNIQUE_H

enum ShaderVariableType
{
    WORLD,
    VIEW,
    PROJECTION,
    WVP,
    WORLD_INVERSE,
    WORLD_INVERSE_TRANSPOSE,
    LIGHT_POS,
    EYE_POS,
    TEXTURE_0,
    TEXTURE_1,
    TEXTURE_2,
    ANY
};

class ShaderTechnique    
{
public:
    ShaderTechnique(const std::string& sEffectFileName, const std::string& sEffectTechniqueName,
        VertexLayoutType layoutType = VertexLayoutType::PTN_VERTEX);
    virtual ~ShaderTechnique();

    virtual bool Initialize();

    ID3D10EffectTechnique* GetShaderTechnique();

    ID3D10InputLayout* GetVertexLayout();
    D3D10_INPUT_ELEMENT_DESC* GetLayout();

    void AddTexture(const std::string& sTextureFileName);

    void RegisterMatrixVariable(std::string sVariableName,
        ShaderVariableType variableType = ShaderVariableType::ANY);

    void RegisterVectorVariable(std::string sVariableName,
        ShaderVariableType variableType = ShaderVariableType::ANY);

    void RegisterScalarVariable(std::string sVariableName,
        ShaderVariableType variableType = ShaderVariableType::ANY);

    void RegisterVariable(std::string sVariableName,
        ShaderVariableType variableType = ShaderVariableType::ANY);

    void RegisterTextureVariable(std::string sTextureName,
        ShaderVariableType variableType = ShaderVariableType::ANY);

    void SetMatrix(const std::string& sMatrixName, D3DXMATRIX* mat);
    void SetColor(const std::string& sVectorName, D3DXCOLOR& col);
    void SetVector(const std::string& sVectorName, D3DXVECTOR3& vec);
    void SetVector(const std::string& sVectorName, D3DXVECTOR2& vec);
    void SetScalar(const std::string& sScalarName, float fValue);
    void SetVariable(const std::string& sVariableName, void* pValue, unsigned int iSizeInBytes);

    void SetTexture(const std::string& sTextureName, const std::string& sTextureAlias);
    void SetTexture(const std::string& sTextureName, unsigned int iSlot);
    void SetTextureRV(const std::string& sTextureName, ID3D10ShaderResourceView* pTextureRV);

    std::string GetShaderVariableName(ShaderVariableType variableType);

    void SetAutoMatrix(ShaderVariableType variableType);
    void SetAutoVector(ShaderVariableType variableType);

    virtual void SetupShaderVariables() {}
    virtual void Update(float fTimeSinceLastFrame) {}

    void SetCurrentWorldMatrix(const D3DXMATRIX& pWorldMatrix);

private:
    ID3D10Effect* m_pEffect;
    ID3D10EffectTechnique* m_pTechnique;

    D3D10_INPUT_ELEMENT_DESC* m_layout;

    ID3D10InputLayout* m_pVertexLayout;

    std::map<std::string, ID3D10EffectMatrixVariable*> m_matrixVariables;
    std::map<std::string, ID3D10EffectVectorVariable*> m_vectorVariables;
    std::map<std::string, ID3D10EffectScalarVariable*> m_scalarVariables;

    // On peut se servir de variables génériques afin de passer des structures
    // au programme shader
    std::map<std::string, ID3D10EffectVariable*> m_variables;

    std::map<std::string, ID3D10EffectShaderResourceVariable*> m_ressourceVariables;

    std::map<std::string, unsigned int> m_texturesAliasSlot;

    // Un tableau pour la gestion automatique des variables shader
    std::map<ShaderVariableType, std::string> m_variablesTypes;

    std::string m_sEffectFileName;
    std::string m_sEffectTechniqueName;
    VertexLayoutType m_layoutType;

    bool m_bInitDone;

    D3DXMATRIX m_pCurrentWorld;
    unsigned int m_iCurrentTextureSlot;    

    static unsigned int m_iCurrentShaderTechnique;
};

#endif

 

Voici le fichier ShaderTechnique.cpp :

#include "ShaderTechnique.h"

unsigned int ShaderTechnique::m_iCurrentShaderTechnique = 0;

ShaderTechnique::ShaderTechnique(const std::string& sEffectFileName, const std::string& sEffectTechniqueName, VertexLayoutType layoutType) :
m_pEffect(nullptr),
m_pTechnique(nullptr),
m_pVertexLayout(nullptr),
m_layout(nullptr),
m_bInitDone(false),
m_sEffectFileName(sEffectFileName),
m_sEffectTechniqueName(sEffectTechniqueName),
m_layoutType(layoutType),
m_iCurrentTextureSlot(0)
{
    D3DXMatrixIdentity(&m_pCurrentWorld);
    m_iCurrentShaderTechnique++;
}

ShaderTechnique::~ShaderTechnique()
{
    SAFE_RELEASE(m_pEffect);
    SAFE_RELEASE(m_pVertexLayout);
}

bool ShaderTechnique::Initialize()
{
    if (m_bInitDone)
    {
        return false;
    }

    DWORD dwShaderFlags = D3D10_SHADER_ENABLE_STRICTNESS;
 
#if defined( DEBUG ) || defined( _DEBUG )
// Permet d'afficher les éventuelles erreurs de la compilation d'un shader
dwShaderFlags |= D3D10_SHADER_DEBUG;
#endif

    HRESULT hr;
 
    ID3D10Blob* pBlob = nullptr;
    // Pour la création du shader, on le compile
    hr = D3DX10CreateEffectFromFileA(m_sEffectFileName.c_str(), nullptr, nullptr, "fx_4_0", dwShaderFlags, 0, D3D10_RENDERER->GetDevice(), nullptr,
                                    nullptr, &m_pEffect, &pBlob, nullptr);
 
    if (FAILED(hr))
    {
        if (pBlob)
        {
            // Affiche l'erreur de compilation du shader
            MessageBoxA(NULL, (PCSTR)pBlob->GetBufferPointer(), "Erreur sur le fichier shader", MB_ICONHAND | MB_OK);
        }
        else
        {
            ShowMessageBoxDXError(hr);
        }
 
        return false;
    }

    unsigned int iNumElements = 0;

    if (m_layoutType == PTN_VERTEX) // Position, texture, normal
    {
        m_layout = new D3D10_INPUT_ELEMENT_DESC[3];

        memcpy(m_layout, layoutPTN, sizeof(D3D10_INPUT_ELEMENT_DESC) * 3);
        iNumElements = sizeof( layoutPTN ) / sizeof( layoutPTN[0] );
    }
    else if (m_layoutType == PT_VERTEX)
    {
        m_layout = new D3D10_INPUT_ELEMENT_DESC[2];

        memcpy(m_layout, layoutPT, sizeof(D3D10_INPUT_ELEMENT_DESC) * 2);
        iNumElements = sizeof( layoutPT ) / sizeof( layoutPT[0] );
    }
    else if (m_layoutType == PN_VERTEX) // Position, Normal
    {
        m_layout = new D3D10_INPUT_ELEMENT_DESC[2];

        memcpy(m_layout, layoutPN, sizeof(D3D10_INPUT_ELEMENT_DESC) * 2);
        iNumElements = sizeof( layoutPN ) / sizeof( layoutPN[0] );
    }
    else if (m_layoutType == PC_VERTEX) // Position, couleur
    {
        m_layout = new D3D10_INPUT_ELEMENT_DESC[2];

        memcpy(m_layout, layoutPC, sizeof(D3D10_INPUT_ELEMENT_DESC) * 2);
        iNumElements = sizeof( layoutPC ) / sizeof( layoutPC[0] );
    }
    else if (m_layoutType == PTC_VERTEX) // Position, couleur
    {
        m_layout = new D3D10_INPUT_ELEMENT_DESC[3];

        memcpy(m_layout, layoutPTC, sizeof(D3D10_INPUT_ELEMENT_DESC) * 3);
        iNumElements = sizeof( layoutPTC ) / sizeof( layoutPTC[0] );
    }
    else if (m_layoutType == WATER_VERTEX)
    {
        m_layout = new D3D10_INPUT_ELEMENT_DESC[3];

        memcpy(m_layout, layoutWater, sizeof(D3D10_INPUT_ELEMENT_DESC) * 3);
        iNumElements = sizeof( layoutWater ) / sizeof( layoutWater[0] );
    }
    else
    {
        FastMessageBox("Erreur de type de layout !");

        return false;
    }

    // On acquiert la technique du shader HLSL
    m_pTechnique = m_pEffect->GetTechniqueByName(m_sEffectTechniqueName.c_str());

    if (m_pTechnique == nullptr)
    {
        FastMessageBox("Erreur du nom de la technique passé en paramètre");

        return false;
    }

    D3D10_PASS_DESC PassDesc;
    m_pTechnique->GetPassByIndex(0)->GetDesc(&PassDesc);

    if (!m_pTechnique->IsValid())
    {
        std::string sErrorMsg = "Erreur sur la technique du fichier shader : " + m_sEffectFileName;

        FastMessageBox(sErrorMsg.c_str());

        return false;
    }

    hr = D3D10_RENDERER->GetDevice()->CreateInputLayout(m_layout, iNumElements, PassDesc.pIAInputSignature,
                                PassDesc.IAInputSignatureSize, &m_pVertexLayout);

    if (FAILED(hr))    
    {
        ShowMessageBoxDXError(hr);

        return false;
    }

    m_bInitDone = true;

    return true;
}

ID3D10EffectTechnique* ShaderTechnique::GetShaderTechnique()
{
    return m_pTechnique;
}

ID3D10InputLayout* ShaderTechnique::GetVertexLayout()
{
    return m_pVertexLayout;
}

D3D10_INPUT_ELEMENT_DESC* ShaderTechnique::GetLayout()
{
    return m_layout;
}

// Ajoute une texture au gestionnaire de texture et fait correspondre
// un slot à cette classe
void ShaderTechnique::AddTexture(const std::string& sTextureFileName)
{
    std::string sTextureAliasName = std::to_string(m_iCurrentShaderTechnique) + std::to_string(m_iCurrentTextureSlot++);

    m_texturesAliasSlot[sTextureAliasName] = m_iCurrentTextureSlot;

    TEXTURE_MANAGER->AddTexture(sTextureFileName, sTextureAliasName);
}

void ShaderTechnique::RegisterTextureVariable(std::string sTextureName, ShaderVariableType variableType)
{
    ID3D10EffectShaderResourceVariable* pDiffuseVariable = nullptr;

    pDiffuseVariable = m_pEffect->GetVariableByName(sTextureName.c_str())->AsShaderResource();

    m_ressourceVariables[sTextureName] = pDiffuseVariable;
}

void ShaderTechnique::RegisterMatrixVariable(std::string sVariableName, ShaderVariableType variableType)
{
    ID3D10EffectMatrixVariable* pMatrixVariable = nullptr;

    pMatrixVariable = m_pEffect->GetVariableByName(sVariableName.c_str())->AsMatrix();

    m_matrixVariables[sVariableName] = pMatrixVariable;

    m_variablesTypes[variableType] = sVariableName;
}

void ShaderTechnique::RegisterVectorVariable(std::string sVariableName, ShaderVariableType variableType)
{
    ID3D10EffectVectorVariable* pVectorVariable = nullptr;

    pVectorVariable = m_pEffect->GetVariableByName(sVariableName.c_str())->AsVector();

    m_vectorVariables[sVariableName] = pVectorVariable;

    m_variablesTypes[variableType] = sVariableName;
}

void ShaderTechnique::RegisterScalarVariable(std::string sVariableName, ShaderVariableType variableType)
{
    ID3D10EffectScalarVariable* pScalarVariable = nullptr;

    pScalarVariable = m_pEffect->GetVariableByName(sVariableName.c_str())->AsScalar();

    m_scalarVariables[sVariableName] = pScalarVariable;

    m_variablesTypes[variableType] = sVariableName;
}

void ShaderTechnique::RegisterVariable(std::string sVariableName, ShaderVariableType variableType)
{
    ID3D10EffectVariable* pVariable = nullptr;

    pVariable = m_pEffect->GetVariableByName(sVariableName.c_str());

    m_variables[sVariableName] = pVariable;

    m_variablesTypes[variableType] = sVariableName;
}

void ShaderTechnique::SetMatrix(const std::string& sMatrixName, D3DXMATRIX* mat)
{
    if (m_matrixVariables.count(sMatrixName) > 0)
    {
        m_matrixVariables[sMatrixName]->SetMatrix((float*)mat);
    }
}

void ShaderTechnique::SetColor(const std::string& sVectorName, D3DXCOLOR& col)
{
    if (m_vectorVariables.count(sVectorName) > 0)
    {
        m_vectorVariables[sVectorName]->SetFloatVector(col);
    }
}

void ShaderTechnique::SetVector(const std::string& sVectorName, D3DXVECTOR3& vec)
{
    if (m_vectorVariables.count(sVectorName) > 0)
    {
        m_vectorVariables[sVectorName]->SetFloatVector(vec);
    }
}

void ShaderTechnique::SetVector(const std::string& sVectorName, D3DXVECTOR2& vec)
{
    if (m_vectorVariables.count(sVectorName) > 0)
    {
        m_vectorVariables[sVectorName]->SetFloatVector(vec);
    }
}

void ShaderTechnique::SetScalar(const std::string& sScalarName, float fValue)
{
    if (m_scalarVariables.count(sScalarName) > 0)
    {
        m_scalarVariables[sScalarName]->SetFloat(fValue);
    }
}

void ShaderTechnique::SetVariable(const std::string& sVariableName, void* pValue, unsigned int iSizeInBytes)
{
    if (m_variables.count(sVariableName) > 0)
    {
        m_variables[sVariableName]->SetRawValue(pValue, 0, iSizeInBytes);
    }
}

void ShaderTechnique::SetTexture(const std::string& sTextureName, unsigned int iSlot)
{
    std::string sTextureAliasName = std::to_string(m_iCurrentShaderTechnique) + std::to_string(iSlot);

    if (m_texturesAliasSlot.count(sTextureAliasName) > 0)
    {
        ID3D10ShaderResourceView* pTextureRV = TEXTURE_MANAGER->GetTexture(sTextureAliasName);

        if (pTextureRV != nullptr)
        {
            m_ressourceVariables[sTextureName]->SetResource(pTextureRV);
        }
    }
}

void ShaderTechnique::SetTexture(const std::string& sTextureName, const std::string& sTextureAlias)
{
    if (TEXTURE_MANAGER->HasTexture(sTextureAlias))
    {
        ID3D10ShaderResourceView* pTextureRV = TEXTURE_MANAGER->GetTexture(sTextureAlias);

        if (pTextureRV != nullptr)
        {
            m_ressourceVariables[sTextureName]->SetResource(pTextureRV);
        }
    }
}

void ShaderTechnique::SetTextureRV(const std::string& sTextureName, ID3D10ShaderResourceView* pTextureRV)
{
    if (m_ressourceVariables.count(sTextureName) > 0)
    {
        m_ressourceVariables[sTextureName]->SetResource(pTextureRV);
    }
}

std::string ShaderTechnique::GetShaderVariableName(ShaderVariableType variableType)
{
    if (m_variablesTypes.count(variableType))
    {
        return m_variablesTypes[variableType];
    }
    else
    {
        return UNKNOWN;
    }
}

void ShaderTechnique::SetCurrentWorldMatrix(const D3DXMATRIX& pWorldMatrix)
{
    m_pCurrentWorld = pWorldMatrix;
}

void ShaderTechnique::SetAutoMatrix(ShaderVariableType variableType)
{
    std::string sVariableName = GetShaderVariableName(variableType);

    switch (variableType)
    {
        case ShaderVariableType::WORLD:
        {
            SetMatrix(sVariableName, &m_pCurrentWorld);
            break;
        }

        case ShaderVariableType::VIEW:
        {
            SetMatrix(sVariableName, SCENE_MANAGER->GetActiveCamera()->GetViewMatrix());
            break;
        }

        case ShaderVariableType::PROJECTION:
        {
            SetMatrix(sVariableName, SCENE_MANAGER->GetActiveCamera()->GetProjectionMatrix());
            break;
        }
        case ShaderVariableType::WORLD_INVERSE:
        {
            D3DXMATRIX worldInverse;
            D3DXMatrixIdentity(&m_pCurrentWorld);

            D3DXMatrixInverse(&worldInverse, nullptr, &m_pCurrentWorld);

            SetMatrix(sVariableName, &worldInverse);

            break;
        }

        case ShaderVariableType::WORLD_INVERSE_TRANSPOSE:
        {
            D3DXMATRIX worldInverseTranspose;

            D3DXMatrixTranspose(&worldInverseTranspose, &worldInverseTranspose);
            D3DXMatrixInverse(&worldInverseTranspose, nullptr, &m_pCurrentWorld);

            SetMatrix(sVariableName, &worldInverseTranspose);
            break;
        }
    }
}

void ShaderTechnique::SetAutoVector(ShaderVariableType variableType)
{
    std::string sVariableName = GetShaderVariableName(variableType);

    switch (variableType)
    {
        case ShaderVariableType::EYE_POS:
        {
            SetVector(sVariableName, SCENE_MANAGER->GetActiveCamera()->GetPosition());
            break;
        }

        case ShaderVariableType::LIGHT_POS:
        {
            static float r = 0.0f;
            r += 0.00008f;

            D3DXVECTOR3 lightPos = D3D10_RENDERER->MatrixRotationAxis(D3DXVECTOR3(0.0f, 1.0f, 0.0f), D3DXVECTOR3(0.0f, 0.0f, -5.0f), r);

            SetVector(sVariableName, lightPos);
            break;
        }
    }
}

 

Résumé :

Cette classe vous permettra de paramétrer vos fichiers shaders facilement.

Entrées clavier et souris avec DirectInput

 

souris

Intro :

DirectInput est un composant de DirectX qui permet de gérer les entrées clavier (touches) et de la souris (position).

Bien qu’il soit tendance à n’être plus utilisé, il est toujours utile car il permet de capturer directement les entrées clavier à partir des drivers périphériques correspondants.

Prérequis :

– Savoir un peu lire du C++.

– Savoir initialiser DirectX 10.1.

– Savoir utiliser la classe Singleton.

Explications :

Voici le fichier InputManager.h :

#ifndef INPUT_MANAGER_H
#define INPUT_MANAGER_H

#define DIRECTINPUT_VERSION 0x0800

#include <dinput.h>
#include <map>
#include <vector>

#include "Singleton.h"
#include "System.h"

class KeyEvent
{
public:
    KeyEvent(int kc, wchar_t txt) : key(kc), text(txt) {}
    virtual ~KeyEvent() {}

    // L'ID de la touche
    const int key;
    // La caractère correspondant de la touche
    const wchar_t text;
};

class KeyListener
{
public:
    virtual ~KeyListener() {}

    virtual void RepetitiveKeyPressed(const KeyEvent &arg) = 0;
    virtual void RepetitiveKeyReleased(const KeyEvent &arg) = 0;

    virtual void SoloKeyPressed(const KeyEvent &arg) = 0;
    virtual void SoloKeyReleased(const KeyEvent &arg) = 0;
};

class InputManager : public Singleton<InputManager>
{
public:
    enum Modifier
    {
        SHIFT = 0x0000001,
        CTRL  = 0x0000010,
        ALT   = 0x0000100
    };

    InputManager();
    virtual ~InputManager();

    bool Initialize(HINSTANCE hInstance, HWND hwnd, int iScreenWidth, int iScreenHeight);

    void Frame();

    void GetMouseLocation(int& mouseX, int& mouseY);

    bool IsKeyDown(char key);
    bool IsMouseButtonDown(unsigned int button);
    bool IsModifierDown(Modifier modifier);

    void SetBuffered(bool bBuffered);

    // Enregistre le receveur de touches clavier
    void SetKeyListener(KeyListener* pKeyListener);

    // Renvoie la signification de la clé, ex : "F1" pour la touche F1
    std::string GetKeyCodeAsString(char key);

    // Renvoie la touche correspondante à la clé
    wchar_t GetKeyCodeAsWChar(char key);

private:    
    bool ReadKeyboard();
    bool ReadMouse();
    void ProcessInput();

    void UpdateBuffered();
    void UpdateNonBuffered();

private:
    IDirectInput8* m_pDI;
    IDirectInputDevice8* m_pKeyboard;
    IDirectInputDevice8* m_pMouse;

    unsigned char m_keyboardState[256];

    DIMOUSESTATE m_mouseState;

    int m_iScreenWidth;
    int m_iScreenHeight;

    int m_iMouseX;
    int m_iMouseY;

    wchar_t m_deadkey;

    bool m_bBufferedMode;

    unsigned int m_iModifiers;

    std::map<int, bool> m_pressedKeys;
 
    /* Pourquoi ici un unique objet ?
    Car on a besoin que d'un seul KeyListener car
    c'est soit la console, soit la messagerie qui ont besoin des entrées clavier
    à la fois */
    KeyListener* m_pKeyListener;

    // Variables conçues pour que l'appel à la callback KeyListener soit appelée moins de fois dans le temps
    unsigned long m_iElapsedTime;
    unsigned long m_iLastTime;
    unsigned int m_iWaitTime;

    // Variables conçues pour la touche appuyée soit repétée quand on reste longtemps appuyée dessus
    unsigned long m_iElapsedTime2;
    unsigned long m_iLastTime2;
    unsigned int m_iWaitTime2;

    bool m_bCanRepeatKey;
    unsigned int m_iCurrentKeyCode;
};

#endif

 

Voici le fichier InputManager.cpp :


#include "Console.h"
#include "InputManager.h"

#include <sstream>
#include <DxErr.h>

template<> InputManager* Singleton<InputManager>::ms_instance = nullptr;

InputManager::InputManager() :
m_pDI(nullptr),
m_pKeyboard(nullptr),
m_pMouse(nullptr),
m_iMouseX(0),
m_iMouseY(0),
m_deadkey('\0'),
m_bBufferedMode(false),
m_iModifiers(0),
m_pKeyListener(nullptr),
m_bCanRepeatKey(false),
m_iWaitTime(INPUT_MANAGER_REPETITIVE_WAIT_TIME),
m_iLastTime(0),
m_iElapsedTime(0),
m_iWaitTime2(INPUT_MANAGER_REPETITIVE_WAIT_TIME_2),
m_iLastTime2(0),
m_iElapsedTime2(0),
m_iCurrentKeyCode(0)
{
    SetBuffered(true);
}

InputManager::~InputManager()
{
    if (m_pMouse)
    {
        m_pMouse->Unacquire();
        SAFE_RELEASE(m_pMouse);
    }

    if (m_pKeyboard)
    {
        m_pKeyboard->Unacquire();
        SAFE_RELEASE(m_pKeyboard);
    }

    if (m_pDI)
    {
        SAFE_RELEASE(m_pDI);
    }

}

bool InputManager::Initialize(HINSTANCE hInstance, HWND hwnd, int iScreenWidth, int iScreenHeight)
{
    HRESULT hr;

    m_iScreenWidth = iScreenWidth;
    m_iScreenHeight = iScreenHeight;
    
    /****** Setup du clavier ******/
    hr= DirectInput8Create(hInstance, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&m_pDI, nullptr);
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    hr = m_pDI->CreateDevice(GUID_SysKeyboard, &m_pKeyboard, nullptr);
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    hr = m_pKeyboard->SetDataFormat(&c_dfDIKeyboard);
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    hr = m_pKeyboard->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    /***********/
    
    // On configure la taille du buffer qui sert à sauvegarder l'état
    // des touches du clavier entrées
    DIPROPDWORD dipdw;
    dipdw.diph.dwSize  = sizeof(DIPROPDWORD);
    dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
    dipdw.diph.dwObj = 0;
    dipdw.diph.dwHow = DIPH_DEVICE;
    dipdw.dwData = INPUT_MANAGER_BUFFER_SIZE;

    hr = m_pKeyboard->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph);

    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    /***********/
    hr = m_pKeyboard->Acquire();
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    /****** Setup de la souris ******/

    hr = m_pDI->CreateDevice(GUID_SysMouse, &m_pMouse, nullptr);
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    hr = m_pMouse->SetDataFormat(&c_dfDIMouse);
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    hr = m_pMouse->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    hr = m_pMouse->Acquire();
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    return true;
}

void InputManager::SetKeyListener(KeyListener* pKeyListener)
{
    assert(pKeyListener);

    m_pKeyListener = pKeyListener;
}

bool InputManager::IsModifierDown(Modifier modifier)
{
    return m_iModifiers & MKF_MODIFIERS;
}

void InputManager::Frame()
{
    ReadKeyboard();

    ReadMouse();

    ProcessInput();
}

void InputManager::GetMouseLocation(int& mouseX, int& mouseY)
{
    mouseX = m_iMouseX;
    mouseY = m_iMouseY;
}

bool InputManager::ReadKeyboard()
{
    if (m_bBufferedMode)
    {
        UpdateBuffered();
    }
    else
    {
        UpdateNonBuffered();
    }

    return true;
}

/* setcapture & getcapture (essayer) */
void InputManager::UpdateBuffered()
{
    DIDEVICEOBJECTDATA diBuff[INPUT_MANAGER_BUFFER_SIZE];
    DWORD entries = INPUT_MANAGER_BUFFER_SIZE;

    HRESULT hr;
    hr = m_pKeyboard->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), diBuff, &entries, 0);

    if (FAILED(hr))
    {
        if ((hr == DIERR_INPUTLOST) || (hr == DIERR_NOTACQUIRED))
        {
            ZeroMemory(m_keyboardState, sizeof(m_keyboardState));
            m_pKeyboard->Acquire();
        }
    }

    // Pour la touche soit repétée quand on reste longtemps appuyée dessus
    if (m_iCurrentKeyCode != 0)
    {
        m_iElapsedTime2 = GetTickCount() - m_iLastTime2;
        if (m_iElapsedTime2 > m_iWaitTime2)
        {
            m_bCanRepeatKey = true; 

            m_iLastTime2 = GetTickCount();
        }
    }
    else
    {
        // On reset le timer au-dessus
        m_iLastTime2 = GetTickCount();
    }

    if (!SYSTEM->HasFocus())
    {
        m_bCanRepeatKey = false;
        m_iElapsedTime2 = 0;
        m_iCurrentKeyCode = 0;
    }

    // On parcourt les touches appuyées
    for (unsigned int i = 0; i < entries; i++)
    {
        int iKeyCode = diBuff[i].dwOfs;

        if (diBuff[i].dwData & 0x80)
        {        
            m_pressedKeys[iKeyCode] = true;

            m_iCurrentKeyCode = diBuff[i].dwData;    

            if (iKeyCode == DIK_LCONTROL || iKeyCode == DIK_RCONTROL)
            {
                m_iModifiers |= CTRL;
            }
            else if (iKeyCode == DIK_LSHIFT || iKeyCode == DIK_RSHIFT)
            {
                m_iModifiers |= SHIFT;
            }
            else if (iKeyCode == DIK_LMENU || iKeyCode == DIK_RMENU)
            {
                m_iModifiers |= ALT;
            }

            if (m_pKeyListener)
            {
                m_pKeyListener->SoloKeyPressed( KeyEvent(iKeyCode, GetKeyCodeAsWChar(iKeyCode)) );
            }
        }
        else
        {
            m_pressedKeys[iKeyCode] = false;
            m_bCanRepeatKey = false;
            m_iCurrentKeyCode = 0;

            if (iKeyCode == DIK_LCONTROL || iKeyCode == DIK_RCONTROL)
            {
                m_iModifiers &= ~CTRL;
            }
            else if (iKeyCode == DIK_LSHIFT || iKeyCode == DIK_RSHIFT)
            {
                m_iModifiers &= ~SHIFT;
            }
            else if (iKeyCode == DIK_LMENU || iKeyCode == DIK_RMENU)
            {
                m_iModifiers &= ~ALT;
            }

            if (m_pKeyListener)
            {
                m_pKeyListener->SoloKeyReleased( KeyEvent(iKeyCode, GetKeyCodeAsWChar(iKeyCode)) );
            }
        }
    }

    /********** Entrées répétées ***********/

    // On cherche quelle touche du clavier a été appuyée
    int iKeyCode = 0;
    for (auto it = m_pressedKeys.begin(); it != m_pressedKeys.end(); it++)
    {
        bool bPressed = it->second;
        if (bPressed)
        {
            iKeyCode = it->first;
        }
    }

    if (m_pKeyListener && iKeyCode != 0)
    {
        m_iElapsedTime = GetTickCount() - m_iLastTime;

        // On appel la callback a une certaine fréquence de temps
        if (m_iElapsedTime > m_iWaitTime)
        {
            m_pKeyListener->RepetitiveKeyPressed( KeyEvent(iKeyCode, GetKeyCodeAsWChar(iKeyCode)) );

            if (m_bCanRepeatKey)
            {
                m_pKeyListener->SoloKeyPressed( KeyEvent(iKeyCode, GetKeyCodeAsWChar(iKeyCode)) );
            }

            m_iLastTime = GetTickCount();
        }
    }
}

void InputManager::UpdateNonBuffered()
{    
    HRESULT hr;

    hr = m_pKeyboard->GetDeviceState(sizeof(m_keyboardState), (LPVOID)&m_keyboardState);

    if (FAILED(hr))
    {
        if ((hr == DIERR_INPUTLOST) || (hr == DIERR_NOTACQUIRED))
        {
            ZeroMemory(m_keyboardState, sizeof(m_keyboardState));
            m_pKeyboard->Acquire();
        }
    }
}

bool InputManager::ReadMouse()
{
    HRESULT hr;

    hr = m_pMouse->GetDeviceState(sizeof(DIMOUSESTATE), (LPVOID)&m_mouseState);

    if (FAILED(hr))
    {
        if ((hr == DIERR_INPUTLOST) || (hr == DIERR_NOTACQUIRED))
        {
            m_pMouse->Acquire();
        }
        else
        {
            return false;
        }
    }

    return true;
}

void InputManager::ProcessInput()
{
    m_iMouseX += m_mouseState.lX;
    m_iMouseY += m_mouseState.lY;

    if (m_iMouseX < 0)
    {
        m_iMouseX = 0;
    }

    if (m_iMouseY < 0)  
    {
        m_iMouseY = 0;
    }
    
    if (m_iMouseX > m_iScreenWidth)  
    {
        m_iMouseX = m_iScreenWidth;
    }

    if (m_iMouseY > m_iScreenHeight)
    {
        m_iMouseY = m_iScreenHeight;
    }
    
    return;
}

bool InputManager::IsKeyDown(char key)
{
    return KEYDOWN(m_keyboardState, key);
}

bool InputManager::IsMouseButtonDown(unsigned int button)
{
    return (m_mouseState.rgbButtons[button] & 0x80) != 0;
}

std::string InputManager::GetKeyCodeAsString(char key)
{
    std::string sKeyCode;

    char temp[256];

    DIPROPSTRING prop;
    prop.diph.dwSize = sizeof(DIPROPSTRING);
    prop.diph.dwHeaderSize = sizeof(DIPROPHEADER);
    prop.diph.dwObj = static_cast<DWORD>(key);
    prop.diph.dwHow = DIPH_BYOFFSET;

    if (SUCCEEDED(m_pKeyboard->GetProperty(DIPROP_KEYNAME, &prop.diph)))
    {
        if (WideCharToMultiByte(CP_ACP, 0, prop.wsz, -1, temp, sizeof(temp), nullptr, nullptr))
        {
            return sKeyCode.assign(temp);
        }
    }

    std::stringstream ss;
    ss << "Key_" << (int) key;

    return sKeyCode.assign(ss.str());
}

wchar_t InputManager::GetKeyCodeAsWChar(char key)
{
    BYTE keyState[256];
    HKL layout = GetKeyboardLayout(0);

    if (GetKeyboardState(keyState) == 0)
    {
        return 0;
    }

    unsigned int vk = MapVirtualKeyEx(key, 3, layout);
    if (vk == 0)
    {
        return 0;
    }

    WCHAR buff[3] = {0};
    int ascii = ToUnicodeEx(vk, key, keyState, buff, 3, 0, layout);

    if(ascii == 1 && m_deadkey != '\0' )
    {
        WCHAR wcBuff[3] = {buff[0], m_deadkey, '\0'};
        WCHAR out[3];
        
        m_deadkey = '\0';
        if (FoldStringW(MAP_PRECOMPOSED, (LPWSTR)wcBuff, 3, (LPWSTR)out, 3))
        {
            return out[0];
        }
    }
    else if (ascii == 1)
    {        
        m_deadkey = '\0';
        return buff[0];
    }
    else if (ascii == 2)
    {
        switch (buff[0])    
        {
            case 0x5E: // Circumflex accent: â
                m_deadkey = 0x302;
                break;
            case 0x60: // Grave accent: à
                m_deadkey = 0x300;
                break;
            case 0xA8: // Diaeresis: ü
                m_deadkey = 0x308;
                break;
            case 0xB4: // Acute accent: é
                m_deadkey = 0x301;
                break;
            case 0xB8: // Cedilla: ç
                m_deadkey = 0x327;
                break;
            default:
                m_deadkey = buff[0];
                break;
        }
    }

    return 0;
}

void InputManager::SetBuffered(bool bBuffered)
{
    m_bBufferedMode = bBuffered;
}

 

Résumé :

Avec l’interface DirectInput, on peut élaborer un système d’entrée clavier souris tout simple.

Entrées clavier et souris avec la boucle de message WndProc

keyboard

todo : remplacer les scancodes par les virtual codes

Intro :

A l’inverse de l’utilisation du composant DirectInput de DirectX, nous pouvons directement utiliser les réponses de la boucle de message WndProc en tant qu’entrées clavier-souris.

Cet article fournira la classe InputManager utilisée dans d’autres articles.

Explications :

Voici le code pour le fichier InputManager.h

#ifndef INPUT_MANAGER_H
#define INPUT_MANAGER_H

#include <windows.h>
#include <map>
#include <vector>
#include <sstream>
#include <iostream>

#define BIT(x) 1 << x

#include "Singleton.h"

// Objet d'entrée clavier qui sera envoyé au KeyListener enregistré
// dans la classe InputManager
class KeyEvent
{
public:
    KeyEvent(int kc, wchar_t txt) : keyCode(kc), text(txt) {}
    virtual ~KeyEvent() {}

    // L'ID de la touche
    const int keyCode;

    // La caractère correspondant de la touche
    const wchar_t text;
};

// Classe KeyListener qui permet à n'importe quel objet héritant de
// cette classe de recevoir les entrées clavier
class KeyListener
{
public:
    virtual ~KeyListener() {}

    virtual void OnKeyPressed(const KeyEvent &arg) = 0;
    virtual void OnKeyReleased(const KeyEvent &arg) = 0;
};

class InputManager : public Singleton<InputManager>
{
public:
    // Touches spéciales
    enum KeyModifier
    {
        SHIFT = BIT(0),
        CTRL  = BIT(1),
        ALT   = BIT(2)
    };

    // Bouttons de la souris
    enum MouseButton
    {
        MouseButtonLeft,
        MouseButtonRight,
        MouseButtonMiddle,
        MouseButtoNone
    };

    InputManager();
    virtual ~InputManager();

    // Méthodes d'enregistrements des entrées clavier de la boucle
    // de message Win32 WndProc
    void InjectKeyDown(unsigned int iVirtualKeyCode, unsigned int iScanCode);
    void InjectKeyUp(unsigned int iVirtualKeyCode, unsigned int iScanCode);
    void InjectKeyModifierDown(KeyModifier modifier);
    void InjectKeyModifierUp(KeyModifier modifier);

    // Méthodes d'enregistrements des entrées souris de la boucle
    // de message Win32 WndProc
    void InjectMousePosition(int iMouseX, int iMouseY);
    void InjectMouseButtonDown(MouseButton button);
    void InjectMouseButtonUp();

    // Méthodes de récupérations de la position de la souris
    void GetMouseAbsoluteLocation(int& iMouseX, int& iMouseY);
    void GetMouseRelativePosition(float& fX, float& fY);

    // Méthodes de récupérations des entrées clavier
    bool IsKeyDown(unsigned int key);
    bool IsKeyUp(unsigned int key);
    bool IsMouseButtonDown(unsigned int button);
    bool IsKeyModifierDown(KeyModifier modifier);

    // Enregistre le receveur de touches clavier
    void SetKeyListener(KeyListener* pKeyListener);

    // Renvoie le caractère wchar_t correspondant à la clé spécifié
    wchar_t GetKeyCodeAsWChar(char key);

    // Renvoie une chaîne de caractère correspondant à la clé spécifié
    std::string GetKeyCodeAsString(char key);

    // Met le curseur au centre de la fenêtre
    void CenterMouseCursor();

    // Je place cette fonction utilitaire au sein même
    // de la classe, mais sa place serait plus juste ailleurs
    std::string Formater(const std::wstring& src)
    {
        char outString[512];
        CharToOemW(src.c_str(), outString);

        return std::string(outString);
    }

private:
    // Coordonnées de la souris
    int m_iMouseX;
    int m_iMouseY;

    // Variable représentant les combinaison de touches
    // de modification
    unsigned int m_iModifiers;

    // Touches préssées
    std::map<unsigned int, bool> m_pressedKeys;
 
    // L'écouteur d'entrée clavier
    KeyListener* m_pKeyListener;

    // Le boutton de la souris^^
    MouseButton m_iMouseButton;

    // variable ppour gérer les accents comme :
    // û, ì, ò, ô, etc...
    wchar_t m_deadKey;
};

#endif

 

Voici le code pour le fichier InputManager.cpp :

#include "InputManager.h"
#include "System.h"

#include <sstream>

template<> InputManager* Singleton<InputManager>::ms_instance = nullptr;

InputManager::InputManager() :
m_iMouseX(0),
m_iMouseY(0),
m_iModifiers(0),
m_pKeyListener(nullptr),
m_iMouseButton(MouseButton::MouseButtoNone),
m_deadKey('\0')
{
}

InputManager::~InputManager()
{
}

void InputManager::SetKeyListener(KeyListener* pKeyListener)
{
    assert(pKeyListener);

    m_pKeyListener = pKeyListener;
}

bool InputManager::IsKeyModifierDown(KeyModifier modifier)
{
    return m_iModifiers & modifier;
}

void InputManager::InjectKeyModifierDown(KeyModifier modifier)
{
    m_iModifiers |= modifier;
}

void InputManager::InjectKeyModifierUp(KeyModifier modifier)
{
    m_iModifiers &= ~(modifier);
}

void InputManager::InjectKeyDown(unsigned int iVirtualKeyCode, unsigned int iScanCode)
{
    m_pressedKeys[iVirtualKeyCode] = true;

    if (m_pKeyListener != nullptr)
    {
        wchar_t c = GetKeyCodeAsWChar(iScanCode);

        KeyEvent keyEvent(iScanCode, c);
        m_pKeyListener->OnKeyPressed(keyEvent);

    }
}

void InputManager::InjectKeyUp(unsigned int iVirtualKeyCode, unsigned int iScanCode)
{
    m_pressedKeys[iVirtualKeyCode] = false;

    if (m_pKeyListener != nullptr)
    {
        wchar_t c = GetKeyCodeAsWChar(iScanCode);

        KeyEvent keyEvent(iScanCode, c);
        m_pKeyListener->OnKeyReleased(keyEvent);
    }
}

void InputManager::InjectMousePosition(int iMouseX, int iMouseY)
{
    m_iMouseX = iMouseX;
    m_iMouseY = iMouseY;
}

void InputManager::GetMouseAbsoluteLocation(int& mouseX, int& mouseY)
{
    mouseX = m_iMouseX;
    mouseY = m_iMouseY;
}

// todo : essayer avec les m_iMouseX et m_iMouseY
void InputManager::GetMouseRelativePosition(float& fX, float& fY)
{
    POINT p;
    if (!GetCursorPos(&p))
    {
        DWORD xy = GetMessagePos();
        p.x = LOWORD(xy);
        p.y = HIWORD(xy);
    }

    int borderX = GetSystemMetrics(SM_CXSIZEFRAME);
    int borderY = GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYSIZEFRAME);

    RECT rect;
    GetWindowRect(SYSTEM->GetHwnd(), &rect);

    float x = p.x - rect.left - borderX;
    float y = p.y - rect.top - borderY;

    fX = ((float) x / (float) SYSTEM->GetWindowWidth()) - 0.5f;
    fY = ((float) y / (float) SYSTEM->GetWindowHeight()) - 0.5f;
}

bool InputManager::IsKeyDown(unsigned int key)
{
    return m_pressedKeys[key] == true;
}

bool InputManager::IsKeyUp(unsigned int key)
{
    return m_pressedKeys[key] == false;
}

void InputManager::InjectMouseButtonDown(MouseButton button)
{
    m_iMouseButton = button;
}

void InputManager::InjectMouseButtonUp()
{
    m_iMouseButton = MouseButtoNone;
}

bool InputManager::IsMouseButtonDown(unsigned int button)
{
    return m_iMouseButton == button;
}

std::string InputManager::GetKeyCodeAsString(char key)
{
    wchar_t w = GetKeyCodeAsWChar(key);

    std::wstring wStr;
    wStr.push_back( w );

    std::string str = Formater(wStr);

    return str;
}

wchar_t InputManager::GetKeyCodeAsWChar(char key)
{
    BYTE keyState[256];
    HKL layout = GetKeyboardLayout(0);

    if (GetKeyboardState(keyState) == 0)
    {
        return 0;
    }

    unsigned int vk = MapVirtualKeyEx(key, 3, layout);

    if (vk == 0)
    {
        return 0;
    }

    WCHAR buff[3] = {0};
    int ascii = ToUnicodeEx(vk, key, keyState, buff, 3, 0, layout);

    if (ascii == 1 && m_deadKey != '\0')
    {
        WCHAR wcBuff[3] = {buff[0], m_deadKey, '\0'};
        WCHAR out[3];

        if (FoldStringW(MAP_PRECOMPOSED, (LPWSTR)wcBuff, 3, (LPWSTR)out, 3))
        {
            return out[0];
        }
    }
    else if (ascii == -1)
    {    
        return buff[0];
    }
    else if (ascii == 2)
    {
        switch (buff[0])    
        {
            case 0x5E: // Circumflex accent: â
                m_deadKey = 0x302;
                break;
            case 0x60: // Grave accent: à
                m_deadKey = 0x300;
                break;
            case 0xA8: // Diaeresis: ü
                m_deadKey = 0x308;
                break;
            case 0xB4: // Acute accent: é
                m_deadKey = 0x301;
                break;
            case 0xB8: // Cedilla: ç
                m_deadKey = 0x327;
                break;
            default:
                m_deadKey = buff[0];
                break;
        }

    }    
    else if (ascii == 1)
    {        
        m_deadKey = '\0';
        return buff[0];
    }

    return 0;
}

void InputManager::CenterMouseCursor()
{
    int borderX = GetSystemMetrics(SM_CXSIZEFRAME);
    int borderY = GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYSIZEFRAME);

    RECT rect;
    HWND hwnd = SYSTEM->GetHwnd();

    GetWindowRect(hwnd, &rect);

    unsigned int iWindowWidth = SYSTEM->GetWindowWidth();
    unsigned int iWindowHeight = SYSTEM->GetWindowHeight();

    SetCursorPos(0.5f * iWindowWidth + rect.left + borderX, 0.5f * iWindowHeight + rect.top + borderY);
}

 

Voici la fonction WndProc utilisée pour obtenir les entrées afin de les passer à la classe InputManager :

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    if (!InputManager::IsCreated())
    {
        return DefWindowProc(hWnd, message, wParam, lParam);
    }

    switch (message)
    {
        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        case WM_KEYDOWN:    
            {
                INPUT_MANAGER->InjectKeyDown(wParam, HIWORD(lParam));

                if (wParam == VK_SHIFT)
                {
                    INPUT_MANAGER->InjectKeyModifierDown(InputManager::KeyModifier::SHIFT);
                }

                if (wParam == VK_CONTROL)
                {
                    INPUT_MANAGER->InjectKeyModifierDown(InputManager::KeyModifier::CTRL);
                }

                if (wParam == VK_MENU)
                {
                    INPUT_MANAGER->InjectKeyModifierDown(InputManager::KeyModifier::ALT);
                }
            }
            break;

        case WM_KEYUP:
            {
                INPUT_MANAGER->InjectKeyUp(wParam, HIWORD(lParam));

                if (wParam == VK_SHIFT)
                {
                    INPUT_MANAGER->InjectKeyModifierUp(InputManager::KeyModifier::SHIFT);
                }

                if (wParam == VK_CONTROL)
                {
                    INPUT_MANAGER->InjectKeyModifierUp(InputManager::KeyModifier::CTRL);
                }

                if (wParam == VK_MENU)
                {
                    INPUT_MANAGER->InjectKeyModifierUp(InputManager::KeyModifier::ALT);
                }

                break;
            }
        case WM_MOUSEMOVE:
            {
                int iMouseX = (int) LOWORD(lParam);
                int iMouseY = (int) HIWORD(lParam);

                INPUT_MANAGER->InjectMousePosition(iMouseX, iMouseY);

                break;
            }
        case WM_LBUTTONDOWN:
            {
                INPUT_MANAGER->InjectMouseButtonDown(InputManager::MouseButton::MouseButtonLeft);

                break;
            }
        case WM_RBUTTONDOWN:
            {
                INPUT_MANAGER->InjectMouseButtonDown(InputManager::MouseButton::MouseButtonRight);

                break;
            }
        case WM_MBUTTONDOWN:
            {
                INPUT_MANAGER->InjectMouseButtonDown(InputManager::MouseButton::MouseButtonMiddle);

                break;
            }
        case WM_MBUTTONUP:
        case WM_LBUTTONUP:
        case WM_RBUTTONUP:
            {
                INPUT_MANAGER->InjectMouseButtonUp();

                break;
            }
        default:
            {            
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
    }
 
    return 0;
}

 

Résumé :

Nous avons présenter une manière de récupérer les entrées clavier de votre jeu ou application.

Voici l’archive du code complet pour cet RawInputManager.zip

Références :

Combiner des paramètres (flags) dans une déclaration enum

bits

Intro :

Parfois on a besoin de combiner et de regrouper des paramètres d’une enum ensemble.

Prérequis :

– Comprendre l’essentiel de la logique booléenne

– Savoir ce qu’est une énumération en C++

Explications :

Par exemple, pour gérer les entrées clavier des touches SHIFT, CTRL, ALT, nous devons parfois les combiner afin d’effectuer une action spéciale dans une application.

On pourrait déclarer ces possibilités par l’énumération de la manière suivante :

enum KeyModifier
{
    SHIFT,
    CTRL,
    ALT
};

Mais on aimerait les combiner avec l’opérateur booléen : | (le ou logique) de la façon suivante :

// Cette fonction reçoit deux champs d'une énumération combinés
KeyInputReceiver(SHIFT | CTRL);

 

Cette combinaison représente le fait que l’on appuie en même temps sur la touche SHIFT et sur la touche CTRL.

Alors il est nécessaire, pour combiner ces derniers champs, de spécifier des valeurs d’une certaine façon à cette énumération :

enum KeyModifier
{
    SHIFT = 0x01,
    CTRL  = 0x02,
    ALT   = 0x03
};

Ceci afin de nous permettre de faire des opérations binaires (bitwise) sur ces derniers champs.

Pour simplifier cette dernière déclaration, on utilisera cette macro suivante qui aidera à assigner, dans votre déclaration enum, les différents champs binaires (bit flag)

#define BIT(x) 1 << x


Mais que fait cette dernière macro ?

Elle remplace les champs de déclarations binaires de manière simple.

enum KeyModifier
{
    SHIFT = BIT(1),
    CTRL  = BIT(2),
    ALT   = BIT(3)
};

 

Résumé :

Nous avons appris comment combiner plusieurs paramètres d’une énumération en C++.

Références :

– http://www.learncpp.com/cpp-tutorial/38-bitwise-operators/

Afficher des caractères accentués dans la console

Intro :

Quand on écrit une chaine de caractères avec des caractères de l’alphabet français (é, ù, ç, à, etc…) dans la console, ils s’affichent de la mauvaise façon.

Exemple :

#include <iostream>

int main()
{
   std::cout << "J'écris ça avec plein de caractères spéciaux : éùàè" << std::endl;
}

 

Ensuite il s’affiche dans la console :

J'Úcris Þa...

 

console_caractères_mauvais

 

Il y a donc un problème de reconnaissance des caractères spéciaux.

Explications :

Une façon de résoudre ce problème est d’utiliser cette fonction :

#include <Windows.h>
#include <string>

std::string Formater(const std::wstring& src)
{
    char outString[512];
    CharToOemW(src.c_str(), outString);

    return std::string(outString);
}

// Exemple d'utilisation :
std::cout << Formater("J'écris ça avec plein de caractères spéciaux : éùàè") << std::endl;

 

Maintenant on peut afficher correctement les caractères spéciaux :

console_caractères

 

Résumé :

Avec l’alphabet français on ne pouvait pas écrire de texte avec les caractères spéciaux.

Mais avec la fonction Formater() présenté, les caractères s’affichent correctement.

Références :

– http://h-deb.clg.qc.ca/Sujets/AuSecours/Afficher–Accents.html

Une caméra à la première personne (FPS)

Intro :

camera

Pour pouvoir observer nos entités dans une scène 3D, il faut pour cela mouvoir une caméra que l’on peut bouger aisément.

Prérequis :

– Savoir lire et écrire du C++

– Savoir utiliser les objets et fonctions mathématiques de la librairie D3DX

– Savoir ce qu’est une matrice

– Savoir ce que sont les transformations de repère

– Savoir utiliser la classe InputManager utilisée dans le code présenté

– Savoir utiliser les mathématiques des vecteurs (produit vectoriel et produit scalaire)

Explications :

Voici le code tout simplement de notre caméra à la première personne.

A noté l’utilisation de la classe InputManager avec les appels à INPUT_MANAGER dans le code.

Voici le code pour le fichier FPSCamera.h :

#ifndef FPS_CAMERA_H
#define FPS_CAMERA_H

#include <d3d10.h>
#include <d3dx10.h>

class FPSCamera
{
public:
    FPSCamera();
    virtual ~FPSCamera();

    D3DXMATRIX* GetViewMatrix();
    D3DXMATRIX* GetProjectionMatrix();

    D3DXVECTOR3 GetPosition();

    void LookAt(D3DXVECTOR3& target);

    void UpdateLens(float fFov, float fAspect, float fNearZ, float fFarZ);
    
    void SetSpeed(float fSpeed);

    void Update(float dt);

    void Walk(float d);

    void Strafe(float d);

private:
    void BuildView();

private:
    D3DXMATRIX m_view;
    D3DXMATRIX m_proj;

    D3DXVECTOR3 m_pos;

    D3DXVECTOR3 m_right;
    D3DXVECTOR3 m_up;
    D3DXVECTOR3 m_lookAt;

    float m_fSpeed;
};

 

Voici le code pour le fichier FPSCamera.cpp :

#include "FPSCamera.h"

FPSCamera::FPSCamera() :
m_fSpeed(30)
{
    D3DXMatrixIdentity(&m_view);
    D3DXMatrixIdentity(&m_proj);

    m_pos = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
    m_right = D3DXVECTOR3(1.0f, 0.0f, 0.0f);
    m_up = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
    m_lookAt = D3DXVECTOR3(0.0f, 0.0f, 1.0f);

    UpdateLens((float)D3DX_PI * 0.25f, (float)D3D10_RENDERER->GetViewportWidth() / (float)D3D10_RENDERER->GetViewportHeight(), 0.1f, 100.0f);
}

FPSCamera::~FPSCamera()
{
}

D3DXMATRIX* FPSCamera::GetViewMatrix()
{
    return &m_view;
}

D3DXMATRIX* FPSCamera::GetProjectionMatrix()
{
    return &m_proj;
}

D3DXVECTOR3 FPSCamera::GetPosition()
{
    return m_pos;
}

void FPSCamera::LookAt(D3DXVECTOR3& target)
{
    D3DXVECTOR3 L = target - m_pos;
    D3DXVec3Normalize(&L, &L);

    D3DXVECTOR3 R;
    D3DXVec3Cross(&R, &m_up, &L);
    D3DXVec3Normalize(&R, &R);

    D3DXVECTOR3 U;
    D3DXVec3Cross(&U, &L, &R);
    D3DXVec3Normalize(&U, &U);

    m_pos = m_pos;
    m_right = R;
    m_up = U;
    m_lookAt = L;

    BuildView();
}

void FPSCamera::UpdateLens(float fFov, float fAspect, float fNearZ, float fFarZ)
{
    D3DXMatrixPerspectiveFovLH(&m_proj, fFov, fAspect, fNearZ, fFarZ);
}
    
void FPSCamera::SetSpeed(float fSpeed)
{
    m_fSpeed = fSpeed;
}

void FPSCamera::Walk(float d)
{
    m_pos += d * m_lookAt;
}

void FPSCamera::Strafe(float d)
{
    m_pos += d * m_right;
}

void FPSCamera::Update(float dt)
{
    if (INPUT_MANAGER->IsKeyDown('Z'))
    {
        Walk(dt * m_fSpeed);
    }

    if (INPUT_MANAGER->IsKeyDown('S'))
    {
        Walk(dt * -m_fSpeed);
    }

    if (INPUT_MANAGER->IsKeyDown('D'))
    {
        Strafe(dt * m_fSpeed);
    }

    if (INPUT_MANAGER->IsKeyDown('Q'))
    {
        Strafe(dt * -m_fSpeed);
    }

    float x = 0.0f;
    float y = 0.0f;

    INPUT_MANAGER->GetMouseRelativePosition(x, y);

    float pitch = y * dt * 1800;
    float yAngle = x * dt * 1800;
    
    /* Contrainte du pitch */

    D3DXMATRIX R;
    D3DXMatrixRotationAxis(&R, &m_right, pitch);

    D3DXVECTOR3 tmpVect;
    D3DXVec3TransformCoord(&tmpVect, &m_up, &R);

    D3DXVECTOR3 axisY(0.0f, -1.0f, 0.0f);

    if (D3DXVec3Dot(&tmpVect, &axisY) < 0.0f)
    {
        D3DXVec3TransformCoord(&m_up, &m_up, &R);
        D3DXVec3TransformCoord(&m_lookAt, &m_lookAt, &R);    
    }

    /* Rotate Y */

    D3DXMatrixRotationY(&R, yAngle);
    D3DXVec3TransformCoord(&m_right, &m_right, &R);
    D3DXVec3TransformCoord(&m_up, &m_up, &R);
    D3DXVec3TransformCoord(&m_lookAt, &m_lookAt, &R);

    BuildView();

    INPUT_MANAGER->CenterMouseCursor();
}

void FPSCamera::BuildView()
{
    D3DXVec3Normalize(&m_lookAt, &m_lookAt);

    D3DXVec3Cross(&m_up, &m_lookAt, &m_right);
    D3DXVec3Normalize(&m_up, &m_up);

    D3DXVec3Cross(&m_right, &m_up, &m_lookAt);
    D3DXVec3Normalize(&m_right, &m_right);

    float x = -D3DXVec3Dot(&m_pos, &m_right);
    float y = -D3DXVec3Dot(&m_pos, &m_up);
    float z = -D3DXVec3Dot(&m_pos, &m_lookAt);

    m_view(0,0) = m_right.x;
    m_view(1,0) = m_right.y;
    m_view(2,0) = m_right.z;
    m_view(3,0) = x;

    m_view(0,1) = m_up.x;
    m_view(1,1) = m_up.y;
    m_view(2,1) = m_up.z;
    m_view(3,1) = y;

    m_view(0,2) = m_lookAt.x;
    m_view(1,2) = m_lookAt.y;
    m_view(2,2) = m_lookAt.z;
    m_view(3,2) = z;

    m_view(0,3) = 0.0f;
    m_view(1,3) = 0.0f;
    m_view(2,3) = 0.0f;
    m_view(3,3) = 1.0f;
}

#endif

 

Comment se servir du code précédent ? 

Ce qui nous intéresse dans cette implémentation de caméra ce sont la matrice de vue (m_view) et la matrice de projection (m_proj).

Dans votre propre code, avant d’effectuer le rendu de vos modèles 3D vos devez spécifier ces matrices au shader correspondant :

m_pViewShaderVariable->SetMatrix((float*)CAMERA->GetViewMatrix());
m_pProjShaderVariable->SetMatrix((float*)CAMERA->GetProjectionMatrix();

 

Et voilà c’est tout simple !

 

Résumé :

Nous avons implémenté une caméra à la première personne (FPS) qui nous permet de naviguer facilement dans notre scène 3D.

Nous accédons aux matrices de vue et de projection avec les méthodes GetViewMatrix() et GetProjectionMatrix().

Références :

– Introduction to DirectX 9.0c – A shader approach

Rotation autour d’un axe quelconque

Intro :

Voici les matrices de rotation des axes X, Y et Z :

matrix_rotation

Nous savons comment faire une rotation autour des axes X, Y, et Z, mais comment faire une rotation à partir d’un axe quelconque différent des précédents ?

Prérequis :

– Savoir ce qu’est une matrice

– Savoir ce qu’est une transformation de repère

– Savoir utiliser les objets et fonctions mathématiques de la librairie D3DX

Explications :

Nous allons présenter deux méthodes : la formule de Rodrigues en deux variantes et une fonction correspondante de la librairie D3DX.

 


 

La formule de Rodrigues suivante permet de calculer la position d’un point subissant une rotation autour d’un axe quelconque.

Les variables sont : {v} la position d’un point qui subit la rotation ; {\theta} un angle de rotation ; {k} vecteur d’unité 1 qui joue le rôle d’axis de rotation.

{v}_\mathrm{rot} = {v} \cos\theta + ({k} \times {v})\sin\theta + {k} ({k} \cdot {v}) (1 - \cos\theta)

 

Voici l’implémentation de cette formule en C++ :

D3DXVECTOR3 MatrixRotationAxis(D3DXVECTOR3& axis, D3DXVECTOR3& v, float fAngle)
{
    D3DXVec3Normalize(&axis, &axis);

    D3DXVECTOR3 vect1 = v * cos(fAngle);

    D3DXVECTOR3 vect2;
    D3DXVec3Cross(&vect2, &axis, &v);

    vect2 *= sin(fAngle);

    float fDotProduct = D3DXVec3Dot(&axis, &v);

    D3DXVECTOR3 vect3 = axis * fDotProduct * (1 - cos(fAngle));
        
    return vect1 + vect2 + vect3;
}

 

Une autre formule de Rodrigues permet de calculer rapidement la matrice de révolution autour d’un axe et d’un angle quelconques.

Les variables sont : la matrice de rotation \mathbf{R}_{\mathbf{K}}(\theta) autour d’un axe {k} = ( k_{1}, k_{2}, k_{3} ) d’unité 1 et d’angle \theta ; \mathbf{I} étant la matrice identité ;

et la matrice \mathbf{K} = \left[\begin{array}{ccc} 0 & -k_3 & k_2 \\ k_3 & 0 & -k_1 \\ -k_2 & k_1 & 0 \end{array}\right]

 

\mathbf{R}_{\mathbf{K}}(\theta) = \mathbf{I} + (\sin\theta) \mathbf{K}+ (1-\cos\theta)\mathbf{K}{^2}

 

Voici l’implémentation de cette formule en C++ :

D3DXMATRIX D3D10Renderer::MatrixRotationAxis(D3DXVECTOR3& axis, float fAngle)
{
    // Il faut convertir le vecteur d'axis en unité 1 sinon cela
    // ne fonctionne pas
    D3DXVec3Normalize(&axis, &axis);

    D3DXMATRIX K;
    D3DXMatrixIdentity(&K);
 
    K._11 = 0;
    K._12 = -axis.z;
    K._13 = axis.y;
 
    K._21 = axis.z;
    K._22 = 0.0f;
    K._23 = -axis.x;
 
    K._31 = -axis.y;
    K._32 = axis.x;
    K._33 = 0;
 
    // Pour éviter que ce champ soit multiplié
    // par les scalaires, on le met à 0
    K._44 = 0;
 
    D3DXMATRIX identity;
    D3DXMatrixIdentity(&identity);
 
    D3DXMATRIX squared;
    D3DXMatrixMultiply(&squared, &K, &K);
 
    D3DXMATRIX rot = identity + K * sin(fAngle) + squared * (1 - cos(fAngle));
 
    return rot;
}

 


 

D’autre part, on peut aussi se servir de la fonction D3DXMatrixRotationAxis équivalente à la précédente formule de Rodrigues :

#include <d3dx10math.h>

D3DXMATRIX worldMatrix;
D3DXMatrixIdentity(&worldMatrix);

static float r = 0.0f;
r += fTimeSinceLastFrame;
D3DXVECTOR3 axis(1.0f, -1.0f, 0.0f);

D3DXMatrixRotationAxis(&worldMatrix, &axis, r);

 

Résumé :

Nous avons appris deux méthodes qui permet de faire la rotation d’un point.

Références :

– https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula

Structures de DirectX 10

Intro :

1211804249

DirectX 10 utilise différentes structures pour se configurer.
Il peut être utile de les connaître afin de maitriser l’initialisation de DirectX 10.

[à faire]     D3D10_TEXTURE2D_DESC textureDesc;
D3D10_RENDER_TARGET_VIEW_DESC renderTargetViewDesc;
D3D10_SHADER_RESOURCE_VIEW_DESC shaderResourceViewDesc;

Les voici énumérées :

– DXGI_SWAP_CHAIN_DESC :

– utilisée pour configurer la Swap Chain au tout début de l’initialisation de DirectX

– D3D10_TEXTURE2D_DESC :

– utilisée pour décrire une surface 2D

– D3D10_BUFFER_DESC :

– utilisée pour décrire un buffer quelconque

– D3D10_RASTERIZER_DESC :

– utilisée pour configurer l’étape Rasterizer de la chaîne de rendu 3D

– D3D10_VIEWPORT :

– utilisée pour configurer un cadre de vue 3D

– D3D10_SAMPLER_DESC :

– utilisée pour décrire l’échantillonnage d’une texture utilisable dans un shader

– D3D10_COMPARISON_FUNC :

– utilisée pour spécifier la fonction de comparaison entre deux valeurs

D3D10_USAGE :

– utilisée pour spécifier comment est gérée une ressource par le CPU ou le GPU

 –  DXGI_SWAP_CHAIN_DESC :

Explications :

Avant tout configuration de structure, pensez à l’initialiser à zéro avec la macro :

ZeroMemory(&structure_variable, sizeof(Structure));

 

Pour DXGI_SWAP_CHAIN_DESC :

typedef struct DXGI_SWAP_CHAIN_DESC {
  DXGI_MODE_DESC   BufferDesc;
  DXGI_SAMPLE_DESC SampleDesc;
  DXGI_USAGE       BufferUsage;
  UINT             BufferCount;
  HWND             OutputWindow;
  BOOL             Windowed;
  DXGI_SWAP_EFFECT SwapEffect;
  UINT             Flags;
} DXGI_SWAP_CHAIN_DESC;

On la configure ainsi :

DXGI_SWAP_CHAIN_DESC swapChainDesc;

// La taille de la surface en largeur
swapChainDesc.BufferDesc.Width

// La taille de la surface en hauteur
swapChainDesc.BufferDesc.Height

// Le format de la surface
swapChainDesc.BufferDesc.Format

// Le numérateur du taux de rafraichissement - (usuellement 60)
swapChainDesc.BufferDesc.RefreshRate.Numerator

// Le dénominateur du taux de rafraichissement - (usuellement 1)
swapChainDesc.BufferDesc.RefreshRate.Denominator

// Le nombre de buffers dans la Swap Chain
// (y compris le front buffer)
swapChainDesc.BufferCount

// Le nombre de sample par pixel pour l'antialiasing
swapChainDesc.SampleDesc.Count
// Le niveau de qualité de l'image pour l'antialiasing
swapChainDesc.SampleDesc.Quality

// L'handle de la fenêtre à laquelle on va afficher dessus
swapChainDesc.OutputWindow

// Précise si on est en mode fenetré ou plein écran
swapChainDesc.Windowed

// Gère comment DirectX 10 se comporte après l'appel de ->Present(0, 0)
swapChainDesc.SwapEffect

 

Pour D3D10_TEXTURE2D_DESC :

typedef struct D3D10_TEXTURE2D_DESC {
  UINT             Width;
  UINT             Height;
  UINT             MipLevels;
  UINT             ArraySize;
  DXGI_FORMAT      Format;
  DXGI_SAMPLE_DESC SampleDesc;
  D3D10_USAGE      Usage;
  UINT             BindFlags;
  UINT             CPUAccessFlags;
  UINT             MiscFlags;
} D3D10_TEXTURE2D_DESC;

On la configure ainsi :

D3D10_TEXTURE2D_DESC textureDesc;

// Taille de la texture en largeur (l'unité est en texels)
textureDesc.Width

// Taille de la texture en hauteur
textureDesc.Height

// Le nombre de niveau de mipmaps de la texture
textureDesc.MipLevels

// Le nombre de texture dans le tableau
textureDesc.ArraySize

// Le format de la texture
textureDesc.Format

// Le nombre d'échantillon par pixel pour le multisampling
textureDesc.SampleDesc.Count

// Le niveau de qualité de l'image pour le multisampling
textureDesc.SampleDesc.Quality

// Décrit commment est utilisée la texture par le CPU et le GPU
// (voir l'annexe tout en bas)
textureDesc.Usage

// Décrit à quelle étape de la chaîne de rendu 3D est liée cette texture
textureDesc.BindFlags

// Décrit comment le CPU peut accéder à cette ressource texture
// (en lecture ou écriture)
// si 0 : le CPU n'a pas besoin d'accéder à cette ressource
textureDesc.CPUAccessFlags

// Autres options pour cette ressource
textureDesc.MiscFlags

 

Pour D3D10_BUFFER_DESC :

typedef struct D3D10_BUFFER_DESC {
  UINT        ByteWidth;
  D3D10_USAGE Usage;
  UINT        BindFlags;
  UINT        CPUAccessFlags;
  UINT        MiscFlags;
} D3D10_BUFFER_DESC;

On la configure ainsi :

D3D10_BUFFER_DESC bufferDesc;

// Taille du buffer en octet (bytes)
bufferDesc.ByteWidth

// Décrit commment est utilisée la texture par le CPU et le GPU
// (voir l'annexe tout en bas)
bufferDesc.Usage

// Décrit à quelle étape de la chaîne de rendu 3D est liée cette texture
bufferDesc.BindFlags

// Décrit comment le CPU peut accéder à cette ressource texture
// (en lecture ou écriture)
// si 0 : le CPU n'a pas besoin d'accéder à cette ressource
bufferDesc.CPUAccessFlags

// Autres options
bufferDesc.MiscFlags

 

Pour D3D10_RASTERIZER_DESC :

typedef struct D3D10_RASTERIZER_DESC {
  D3D10_FILL_MODE FillMode;
  D3D10_CULL_MODE CullMode;
  BOOL            FrontCounterClockwise;
  INT             DepthBias;
  FLOAT           DepthBiasClamp;
  FLOAT           SlopeScaledDepthBias;
  BOOL            DepthClipEnable;
  BOOL            ScissorEnable;
  BOOL            MultisampleEnable;
  BOOL            AntialiasedLineEnable;
} D3D10_RASTERIZER_DESC;

On la configure ainsi :

D3D10_RASTERIZER_DESC rasterizerDesc;

// Précise le mode de remplissage des primitives
// D3D10_FILL_WIREFRAME ou D3D10_FILL_SOLID
rasterizerDesc.FillMode

// Spécifie si on affiche les triangles qui sont orientés "en face" ou pas
// D3D10_CULL_NONE, D3D10_CULL_FRONT ou D3D10_CULL_BACK
rasterizerDesc.CullMode

// Détermine dans quel sens les triangles sont considérés orientés "en face"
rasterizerDesc.FrontCounterClockwise

// Spécifie le valeur de profondeur DepthBias ajouté à un pixel
rasterizerDesc.DepthBias

// Spécifie la valeur maximale de DepthBias d'un pixel
rasterizerDesc.DepthBiasClamp

// Spécifie l'échelle de grandeur de DepthBias d'un pixel
rasterizerDesc.SlopeScaledDepthBias

// Active ou non la coupure de primitives basée sur la distance
rasterizerDesc.DepthClipEnable

// Active ou non la méthode "rectangle-ciseau"
// qui permet de ne pas rendre les triangles dépassants ce
// rectangle
rasterizerDesc.ScissorEnable

// Active ou non la technique d'antialiasing de multi-échantillonage
rasterizerDesc.MultisampleEnable

// Active ou non la technique de l'antialiasing de ligne
rasterizerDesc.AntialiasedLineEnable

 

Pour D3D10_VIEWPORT :

typedef struct D3D10_VIEWPORT {
  INT   TopLeftX;
  INT   TopLeftY;
  UINT  Width;
  UINT  Height;
  FLOAT MinDepth;
  FLOAT MaxDepth;
} D3D10_VIEWPORT;

On la configure ainsi :

D3D10_VIEWPORT viewPortDesc;

// Abscisse de la position du cadre de vue en partant de haut à gauche
viewPortDesc.TopLeftX 

// Ordonnée basse de la position du cadre de vue en partant de haut à gauche
viewPortDesc.TopLeftY 

// Largeur du cadre de vue
viewPortDesc.Width 

// Hauteur du cadre de vue
viewPortDesc.Height 

// Profondeur minimum du cadre de vue
viewPortDesc.MinDepth 

// Profondeur maximale du cadre de vue
viewPortDesc.MaxDepth

 

Pour D3D10_SAMPLER_DESC :

typedef struct D3D10_SAMPLER_DESC {
  D3D10_FILTER               Filter;
  D3D10_TEXTURE_ADDRESS_MODE AddressU;
  D3D10_TEXTURE_ADDRESS_MODE AddressV;
  D3D10_TEXTURE_ADDRESS_MODE AddressW;
  FLOAT                      MipLODBias;
  UINT                       MaxAnisotropy;
  D3D10_COMPARISON_FUNC      ComparisonFunc;
  FLOAT                      BorderColor[4];
  FLOAT                      MinLOD;
  FLOAT                      MaxLOD;
} D3D10_SAMPLER_DESC;

On la configure ainsi :

D3D10_SAMPLER_DESC samplerDesc;

// Méthode de filtrage utilisée pour échantillonner la texture
samplerDesc.Filter

// Méthode utilisée pour afficher la texture lorsque ses coordonnées U
// sont en dehors de l'intervalle de 0.0f - 1.0f
// (voir l’annexe)
samplerDesc.AddressU 

// Méthode utilisée pour afficher la texture lorsque ses coordonnées V
// sont en dehors de l'intervalle de 0.0f - 1.0f
// (voir l’annexe)
samplerDesc.AddressV

// Méthode utilisée pour afficher la texture lorsque ses coordonnées W
// sont en dehors de l'intervalle de 0.0f - 1.0f
// (voir l’annexe)
samplerDesc.AddressW

// Augmente la valeur préétablis du niveau de mimap de la texture
samplerDesc.MipLODBias

// Spécifie le niveau d'anisotropie de la texture
samplerDesc.MaxAnisotropy

// Spécifie la fonction de comparaison entre chaque données de
// la texture eux-mêmes
samplerDesc.ComparisonFunc

// Spécifie la couleur de bordure si l'on a passé
// D3D10_TEXTURE_ADDRESS_BORDER comme valeur aux AddressU, V ou W
samplerDesc.BorderColor

//
samplerDesc.MinLOD 

//
samplerDesc.MaxLOD

 

Pour D3D10_USAGE :

typedef enum D3D10_USAGE {
  D3D10_USAGE_DEFAULT     = 0,
  D3D10_USAGE_IMMUTABLE   = 1,
  D3D10_USAGE_DYNAMIC     = 2,
  D3D10_USAGE_STAGING     = 3
} D3D10_USAGE;

Voici comment la configurer :


 

Résumé :

Nous avons appris le rôle que joue chaque structure de DirectX 10 afin de mieux comprendre son fonctionnement.

Références :

– DirectX June 2010 SDK Documentation

Calculer la tangente et la bitangente d’une face

NTBFromUVs_cadre

Intro :

Pour certains algorithmes de rendu 3D par shader (comme celui du normal mapping) nous avons besoin, en plus de la normale, de deux vecteurs perpendiculaires à celle-ci : ce sont la tangente et la bitangente (ou binormale).

Voici trois méthode pour les obtenir et les calculer.

Explications :

Mathématiquement, le calcul de ces deux autres composantes autres que la normale s’énonce ainsi :

\vec{Edge1} = \vec{V}_2 - \vec{V}_1

 

\vec{Edge2} = \vec{V}_3 - \vec{V}_1

 

\vec{UVEdge1} = \vec{UV}_2 - \vec{UV}_1

 

\vec{UVEdge2} = \vec{UV}_3 - \vec{UV}_1

 

p = (\vec{UVEdge1}_y * \vec{UVEdge2}_x) - \newline (\vec{UVEdge1}_x * \vec{UVEdge2}_y)

 

c = 1 / p

 

\vec{Tangent} = [\enspace (\vec{Edge1} * -\vec{UVEdge2}_y) + (\vec{Edge2} * \vec{UVEdge1}_y) \enspace ]\newline * c

 

\vec{Bitangent} = [\enspace(\vec{Edge1} * -\vec{UVEdge2}_x) + (\vec{Edge2} * \vec{UVEdge1}_x)\enspace] * c

 

Je vous montre une fonction permettant de calculer la tangente et la bitangente.

/* P1, P2, P3 représentent les points d'une face donnée
   UV1, UV2, UV2 représentent les coordonnées de la texture de cette dernière face
*/
void ComputeTangentAndBinormal(const D3DXVECTOR3& P1, const D3DXVECTOR3& P2,
                               const D3DXVECTOR3& P3, const D3DXVECTOR2& UV1,
                               const D3DXVECTOR2& UV2, const D3DXVECTOR2& UV3,
                               D3DXVECTOR3& tangent, D3DXVECTOR3& bitangent)
{
   D3DXVECTOR3 Edge1 = P2 - P1;
   D3DXVECTOR3 Edge2 = P3 - P1;
   D3DXVECTOR2 Edge1uv = UV2 - UV1;
   D3DXVECTOR2 Edge2uv = UV3 - UV1;

   float cp = Edge1uv.y * Edge2uv.x - Edge1uv.x * Edge2uv.y;

   if (cp != 0.0f)
   {
      float mul = 1.0f / cp;
      Tangent   = (Edge1 * -Edge2uv.y + Edge2 * Edge1uv.y) * mul;
      Bitangent = (Edge1 * -Edge2uv.x + Edge2 * Edge1uv.x) * mul;

      D3DXVEC3Normalize(&Tangent, &Tangent);          
      D3DXVEC3Normalize(&Bitangent, &Bitangent);  
   }
}

 

Un autre algorithme est disponible si vous ne voulez pas calculer les données face par face !

/* Ceci est la structure d'un vertex en mémoire vidéo */
struct PTVertex
{
    D3DXVECTOR3 position;
    D3DXVECTOR2 texture;
}

/* Cette fonction prend en paramètres le tableau des vertices
   d'un modèle donné.
   Elle prend aussi l'index courant de la face en question
   (premier vertex de celle-ci)
*/
void ComputeTBN_Vectors(PTVertex* vertices,
                         uint32 index,
                         D3DXVECTOR3& v3Tangent,
                         D3DXVECTOR3& v3Binormal,
                         D3DXVECTOR3& v3Normal)
{
    if (index < 0 || vertices == nullptr)
    {
        return;
    }
    
    D3DXVECTOR3 edge1;
    D3DXVECTOR3 edge2;

    D3DXVECTOR2 tuVector;
    D3DXVECTOR2 tvVector;

    PTVertex vertex1 = &vertices[index];
    index++;

    PTVertex vertex2 = &vertices[index];
    index++;

    PTVertex vertex3 = &vertices[index];
    index++;

    edge1 = vertex2->position - vertex1->position;
    edge2 = vertex3->position - vertex1->position;

    tuVector.x = vertex2->texture.x - vertex1->texture.x;
    tvVector.x = vertex2->texture.y - vertex1->texture.y;

    tuVector.y = vertex3->texture.x - vertex1->texture.x;
    tvVector.y = vertex3->texture.y - vertex1->texture.y;

    float den = 1.0f / (tuVector.x * tvVector.y - tuVector.y * tvVector.x);

    tangent = (edge1 * tvVector.y - edge2 * tvVector.x) * den;
    binormal = (edge2 * tuVector.x - edge1 * tuVector.y) * den;

    D3DXVec3Normalize(&tangent, &tangent);
    D3DXVec3Normalize(&binormal, &binormal);

    D3DXVECTOR3 normal;
    D3DXVec3Cross(&normal, &tangent, &binormal);
    D3DXVec3Normalize(&normal, &normal);

    v3Tangent = tangent;
    v3Binormal = binormal;
    v3Normal = normal;
}

 

Voici la troisième fonction (sans doute la meilleure) :

void ComputeTangentBasis(
    // Entrées
    const std::vector<D3DXVECTOR3>& vertices,
    const std::vector<D3DXVECTOR2>& uvs,
    // Sorties
    std::vector<D3DXVECTOR3>& tangents,
    std::vector<D3DXVECTOR3>& bitangents
)
{
    for (int i = 0; i < vertices.size(); i += 3)
    {

        // Les vertices
        const D3DXVECTOR3& v0 = vertices[i + 0];
        const D3DXVECTOR3& v1 = vertices[i + 1];
        const D3DXVECTOR3& v2 = vertices[i + 2];

        // Les coordonnées des textures
        const D3DXVECTOR2& uv0 = uvs[i + 0];
        const D3DXVECTOR2& uv1 = uvs[i + 1];
        const D3DXVECTOR2& uv2 = uvs[i + 2];

        // Les côtés du triangle : "position delta"
        D3DXVECTOR3 deltaPos1 = v1 - v0;
        D3DXVECTOR3 deltaPos2 = v2 - v0;

        // UV delta
        D3DXVECTOR2 deltaUV1 = uv1 - uv0;
        D3DXVECTOR2 deltaUV2 = uv2 - uv0;
        
        float r = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x);
        D3DXVECTOR3 tangent = (deltaPos1 * deltaUV2.y   - deltaPos2 * deltaUV1.y) * r;
        D3DXVECTOR3 bitangent = (deltaPos2 * deltaUV1.x   - deltaPos1 * deltaUV2.x) * r;

        // Assigne la même tangente pour les trois vertex en question
        tangents.push_back(tangent);
        tangents.push_back(tangent);
        tangents.push_back(tangent);

        // Pareil pour les binormales / bitangentes
        bitangents.push_back(bitangent);
        bitangents.push_back(bitangent);
        bitangents.push_back(bitangent);
    }
}

 

Voilà c’est tout pour les algorithmes !

Sachez que comme vous l’avez vu, la méthode standard indique que la tangente doit être orientée dans la même direction que les coordonnées de texture du triangle en question.

D’autre part ce n’est pas l’objet de cet article, mais je vous apprend que la normale, la binormale et la tangente sont trois vecteurs qui forment une base permettant de transformer les normales issues (et extraites) d’une image RGB spéciale VERS l’espace / coordonnées du modèle du mesh contenant les triangles (bind space ou model space en anglais).

Cette base est représentée mathématiquement par la matrice suivante :

\begin{bmatrix} \textbf{T}_x & \textbf{B}_x & \textbf{N}_x \\ \textbf{T}_y & \textbf{B}_y & \textbf{N}_y \\ \textbf{T}_z & \textbf{B}_z & \textbf{N}_z \end{bmatrix}

 

\textbf{T} représentant la tangente. \textbf{B} représentant la binormale. \textbf{N} représentant la normale.

Il ne reste plus qu’à multiplier le vecteur normal par cette matrice est le tour est joué !

 

Résumé :

Nous avons présenté l’algorithme permettant de calculer les binormales et les bitangentes en utilisant les coordonnées des textures du mesh en question.

Références :

– http://www.3dkingdoms.com/weekly/weekly.php?a=37

– http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-13-normal-mapping/

Un cube tout simple en 3D

cube

Intro :

Voici l’implémentation d’un cube tout simple par la définition de ses vertices et indices.

Il peut être utilisé pour faire des tests ou pour déboguer vos jeux.

Explications :

Dans le fichier Cube.h :

#ifndef CUBE_MESH_H
#define CUBE_MESH_H
 
#include <d3d10.h>
#include <d3dx10.h>

#include "Defines.h"
#include "D3D10Renderer.h"

class Cube
{
public:
    struct PTNVertex
    {
        D3DXVECTOR3 position;
        D3DXVECTOR2 texture;
        D3DXVECTOR3 normal;
 
        PTNVertex()
        {
        }
 
        PTNVertex(D3DXVECTOR3 p, D3DXVECTOR2 t, D3DXVECTOR3 n)
        {
            position = p;
            texture = t;
            normal = n;
        }
    };
 
    Cube();
    virtual ~Cube();
 
    bool Initialize();
 
    void Render(float fTimeSinceLastFrame);
 
private:
    ID3DX10Mesh* m_pMesh;
 
    D3DXMATRIX m_worldMatrix;
    D3DXMATRIX m_viewMatrix;
    D3DXMATRIX m_projMatrix;

    ID3D10EffectMatrixVariable* m_pWorldVariable;
    ID3D10EffectMatrixVariable* m_pViewVariable;
    ID3D10EffectMatrixVariable* m_pProjVariable;

    ID3D10EffectShaderResourceVariable* m_pDiffuseVariable;
    ID3D10ShaderResourceView* m_pDiffuseMap;
     
    ID3D10Effect* m_pEffect;
    ID3D10EffectTechnique* m_pTechnique;
     
    ID3D10InputLayout* m_pVertexLayout;
};
 
#endif

 

Dans le fichier Cube.cpp :

#include "Cube.h"

Cube::Cube() :
m_pMesh(nullptr),
m_pWorldVariable(nullptr),
m_pDiffuseVariable(nullptr),
m_pDiffuseMap(nullptr),
m_pTechnique(nullptr),
m_pEffect(nullptr),
m_pVertexLayout(nullptr)
{
    D3DXMatrixIdentity(&m_worldMatrix);

    D3DXVECTOR3 Eye(0.0f, 5.0f, -6.0f);
    D3DXVECTOR3 At(0.0f, 0.0f, 0.0f);
    D3DXVECTOR3 Up(0.0f, 1.0f, 0.0f);
 
    D3DXMatrixLookAtLH(&m_viewMatrix, &Eye, &At, &Up);
 
    D3DXMatrixPerspectiveFovLH(&m_projMatrix, D3DX_PI * 0.25f,
        (float) D3D10_RENDERER->GetViewportWidth() / (float)D3D10_RENDERER->GetViewportHeight(),
        0.1f, 100.0f);
}
 
Cube::~Cube()
{
    SAFE_RELEASE(m_pMesh);
}
 
bool Cube::Initialize()
{
    PTNVertex v[24];
     
    v[0] = PTNVertex( D3DXVECTOR3( -1.0f, 1.0f, -1.0f ), D3DXVECTOR2( 0.0f, 0.0f ), D3DXVECTOR3( 0.0f, 1.0f, 0.0f ) );
    v[1] = PTNVertex( D3DXVECTOR3( 1.0f, 1.0f, -1.0f ), D3DXVECTOR2( 1.0f, 0.0f ), D3DXVECTOR3( 0.0f, 1.0f, 0.0f ) );
    v[2] = PTNVertex( D3DXVECTOR3( 1.0f, 1.0f, 1.0f ), D3DXVECTOR2( 1.0f, 1.0f ), D3DXVECTOR3( 0.0f, 1.0f, 0.0f ) );
    v[3] = PTNVertex( D3DXVECTOR3( -1.0f, 1.0f, 1.0f ), D3DXVECTOR2( 0.0f, 1.0f ), D3DXVECTOR3( 0.0f, 1.0f, 0.0f ) );
 
    v[4] = PTNVertex( D3DXVECTOR3( -1.0f, -1.0f, -1.0f ), D3DXVECTOR2( 0.0f, 0.0f ), D3DXVECTOR3( 0.0f, -1.0f, 0.0f ) );
    v[5] = PTNVertex( D3DXVECTOR3( 1.0f, -1.0f, -1.0f ), D3DXVECTOR2( 1.0f, 0.0f ), D3DXVECTOR3( 0.0f, -1.0f, 0.0f ) );
    v[6] = PTNVertex( D3DXVECTOR3( 1.0f, -1.0f, 1.0f ), D3DXVECTOR2( 1.0f, 1.0f ),  D3DXVECTOR3( 0.0f, -1.0f, 0.0f ) );
    v[7] = PTNVertex( D3DXVECTOR3( -1.0f, -1.0f, 1.0f ), D3DXVECTOR2( 0.0f, 1.0f ),  D3DXVECTOR3( 0.0f, -1.0f, 0.0f ));
 
    v[8] = PTNVertex( D3DXVECTOR3( -1.0f, -1.0f, 1.0f ), D3DXVECTOR2( 0.0f, 0.0f ),  D3DXVECTOR3( -1.0f, 0.0f, 0.0f ) );
    v[9] = PTNVertex( D3DXVECTOR3( -1.0f, -1.0f, -1.0f ), D3DXVECTOR2( 1.0f, 0.0f ), D3DXVECTOR3( -1.0f, 0.0f, 0.0f ) );
    v[10] = PTNVertex( D3DXVECTOR3( -1.0f, 1.0f, -1.0f ), D3DXVECTOR2( 1.0f, 1.0f ),  D3DXVECTOR3( -1.0f, 0.0f, 0.0f ) );
    v[11] = PTNVertex( D3DXVECTOR3( -1.0f, 1.0f, 1.0f ), D3DXVECTOR2( 0.0f, 1.0f ),  D3DXVECTOR3( -1.0f, 0.0f, 0.0f ) );
     
    v[12] = PTNVertex( D3DXVECTOR3( 1.0f, -1.0f, 1.0f ), D3DXVECTOR2( 0.0f, 0.0f ), D3DXVECTOR3( 1.0f, 0.0f, 0.0f ) );
    v[13] = PTNVertex( D3DXVECTOR3( 1.0f, -1.0f, -1.0f ), D3DXVECTOR2( 1.0f, 0.0f ), D3DXVECTOR3( 1.0f, 0.0f, 0.0f) );
    v[14] = PTNVertex( D3DXVECTOR3( 1.0f, 1.0f, -1.0f ), D3DXVECTOR2( 1.0f, 1.0f ), D3DXVECTOR3( 1.0f, 0.0f, 0.0f ) );
    v[15] = PTNVertex( D3DXVECTOR3( 1.0f, 1.0f, 1.0f ), D3DXVECTOR2( 0.0f, 1.0f ),    D3DXVECTOR3( 1.0f, 0.0f, 0.0f) );
 
    v[16] = PTNVertex( D3DXVECTOR3( -1.0f, -1.0f, -1.0f ), D3DXVECTOR2( 0.0f, 0.0f ), D3DXVECTOR3( 0.0f, 0.0f, -1.0f) );
    v[17] = PTNVertex( D3DXVECTOR3( 1.0f, -1.0f, -1.0f ), D3DXVECTOR2( 1.0f, 0.0f ),  D3DXVECTOR3( 0.0f, 0.0f, -1.0f) );
    v[18] = PTNVertex( D3DXVECTOR3( 1.0f, 1.0f, -1.0f ), D3DXVECTOR2( 1.0f, 1.0f ),  D3DXVECTOR3( 0.0f, 0.0f, -1.0f ) );
    v[19] = PTNVertex( D3DXVECTOR3( -1.0f, 1.0f, -1.0f ), D3DXVECTOR2( 0.0f, 1.0f ),  D3DXVECTOR3( 0.0f, 0.0f, -1.0f ) );
 
    v[20] = PTNVertex( D3DXVECTOR3( -1.0f, -1.0f, 1.0f ), D3DXVECTOR2( 0.0f, 0.0f ), D3DXVECTOR3( 0.0f, 0.0f, 1.0f ) );
    v[21] = PTNVertex( D3DXVECTOR3( 1.0f, -1.0f, 1.0f ), D3DXVECTOR2( 1.0f, 0.0f ), D3DXVECTOR3( 0.0f, 0.0f, 1.0f ) );
    v[22] = PTNVertex( D3DXVECTOR3( 1.0f, 1.0f, 1.0f ), D3DXVECTOR2( 1.0f, 1.0f ), D3DXVECTOR3( 0.0f, 0.0f, 1.0f) );
    v[23] = PTNVertex( D3DXVECTOR3( -1.0f, 1.0f, 1.0f ), D3DXVECTOR2( 0.0f, 1.0f ), D3DXVECTOR3( 0.0f, 0.0f, 1.0f ) );
     
    unsigned int i[36] = { 3,1,0,
                           2,1,3,
                           6,4,5,
                           7,4,6,
                           11,9,8,
                           10,9,11,
                           14,12,13,
                           15,12,14,
                           19,17,16,
                           18,17,19,
                           22,20,21,
                           23,20,22
                        };    
                         
     
    D3D10_INPUT_ELEMENT_DESC layout[] =
    {
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
        { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0 },
        { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 20, D3D10_INPUT_PER_VERTEX_DATA, 0 },
    };

    HRESULT hr;
    hr = D3DX10CreateMesh(D3D10_RENDERER->GetDevice(), layout,  3, "POSITION", 24, 36/3, D3DX10_MESH_32_BIT, &m_pMesh);
 
    hr = m_pMesh->SetVertexData(0, v);
 
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }
 
    hr = m_pMesh->SetIndexData(i, 36);
 
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }
 
    hr = m_pMesh->CommitToDevice();
 
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }
 
    ID3D10Blob* pBlob = nullptr;
 
    hr = D3DX10CreateEffectFromFile(L"Cube.fx", nullptr, nullptr, "fx_4_0",
                                    D3D10_SHADER_ENABLE_STRICTNESS | D3D10_SHADER_DEBUG, 0,
                                    D3D10_RENDERER->GetDevice(), nullptr, nullptr, &m_pEffect, &pBlob, nullptr);
    if (FAILED(hr))
    {
        if (pBlob)
        {
            // Affiche l'erreur de compilation du shader
            MessageBoxA(nullptr, (PCSTR)pBlob->GetBufferPointer(), "Erreur dans le fichier shader", MB_ICONHAND | MB_OK);
        }
        else
        {
            ShowMessageBoxDXError(hr);
        }
 
        return false;
    }
 
    m_pTechnique = m_pEffect->GetTechniqueByName("Render");
 
    m_pWorldVariable = m_pEffect->GetVariableByName("World")->AsMatrix();
    m_pViewVariable = m_pEffect->GetVariableByName("View")->AsMatrix();
    m_pProjVariable = m_pEffect->GetVariableByName("Projection")->AsMatrix();
    m_pDiffuseVariable = m_pEffect->GetVariableByName("txDiffuse")->AsShaderResource();
 
    D3D10_PASS_DESC PassDesc;
    m_pTechnique->GetPassByIndex(0)->GetDesc(&PassDesc);
    hr = D3D10_RENDERER->GetDevice()->CreateInputLayout(layout, 3, PassDesc.pIAInputSignature,
                                         PassDesc.IAInputSignatureSize, &m_pVertexLayout);
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

     // On charge la texture
    hr = D3DX10CreateShaderResourceViewFromFile(D3D10_RENDERER->GetDevice(), L"box.jpg", nullptr, nullptr, &m_pDiffuseMap, nullptr);

    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    return true;
}
 
void Cube::Render(float fTimeSinceLastFrame)
{
    D3D10_RENDERER->GetDevice()->IASetInputLayout(m_pVertexLayout);

    // Pour faire tourner le cube autour de l'axe Y
    static float r = 0;
    D3DXMatrixRotationY(&m_worldMatrix, r);
    r += fTimeSinceLastFrame * 0.6f;

    m_pWorldVariable->SetMatrix((float*)&m_worldMatrix);
    m_pViewVariable->SetMatrix((float*)&m_viewMatrix);
    m_pProjVariable->SetMatrix((float*)&m_projMatrix);
    m_pDiffuseVariable->SetResource(m_pDiffuseMap);

    D3D10_TECHNIQUE_DESC techDesc;
    m_pTechnique->GetDesc(&techDesc);

    for(uint32 p = 0; p < techDesc.Passes; p++)
    {
        m_pTechnique->GetPassByIndex(p)->Apply(0);

        m_pMesh->DrawSubset(0);
    }
}

 

Résumé :

Nous avons implémenter le code pour afficher un petit cube.

Voici les fichiers sources : Simple Cube.zip.

Système d’Events pour gérer les événements du jeu (2ème approche)

Intro :

gears2a

Dans la conception d’un jeu il est très important d’énumérer tous les mécanismes intrinsèques au comportement du jeu, c’est-à-dire les événements du gameplay, du son, de l’interface, du réseau, de la gestion des collisions et de l’IA et aussi de pouvoir les invoquer facilement, proprement et de manière élégante.

Ceci constitue la deuxième approche au problème.

Prérequis :

– La compréhension de l’utilisation de la classe DataParameters, voir cet article.

– La compréhension de l’utilisation des delegates en C++11, voir cet article.

Explications :

 

Résumé :

 

Références :

– Game Coding Complete 4 (code LGPL)

Introduction à la programmation réseau (Sockets) – partie 2

48405289

Intro :

Pour concevoir un jeu vidéo multijoueur il vous faut utiliser dans le programme C++ des objets qu’on appelle sockets.

Ceci constitue la deuxième partie de l’ensemble des articles concernant les sockets.

Dans cette partie nous allons concevoir un petit chat en entrant du texte dans la console côté client et côté serveur.

Prérequis :

– Savoir un peu lire et écrire du C++

– Être sous Windows.

– Avoir suivi la première partie de ce tutoriel

Explications :

1 – Tout d’abord nous allons écrire un petit programme de chat par écrit à travers les entrées clavier de l’utilisateur dans la console.

2 – En deuxième partie, j’énumérerais l’ordre d’appel des fonctions sockets selon le rôle du programme (client ou serveur).

 


 

1 – Petit chat client / serveur

Voici le code du chat côté client (ChatClient.cpp) :

//----------------------------------------------------
// Auteur : Clément Profit
// Nom du fichier : ChatClient.cpp
// Date de création : Avril 2015
// Description : Une implémentation d'un client de chat
//----------------------------------------------------

#include <winsock2.h>

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>

#define PORT 23

// On lie la librairie des sockets Windows
#pragma comment(lib, "ws2_32.lib")

// Structure utilisée pour envoyer ces deux paramètres à la fonction
// thread ; étant donné que l'on ne peut qu'envoyer qu'un seul
// paramètre aux fonctions thread
struct ThreadParam
{
    bool bQuit;
    int server_sock;
};

// Message à envoyer au serveur
struct ChatMessage
{
    // Soit c'est un message texte, soit c'est une requête pour cloturer / quitter
    // la connexion
    enum MessageType {CHAT_MESSAGE, CHAT_QUIT};

    ChatMessage(MessageType message, char* information)
    {
        message_type = message;
        strcpy_s(sInfo, information);
    }

    ChatMessage() {};

    MessageType message_type;

    // On utilise un tableau de caractères à taille fixe
    // car on ne peut envoyer un pointeur char* sur une machine distante
    // (le pointeur étant lié uniquement à la mémoire de la machine émettrice)
    char sInfo[4096];
};

// Fonction pour obtenir une chaîne de caractères tappée
// depuis le clavier de l'utilisateur
char* get_string_input()
{
    char* message = new char[4096];

    // La fonction _getch() permet de tapper un caractère
    // sans l'afficher dans la console
    char ch = _getch();
    
    // Si on tappe la touche entrée on ne fait rien
    if (ch == 13)
    {
        return nullptr;
    }

    unsigned int length = 0;

    // On tappe un caractère par tour de boucle
    while (ch != 13) // Le caractère 13 est la touche entrée
    {
        message[length] = ch;
        length++;

        if (length > 4096)
        {
            break;
        }        
        
        ch = _getch();
    }

    // A la fin du message, on met un indicateur de fin de chaîne de caractère
    // pour finaliser la chaîne
    message[length] = '\0';

    return message;
}

// Thread pour entrer des messages à partir du clavier
DWORD WINAPI get_console_message(LPVOID lpParameter)
{
    // On fait un cast pour obtenir notre paramètre
    ThreadParam* param = (ThreadParam*)lpParameter;

    printf("Saisissez un message.\n");
    char* sMessage = nullptr;

    // A chaque tour de boucle correspond un message tappé
    while(true)
    {
        // On obtient le message tappé
        sMessage = get_string_input();

        if (sMessage == nullptr)
        {
            continue;
        }

        // Si l'on tappe "quit", le programme s'arrête
        if (strcmp(sMessage, "quit") == 0)
        {
            printf("Au revoir !\n");    

            system("pause");

            // On envoie un ChatMessage de sortie
            // pour indiquer d'arrêter le programme de la machine
            // distante
            ChatMessage quitMessage(ChatMessage::CHAT_QUIT, "");
            send(param->server_sock, (char*)&quitMessage, sizeof(quitMessage), 0);

            // Nous forçons aussi l'arrêt de notre programme sur la machine locale
            param->bQuit = true;

            break;
        };

        // On envoie un ChatMessage de texte corresopodant à celui tappé
        ChatMessage chatMessage(ChatMessage::CHAT_MESSAGE, sMessage);
        send(param->server_sock, (char*)&chatMessage, sizeof(ChatMessage), 0);

        printf("[Vous]# %s\n", sMessage);
    }

    delete sMessage;

    return 0;
}

/** Client **/
int main(void)
{
    // Il faut appeler ces deux instructions avant toute
    // utilisation de socket
    WSADATA WSAData;
    int error = WSAStartup(MAKEWORD(2,2), &WSAData);
 
    // Voici nos structures de socket
    SOCKET sock;
    SOCKADDR_IN server_in;

    // Si les sockets Windows fonctionnent
    if (!error)
    {
        // Création de la socket
        sock = socket(AF_INET, SOCK_STREAM, 0);
 
        // On passe en mode non bloqué, c'est-à-dire que les fonctions recv() et send()
        // ne bloquent pas quand un message n'a pas été envoyé ou reçu
        unsigned long mode = 1;
        ioctlsocket(sock, FIONBIO, &mode);

        // Configuration de la connexion
        // On peut changer ici l'adresse d'un serveur non local
        server_in.sin_addr.s_addr = inet_addr("127.0.0.1");
        server_in.sin_family = AF_INET;
        server_in.sin_port = htons(PORT);
 
        printf("Tentative de connexion au serveur (tappez exit pour quitter)...\n");

        // On se connecte au serveur
        error = connect(sock, (SOCKADDR*)&server_in, sizeof(server_in));

        if (error != SOCKET_ERROR)
        {
            printf("Connection à %s sur le port %d\n", inet_ntoa(server_in.sin_addr), htons(server_in.sin_port));
        }
    
        // Structure de paramètre pour envoyer
        // plus d'une variable à la fonction thread
        ThreadParam param;
        param.bQuit = false;
        param.server_sock = sock;

        // Création d'un thread pour effectuer la lecture d'un message indépendamment
        // de l'écoute par la fonction recv
        CreateThread(nullptr, 0, &get_console_message, (void*)&param, 0, nullptr);  

        ChatMessage* message = nullptr;

        // Comme les packets réseaux reçus peuvent être de différentes tailles
        // et qu'ils sont reçus par segments,
        // nous avons besoin d'un buffer secondaires pour lui ajouter
        // les segments par segments
        int iTotalBytes = sizeof(ChatMessage);
        int iCurrentBytes = 0;

        char* buffer = new char[iTotalBytes];
        char* pRecvBuffer = new char[iTotalBytes];

        while (!param.bQuit)
        {
            // On recoit des données qui peuvent être un segment
            int iBytes = recv(sock, buffer, iTotalBytes, 0);

            if (iBytes != SOCKET_ERROR)
            {
                // On rajoute le segment reçu dans le buffer à chaque fois
                // que des données ont été reçues plus haut
                memcpy(pRecvBuffer + iCurrentBytes, buffer, iBytes);
                iCurrentBytes += iBytes;

                // Dès qu'on a reçu le message au complet
                // c'est-à-dire dès que la taille du buffer a atteint
                // la taille du message
                if (iCurrentBytes >= iTotalBytes)
                {
                    message = (ChatMessage*) pRecvBuffer;

                    // Si l'on recoit un message qui nous indique de quitter
                    if (message->message_type == ChatMessage::CHAT_QUIT)
                    {
                        wprintf(L"Le serveur s'est deconnecté.\n");

                        system("pause");

                        // On quitte la boucle
                        break;
                    }

                    if (message->sInfo != '\0')
                    {
                        // On affiche le message reçu depuis le serveur
                        printf("[Serveur]# : %s\n", message->sInfo);
                    }    

                    iCurrentBytes = 0;
                }
            }
        }

        // On ferme la socket
        closesocket(sock);
 
        // Termine l'utilisation de Winsock 2
        WSACleanup();
    }

    return EXIT_SUCCESS;
}

 

Voici le code du chat côté serveur (ChatServeur.cpp) :

//----------------------------------------------------
// Auteur : Clément Profit
// Nom du fichier : ChatServeur.cpp
// Date de création : Avril 2015
// Description : Une implémentation d'un serveur de chat
//----------------------------------------------------

#include <winsock2.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>

#define PORT 23

// On lie la librairie des sockets Windows
#pragma comment(lib, "ws2_32.lib")

// Structure utilisée pour envoyer ces deux paramètres à la fonction
// thread ; étant donné que l'on ne peut qu'envoyer qu'un seul
// paramètre aux fonctions thread
struct ThreadParam
{
    bool bQuit;
    int client_sock;
};

// Message à envoyer au serveur
struct ChatMessage
{
    // Soit c'est un message texte, soit c'est une requête pour cloturer / quitter
    // la connexion
    enum MessageType {CHAT_MESSAGE, CHAT_QUIT};

    ChatMessage(MessageType message, char* information)
    {
        message_type = message;
        strcpy_s(sInfo, information);
    }

    ChatMessage() {};

    MessageType message_type;

    // On utilise un tableau de caractères à taille fixe
    // car on ne peut envoyer un pointeur char* sur une machine distante
    // (le pointeur étant lié uniquement à la mémoire de la machine émettrice)
    char sInfo[4096];
};

// Si un erreur a lieu, on affiche la raison
// puis on quitte
void exit_on_fail(int sock_err)
{
    if (sock_err == SOCKET_ERROR)
    {
        printf("Socket Error Code = %i", WSAGetLastError());
 
        system("pause");
 
        exit(EXIT_FAILURE);
    }
}

// Fonction pour obtenir une chaîne de caractères tappée
// depuis le clavier de l'utilisateur
char* get_string_input()
{
    char* message = new char[4096];

    // La fonction _getch() permet de tapper un caractère
    // sans l'afficher dans la console
    char ch = _getch();

    // Si on tappe la touche entrée on ne fait rien
    if (ch == 13)
    {
        return nullptr;
    }

    unsigned int length = 0;

    // On tappe un caractère par tour de boucle
    while (ch != 13) // Le caractère 13 est la touche entrée
    {
        message[length] = ch;
        length++;

        if (length > 4096)
        {
            break;
        }        
        
        ch = _getch();
    }

    // A la fin du message, on met un indicateur de fin de chaîne de caractère
    // pour finaliser la chaîne
    message[length] = '\0';

    return message;
}

// Thread pour entrer des messages à partir du clavier
DWORD WINAPI get_console_message(LPVOID lpParameter)
{
    // On fait un cast pour obtenir notre paramètre
    ThreadParam* param = (ThreadParam*)lpParameter;

    printf("Saisissez un message.\n");
    char* sMessage = nullptr;

    // A chaque tour de boucle correspond un message tappé
    while(true)
    {
        // On obtient le message tappé
        sMessage = get_string_input();

        if (sMessage == nullptr)
        {
            continue;
        }

        // Si l'on tappe "exit", le programme s'arrête
        if (strcmp(sMessage, "quit") == 0)
        {
            printf("Au revoir !\n");
            system("pause");

            // On envoie un ChatMessage de sortie
            // pour indiquer d'arrêter le programme de la machine
            ChatMessage quitMessage(ChatMessage::CHAT_QUIT, "");

            send(param->client_sock, (char*)&quitMessage, sizeof(ChatMessage), 0);

            // Nous forçons aussi l'arrêt de notre programme sur la machine locale
            param->bQuit = true;
                
            break;
        };

        // On envoie un ChatMessage de texte correspondant à celui tappé
        ChatMessage chatMessage(ChatMessage::CHAT_MESSAGE, sMessage);
        send(param->client_sock, (char*)&chatMessage, sizeof(ChatMessage), 0);

        printf("[Vous]# %s\n", sMessage);
    }

    delete sMessage;

    return 0;
}

/** Serveur **/
int main(void)
{
    // Il faut appeler ces deux instructions avant toute
    // utilisation de socket
    WSADATA WSAData;
    int error = WSAStartup(MAKEWORD(2,2), &WSAData);

    SOCKET sock;
    SOCKET client_sock;

    // Voici nos structures de socket
    SOCKADDR_IN server_in;
    SOCKADDR_IN client_in;

    int recsize = sizeof(client_in);

    int sock_err;
 
    // Si les sockets Windows fonctionnent
    if (!error)
    {
        // Création de la socket
        sock = socket(AF_INET, SOCK_STREAM, 0);

        // On passe en mode non bloqué, c'est-à-dire que les fonctions recv() et send()
        // ne bloquent pas quand un message n'a pas été envoyé ou reçu
        unsigned long mode = 1;
        ioctlsocket(sock, FIONBIO, &mode);

        // Si la socket est valide
        if (sock != INVALID_SOCKET)
        {
            printf("Bienvenu sur le chat (tappez exit pour quitter) !\n\nLe serveur est maintenant ouvert en mode TCP/IP sur le port %d\n\n", PORT);
 
            // Configuration de la socket
            server_in.sin_addr.s_addr = htonl(INADDR_ANY);
            server_in.sin_family = AF_INET;
            server_in.sin_port = htons(PORT); 

            // On configure le serveur
            sock_err = bind(sock, (SOCKADDR*)&server_in, sizeof(server_in));

            exit_on_fail(sock_err);

            sock_err = listen(sock, 5);

            exit_on_fail(sock_err);

            printf("Patientez pendant que le client se connecte...\n");        

            // On boucle pour attendre la connexion d'un client
            while (true)
            {
                client_sock = accept(sock, (SOCKADDR*)&client_in, &recsize);

                // Si un client se connecte
                if (client_sock != SOCKET_ERROR)
                {
                    printf("\nUn client se connecte avec la socket %d de %s:%d\n", client_sock, inet_ntoa(client_in.sin_addr), htons(client_in.sin_port));
                    break;
                }
            }

            // Structure de paramètre pour envoyer
            // plus d'une variable à la fonction thread
            ThreadParam param;
            param.bQuit = false;
            param.client_sock = client_sock;

            // Création d'un thread pour effectuer la lecture d'un message indépendamment
            // de l'écoute de la socket
            CreateThread(nullptr, 0, &get_console_message, (void*)&param, 0, nullptr);

            ChatMessage* message = nullptr;

            // Comme les packets réseaux reçus peuvent être de différentes tailles
            // et qu'ils sont reçus par segments,
            // nous avons besoin d'un buffer secondaires pour lui ajouter
            // les segments par segments
            int iTotalBytes = sizeof(ChatMessage);
            int iCurrentBytes = 0;

            char* buffer = new char[iTotalBytes];
            char* pRecvBuffer = new char[iTotalBytes];

            while (!param.bQuit)
            {
                // On recoit des données qui peuvent être un segment
                int iBytes = recv(client_sock, buffer, iTotalBytes, 0);

                if (iBytes != SOCKET_ERROR)
                {    
                    // On rajoute le segment reçu dans le buffer à chaque fois
                    // que des données ont été reçues plus haut
                    memcpy(pRecvBuffer + iCurrentBytes, buffer, iBytes);
                    iCurrentBytes += iBytes;

                    // Dès qu'on a reçu le message au complet
                    // c'est-à-dire dès que la taille du buffer a atteint
                    // la taille du message
                    if (iCurrentBytes >= iTotalBytes)
                    {
                        message = (ChatMessage*) pRecvBuffer;

                        // Si l'on recoit un message qui nous indique de quitter
                        if (message->message_type == ChatMessage::CHAT_QUIT)
                        {
                            wprintf(L"Le client s'est deconnecté.\n");

                            system("pause");

                            // On quitte la boucle
                            break;
                        }

                        if (message->sInfo != '\0')
                        {
                            // On affiche le message reçu depuis le client
                            printf("[Client]# : %s\n", message->sInfo);
                        }

                        iCurrentBytes = 0;
                    }

                }
            }

            // On ferme la socket
            closesocket(sock);
        }
 
        WSACleanup();
    }

 
    return EXIT_SUCCESS;
}

 


 

2 – Ordre d’appel des fonctions socket selon le rôle de l’application (client ou serveur) :

OrdreTCP

 

Résumé :

Nous avons construit une application client / serveur pour communiquer en messagerie par le biais de la fenêtre console.

Références :

Apprendre le C++

C++-unofficial.sh-600x600

Intro :

Il vous faut apprendre le C++ avant de concevoir des mécanismes internes à la conception de jeux vidéo.

Il existe beaucoup de tutoriels sur comment apprendre à programmer en C++ sur la toile (et en français !)

Je vous liste une série de tutoriels qui m’ont parus intéressants.

Explications :

– L’excellent tutoriel C++ de Mathieu Nebra sur openclassroom.com (anciennement Site Du Zéro) :

http://openclassrooms.com/courses/programmez-avec-le-langage-c

–  La FAQ C++ de developpez.net :

http://cpp.developpez.com/faq/cpp/

Résumé :

Sur ce site je ne vous apprend pas à utiliser le C++ ; je vous fourni cependant quelques tutoriels sur le sujet.

Les pointeurs intelligents

AymB9

Intro :

Les pointeurs intelligents sont une extension des pointeurs bruts et ont été inventés afin d’éviter les fuites de mémoire.

Explications :

Le principe est qu’un pointeur intelligent encapsule un pointeur brut : le pointeur habituel et le muni d’un système de libération automatique de la mémoire. Il suit le principe RAII.

C’est principalement autour de la notion de « durée de vie » qu’un pointeur tient son utilité. En effet le pointeur intelligent opère sur les actions de création, de copie, de destruction, etc… d’un objet.

Dans la version du C++11 il existe trois types différents de pointeurs intelligents.

– unique_ptr, le pointeur ne peut pas être partagé (il ne peut y avoir qu’un seul propriétaire).
Il est préférable d’utiliser ce type de pointeur plutôt que l’ancien type de pointeur (d’avant C++11) : auto_ptr. En effet auto_ptr ne dispose pas d’un système de protection de la copie !

– shared_ptr, le pointeur peut être partagée par un système de comptage de référence.

L’idée est qu’un pointeur intelligent est automatiquement détruit lorsqu’il sort de portée du bout de code.

Voici les opérateurs surchargés d’un pointeur intelligent :

template <typename T>
class PointeurIntelligent
{
    T* operator->() const;
    T& operator*() const;
};


Exemple :

Une fonction retournant un pointeur : une fois retourné on ne sait pas , quand et comment libérer la mémoire allouée par ce pointeur !

#include <memory>

Data* getData();

 

Mais depuis C++11, on peut utiliser un type le pointeur intelligent nommé unique_ptr comme suit :

#include <memory>

std::unique_ptr<Data> getData();

 

Il sera détruit automatiquement à la fin de la portée du bloc de code ! Comme le pointeur brut est encapsulé dans l’objet unique_ptr<> : il sera aussi détruit avec. Une de ses particularité est qu’il a toujours un unique propriétaire. Mais la fonction std::move permet d’échanger celui-ci.

Un autre type de pointeur intelligent est le shared_ptr, il peut y être adjoint plusieurs références. Lorsque la dernière référence est disjointe, le pointeur est détruit !

#include <memory>

std::shared_ptr<Data> getData();


Pour finir, il est important de noter que l’on peut créer un objet sur le tas en utilisant std::unique_ptr<Objet> !!

Résumé :

Nous avons présenté le concept et les objets des pointeurs intelligents du C++11 !

Références :

– https://msdn.microsoft.com/fr-fr/library/hh279674.aspx

– http://en.wikipedia.org/wiki/Smart_pointer

– http://www.informit.com/articles/article.aspx?p=2085179

Debugguez vos fichiers .FX avec l’interface ID3D10Blob

AMD_6990

Intro :

Quand on compile un effet shader .fx, on a besoin de savoir où se situe l’erreur de compilation lorsque la compilation a échouée.

Explications :

Voici comment procéder avec le bout de code suivant :

#define ShowMessageBoxDXError(hr) char buf[2048]; \
       sprintf(buf, "Error: %s error description: %s\n", DXGetErrorStringA(hr), \
       DXGetErrorDescriptionA(hr)); \
       MessageBoxA(nullptr, buf, "Erreur", MB_ICONHAND | MB_OK);

DWORD dwShaderFlags = D3D10_SHADER_ENABLE_STRICTNESS;

#if defined( DEBUG ) || defined( _DEBUG )
// Permet d'afficher les éventuelles erreurs de la compilation d'un shader
dwShaderFlags |= D3D10_SHADER_DEBUG;
#endif

#define SAFE_RELEASE(x) if (x != nullptr) { x->Release(); x = nullptr; }

ID3D10Blob* pBlob = nullptr;
// Pour la création du shader, on le compile
HRESULT hr = D3DX10CreateEffectFromFile(L"Effet.fx", NULL, NULL, "fx_4_0", dwShaderFlags, 0,
                                D3D10_RENDERER->GetDevice(), NULL,
                                NULL, &m_pEffect, &pBlob, NULL);

if (FAILED(hr))
{
    if (pBlob)
    {
        // Affiche l'erreur de compilation du shader
        MessageBoxA(NULL, (PCSTR)pBlob->GetBufferPointer(), "Erreur dans le fichier shader", MB_ICONHAND | MB_OK);
    }
    else
    {
        ShowMessageBoxDXError(hr);
    }

    SAFE_RELEASE(pBlob);

    return false;
}

SAFE_RELEASE(pBlob);

 

Résumé :

On a expliqué comment procéder pour savoir et afficher où se situe l’erreur de compilation d’un fichier shader .fx.

Théorie sur les lumières

light

Intro :

Dans les jeux vidéo, la gestion du rendu des lumières est cruciale pour effectuer un rendu à la fois réaliste et joli.

Dans cet article nous allons présenter les rudiments de la théorie sur le rendu des lumières.

Le rendu lumineux est un sujet vaste et complexe et pour cause le sujet est encore soumis à des recherches académiques ou en R&D.

En effet, ces dernières années les jeux vidéos les plus récents affichent des rendus de lumière de plus en plus réalistes, en utilisant par exemple un type de rendu nommé Deferred Shading.

[éclater en plusieurs articles]

Prérequis : 

– Savoir ce qu’est une normale

– Savoir lire du C++

– Savoir lire un programme shader HLSL

Explications :

Avant tout, éclaircissons un peu de vocabulaire.

Les deux notions qui suivent ont des propriétés de nature différente :

Les propriétés d’un matériau donnée (or, glace, bois, métal, etc…) déterminent comment un modèle est affiché :

Emissive :

– ce terme désigne la valeur d’émission d’un matériau permettant d’émettre une couleur de lumière comme si elles avaient été toutes désactivées. Notée Ke.

Ambient :

– ce terme désigne la valeur ambiante d’émission d’une lumière, comme le soleil.

Diffuse :

– ce terme désigne la couleur diffuse qu’émet la lumière dans toutes les directions.

Specular :

Specular power :

– représente le degré d’éclat de brillance.

Les propriétés d’une lumière donnée déterminent comment la scène est affichée :

Type de lumière :

– il y a 3 types de lumière :

Point : une lumière qui émet dans toutes les directions.

Spot : une lumière qui émet dans une direction spécifique.

Directional : une lumière qui a une direction donnée mais n’a pas de position donnée, comme par exemple le soleil.

Color :

– la couleur de la source de lumière

Position :

– définie la position de la lumière par trois valeurs de coordonnées

–  Direction :

– définie la direction à laquelle pointe la lumière

Le but du calcul lumineux est de combiner ces deux catégories de propriétés afin déterminer la couleur finale de la lumière (avec ses nuances d’ombre).

Lumière ambiante :

La lumière ambiante désigne, comme son nom l’indique, une lumière qui éclaire dans toutes les directions. C’est comme un soleil qui illumine partout.

L’équation est toute simple :

Ip = pa  * Ia 

[utiliser ambient = Ka + Ga]

Ia désigne l’intensité de la lumière ; et pa le coefficient de réflexion de la lumière ambiante. Ip désigne l’intensité de la lumière résultant de la réflexion sur la surface finale.

Ce principe de calcul de la lumière illuminative est très rudimentaire car il n’affiche pas les ombres des modèles que font apparaître indirectement la lumière émissive.

[image lumière ambiante]

La réflexion diffuse :

Le principe de la réflexion diffuse consiste à considérer la position d’émission d’une lumière quelconque (ou plusieurs)

On a remarqué, mathématiquement, que l’intensité en un point de l’effet d’une lumière quelconque dépend de l’angle formé entre le rayon de lumière qui touche le point de la surface et la normale à la surface.

Il se trouve que, plus l’angle formé entre le rayon de lumière et la normale au plan est faible, plus l’intensité lumineuse réfléchie visible par l’observateur est forte.

[ajouter schéma dot product]

Diffuse-Lighting

Types de calcul lumineux :

 

 

 

Résumé :

Références :

– http://irrlicht-fr.org/lire_tuto.php?id=1014

–  https://takinginitiative.wordpress.com/2010/08/30/directx-10-tutorial-8-lighting-theory-and-hlsl/

– http://www.3dgep.com/texturing-lighting-directx-11/

– http://http.developer.nvidia.com/CgTutorial/cg_tutorial_chapter05.html

Nouveautés C++11

5146902896965.jpg

Intro :

Des changements ont été ajoutés dans la nouvelle version du C++ (nommée C++11).

Tous les mots-clés en liaison avec les changements sont représentés dans l’image de l’en-tête.

Explications :

Le mot-clé / constante nullptr :

– Il remplace l’ancien « #define NULL 0  » pour définir un pointeur nul.

Le mot-clé auto :

– Il identifie automatiquement le type de la variable correspondante en fonction de l’affectation décrite. Il permet de simplifier l’écriture du code.

Exemple :

for(auto i = nombres.begin() ; i != nombres.end() ; ++i)
{
    std::cout << (*i) << std::endl;
}


decltype :

– Permet de retrouver le type de l’objet lorsque l’on a utilisé le mot-clé auto.

Variadic templates :

– Permet d’utiliser un nombre variable de spécifications de classe sur les templates.

unique_ptr<> :

– Un pointeur intelligent possédant qu’un seul (et unique) propriétaire !

Initialisation des données membres non-statique :

– Les variables membres non-statiques peuvent être définit au sein même de la déclaration de la classe.

class UneClasse
{
public:
    UneClasse() {}
 
    int a = 7;
    int b = 5;
 
private:
    std::string s{"Une chaîne"};
};


Alias de templates :

– On peut désormais utiliser le mot-clé « using » pour faire un alias à la place du classique typedef

Constructeurs délégués :

– Avec le C++11, un constructeur peut appeler un autre constructeur directement.

struct A
{
    A(int);
    A(): A(42) { } // délègue au constructeur A(int)
};


Range-based for :

– On peut parcourir un tableau, une liste, un vector, etc… avec une boucle simplifiée :


std::vector<double> v;

for (double d : v)
{
    std::cout << d << std::endl;
}

for (auto d : v)
{
    std::cout << d << std::endl;
}

for (auto& d : v)
{
    ++d;
}

for (auto const& d : v)
{
    ++d;
}

std::map<int, std::string> m;

for (auto element : m)
{
    printf("%d\n", element.first);
    printf("%s\n", element.second);
}


Les fonctions lambda : 

– La syntaxe est : [ liste de capture ] ( paramètres ) retour { code }

Exemple :

[](int element) {
    cout << element << endl;
}

On peut passer une fonction anonyme à une variable :

std::function<int (int, int)> addition = [](int x, int y) -> int {
    return x + y;
};


Les énumérations fortement typées :

Dorénavant les numérations peuvent être fortement typées ainsi :

// On rajoute le mot-clé "class"
enum class Direction { Haut, Droite, Bas, Gauche }; // Se comporte comme des attributs !

//  On est obligé de spécifier la classe d'origine "Direction::"
Direction direction = Direction::Haut;

// Pour afficher sa valeur entière on doit convertir :
std::cout << static_cast<int>(direction) << std::endl;


Bug template corrigé :

// Avant :
std::vector<std::vector<int> > nombres;

// Après :
std::vector<std::vector<int>> nombres;


Les listes d’initialisateurs :

std::vector<int> nombres = { 1, 2, 3, 4, 5 };
std::map<int, std::string> nombres = { { 1, "un" }, { 2, "deux" }, { 3, "trois" } };


Les tableaux à taille fixe :

std::array<int, 5> tableauFixe;


Tuple :

Mémoire locale pour un thread :

– Avec le mot-clé std::thread_local, on peut spécifier une variable locale à un thread !

Sizeof sur les attributs de classes :

– On peut désormais savoir la taille qu’occupe un attribut d’une classe.

Références :

– http://cpp.developpez.com/redaction/data/pages/users/gbdivers/cpp11/

– http://fr.wikipedia.org/wiki/C%2B%2B11

– http://openclassrooms.com/courses/introduction-a-c-2011-c-0x

Mettre en surbrillance l’icone de la barre des tâches

barre

Intro :

Parfois on a besoin de captiver l’attention du joueur lorsque l’application est minimisée, par exemple lorsque le joueur reçoit un message depuis la messagerie.

Pour cela l’icône dans la barre des tâches peut être mise en surbrillance lorsque l’application est minimisée.

Prérequis :

– Aucun

Explications :

void FlashWindow()
{   
    if (IsIconic(m_Hwnd))
    {
        DWORD now = timeGetTime();
        DWORD then = now;

        // C'est la fonction qui permet de mettre en surbrillance
        FlashWindow(m_Hwnd, true);
         
        while (true)
        {
            if (!IsIconic(m_Hwnd))
            {
                FlashWindow(m_Hwnd, false);
                break;
            }
            else
            {
                // On met en surbrillance l'icône périodiquement !
                now = timeGetTime();
                DWORD timeSpan = now > then ? (now - then) : (then - now);
                if (timeSpan > 1000)
                {
                    then = now;
                    FlashWindow(m_Hwnd, true);
                    break;
                }
            }
        }
    }
}

Résumé :

Nous avons appris comment mettre en surbrillance l’icône de votre application dans la barre des tâches.

Fonctions delegates avec std::function et std::bind

 

5146902896965.jpg

Intro :

Nous allons implémenter un système de delegates comme celles existantes en C#.

Tout d’abord qu’est-ce qu’une fonction anonyme (appelée aussi fonction lambda) ?
Une fonction anonyme est tout simplement une fonction qui n’a pas de nom et qui peut être définie à la volée.

Depuis la dernière version C++11 on peut définir des fonctions anonymes.

Dans ce tutoriel nous allons apprendre à utiliser deux objets de la STL que sont : std::function et std::bind.

A noter que je me suis servi de ces delegates pour implémenter le système d’Events de cet article.

Prérequis :

– Savoir un peu lire du C++.

– Disposer d’un compilateur compatible C++11.

Explications :

Une fonction anonyme peut être définie ainsi :

#include <functional>

// Définition d'une fonction anonyme
auto UneFonction = [](int i) -> double { return 2 * i; };

// Appel de cette fonction anonyme
double d = UneFonction(1);

On place des crochets au début pour annoncer une fonction lambda.
Entre parenthèses on indique les paramètres.
Le type indiqué juste après la flèche indique le type de retour de la fonction.

 

L’objet std::function peut être affecté par une simple fonction ou par une fonction anonyme :

#include <functional>

// Une fonction toute simple
int my_func(int a, int b) { return a + b; }
 
// On peut affecter cette précédente fonction à un objet std::function
std::function<int (int, int)> f = my_func;

// On peut affecter directement une fonction anonyme
std::function<int (int)> UneFonction = [](int i) -> double { return 2 * i; };

On met entre crochets : le type de retour et le type des paramètres entre parenthèses.

 

L’objet std::bind permet de lier un ou plusieurs paramètres de manière préétablis à un objet std::function :

#include <functional>
#include <iostream>

// Une fonction standard
void Afficher(std::string& texte)
{
    std::cout << "TEXTE: " << texte << std::endl;
}

// On peut lier à cette précédente fonction un paramètre prédéfini
std::function <void ()> f = std::bind(Afficher, "Du texte !!");

f(); // Affiche "TEXTE : Du texte !!"

 

On peut faire spécifier ultérieurement un paramètre avec std::placeholder::_* :

#include <functional>
#include <iostream>
 
int multiply(int a, int b)
{
    return a * b;
}
 
int main()
{
    auto f = std::bind(multiply, 5, std::placeholders::_1);
    for (int i = 0; i < 10; i++)
    {
        std::cout << "5 * " << i << " = " << f(i) << std::endl;
    }

    return 0;
}

 

On peut stocker des std::function dans un std::vector :

#include <functional>
#include <vector>

std::vector<std::function<double (int)>> fs;

auto UneFonction = [](int i) -> double { return 2 * i; };

fs.push_back(UneFonction);

// On peut invoquer ces fonctions par une boucle automatique
for (auto& f : fs)
{
    f();
}


Résumé :

Nous avons appris comment définir des fonctions delegates comme celle disponibles en C#.

Références :

– http://fr.cppreference.com/w/cpp/utility/functional/function

– http://fr.cppreference.com/w/cpp/utility/functional/bind

– http://oopscenities.net/2012/02/24/c11-stdfunction-and-stdbind/

– http://openclassrooms.com/courses/introduction-a-c-2011-c-0x

Un Performance Timer pour synchroniser vos entités

timer-image

Intro :

Dans un jeu, il peut être utile de calculer le temps entre chaque affichage pour synchroniser la mise à jour de certaines entités.

En effet la vitesse de calcul fait par votre processeur peut rendre la vitesse de ces entités trop rapide.

Par exemple la vitesse de déplacement d’un modèle peut être plus rapide sur la machine d’un PC avec un processeur plus rapide que le vôtre, parce que la boucle de rendu est appelée plus fréquemment que chez vous.

Un mécanisme de calcul du temps passé entre chaque rendu d’affichage permet de pallier ce problème.

Prérequis :

– Savoir lire du C++.

Explications :

Nous allons utiliser la fonction QueryPerformanceCounter qui donne le temps présent en « count ».

#include <windows.h>

__int64 A = 0;
QueryPerformanceCounter((LARGE_INTEGER*)&A);

/* Instructions */

__int64 B = 0;
QueryPerformanceCounter((LARGE_INTEGER*)&B);

On fait la soustraction : B – A, et on a le temps passé (en count) entre l’exécution de ces instructions !

D’autre part il nous faut le temps en seconde (et non en count !)

Dond avec la fonction QueryPerformanceFrequency on obtient le count par seconde.
On aura donc le temps escompté en seconde !


__int64 cntsPerSec = 0;
QueryPerformanceFrequency((LARGE_INTEGER*)&cntsPerSec);

 

Le nombre de secondes escompté en un count est calculé par :

// Secondes par count :
float secsPerCnt = 1.0f / (float)cntsPerSec;

// Temps en secondes :
valueInSecs = valueInCounts * secsPerCnt;


Temps passé entre chaque affichage :

On peut calculer le temps passé entre chaque rendu d’affichage. Notons t le temps passé ;
et t _{i} le temps passé depuis le ième affichage et t _{i-1} le temps passé depuis le précédent affichage.

\Delta t = t _{i} { - } t_{i-1}

 

On calcule ainsi le temps passé entre chaque affichage. On utilise cette valeur de temps afin de synchroniser la mise à jour de nos entités dans la boucle de rendu :


void MaBoucleDeRendu()
{
    /* Instructions */
    
    __int64 currTimeStamp = 0;
    QueryPerformanceCounter((LARGE_INTEGER*)&currTimeStamp);
    
    float fTimeSinceLastFrame = (currTimeStamp - prevTimeStamp) * cntsPerSec;

    UpdateScene(fTimeSinceLastFrame);
    DrawScene();
    
    prevTimeStamp = currTimeStamp;
    
    /* Instructions */
}

 

Voici un exemple de mise à jour d’une entité en utilisant la valeur fTimeSinceLastFrame (toujours changeante) :

 

Résumé :

Nous avons vu de quelle façon synchroniser la mise à jour de vos entités de façon à ce qu’elles varient dans le temps à la même vitesse sur toutes les machines.

Génération procédurale d’un labyrinthe 3D

laby

Intro :

La génération procédurale d’un contenu par un quelconque algorithme permet de le créer de manière intelligente !

Dans cet article nous allons voir comment générer à la volée un labyrinthe en 3D.

Prérequis :

– Savoir lire du C++.

– Savoir ce qu’est un vertex buffer et un index buffer.

– Savoir manipuler un tableau à deux dimensions.

Explications :

Nous allons utiliser une classe Array2D qui nous permettra de manipuler un tableau à deux dimensions.

Voilà le code pour ce tableau spécial et simple d’utilisation (Array2D.h) :

#ifndef ARRAY_2D_H
#define ARRAY_2D_H

// Classe pour gérer facilement un tableau à deux dimensions.
template<typename T>
class Array2D
{
public:
    // Constructeur.
    Array2D(int iWidth = 0, int iHeight = 0) :
    m_iWidth(iWidth),
    m_iHeight(iHeight),
    m_vArray(iWidth*iHeight)
    {
    }

    // Redimensionner.
    void resize(int iWidth, int iHeight)
    {
        m_iWidth = iWidth;
        m_iHeight = iHeight ;
        m_vArray.resize(iWidth*iHeight);
    }

    inline T& operator() (unsigned int x, unsigned int y)
    {
        assert(((y*m_iWidth) + x) < m_vArray.size());
        return m_vArray[ (y*m_iWidth) + x ];
    }

    inline T& operator[] (int pos)
    {
        return m_vArray[pos];
    }

    inline const T& operator() (unsigned int x, unsigned int y) const
    {
        assert(((y*m_iWidth) + x) < m_vArray.size());
        return m_vArray[ (y*m_iWidth) + x ];
    }

    inline const T& operator[] (int x) const
    {
        return m_vArray[pos];
    }   

    inline int getWidth() const { return m_iWidth; }
    inline int getHeight() const { return m_iHeight; }

private:
    int m_iWidth; // Largeur.
    int m_iHeight; // Hauteur.
    std::vector<T> m_vArray; // Le vecteur qui va servir de tableau à deux dimensions.
};

#endif

Voici le fichier d’en-tête MazeGenerator.h

#ifndef MAZE_GENERATOR_H
#define MAZE_GENERATOR_H

#include "Array2D.h"

class MazeGenerator
{
public:
    MazeGenerator();
    virtual ~MazeGenerator() {};

    void generate(const int iRowCellNumber, const int iColumnCellNumber);

    void computeNeighbourgsCeils();

    void createMazeNode(scene::ISceneManager* pSceneManager, NewtonWrapper::CollisionManager* pCollisionManager);

    int generateRandomCellNumber()
    {
        assert(m_iRowCellNumber && m_iColumnCellNumber);

        return rand() % m_iCellNumber;
    }

    scene::CDynamicMeshBuffer* createMeshBuffer();

private:
    Array2D<MazeCell*> m_aMaze;
    int m_iRowCellNumber;
    int m_iColumnCellNumber;
    int m_iCellNumber;
};

#endif

Références :

– http://www.mazeworks.com/mazegen/mazetut/

RAII (Resource Acquisition Is Initialization)

02_clipmaps_02

Intro :

Parfois on a besoin simplement d’utiliser une variable ou un objet de manière temporaire, pour ensuite le ou la détruire facilement après son utilisation.

En C++, les objets résidant sur la pile sont automatiquement détruits lors de la sortie d’un bloc de portée, y compris dans le cas d’une levée d’exception ; leur destructeur est appelé avant que l’exception ne se propage.

On peut alors se servir de ce mécanisme pour détruire automatiquement un objet une fois effectué son utilisation.

Prérequis :

– Avoir quelques notions de C++

Explications :

Il s’agit de manipuler un objet local qui sera utilisé dans la portée d’un bout de code et qui sera ensuite libérée lors de sa destruction.

Système de profiling :

Exemple :

void ExempleAvecRAII()
{
  // On acquiert une ressource (un fichier)
  Fichier logFile("Log.txt");
 
  logFile.Write("Bonjour!");

  // Le fichier logFile sera automatiquement détruit
  // après avoir atteint la fin de ce bloc de code.

  // On n'a pas besoin d'appeler une quelconque fonction
  // close()

  /* On peut lever aussi une exception juste ici. */
  if (logFile.writeHasFailed())
  {
      throw std::exception("Erreur!");
  }
}

 

Résumé :

Nous avons présenté le principe du RAII. Ce principe permet de simplifier les initialisation du constructeur d’un objet, sans avoir à appeler le destructeur.

Récapitulatif C++

C++-unofficial.sh-600x600

Intro :

Le langage C++ est le langage le plus utilisé dans le monde du développement de jeu-vidéo.

Ce petit tutoriel à pour but de vous faire réviser certaines notions de C++.

Prérequis :

– Avoir quelques notions de C++

Explications :

Le C++ est un langage orienté objet. C’est-à-dire que l’on manipule des objets en déclarant des classes au sein du code.

Qu’est-ce qu’une déclaration de fonction / classe ?

Une déclaration indique l’identificateur d’une variable et sa nature (c’est-à-dire respectivement son nom et sa nature).

Qu’est-ce qu’une définition de fonction / classe ?

Une définition implémente la structure d’une fonction ou d’une classe afin de pouvoir l’utiliser.

Commentaires :


/* Ceci est un commentaire
sur plusieurs lignes */

// Ceci est un commentaire en une seule ligne

Qu’est-ce qu’une classe ?

Une classe permet de regrouper dans une même entité des données et des fonctions membres (appelées aussi méthodes) permettant de manipuler ces données.

Créer un objet à partir d’une classe :

Les opérateurs new et delete permettent de créer un objet en allouant sa quantité de mémoire.
A chaque new doit correspondre un delete, sinon il risque d’avoir une fuite de mémoire (memory leak) !
L’opérateur delete appelle le constructeur à détruire de la classe.

Qu’est-ce qu’un objet ?

Un objet est l’instance d’une certaine classe.

L’héritage :

En C++, les classes peuvent hériter d’autres classes et la relation d’héritage est exprimée à l’aide de l’opérateur de dérivation « : ».

class Chat : public Animal
{
public:
private:
};

L’encapsulation :

class Sprite2D
{
public:
    Sprite2D();
    virtual ~Sprite2D();

protected:

private:
    int m_iVariable;
};

– les déclarations en dessous de « public: » seront accessibles depuis n’importe quelle fonction.
– en dessous de « protected: » : pareil que private mais accessible depuis les classes
filles.
– en dessous de « private: » : accessible que depuis les fonctions de la classes.

L’opérateur sizeof :

Sert à calculer la taille de n’importe quel type de données.
On note que sizeof(char) = 1.

sizeof(x) = 1 ssi x est un byte. (Un byte est la plus petite unité adressable d’un ordinateur)

Opérateurs d’entrées-sorties :

On peut écrire dans la console avec l’instruction suivante :


#include <iostream>

std::cout << "Une phrase." << std:endl;

On peut écrire du code UTF-8 aussi :


#include <iostream>

std::wcout << "Une phrase en français." << std:endl;

Les pointeurs :

Un pointeur désigne une adresse d’une variable d’un certain type.


int* pEntier; // Déclare un pointeur de type int

int iEntier;

&iEntier // permet d'obtenir l'adresse de iEntier

pEntier = &iEntier; // Affectation du pointeur

Le références :

Une référence peut être vue comme un alias d’une variable. C’est un « synonyme » d’une autre variable. Une modification de l’une modifiera l’autre.

int i = 3;
int& i; // Un référence à la variable i

Valeur par défaut des paramètres d’une fonction :


// Déclaration d'une fonction à paramètres par défaut
void print(long valeur, int base = 10);

Fonctions inline :

Les fonction inline fonctionnent comme les macros.

Surcharge de fonctions :

Tas et Pile :

Tas :

Pile :

Instanciation d’une classe :

Avion avion; // Sur la pile
Avion* pAvion = new Avion(); // Sur le tas

Fonctions membres statiques :

– existent même s’il n’y a aucune instance de classe.

– ne peuvent accéder qu’aux membres statiques de la classe.

Exceptions :

Moyen de gérer les erreurs dans un programme C++.

Throw : // Le mot-clé pour lever une exception.

/* Bloc try d'une gestion d'exception */
try
{
    // instructions pouvant déclencher des exceptions
    // dérivant de std::exception
}
catch ( const std::exception & e )
{
    std::cerr << e.what();
}

Caméra à la 3ème personne trigonométrique

caméra

Intro :

Pour se déplacer dans la scène 3D il faut mouvoir une caméra.

Dans cet article nous allons implémenter une caméra qui tourne autour d’un joueur (ou autour d’une position)

Prérequis :

– Comprendre un minimum de notions trigonométriques

Explications :

Déclarer ces trois variables dans un fichier quelconque :

float m_fCameraSpeed;
float m_fDistance;
D3DXVECTOR2 m_v2CameraAngle;

 

Voilà les fonctions d’initialisation et de mise à jour de la caméra à la 3ème personne :


void CameraManager::Initialize()
{
    m_v2CameraAngle = D3DXVECTOR2(0.0f, 0.0f);
}

void CameraManager::UpdateThirdPersonCamera()
{
    // C'est deux fonctions retournent simplement la quantité de mouvement
    // du pointeur de la souris
    // Voir l'article sur le InputManager
    float fX = InputManager::getSingletonPtr()->getMouseRelativePos().x;
    float fY = InputManager::getSingletonPtr()->getMouseRelativePos().y;

#define CAMERA_SPEED_ROTATE_AROUND 0.0037f
    // Normalement il faut utiliser le temps passé depuis le dernier affichage
    // (voir l'article sur la variable fTimeSinceLastFrame "Performance Timer")
    m_v2CameraAngle.x += fX * CAMERA_SPEED_ROTATE_AROUND;
    m_v2CameraAngle.y -= fY * CAMERA_SPEED_ROTATE_AROUND;
    // Clamp : une fonction qui permet de garder le résultat
    // passé entre un intervalle donné
    m_v2CameraAngle.y = Clamp(camAngle.y, 0.003f, Ogre::Math::PI - 0.003f);

    // Usage de la trigonométrie pour faire en sorte que la caméra
    // fasse une rotation autour d'un point
    float cosX = cos(m_v2CameraAngle.x);
    float cosY = cos(m_v2CameraAngle.y);
    float sinY = sin(m_v2CameraAngle.y);
    float sinX = sin(m_v2CameraAngle.x);

    float xf = cosX * sinY;
    float yf = cosY;
    float zf = sinX * sinY;

    D3DXVECTOR3 finalPos(xf, yf, zf);

    /* Position du joueur (où de l'objet auquel vous voulez tourner autour !*/
    D3DXVECTOR3 playerPos = GAME->getPlayer()->getSceneNode()->getPosition();

    // Votre objet caméra utilisé
    m_pCamera->setPosition(finalPos * m_fDistance + playerPos);
    m_pCamera->lookAt(GAME->getPlayer()->getSceneNode()->getPosition());

    /** Pour centrer le curseur de ma souris */
    centerCursorPos();
}

Résumé :

Nous avons présenté un moyen d’implémenter une caméra trigonométrique qui opère une révolution autour de la position du joueur !

Principe du Z-buffer

zbuffer

Intro :

L’utilisation du Z-buffer est une méthode consistant à afficher dans le bon ordre de profondeur les primitives géométriques envoyées au rendu 3D.

Prérequis :

– Comprendre les principales étapes de rendu de DirectX : voir cet article

Explications :

La technique du Z-buffer consiste à enregistrer pour chaque pixel une valeur de profondeur dans un tampon dédié (en l’occurrence le Z-buffer). Ce tampon a la même dimension que celui du frame buffer. Ces valeurs dans le tampon varient de 0.0f à 1.0f.

Lors de la transformation du repère 3D (celui des meshes / modèles) vers le repère 2D (celui de l’écran) nous avons bien les pixels sur les coordonnées (x, y) conservées mais nous avons perdu l’information sur la profondeur (z) ; il faut la recalculer par un algorithme adjacent.

 

1) Comment ça fonctionne ?

Avant qu’un pixel est affiché dans le tampon de rendu, sa valeur de profondeur est comparée
par sa valeur correspondante actuelle dans le Z-buffer. Si le pixel a sa valeur de profondeur inférieure ou égale alors le pixel est affiché et cette valeur est enregistrée dans le Z-buffer ; autrement le pixel est ignoré.

La carte graphique compare deux profondeurs (Z) une à une, et n’affiche que le pixel le plus proche de la caméra. Ensuite DirectX va automatiquement ignorer l’affichage des triangles cachés par d’autres triangles.

2) Comment le paramétrer ?

Le calcul effectué par le Z-buffer peut être ignoré et désactiver par un appel de l’API DirectX (voir cet article). Lorsqu’il est ignoré par DirectX, les derniers éléments dessinés seront affiché par dessus tous les autres.

Il faut noter aussi qu’à chaque tour de boucle le Z-buffer est réinitialisé et recalculé par cet appel :

m_pDevice->Clear(...)


Résumé :

Nous avons expliqué quel est le rôle du tampon de profondeur (le Z-buffer) dans le rendu 3D.
En effet cette étape permet de cacher les triangles placés à l’arrière d’autres triangles moins proches de la caméra.

Références :

– http://fr.wikipedia.org/wiki/Z-buffer

– http://www.toymaker.info/Games/html/z-buffer.html

Les principales fonctions de la bibliothèque D3DX (DirectX 10)

jigsawpuzzle

Intro :

La bibliothèque D3DX (D3DX utility library) implémente plusieurs fonctions courantes afin de faciliter certaines tâches d’utilisation de l’API 3D DirectX 10.

Nous verrons les objets de cette bibliothèque suivant :

– ID3DX10Sprite

– ID3DX10Font

– ID3DX10Mesh

Prérequis :

– Savoir comment initialiser correctement DirectX 10 (voir ces articles)

Explications :

 


 

ID3DX10Sprite :

Permet de simplifier l’affichage d’images 2D.

On créé cet objet avec la fonction : D3DX10CreateSprite()

J’ai déjà expliqué son utilisation dans cet article (dans la première méthode)

 


 

ID3DX10Font :

Permet d’afficher du texte à l’écran avec une police de font donnée.

On déclare les objets ID3DX10Font et ID3DX10Sprite :

ID3DX10Font* m_pFont;
ID3DX10Sprite* m_pSprite;

On initialise plus loin ces objets comme suit :

// Il faut créer un objet ID3DX10Sprite
D3DX10CreateSprite(m_pd3dDevice, 0, &m_pSprite);

/* Détaille les attributs de cette police d'affichage */
D3DX10_FONT_DESC fd;

// Définit la hauteur d'un caractère de cette police
fd.Height = 30;
// Définit la largeur d'un caractère de cette police
fd.Width = 18;
fd.Weight = 0;
fd.MipLevels = 4;
// En italique ou non
fd.Italic = false;
fd.CharSet = OUT_DEFAULT_PRECIS;
fd.Quality = DEFAULT_QUALITY;
fd.PitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
// Définit le nom de la police à utiliser
wcscpy(fd.FaceName, L"Impact");
 
D3DX10CreateFontIndirect(m_pd3dDevice, &fd, &m_pFont);

 

Dans la boucle de rendu on ajoute :


EnableZBuffer(false);

m_pSprite->Begin(D3DX10_SPRITE_SAVE_STATE);
     m_pFont->DrawText(m_pSprite, L"Hello World!", -1, &rectangle, DT_NOCLIP, color);
m_pSprite->End();

EnableZBuffer(true);

Le paramètre D3DX10_SPRITE_SAVE_STATE permet de restaurer les états du rendu 3D après l’appel End() comme ils étaient configurés avant l’appel Begin().

On désactive l’écriture sur le Z-Buffer pour préserver la profondeur de ce qui est affiché avant l’appel à m_pFontText().

 


 

ID3DX10Mesh :

 

Bien configurer un projet Visual C++

index

[rajouter utf-8]

Intro :

Les débutants en C++ sous Windows ont souvent du mal à compiler leur premier programme.
En effet pour compiler un programme il faut indiquer au compilateur où chercher ses dépendances (répertoires de fichiers d’en-têtes et de bibliothèques)

Prérequis :

– Savoir un peu écrire et lire du C++

Explications :

1) Ouvrez votre projet C++

2) Allez dans les propriétés du projet (ALT+F7)

proprieties_2

3) Allez dans la partie « Répertoires C++ »

repertoires_1

Vous verrez deux champs qui sont là pour indiquer où chercher vos fichiers #include et bibliothèques .lib des dépendances de votre projet.

4) Il vous reste plus qu’à sélectionner le répertoire de votre dépendance !

repertoires_2

5) Compilez pour tester si votre projet est bien configuré !

 Résumé :

Dans cet article nous avons configuré Visual C++ de façon à ce qu’il trouve les dépendances externes à votre projet afin de le compiler sans erreurs apparentes.

 

Les fichiers d’en-têtes précompilés (Visual C++)

PrecompiledHeaders

Intro :

Parfois, étant donné le nombre faramineux de fichiers d’en-tête nécessaires pour compiler un fichier source .cpp, il peut etre nécessaire de tous les regrouper en seul fichier afin de le compiler en une seule fois.
Cette compilation accélère immédiatement la compilation des autres fichiers .cpp.

Prérequis :

– Savoir un peu lire du C++

Explications :

1) Ouvrez votre projet VC ++

2) Créez un fichier Stdafx.h où vous mettrez tous vos fichiers .h les uns à la suite des autres (comme dans la première image).

3) Créez juste un fichier Stdafx.cpp où vous metterez :

#include "Stdafx.h"

4) Faite un clic droit sur ce dernier fichier :

proprieties_4

5) Allez dans la partie « En-tête précompilés ». Choisissez « Création /Yc » et ajouter comme dans l’image « Stdafx.h »

proprieties_5

6) Ensuite allez dans les propriétés du projet (ALT+F7)

proprieties_2

7) Veillez à ce que « Stdafx.h » soit écrit et choisissez « Utilisation /Yu » :

proprieties

8) Dans le fichier Stdafx.h, vous pouvez mettre tous vos fichier #include :

#include "BoundingVolumeTrigger.h"
#include "Mob.h"
#include "NPC.h"
#include "TextAnnouncer.h"
#include "EventManager.h"
#include "M2Importer.h"
#include "ComputerInfo.h"
#include "CommandsManager.h"
#include "DataParameters.h"
#include "Commands.h"
#include "Actor.h"
#include "GUIScrollBar.h"
#include "GUIManager.h"
#include "GUIButton.h"
#include "GUIDraggableWindow.h"

9) Il reste plus qu’à mettre dans tous vos fichiers .cpp une seule en-tête :

#include "Stdafx.h"

10) Compilez et admirez la vitesse !

Résumé :

Nous avons appris comment regrouper un grand nombre de fichiers d’en-tête (des #include) en un seul fichier afin de rendre la compilation beaucoup plus rapide.

Introduction à la programmation réseau (Sockets) – partie 1

48405289

Intro :

Pour concevoir un jeu vidéo multijoueur il vous faut utiliser dans le programme C++ des objets qu’on appelle sockets. Ceci afin de pouvoir faire communiquer des ordinateurs à distance.

Prérequis :

– savoir un peu lire et écrire du C++

– être sous Windows.

Explications :

Les sockets sont des objets représentants une interface de connexion liée à une machine afin de faire communiquer les ordinateurs à travers un réseau (a fortiori par Internet)

1 – Tout d’abord je vais vous présenter un petit programme qui affiche une page HTML dans la console à partir d’une URL web en utilisant les sockets. Rien ne vaut un bel exemple concret pour comprendre !

2 – Aussi, comme il est important de connaître de quoi l’on parle, je vais énumérer toutes les fonctions et mots-clés utilisés dans la programmation socket en C.

 


 

1 – Voici ce premier programme :

/** Inspirez-vous des commentaires pour comprendre **/

#include <winsock2.h>
#include <stdio.h>
#include <stdlib.h>

// Fonction qui permet d'afficher le contenu d'une page web
void FetchAndPrintPage(SOCKET socket, const char* sHost, const char* sPage)
{
    // Chaîne de caractère représentant la requête HTTP
    const char* sRequestFormat = "GET %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: FetchAndPrint.c\r\n\r\n";
    char* sMsg = new char[4096];

    // On compose les paramètres dans une chaîne de caractères finale
    // représentant la requête HTTP à envoyer
    sprintf(sMsg, sRequestFormat, sPage, sHost);

    // La fonction send() permet d'envoyer des informations vers
    // l'ordinateur distant (le serveur) ; ici on envoit la requête HTTP
    if (send(socket, sMsg, strlen(sMsg), 0) != SOCKET_ERROR)
    {
        // Comme la fonction recv(), qui suit, ne réceptionne qu'une partie du message
        // reçu, il faut l'invoquer en boucle jusqu'à ce que le message
        // est entièrement réceptionné.
        while (true)
        {
            // Le nombre d'octet reçu
            int iBytes;
            // Le tampon de caractères stockant le contenu de la page web
            char sBuffer[4096];

            // La fonction recv() ne réceptionne qu'une partie du message
            // reçu ; elle retourne le nombre d'octets reçu et stocke ce contenu
            // dans la variable tampon sBuffer
            iBytes = recv(socket, sBuffer, 4096, 0);

            // On sort de la boucle s'il n'y a plus de message à receptionner ou
            // qu'une erreur a eu lieu !
            if (iBytes == 0 && iBytes == -1)
            {
                break;
            }

            // Pour finaliser la chaîne on doit rajouter le caractère nul à celle-ci
            sBuffer[iBytes-1] = '\0';

            // On affiche le contenu de la page web dans la console
            printf("\n\n%s\n", sBuffer);
            // On met en pause le programme afin de lire la portion du texte reçu
            system("pause");
        }
    }
    else
    {
        printf("Socket Error Code = %i\n", WSAGetLastError());
    }

    delete sMsg;
}

int main()
{
    // Ces deux suivantes instructions initialisent
    // l'utilisation des sockets sous Windows
    WSADATA WSAData;
    WSAStartup(MAKEWORD(2,2), &WSAData);

    // Structure utilisée afin de stocker les informations d'un socket
    in_addr addr;

    const char* sHost = "anthroponaute.fr";

    // Cette fonction créé un socket
    SOCKET s = socket(AF_INET, SOCK_STREAM, 0);

    if (s == INVALID_SOCKET)
    {
        // Affiche la signification de l'erreur
        printf("Socket Error Code = %i\n", WSAGetLastError());

        system("pause");

        exit(EXIT_FAILURE);
    }
    
    // Pour obtenir des informations socket sur cette adresse web
    hostent* pHost = gethostbyname(sHost);

    if (pHost != nullptr)
    {
        printf("Adress Name = %s\n", pHost->h_name);

        addr.s_addr = *(u_long*) pHost->h_addr_list[0];

        printf("IP Address = %s\n", inet_ntoa(addr));
    }
    else
    {
        printf("Socket Error Code = %i", WSAGetLastError());

        system("pause");

        exit(EXIT_FAILURE);
    }

    // On remplit cette structure afin de configurer et situer notre socket
    SOCKADDR_IN adress_in;
    adress_in.sin_family = AF_INET;
    adress_in.sin_addr = addr;
    adress_in.sin_port = htons(80);

    /*************/

    // On se connecte sur le serveur web
    if (connect(s, (SOCKADDR*)&adress_in, sizeof(adress_in)) == SOCKET_ERROR)
    {
        printf("Socket Error Code = %i\n", WSAGetLastError());

        system("pause");

        exit(EXIT_FAILURE);
    }
    else
    {
        printf("Connected !\n");
    }
    // On affiche la page du site en spécifiant son répertoire
    FetchAndPrintPage(s, sHost, "/blog-informatique/");

    WSACleanup();

    return 0;
}

 

Copiez / collez et compilez !

 


 

2 – Voici maintenant tous les principes, concepts et fonctions de la programmation de sockets :

Voici les fichiers d’en-tête nécessaires :

#include <winsock2.h>
#include <stdio.h>
#include <stdlib.h>

 

Voici les #define préétablis :

SOCKET pour l'utilisation de socket (à la place de int)
SOCKADDR_IN pour struct sockaddr_in
SOCKADDR pour struct sockaddr
IN_ADDR pour struct in_addr
INVALID_SOCKET -1
SOCKET_ERROR -1

 

Voici la structure principale à utiliser :

struct sockaddr_in
{
    short              sin_family;
    unsigned short     sin_port;
    struct   in_addr   sin_addr;
    char               sin_zero[8];
};

Elle est utilisée pour configurer la socket.

 

Voici comment l’utiliser :

SOCKADDR_IN sin;
sin.sin_addr.s_addr = htonl(INADDR_ANY);   
sin.sin_family = AF_INET;
sin.sin_port = htons(23);

sin.sin_addr.s_addr : c’est l’adresse qu’on affecte à cette configuration de socket (c’est peut-être l’adresse représentant un client ou un serveur, cela dépend du rôle de notre socket)

INADDR_ANY : adresse IP automatique

Pour spécifier une adresse manuellement on peut utiliser la fonction inet_addr()

sin.sin_addr.s_addr = inet_addr("127.0.0.1");

sin.sin_family = AF_INET : spécifie que l’on passe la socket en mode Internet

sin.sin_port : spécifie le numéro de port de la socket à utiliser

En occurrence, qu’est-ce qu’un port ?

Un port sert à spécifier à quel service d’application les informations doivent être envoyées.

En effet sur le réseau Internet global, chaque ordinateur est représenté par un numéro d’IP, mais cette adresse IP ne spécifie pas à quelle application de l’ordinateur l’information doit être envoyé.

En conséquence les applications / services sont identifiés par des numéros de port (ex : port 80 pour
le service HTTP)

 

Ces deux fonctions doivent être appelées pour initialiser ou clôturer l’utilisation des sockets :

// Au début du programme :
WSADATA WSAData;
WSAStartup(MAKEWORD(2,2), &WSAData);

// A la fin du programme :
WSACleanup();

 

Pour initialiser une socket :

SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);

Voici les différents paramètres de cette fonction :

AF_INET // Spéficie que l'on va utiliser le mode Internet

Types de socket :

SOCK_STREAM  // Spéficie que l'on va utiliser le protocole TCP/IP. 

SOCK_DGRAM // Spéficie que l'on va utiliser le protocole UDP/IP.

– Le protocole TCP est un protocole dit connecté. Il contrôle si le paquet
est arrivé à destination si ce n’est pas le cas il le renvoie.

– Le procotole UDP est un protocole dit non connecté. A l’inverse du protocole TCP
il ne contrôle pas si le paquet est arrivé à destination.

 

Cette fonction permet de convertir une addresse IP (en char*) afin d’être
affectée à la structure IN_ADDR.

inet_addr("127.0.0.1");
inet_ntoa()
int connect(int socket, struct sockaddr* addr, socklen_t addrlen);
int bind(int socket, const struct sockaddr* addr, socklen_t addrlen);
int listen(int socket, int backlog);
int accept(int socket, struct sockaddr* addr, socklen_t* addrlen);

Résumé :

Nous avons créé un programme qui affiche une page web afin de comprendre la programmation sockets.

Au final nous avons énuméré tous les concepts liés à la programmation sockets.

Références :

– http://pub.phyks.me/sdz/sdz/les-sockets.html

Intégration de Newton Dynamics dans Irrlicht 3D

physics

Intro :

Un moteur physique dans un jeu vidéo permet de simuler les phénomènes physiques du monde réél. Il fait bouger les objets dans le jeu par des mouvements réalistes.

Ici nous allons utiliser le moteur physique Newton Dynamics pour l’intégrer dans le moteur 3D Irrlicht.

Site web de Newton Dynamics : www.newtondynamics.com

Prérequis :

Savoir lire un peu du C++.

Explications :

Mettez dans un fichier PhysicsManager.h le code suivant :


#ifndef PHYSICS_MANAGER_H
#define PHYSICS_MANAGER_H

#include <irrlicht.h>
#include <Newton.h>

#include <string>
#include <iostream>
#include <fstream>

#define GAME_CUBE_MASS 15.0f

using namespace irr;

class PhysicsManager
{
public:
    PhysicsManager();
    virtual ~PhysicsManager();

    // Lance un cube depuis la position de la caméra
    void LaunchCube(scene::ISceneNode* pCubeSceneNode, const core::vector3df& direction);

    // Analyse un mesh et construit un objet de collision Newton correspondant
    NewtonCollision* CreateCollisionFromMesh(scene::IMesh* pIrrlichtMesh, const core::vector3df& v3Scale);

    // Créé un objet de collision Newton d'une forme englobante et correspondante avec la forme complexe du mesh passé
    // en paramètre
    NewtonCollision* CreateCollisionConvexHullFromMesh(scene::IMesh* pIrrlichtMesh);

    // Prépare un objet Newton Body pour la gestion physique
    void InitializeRigidBody(const scene::ISceneNode* pSceneNode, NewtonBody* pBody);

    // Retourne vrai si le fichier cache de collision Newton existe
    bool PhysicsManager::CollisionCacheFileExist(const std::string& sCacheFileName);

    /* Callbacks */
    static void SetTransform(const NewtonBody* pBody, const float* matrix, int threadIndex);
    static void ApplyGravity(const NewtonBody* pBody, float timestep, int threadIndex);

    // Rafraichit le moteur physique Newton
    void Update(float fValue);

    // Ajoute un objet à traiter au moteur 3D
    void CreateDynamicBody(scene::IMeshSceneNode* pMeshNode, float fMass,
                           bool bUseCache, const std::string& sCacheName);

    static void SerializeCollision(void* serializeHandle, const void* buffer, int size);
    static void DeSerializeCollision(void* serializeHandle, void* buffer, int size);

private:
    // L'objet principale de la librairie Newton
    NewtonWorld* m_pNewtonWorld;
};

#endif

 

Mettez dans un fichier PhysicsManager.cpp le code suivant :

Cette fonction s’occupe de lancer un cube dans la direction spécifiée.

void PhysicsManager::LaunchCube(scene::ISceneNode* pCubeSceneNode, const core::vector3df& direction)
{
    NewtonCollision* pNewtonCollisionBox = nullptr;  
    NewtonBody* pBody = nullptr;

    // Calcule la taille englobante de la scene node
    core::vector3df size(pCubeSceneNode->getBoundingBox().MaxEdge - pCubeSceneNode->getBoundingBox().MinEdge);

    // En effet un objet NewtonCollision doit être agrandi ou réduit par rapport à la véritable échelle de la SceneNode
    pNewtonCollisionBox = NewtonCreateBox(m_pNewtonWorld, size.X, size.Y, size.Z, 0, 0);
        
    // Créé l'objet NewtonBody
    const float* matrix = core::IdentityMatrix.pointer();
    pBody = NewtonCreateDynamicBody(m_pNewtonWorld, pNewtonCollisionBox, matrix);
    
    // On détruit l'objet collision dont on n'a plus besoin
    NewtonDestroyCollision(pNewtonCollisionBox);    
    
    // On lui envoit les transformations de la scène node (position, rotation).
    NewtonBodySetMatrix(pBody, pCubeSceneNode->getRelativeTransformation().pointer());

    // On envoit un pointeur de la scène node pour pouvoir le récupérer plus tard.
    NewtonBodySetUserData(pBody, pCubeSceneNode);

    // Prépare l'objet pour le traitement physique 3D
    InitializeRigidBody(pCubeSceneNode, pBody);

    float directionArray[3];

    directionArray[0] = direction.X;
    directionArray[1] = direction.Y;
    directionArray[2] = direction.Z;

    // On lui impulse un mouvement dans la direction directionArray
    NewtonBodySetVelocity(pBody, directionArray);
}

 

La fonction suivante s’occupe de créer un objet Newton Body complètement statique et muni d’une collision solide.

NewtonCollision* PhysicsManager::CreateCollisionFromMesh(scene::IMesh* pIrrlichtMesh, const core::vector3df& v3Scale)
{
    // On prépare un certain type d'objet de collision
    NewtonCollision* pNewtonCollision = NewtonCreateTreeCollision(m_pNewtonWorld, 0);

    NewtonTreeCollisionBeginBuild(pNewtonCollision);

    scene::IMeshBuffer* pMeshBuffer = nullptr;
    int index1, index2, index3;

    /** Faire attention au format des vertex !! **/
    /** Si vous utilisez une autre déclaration de vertex, changez la ici ! **/
    const video::S3DVertex2TCoords* pVertices = nullptr;
    const u16* pIndices = nullptr;
    float floatArray[9] = {0};

    for (unsigned int iMeshBufferNumber = 0; iMeshBufferNumber < pIrrlichtMesh->getMeshBufferCount() ; ++iMeshBufferNumber)
    {
        pMeshBuffer = pIrrlichtMesh->getMeshBuffer(iMeshBufferNumber);

        pIndices = pMeshBuffer->getIndices();
        pVertices = (video::S3DVertex2TCoords*) pMeshBuffer->getVertices();

        for (unsigned int j = 0; j < pMeshBuffer->getIndexCount(); j += 3)
        {
            index1 = pIndices[j + 0];
            index2 = pIndices[j + 1];
            index3 = pIndices[j + 2];

            floatArray[0] = pVertices[index1].Pos.X * v3Scale.X;
            floatArray[1] = pVertices[index1].Pos.Y * v3Scale.Y;
            floatArray[2] = pVertices[index1].Pos.Z * v3Scale.Z;

            floatArray[3] = pVertices[index2].Pos.X * v3Scale.X;
            floatArray[4] = pVertices[index2].Pos.Y * v3Scale.Y;
            floatArray[5] = pVertices[index2].Pos.Z * v3Scale.Z;

            floatArray[6] = pVertices[index3].Pos.X * v3Scale.X;
            floatArray[7] = pVertices[index3].Pos.Y * v3Scale.Y;
            floatArray[8] = pVertices[index3].Pos.Z * v3Scale.Z;

            NewtonTreeCollisionAddFace(pNewtonCollision, 3, floatArray, sizeof(float)*3, 0);
        }
    }
    
    // Finalise la contrustion de l'objet collision
    NewtonTreeCollisionEndBuild(pNewtonCollision, 1);
    
    return pNewtonCollision;
}

 

La fonction suivante créée un objet collision englobant la forme du mesh passé en paramètre.

// Créée un objet collision avec la forme englobante d'un mesh
NewtonCollision* PhysicsManager::CreateCollisionConvexHullFromMesh(scene::IMesh* pIrrlichtMesh)
{
    unsigned int iMeshBufferNumber = 0;
    scene::IMeshBuffer* pMeshBuffer = nullptr;
    float* afVertices = nullptr;

    unsigned int iTotalVertices = 0;

    for (iMeshBufferNumber = 0; iMeshBufferNumber < pIrrlichtMesh->getMeshBufferCount() ; ++iMeshBufferNumber)
    {
        iTotalVertices += pIrrlichtMesh->getMeshBuffer(iMeshBufferNumber)->getVertexCount();
    }

    afVertices = new float[iTotalVertices * 3];
    u32 iTmpCounter = 0;

    for (iMeshBufferNumber = 0 ; iMeshBufferNumber < pIrrlichtMesh->getMeshBufferCount() ; ++iMeshBufferNumber)
    {
        pMeshBuffer = pIrrlichtMesh->getMeshBuffer(iMeshBufferNumber);

        video::S3DVertex2TCoords* S3vertices = (video::S3DVertex2TCoords*)pMeshBuffer->getVertices();

        for(unsigned int i = 0; i < pMeshBuffer->getVertexCount(); ++i)
        {
            afVertices[iTmpCounter++] = S3vertices[i].Pos.X;
            afVertices[iTmpCounter++] = S3vertices[i].Pos.Y;
            afVertices[iTmpCounter++] = S3vertices[i].Pos.Z;
        }
    }

    NewtonCollision* pNewtonCollision = NewtonCreateConvexHull(m_pNewtonWorld, iTotalVertices, afVertices,
        sizeof(float)*3, 0.1f, 0, nullptr);

    delete [] afVertices;

    return pNewtonCollision;
} 

 

Les fonctions suivantes sont des callbacks (c’est-à-dire des fonctions appelées de manière
interne à Newton Dynamics).
La première sert à Irrlicht, elle permet de  savoir comment doivent être mis en mouvement les SceneNode enregistrées.
La deuxième indique à Newton comment doivent être configuré la pesanteur.

void PhysicsManager::SetTransform(const NewtonBody* const pBody, const float* matrix, int threadIndex)
{
    scene::ISceneNode* pSceneNode = (scene::ISceneNode*) NewtonBodyGetUserData(pBody);

    if (pSceneNode)
    {
        core::matrix4 transform;
        transform.setM(matrix);
        pSceneNode->setPosition(transform.getTranslation());
        pSceneNode->setRotation(transform.getRotationDegrees());
    }    
}

void PhysicsManager::ApplyGravity(const NewtonBody* pBody, float timestep, int threadIndex)
{
   /* Cette fonction est une fonction Callback. Elle sera appelée à chaque fois
   qu'une modification aura lieu sur le corps. */

   // On récupère en premier lieu la masse ainsi que l'inertie
   float masse; // Contiendra la masse de l'objet pris en paramètre par la fonction
   float inertieX, inertieY, inertieZ; // Contiendra l'inertie du corps
   float force[3]; // Spécifiera la force appliquée sur le corps

   NewtonBodyGetMassMatrix(pBody, &masse, &inertieX, &inertieY, &inertieZ);
    
   force[0] = 0.0f; // X
   force[1] = -masse * 9.81f; /// timestep; // 9.81 est l'attraction gravitationnelle de la Terre (Y)
   force[2] = 0.0f; // Z

   NewtonBodyAddForce(pBody, force); // On ajoute la force au corps
}

 

Cette fonction ajoute une MeshSceneNode au traitement de Newton Dynamics.
Elle fait appel à CreateCollisionFromMesh(…) pour créer l’objet de collision.
Elle s’occupe aussi de sauvegarder dans un fichier .cache les données de collisions. Cela permettra d’accélérer le calcul et le traitement de la structure de collision du Mesh si un fichier .cache à été trouvé.

void PhysicsManager::CreateDynamicBody(scene::IMeshSceneNode* pMeshNode, float fMass,
                                       bool bUseCache, const std::string& sCacheName)
{    
    std::string sCacheFileName = "";
    NewtonCollision* pNewtonCollision = nullptr;
    FILE* pCacheFile = nullptr;
    
    if (sCacheName == "")
        sCacheFileName = pMeshNode->getName(); // On utilise le nom de la scene node pour nommer le fichier cache

    if (sCacheName != "")
        sCacheFileName = sCacheName; // On utilise un cache spécifique

    if (!sCacheFileName.empty())
        sCacheFileName += ".cache";

    // On utilise le cache si le fichier existe déjà
    if (bUseCache && CollisionCacheFileExist(sCacheFileName))
    {
        pCacheFile = fopen(sCacheFileName.c_str(), "rb");
        // On créér un objet collision directement depuis un cache précédemment créé
        pNewtonCollision = NewtonCreateCollisionFromSerialization(m_pNewtonWorld, DeSerializeCollision, pCacheFile);
    }
    else
    {
        // On créér un objet collision directement depuis le ou les meshbuffers de la SceneNode
        pNewtonCollision = CreateCollisionFromMesh(pMeshNode->getMesh(), pMeshNode->getScale());

        // On créé le cache
        if(!sCacheFileName.empty()) // ... si le fichier n'existe pas déjà
        {
            pCacheFile = fopen(sCacheFileName.c_str(), "wb");        
            // On sérialize l'objet NewtonCollision vers un fichier
            NewtonCollisionSerialize(m_pNewtonWorld, pNewtonCollision, SerializeCollision, pCacheFile);
        }
    }
    
    if (pCacheFile)
    {
        fclose(pCacheFile);
        pCacheFile = nullptr;    
    }

    // On met la bonne échelle
    NewtonCollisionSetScale(pNewtonCollision, pMeshNode->getScale().X, pMeshNode->getScale().Y, pMeshNode->getScale().Z);

    const float* matrix = core::IdentityMatrix.pointer();
    NewtonBody* pBody = NewtonCreateDynamicBody(m_pNewtonWorld, pNewtonCollision, matrix);

    NewtonDestroyCollision(pNewtonCollision);    

    // On remet la matrice des correctes transformations
    core::matrix4 transformation;
    transformation.setTranslation(pMeshNode->getPosition());     
    transformation.setScale(pMeshNode->getScale()); 

    NewtonBodySetMatrix(pBody, transformation.pointer());

    // On envoit un pointeur de la scène node pour pouvoir le récupérer plus tard.
    NewtonBodySetUserData(pBody, pMeshNode);
    // On lui envoit les transformations de la scène node (position, rotation).
    NewtonBodySetMatrix(pBody, pMeshNode->getRelativeTransformation().pointer());

    InitializeRigidBody(pMeshNode, pBody);
}

 

Ces fonctions suivantes indiquent à Newton Dynamics comment charger ou sauvegarder
un fichier de collision .cache.

void PhysicsManager::SerializeCollision(void* serializeHandle, const void* buffer, int size)
{
    fwrite(buffer, size, 1, (FILE*)serializeHandle);
}

void PhysicsManager::DeSerializeCollision(void* serializeHandle, void* buffer, int size)
{
    fread(buffer, size, 1, (FILE*)serializeHandle);
}

 

Voici les autres fonctions du fichier PhysicsManager.cpp :

PhysicsManager::PhysicsManager()
{
    m_pNewtonWorld = NewtonCreate();

    NewtonSetSolverModel(m_pNewtonWorld, 1);
}

PhysicsManager::~PhysicsManager()
{
    NewtonDestroyAllBodies(m_pNewtonWorld);
    NewtonDestroy(m_pNewtonWorld);
}

void PhysicsManager::Update(float fValue)
{
    // Rafraichit le moteur Newton
    NewtonUpdate(m_pNewtonWorld, fValue);
}

 

Pour utiliser Newton dans Irrlicht, nous proposons le programme suivant :

#include <Irrlicht.h>
#include <Newton.h>

#include <Windows.h>
#include <iostream>

#include "PhysicsManager.h"

using namespace irr;
using namespace core;
using namespace scene;

/* Il faut intégrer la librairie d'Irrlicht dans les propriété du projet */
#pragma comment(lib, "Irrlicht.lib")

/* J'ai aussi joint dans l'archive .zip plusieurs fichier .lib et .dll  de la libraire de Newton */
#pragma comment(lib, "newton_d.lib")

PhysicsManager *m_pPhysicsManager = nullptr;
ICameraSceneNode* cam = nullptr;
ISceneManager* smgr = nullptr;
video::IVideoDriver* driver = nullptr;

class MyEventReceiver : public IEventReceiver
{
public:
    virtual bool OnEvent(const SEvent& event)
    {
        /** Launch cube **/
        if (event.KeyInput.Key == KEY_SPACE)
        {
            IMeshSceneNode* node = smgr->addCubeSceneNode(24, nullptr, -1, cam->getAbsolutePosition());
            core::vector3df vect = cam->getTarget() - cam->getAbsolutePosition();

            node->setMaterialTexture(0, driver->getTexture("box.jpg"));
            node->setMaterialFlag(video::EMF_LIGHTING, false);

            m_pPhysicsManager->LaunchCube(node, vect);
        }

        return false;
    }
    
    MyEventReceiver()
    {
    }

private:
};

int main()
{
    m_pPhysicsManager = new PhysicsManager();
    MyEventReceiver receiver;

    IrrlichtDevice *device =
        createDevice(video::EDT_OPENGL, dimension2d<u32>(1280, 1024), 16,
            false, false, false, &receiver);
 
    if (!device)
        return 1;
 
    device->setWindowCaption(L"Implémentation Newton Dynamics");
 
    driver = device->getVideoDriver();
    smgr = device->getSceneManager();
    gui::IGUIEnvironment* guienv = device->getGUIEnvironment();
 
    device->getFileSystem()->addZipFileArchive("map-20kdm2.pk3");
    scene::IAnimatedMesh* mesh = smgr->getMesh("20kdm2.bsp");
    IMeshSceneNode* node = 0;

    if (mesh)
        node = smgr->addOctreeSceneNode(mesh->getMesh(0), 0, -1, 1024);

    node->setPosition(core::vector3df(-1300,-144,-1249));

     cam = smgr->addCameraSceneNodeFPS();
    
    // On créé l'objet collision du niveau Quake3 pour Newton Dynamics
    m_pPhysicsManager->CreateDynamicBody(node, 10.0f, true, "Collision");

    device->getCursorControl()->setVisible(false);

    while(device->run())
    {
        driver->beginScene(true, true, video::SColor(255,100,101,140));
 
        smgr->drawAll();
        guienv->drawAll();

        m_pPhysicsManager->Update(1/120.0f);
    
        driver->endScene();
    }
 
    device->drop();

    delete m_pPhysicsManager;
 
    return 0;
}

 

Résumé :

Nous avons intégrer le moteur physique Newton Dynamics dans le moteur 3D Irrlicht.

Obtenir le taux d’occupation du CPU

cpu

Intro :

Dans cet article nous verrons comment obtenir le taux de consommation total du CPU de votre système et puis comment obtenir le taux de consommation du CPU du processus de votre application.

Pour info, j’ai déjà rédigé un article sur un sujet adjacent : les informations système.

Prérequis :

Savoir lire un peu du C++.

Explications :

Créez un fichier CPUTimer.h :

#ifndef CPU_TIMER_H
#define CPU_TIMER_H

#include <pdh.h>

#pragma comment(lib, "pdh.lib")

class CPUTimer
{
public:
    CPUTimer();

    virtual ~CPUTimer();

    void Initialize();
    void Frame();

    int GetCpuTotalPercentage();
    int GetCpuProcessPercentage();

private:

    /* Consommation CPU total du système */
    HQUERY m_queryHandle;
    HCOUNTER m_counterHandle;
    unsigned long m_lastSampleTime;
    double m_cpuTotalUsage;
    double m_cpuProcessUsage;

    /* Consommation CPU de l'application */
    ULARGE_INTEGER lastCPU, lastSysCPU, lastUserCPU;
    int numProcessors;
    HANDLE self;
};

#endif

 

On accède aux pourcentages de consommation du CPU par le biais des fonctions :

GetCpuTotalPercentage();
GetCpuProcessPercentage();

 

Créez un fichier CPUTimer.cpp :

#include "CPUTimer.h"

CPUTimer::CPUTimer()
{
}

CPUTimer::~CPUTimer()
{
    PdhCloseQuery(m_queryHandle);
}

void CPUTimer::Initialize()
{
    m_cpuTotalUsage = 0;
    m_cpuProcessUsage = 0;

    /* Consommation CPU total du système */

    PDH_STATUS status;

    status = PdhOpenQuery(NULL, NULL, &m_queryHandle);

    status = PdhAddEnglishCounter(m_queryHandle, TEXT("\\Processor(_Total)\\% processor time"), NULL, &m_counterHandle);

    /***************************************/

    /* Consommation CPU de l'application */

    SYSTEM_INFO sysInfo;
    FILETIME ftime, fsys, fuser;
    
    GetSystemInfo(&sysInfo);
    numProcessors = sysInfo.dwNumberOfProcessors;
    
    GetSystemTimeAsFileTime(&ftime);
    memcpy(&lastCPU, &ftime, sizeof(FILETIME));
    
    self = GetCurrentProcess();

    GetProcessTimes(self, &ftime, &ftime, &fsys, &fuser);

    memcpy(&lastSysCPU, &fsys, sizeof(FILETIME));
    memcpy(&lastUserCPU, &fuser, sizeof(FILETIME));

    m_lastSampleTime = GetTickCount();
}

void CPUTimer::Frame()
{
    // On met à jour les informations toutes les secondes
    if ((m_lastSampleTime + 1000) < GetTickCount())
    {    
        m_lastSampleTime = GetTickCount(); 

        /* Consommation CPU total du système */

        PDH_FMT_COUNTERVALUE value; 

        PdhCollectQueryData(m_queryHandle);
        
        PdhGetFormattedCounterValue(m_counterHandle, PDH_FMT_LONG, NULL, &value);

        m_cpuTotalUsage = value.longValue;

        /* Consommation CPU de l'application */

        FILETIME ftime, fsys, fuser;
        ULARGE_INTEGER now, sys, user;
        double percent;
    
        GetSystemTimeAsFileTime(&ftime);
        memcpy(&now, &ftime, sizeof(FILETIME));
    
        GetProcessTimes(self, &ftime, &ftime, &fsys, &fuser);

        memcpy(&sys, &fsys, sizeof(FILETIME));
        memcpy(&user, &fuser, sizeof(FILETIME));

        percent = (sys.QuadPart - lastSysCPU.QuadPart) +
            (user.QuadPart - lastUserCPU.QuadPart);

        percent /= (now.QuadPart - lastCPU.QuadPart);

        percent /= numProcessors;
        lastCPU = now;

        lastUserCPU = user;
        lastSysCPU = sys;

        m_cpuProcessUsage = percent * 100;
    }
}

int CPUTimer::GetCpuTotalPercentage()
{
    return m_cpuTotalUsage;
}

int CPUTimer::GetCpuProcessPercentage()
{
    return m_cpuProcessUsage;
}


Résumé :

Dans cet article nous avons appris comment obtenir le taux d’occupation du CPU de votre système mais aussi de votre application !

Introduction à DirectX 10 – Rendu 2D – partie 5

directx9c

Intro :

Dans cette cinquième partie, nous allons apprendre comment afficher une image 2D à l’écran.
Nous proposerons deux façon d’implémenter cet affichage.

Prérequis :

Avoir suivi la cinquième partie de ce tutoriel.

Quatrième partie :

Afficher une image 2D à l’écran.

Explications (1ère façon plus simple, sans utiliser de shader) :

Voici le fichier Sprite2D.h :

#ifndef SPRITE_2D_H
#define SPRITE_2D_H

#include <d3dx10.h>
#include <string>

class Sprite2D
{
public:
    Sprite2D(ID3D10Device* pDevice, WCHAR* sTextureFilename, int iImageWidth, int iImageHeight);
    virtual ~Sprite2D();

    bool Initialize();
    void Render();

    void SetPosition(int iPosX, int iPosY);

private:    
    bool Update();

    ID3D10Device* m_pDevice;

    int m_iImageWidth;
    int m_iImageHeight;

    int m_iPosX;
    int m_iPosY;

    ID3D10ShaderResourceView* m_pTextureRessourceView;
    
    std::wstring m_sTextureFilename;

    D3DX10_SPRITE m_pSpriteDesc;
    ID3DX10Sprite* m_pSprite;

    ID3D10Texture2D* m_pTexture;;

    D3DXMATRIX m_matProjection;
};

#endif

 

Voici le constructeur de la classe Sprite2D :

Sprite2D::Sprite2D(ID3D10Device* pDevice, WCHAR* sTextureFilename,
     int iImageWidth, int iImageHeight) :
m_pSprite(nullptr),
m_pTexture(nullptr),
m_pTextureRessourceView(nullptr),
m_sTextureFilename(sTextureFilename),
m_pDevice(pDevice),
m_iPosX(0),
m_iPosY(0),
m_iImageWidth(iImageWidth),
m_iImageHeight(iImageHeight)
{
    if (!Initialize())
    {
        MessageBoxA(NULL, "Erreur d'initialisation d'un Sprite !", "Erreur",
                    MB_ICONHAND | MB_OK);
    }
}

 

Voici la méthode d’initialisation :

bool Sprite2D::Initialize()
{
    HRESULT hr = D3DX10CreateTextureFromFile(m_pDevice, m_sTextureFilename.c_str(), nullptr, nullptr, (ID3D10Resource**)&m_pTexture, nullptr);

    if (FAILED(hr))
    {
         return false;
    }

    hr = m_pDevice->CreateShaderResourceView((ID3D10Resource*)m_pTexture, nullptr, &m_pTextureRessourceView);

    if (FAILED(hr))
    {
         return false;
    }

    ZeroMemory(&m_pSpriteDesc, sizeof(D3DX10_SPRITE));

    m_pSpriteDesc.pTexture = m_pTextureRessourceView;
    m_pSpriteDesc.TexCoord.x = 0;
    m_pSpriteDesc.TexCoord.y = 0;
    m_pSpriteDesc.TexSize.x = 1.0f;
    m_pSpriteDesc.TexSize.y = 1.0f;
    m_pSpriteDesc.TextureIndex = 0;
    m_pSpriteDesc.ColorModulate = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);

    hr = D3DX10CreateSprite(m_pDevice, 0, &m_pSprite);

    if (FAILED(hr))
    {
         return false;
    }

    D3DXMatrixOrthoOffCenterLH(&m_matProjection, 0.0f, D3D10_RENDERER->GetViewportWidth(), 0.0f,
        D3D10_RENDERER->GetViewportHeight(), 0.1f, 10);

    hr = m_pSprite->SetProjectionTransform(&m_matProjection);

    if (FAILED(hr))
    {
         return false;
    }

    Update();

    return true;
}

 

Voici les autres méthodes :

void Sprite2D::Render()
{
    m_pSprite->Begin(D3DX10_SPRITE_SORT_TEXTURE | D3DX10_SPRITE_SAVE_STATE);

        m_pSprite->DrawSpritesImmediate(&m_pSpriteDesc, 1, 0, 0);

    m_pSprite->End();
}

bool Sprite2D::Update()
{
    float fSpritePosX = m_iPosX;
    float fSpritePosY = m_iPosY;
    float fSpriteWidth = m_iImageWidth;
    float fSpriteHeight = m_iImageHeight;

    D3DXMATRIX matTranslation;
    D3DXMatrixTranslation(&matTranslation, (fSpriteWidth / 2) + fSpritePosX, (fSpriteHeight / 2) + fSpritePosY, 0.1);

    D3DXMATRIX matScaling;
    D3DXMatrixScaling(&matScaling, fSpriteWidth, fSpriteHeight, 1.0f);

    m_pSpriteDesc.matWorld = matScaling * matTranslation;

    return true;
}

void Sprite2D::SetPosition(int iPosX, int iPosY)
{
    m_iPosX = iPosX;
    m_iPosY = iPosY;

    Update();
}

 

 


 

 

Explications (2ème façon plus compliquée, en utilisant un shader) :

Mettre dans un fichier Sprite2D.h :

#ifndef SPRITE_2D
#define SPRITE_2D

#include <d3dx10.h>
#include <string>

class Sprite2D
{
public:
    // La structure du rectangle à afficher
    struct VertexType
    {
        D3DXVECTOR3 position;
        D3DXVECTOR2 texture;
    };

    Sprite2D(ID3D10Device* pDevice, WCHAR* sTextureFilename, int iImageWidth, int iImageHeight);
    virtual ~Sprite2D();

    bool Initialize();
    void Render();

    void SetPosition(int iPosX, int iPosY);

private:    
    // Met à jour la dimension du quad texturé
    bool Update();

    ID3D10Device* m_pDevice;

    ID3D10Buffer* m_pVertexBuffer;
    ID3D10Buffer* m_pIndexBuffer;

    int m_iVertexCount;
    int m_iIndexCount;

    int m_iScreenWidth;
    int m_iScreenHeight;

    int m_iImageWidth;
    int m_iImageHeight;

    /* Position de l'image */
    int m_iPosX;
    int m_iPosY;

    int m_iPreviousPosX;
    int m_iPreviousPosY;

    ID3D10ShaderResourceView* m_pTexture;
    ID3D10EffectShaderResourceVariable* m_pTextureVariable;

    ID3D10Effect* m_pEffect;
    ID3D10EffectTechnique* m_pTechnique;
    ID3D10InputLayout* m_pLayout;

    ID3D10EffectMatrixVariable* m_pWorldVariable;
    ID3D10EffectMatrixVariable* m_pViewVariable;
    ID3D10EffectMatrixVariable* m_pProjectionVariable;

    D3DXMATRIX m_worldMatrix;
    D3DXMATRIX m_viewMatrix;
    D3DXMATRIX m_orthoMatrix;
    
    // Le nom de la texture de l'image à afficher
    std::wstring m_sTextureFilename;
};

#endif

 

Créer un fichier Sprite2D.cpp où vous metterez :

bool Sprite2D::Initialize()
{
    // On prépare la matrice orthogonale, spécialement conçue pour
    // le rendu 2D.
    D3DXMatrixOrthoLH(&m_orthoMatrix, (float)m_iScreenWidth,
                      (float)m_iScreenHeight, 1.0f, 20.0f);

    VertexType* arVertices;
    unsigned long* indices;

    D3D10_BUFFER_DESC vertexBufferDesc;
    D3D10_BUFFER_DESC indexBufferDesc;

    D3D10_SUBRESOURCE_DATA vertexData;
    D3D10_SUBRESOURCE_DATA indexData;

    HRESULT hr;

    m_iVertexCount = 6;
    m_iIndexCount = m_iVertexCount;
    arVertices = new VertexType[m_iVertexCount];
    indices = new unsigned long[m_iIndexCount];

    memset(arVertices, 0, (sizeof(VertexType) * m_iVertexCount));
    for (unsigned int i = 0; i < m_iIndexCount; i++)
    {
        indices[i] = i;
    }

    vertexBufferDesc.Usage = D3D10_USAGE_DYNAMIC;
    vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_iVertexCount;
    vertexBufferDesc.BindFlags = D3D10_BIND_VERTEX_BUFFER;
    vertexBufferDesc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
    vertexBufferDesc.MiscFlags = 0;

    vertexData.pSysMem = arVertices;

    // Créér le vertex buffer
    hr = m_pDevice->CreateBuffer(&vertexBufferDesc, &vertexData, &m_pVertexBuffer);

    if (FAILED(hr))
    {
        return false;
    }

    indexBufferDesc.Usage = D3D10_USAGE_DEFAULT;
    indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_iIndexCount;
    indexBufferDesc.BindFlags = D3D10_BIND_INDEX_BUFFER;
    indexBufferDesc.CPUAccessFlags = 0;
    indexBufferDesc.MiscFlags = 0;

    indexData.pSysMem = indices;

    // Créér l'index buffer
    hr = m_pDevice->CreateBuffer(&indexBufferDesc, &indexData, &m_pIndexBuffer);

    if (FAILED(hr))
    {
        return false;
    }

    delete [] arVertices;
    arVertices = 0;

    delete [] indices;
    indices = 0;

    /********* Partie préparation du shader *********/

    unsigned int numElements;
    D3D10_PASS_DESC passDesc;

    hr = D3DX10CreateEffectFromFile(L"Texture.fx", NULL, NULL, "fx_4_0",
                                    D3D10_SHADER_ENABLE_STRICTNESS, 0,
                                    m_pDevice, NULL, NULL, &m_pEffect, NULL, NULL);
    if (FAILED(hr))
    {
        return false;
    }

    m_pTechnique = m_pEffect->GetTechniqueByName("TextureTechnique");
    if (!m_pTechnique)
    {
        return false;
    }

    D3D10_INPUT_ELEMENT_DESC polygonLayout[] =
    {
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
        { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D10_APPEND_ALIGNED_ELEMENT, D3D10_INPUT_PER_VERTEX_DATA, 0 },
    };
    numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

    m_pTechnique->GetPassByIndex(0)->GetDesc(&passDesc);

    // Créér le vertex input layout
    hr = m_pDevice->CreateInputLayout(polygonLayout, numElements, passDesc.pIAInputSignature, passDesc.IAInputSignatureSize, &m_pLayout);
    if (FAILED(hr))
    {
        return false;
    }

    m_pWorldVariable = m_pEffect->GetVariableByName("worldMatrix")->AsMatrix();
    m_pViewVariable = m_pEffect->GetVariableByName("viewMatrix")->AsMatrix();
    m_pProjectionVariable = m_pEffect->GetVariableByName("projectionMatrix")->AsMatrix();
    m_pTextureVariable = m_pEffect->GetVariableByName("shaderTexture")->AsShaderResource();

    // Créée la texture
    hr = D3DX10CreateShaderResourceViewFromFile(m_pDevice, m_sTextureFilename.c_str(), NULL, NULL, &m_pTexture, NULL);

    if (FAILED(hr))
    {
        return false;
    }

    return true;
}

 

La fonction de rendu / d’affichage :


void Sprite2D::Render()
{
    Update();

    // On désactive le ZBuffer pour que l'image soit affichée devant
    // tous les rendus
    D3D10_RENDERER->EnableZBuffer(false);

    unsigned int stride = sizeof(VertexType);
    unsigned int offset = 0;
   
    // On prépare les matrices de transformation
    D3DXMatrixIdentity(&m_worldMatrix);
    D3DXMatrixIdentity(&m_viewMatrix);

    D3DXVECTOR3 Eye(0.0f, 0.0f, -1.0f);
    D3DXVECTOR3 At(0.0f, 0.0f, 0.0f);
    D3DXVECTOR3 Up(0.0f, 1.0f, 0.0f);

    D3DXMatrixLookAtLH(&m_viewMatrix, &Eye, &At, &Up);

    // Initialise les variables du shader
    m_pWorldVariable->SetMatrix((float*) &m_worldMatrix);
    m_pViewVariable->SetMatrix((float*) &m_viewMatrix);
    m_pProjectionVariable->SetMatrix((float*) &m_orthoMatrix);

    m_pTextureVariable->SetResource(m_pTexture);

    // Prépare le vertex input layout
    m_pDevice->IASetInputLayout(m_pLayout);

    // Prépare les vertex et index buffers pour être affichés
    m_pDevice->IASetVertexBuffers(0, 1, &m_pVertexBuffer, &stride, &offset);
    m_pDevice->IASetIndexBuffer(m_pIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
    m_pDevice->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    // Affiche le quad texturé
    D3D10_TECHNIQUE_DESC techniqueDesc;
    m_pTechnique->GetDesc(&techniqueDesc);
    for(unsigned int i = 0; i < techniqueDesc.Passes; i++)
    {
        m_pTechnique->GetPassByIndex(i)->Apply(0);
        m_pDevice->DrawIndexed(m_iIndexCount, 0, 0);
    }

    D3D10_RENDERER->EnableZBuffer(true);
}

 

Ici la fonction de mise à jour de la position de l’image :

bool Sprite2D::Update()
{
    float left, right, top, bottom;
    VertexType* arVertices;
    void* pVerticesPtr;
    HRESULT hr;

    // On calcul les coordonnées du quad en fonction de la position
    // de l'image
    left = (float)((m_iScreenWidth / 2) * -1) + (float)m_iPosX;
    right = left + (float)m_iImageWidth;

    top = (float)(m_iScreenHeight / 2) - (float)m_iPosY;
    bottom = top - (float)m_iImageHeight;

    arVertices = new VertexType[m_iVertexCount];

    // Premier triangle
    arVertices[0].position = D3DXVECTOR3(left, top, 0.0f);  // Top left.
    arVertices[0].texture = D3DXVECTOR2(0.0f, 0.0f);

    arVertices[1].position = D3DXVECTOR3(right, bottom, 0.0f);  // Bottom right.
    arVertices[1].texture = D3DXVECTOR2(1.0f, 1.0f);

    arVertices[2].position = D3DXVECTOR3(left, bottom, 0.0f);  // Bottom left.
    arVertices[2].texture = D3DXVECTOR2(0.0f, 1.0f);

    // Deuxième triangle
    arVertices[3].position = D3DXVECTOR3(left, top, 0.0f);  // Top left.
    arVertices[3].texture = D3DXVECTOR2(0.0f, 0.0f);

    arVertices[4].position = D3DXVECTOR3(right, top, 0.0f);  // Top right.
    arVertices[4].texture = D3DXVECTOR2(1.0f, 0.0f);

    arVertices[5].position = D3DXVECTOR3(right, bottom, 0.0f);  // Bottom right.
    arVertices[5].texture = D3DXVECTOR2(1.0f, 1.0f);

    hr = m_pVertexBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, (void**)&pVerticesPtr);

    if (FAILED(hr))
    {
        return false;
    }

    memcpy(pVerticesPtr, (void*)arVertices, (sizeof(VertexType) * m_iVertexCount));

    m_pVertexBuffer->Unmap();

    delete [] arVertices;
    arVertices = 0;

    return true;
}

 

Créez un fichier Texture.fx, où vous mettrez :

matrix worldMatrix;
matrix viewMatrix;
matrix projectionMatrix;
Texture2D shaderTexture;

SamplerState SampleType
{
    Filter = MIN_MAG_MIP_LINEAR;
    AddressU = Wrap;
    AddressV = Wrap;
};

struct VertexInputType
{
    float4 position : POSITION;
    float2 tex : TEXCOORD0;
};

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
};

PixelInputType TextureVertexShader(VertexInputType input)
{
    PixelInputType output;
  
    input.position.w = 1.0f;

    output.position = mul(input.position, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);
    
    output.tex = input.tex;
    
    return output;
}

float4 TexturePixelShader(PixelInputType input) : SV_Target
{
    float4 textureColor;

    textureColor = shaderTexture.Sample(SampleType, input.tex);

    return textureColor;
}

technique10 TextureTechnique
{
    pass pass0
    {
        SetVertexShader(CompileShader(vs_4_0, TextureVertexShader()));
        SetPixelShader(CompileShader(ps_4_0, TexturePixelShader()));
        SetGeometryShader(NULL);
    }
}

Résumé :

Nous avons expliquées deux façon d’afficher du rendu 2D avec DirectX.

texture

Nous nous servirons de cet affichage 2D pour implémenter une UI (Interface Utilisateur).

Références :

– http://mscerts.programming4.us/programming/directx%2010%20game%20programming%20%20%20the%202d%20resurgence%20-%20sprites.aspx

Introduction à DirectX 10 – Lumière – partie 4

directx9c

Intro :

Dans cette quatrième partie, nous allons apprendre à intégrer une lumière afin d’éclairer notre cube.

Prérequis :

Avoir suivi la troisième partie de ce tutoriel.

Quatrième partie :

Afficher un petit cube éclairé d’une lumière.

Explications :

Nous allons utiliser cette structure de vertex :

struct SimpleVertex
{
    D3DXVECTOR3 Pos;
    D3DXVECTOR2 Tex;
    D3DXVECTOR3 Normal;
};

Elle contient un attribut supplémentaire par rapport à la partie 3 : la normale.

La normale d’un vertex est un vecteur perpendiculaire à celui-ci ; nous l’utiliserons afin de calculer le degré de nuance de lumière à cet endroit.
C’est par ce principe que nous effectuerons globalement le calcul lumineux.

Voir cet article pour comprendre comment modéliser la lumière dans un rendu 3D.

 


 

Nous allons utiliser ce programme HLSL – partie4.fx :

//--------------------------------------------------------------------------------------
// Constant Buffer Variables
//--------------------------------------------------------------------------------------
matrix World;
matrix View;
matrix Projection;
float4 vLightDir;
Texture2D txDiffuse;

SamplerState samLinear
{
    Filter = MIN_MAG_MIP_LINEAR;
    AddressU = Wrap;
    AddressV = Wrap;
};

//--------------------------------------------------------------------------------------
struct VS_INPUT
{
    float4 Pos : POSITION;
    float2 Tex : TEXCOORD;
    float3 Norm : NORMAL;
};

struct PS_INPUT
{
    float4 Pos : SV_POSITION;
    float2 Tex : TEXCOORD0;
    float3 Norm : TEXCOORD1;
};

//--------------------------------------------------------------------------------------
// Vertex Shader
//--------------------------------------------------------------------------------------
PS_INPUT VS( VS_INPUT input )
{
    PS_INPUT output = (PS_INPUT)0;
    output.Pos = mul( input.Pos, World );
    output.Pos = mul( output.Pos, View );
    output.Pos = mul( output.Pos, Projection );
    output.Norm = mul( input.Norm, World );
    output.Tex = input.Tex;
    
    return output;
}

//--------------------------------------------------------------------------------------
// Pixel Shader
//--------------------------------------------------------------------------------------
float4 PS( PS_INPUT input) : SV_Target
{
    float4 finalColor = (saturate( dot( (float3)vLightDir, input.Norm) )
    * txDiffuse.Sample( samLinear, input.Tex ));
    finalColor.a = 1;
    return finalColor;
}

//--------------------------------------------------------------------------------------
technique10 Render
{
    pass P0
    {
        SetVertexShader( CompileShader( vs_4_0, VS() ) );
        SetGeometryShader( NULL );
        SetPixelShader( CompileShader( ps_4_0, PS() ) );
    }
}

Si vous ne comprenez rien à ce programme : consulter cet article.

Il faut aussi connaître la notion de matrices et le principe de transformations de coordonnées
pour comprendre ce code : voir cet article.

 

Dans votre fichier D3D10Renderer.h, rajoutez :

// Variables matrices en cours
ID3D10EffectMatrixVariable* m_pWorldVariable;
ID3D10EffectMatrixVariable* m_pViewVariable;
ID3D10EffectMatrixVariable* m_pProjectionVariable;

ID3D10EffectVectorVariable* m_pLightDirVariable;
ID3D10EffectVectorVariable* m_pLightColorVariable; 

// Variable texture en cours
ID3D10EffectShaderResourceVariable* m_pDiffuseVariable;

Ce sont les variables que l’on enverra au shader HLSL.

 

Ensuite dans votre fichier D3D10Renderer.cpp à la fonction Initialize(bool bFullScreen), rajoutez :

/******************** Partie 2 & 3 & 4 ********************/

DWORD dwShaderFlags = D3D10_SHADER_ENABLE_STRICTNESS;

#if defined( DEBUG ) || defined( _DEBUG )
// Permet d'afficher les éventuelles erreurs de la compilation d'un shader
dwShaderFlags |= D3D10_SHADER_DEBUG;
#endif

// Pour la création du shader, on le compile
hr = D3DX10CreateEffectFromFile(L"Partie4.fx", NULL, NULL, "fx_4_0", dwShaderFlags, 0, m_pd3dDevice, NULL,
                                NULL, &m_pEffect, NULL, NULL);
if (FAILED(hr))
    return false;

// On acquiert la technique du shader HLSL
m_pTechnique = m_pEffect->GetTechniqueByName("Render");

// On acquiert les variables du shader HLSL
// cela permettra de faire le lien avec l'application et le programme shader HLSL
m_pWorldVariable = m_pEffect->GetVariableByName("World")->AsMatrix();
m_pViewVariable = m_pEffect->GetVariableByName("View")->AsMatrix();
m_pProjectionVariable = m_pEffect->GetVariableByName("Projection")->AsMatrix();
m_pLightDirVariable = m_pEffect->GetVariableByName( "vLightDir" )->AsVector();
m_pDiffuseVariable = m_pEffect->GetVariableByName("txDiffuse")->AsShaderResource();

/******************/ 

// On définit la structure d'un vertex, ici 3 attributs sont déclarés
D3D10_INPUT_ELEMENT_DESC layout[] =
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
    { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0 },
    { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 20, D3D10_INPUT_PER_VERTEX_DATA, 0 },
};
UINT numElements = sizeof( layout ) / sizeof( layout[0] );

D3D10_PASS_DESC PassDesc;
m_pTechnique->GetPassByIndex(0)->GetDesc(&PassDesc);
hr = m_pd3dDevice->CreateInputLayout(layout, numElements, PassDesc.pIAInputSignature,
                                     PassDesc.IAInputSignatureSize, &m_pVertexLayout);
if (FAILED(hr))
    return false;

m_pd3dDevice->IASetInputLayout(m_pVertexLayout);

 

On créé le vertex buffer d’un cube : il est composé de la position, des coordonées de la texture et des normales de chaque vertex.

Les voici :

// Déclaration des vertices
SimpleVertex vertices[] =
{
    { D3DXVECTOR3( -1.0f, 1.0f, -1.0f ), D3DXVECTOR2( 0.0f, 0.0f ), D3DXVECTOR3( 0.0f, 1.0f, 0.0f ) },
    { D3DXVECTOR3( 1.0f, 1.0f, -1.0f ), D3DXVECTOR2( 1.0f, 0.0f ), D3DXVECTOR3( 0.0f, 1.0f, 0.0f ) },
    { D3DXVECTOR3( 1.0f, 1.0f, 1.0f ), D3DXVECTOR2( 1.0f, 1.0f ), D3DXVECTOR3( 0.0f, 1.0f, 0.0f ) },
    { D3DXVECTOR3( -1.0f, 1.0f, 1.0f ), D3DXVECTOR2( 0.0f, 1.0f ), D3DXVECTOR3( 0.0f, 1.0f, 0.0f ) },

    { D3DXVECTOR3( -1.0f, -1.0f, -1.0f ), D3DXVECTOR2( 0.0f, 0.0f ), D3DXVECTOR3( 0.0f, -1.0f, 0.0f ) },
    { D3DXVECTOR3( 1.0f, -1.0f, -1.0f ), D3DXVECTOR2( 1.0f, 0.0f ), D3DXVECTOR3( 0.0f, -1.0f, 0.0f ) },
    { D3DXVECTOR3( 1.0f, -1.0f, 1.0f ), D3DXVECTOR2( 1.0f, 1.0f ),  D3DXVECTOR3( 0.0f, -1.0f, 0.0f ) },
    { D3DXVECTOR3( -1.0f, -1.0f, 1.0f ), D3DXVECTOR2( 0.0f, 1.0f ),  D3DXVECTOR3( 0.0f, -1.0f, 0.0f ) },

    { D3DXVECTOR3( -1.0f, -1.0f, 1.0f ), D3DXVECTOR2( 0.0f, 0.0f ),  D3DXVECTOR3( -1.0f, 0.0f, 0.0f ) },
    { D3DXVECTOR3( -1.0f, -1.0f, -1.0f ), D3DXVECTOR2( 1.0f, 0.0f ), D3DXVECTOR3( -1.0f, 0.0f, 0.0f ) },
    { D3DXVECTOR3( -1.0f, 1.0f, -1.0f ), D3DXVECTOR2( 1.0f, 1.0f ),  D3DXVECTOR3( -1.0f, 0.0f, 0.0f ) },
    { D3DXVECTOR3( -1.0f, 1.0f, 1.0f ), D3DXVECTOR2( 0.0f, 1.0f ),  D3DXVECTOR3( -1.0f, 0.0f, 0.0f ) },

    { D3DXVECTOR3( 1.0f, -1.0f, 1.0f ), D3DXVECTOR2( 0.0f, 0.0f ), D3DXVECTOR3( 1.0f, 0.0f, 0.0f ) },
    { D3DXVECTOR3( 1.0f, -1.0f, -1.0f ), D3DXVECTOR2( 1.0f, 0.0f ), D3DXVECTOR3( 1.0f, 0.0f, 0.0f ) },
    { D3DXVECTOR3( 1.0f, 1.0f, -1.0f ), D3DXVECTOR2( 1.0f, 1.0f ), D3DXVECTOR3( 1.0f, 0.0f, 0.0f ) },
    { D3DXVECTOR3( 1.0f, 1.0f, 1.0f ), D3DXVECTOR2( 0.0f, 1.0f ),    D3DXVECTOR3( 1.0f, 0.0f, 0.0f ) },

    { D3DXVECTOR3( -1.0f, -1.0f, -1.0f ), D3DXVECTOR2( 0.0f, 0.0f ), D3DXVECTOR3( 0.0f, 0.0f, -1.0f ) },
    { D3DXVECTOR3( 1.0f, -1.0f, -1.0f ), D3DXVECTOR2( 1.0f, 0.0f ),  D3DXVECTOR3( 0.0f, 0.0f, -1.0f ) },
    { D3DXVECTOR3( 1.0f, 1.0f, -1.0f ), D3DXVECTOR2( 1.0f, 1.0f ),  D3DXVECTOR3( 0.0f, 0.0f, -1.0f ) },
    { D3DXVECTOR3( -1.0f, 1.0f, -1.0f ), D3DXVECTOR2( 0.0f, 1.0f ),  D3DXVECTOR3( 0.0f, 0.0f, -1.0f ) },

    { D3DXVECTOR3( -1.0f, -1.0f, 1.0f ), D3DXVECTOR2( 0.0f, 0.0f ), D3DXVECTOR3( 0.0f, 0.0f, 1.0f ) },
    { D3DXVECTOR3( 1.0f, -1.0f, 1.0f ), D3DXVECTOR2( 1.0f, 0.0f ),  D3DXVECTOR3( 0.0f, 0.0f, 1.0f ) },
    { D3DXVECTOR3( 1.0f, 1.0f, 1.0f ), D3DXVECTOR2( 1.0f, 1.0f ),     D3DXVECTOR3( 0.0f, 0.0f, 1.0f ) },
    { D3DXVECTOR3( -1.0f, 1.0f, 1.0f ), D3DXVECTOR2( 0.0f, 1.0f ),  D3DXVECTOR3( 0.0f, 0.0f, 1.0f ) },
};
// Déclaration des indices
DWORD indices[] =
{
    3,1,0,
    2,1,3,

    6,4,5,
    7,4,6,

    11,9,8,
    10,9,11,

    14,12,13,
    15,12,14,

    19,17,16,
    18,17,19,

    22,20,21,
    23,20,22
};

 

Toujours dans votre fichier D3D10Renderer.cpp à la fonction Initialize(bool bFullScreen), rajoutez :

D3DXMatrixIdentity(&m_worldMatrix);

D3DXVECTOR3 Eye(0.0f, 3.0f, -6.0f);
D3DXVECTOR3 At(0.0f, 0.0f, 0.0f);
D3DXVECTOR3 Up(0.0f, 1.0f, 0.0f);

D3DXMatrixLookAtLH(&m_viewMatrix, &Eye, &At, &Up);

// Initialize the projection matrix
D3DXMatrixPerspectiveFovLH( &m_projectionMatrix, ( float )D3DX_PI * 0.25f, width / ( FLOAT )height, 0.1f, 100.0f );

// Update Variables that never change
m_pViewVariable->SetMatrix( ( float* )&m_viewMatrix );
m_pProjectionVariable->SetMatrix( ( float* )&m_projectionMatrix );
m_pDiffuseVariable->SetResource( m_pTextureRV );

 

Toujours dans votre fichier D3D10Renderer.cpp :

void D3D10Renderer::Render()
{
    // Couleur du fond de rendu
    static float afClearColor[4] = {0.0f, 0.125f, 0.3f, 1.0f}; 

    // Efface la surface de rendu
    m_pd3dDevice->ClearRenderTargetView(m_pRenderTargetView, afClearColor);
    
    /*********** Donnée d'une variable de temps ***********/
 
    static float t = 0.0f;
    static DWORD dwTimeStart = 0;
    DWORD dwTimeCur = GetTickCount();
    if( dwTimeStart == 0 )
        dwTimeStart = dwTimeCur;
    t = ( dwTimeCur - dwTimeStart ) / 1000.0f;
    
    /*******************************************************/
    
    // Direction de la lumière
    D3DXVECTOR4 vLightDirs( -1.577f, 0.577f, -0.577f, 1.0f );

    D3DXMATRIX mRotate;
    D3DXVECTOR4 vOutDir;
    D3DXMatrixRotationZ( &mRotate, -2.0f * t );
    D3DXVec3Transform( &vLightDirs, ( D3DXVECTOR3* )&vLightDirs, &mRotate );

    // On met à jour les variables du shader HLSL
    m_pWorldVariable->SetMatrix((float*)&m_worldMatrix);
    m_pLightDirVariable->SetFloatVectorArray((float*)vLightDirs, 0, 2);

    /**********************/

    // Affiche le cube
    D3D10_TECHNIQUE_DESC techDesc;
    m_pTechnique->GetDesc(&techDesc);
    for (UINT p = 0; p < techDesc.Passes; ++p)
    {
        m_pTechnique->GetPassByIndex( p )->Apply( 0 );
        m_pd3dDevice->DrawIndexed( 36, 0, 0 );
    }

    /**********************/

    // On affiche le front buffer
    m_pSwapChain->Present( 0, 0 );
}

Résumé :

Nous avons intégré une lumière près de notre cube afin de l’éclairer.

partie4

Voici l’archive du code complet pour cette partie :  DirectX 10 Tutoriel – Partie 4.zip