La technique du Normal Mapping (ou Bump Mapping)

bump-mapping

Intro :

Cette technique de rendu des textures sur des modèles 3D permet de simuler un relief mélangé par une texture.

En français on parle de « Placage de Relief ».

Explications :

Cette technique permet de générer un jeu de mélange entre la lumière et les ombres sur une surface classique sans avoir à y renseigner les normales en une multitude de points. En effet les normales sont modifiées (par un effet de variation) pour créer un effet de lumière semblable à du relief.

Ici 4 points peuvent suffire pour générer un effet de relief avancé sur une surface 2D.

La technique du Parallax Mapping

GPU

Intro :

Avant tout, que veux dire « parallax » ou parallaxe ? La parallaxe désigne le phénomène physique et dynamique à propos d’un objet qui visiblement change de forme à cause du point de vue de l’œil de l’observateur.

La technique du Parallax Mapping est une technique de déplacement de surface.
La surface traitée donne un résultat de profondeur et est similaire avec la technique utilisée dans le « bump mapping ».

Cela donne un résultat très joli. En effet, elle permet d’émuler le relief d’un objet à partir d’une texture.

Comme toutes les techniques de déplacement de surface, la technique du PM utilise une image texture spéciale pour effectuer le rendu.

Explications :

Il s’agit d’appliquer un décalage des coordonnées (offset) de la texture diffuse directement dans le pixel shader.

Ce décalage dépend de la direction et de la position de la caméra qui observe la scène.
La surface ainsi dessinée à l’écran doit évoluer correctement en respectant la position de la caméra.

La technique du PM utilise une image / texture de couleur noire et blanche.

La simulation s’opère dans l’espace des textures. On opère un décalage des coordonnées des textures avec le vecteur de vue de la caméra plus la valeur courante de la « height map » de texture utilisée.

Le Compute Shader avec DirectX 11 (DirectCompute)

GPU

Intro :

Pour faire simple, le compute shader de DirectX 11 permet d’effectuer des calculs directement par le processeur de la carte graphique (nommé GPU) alors qu’ils auraient pu être fait par le CPU (le processeur de la carte-mère du PC). Il est dénommé par un autre nom : le DirectCompute.

A noter que bien que leur sortie s’est faite avec DirectX 11, on peut utiliser les compute shader avec du matériel DirectX 10.

Explications :

Le compute shader permet le partage de la mémoire entre processus légers synchronisés à l’intérieure de la CG et de la mémoire de l’ordinateur. Il utilise le langage HLSL (High Level Shader Language) classique de DirectX.

En théorie on peut calculer n’importe quoi. Le compute shader fait office d’accès mémoire en entrée / sortie par les programmes exécutés par le processeur.

Maintenant et depuis l’ajout de cette fonctionnalité, avec DirectX on peut faire du calcul non graphique en utilisant la CG.


Voici un petit exemple d’initialisation d’un compute shader :

UINT flags = D3DCOMPILE_ENABLE_STRICTNESS;

#if defined( DEBUG ) || defined( _DEBUG )
flags |= D3DCOMPILE_DEBUG;
#endif

// Prefer higher CS shader profile when possible as CS 5.0 provides better performance on 11-class hardware.
LPCSTR profile = ( device->GetFeatureLevel() >= D3D_FEATURE_LEVEL_11_0 ) ? "cs_5_0" : "cs_4_0";

const D3D_SHADER_MACRO defines[] = 
{
     "EXAMPLE_DEFINE", "1",
      NULL, NULL
};

ID3DBlob* shaderBlob = nullptr;
ID3DBlob* errorBlob = nullptr;

HRESULT hr = D3DCompileFromFile( srcFile, defines, D3D_COMPILE_STANDARD_FILE_INCLUDE,
                                     entryPoint, profile,
                                     flags, 0, &shaderBlob, &errorBlob );

– Avec srcFile comme le nom du fichier .fx d’un compute shader.
– Avec defines comme structure de déclaration des constantes defines à l’intérieur de l’effet.

Quand le compute shader sera exécuté dans la mémoire de la carte graphique, il pourra tirer avantage d’un grand nombre d’unités d’exécution parallèles à l’intérieur du GPU.

Qu’est-ce qu’un « thread group » ?

Un thread group est un groupe d’unités d’exécution en parallèle qui est interprété et conçu par la CG comme un tableau en 3 dimensions dont chaque membre est un « thread ».

On utilise la méthode ID3D11DeviceContext::Dispatch pour envoyer un « thread group » au GPU. C’est-à-dire qu’on utilise cette méthode pour faire exécuter des commandes un compute shader.

En effet un compute shader peut être exécuté sur plusieurs unité d’exécution en parallèle, le tout formant un « thread group ». On identifie un thread (unité d’exécution parallèle) en particulier avec un numéro d’index assimilé par un vecteur en 3 dimensions (x, y, z).

Dans cette image illustrant un exemple, la taille des « threads groups » est de 5x5x2, soit 50 groupes d’unités d’exécution en parallèle (« thread »). Ici un thread en particulier est adressé par le triplet (4,1,1). A noter que chaque groupe de threads est fait de 16×16 = 256 unité ou threads d’exécution.

Single Thread

Chaque processus léger (« thread ») peut exécuter plusieurs élément de données à travers le compute shader. Et l’application C++ qui gère tous ces calculs peut définir le nombre de threads en exécution.


La principale capacité des compute shaders est le partage et l’accès de la mémoire par plusieurs threads en parallèle.

Le principe de base du calcul par le biais de ces shaders est l’envoi régulier d’une grille de threads. Les threads d’un même groupe s’exécute en parallèle, mais des threads de groupes différents le peuvent dans certaines conditions.

Les données accessibles par le compute shader peuvent être les Buffers et Textures de DirectX 11.


Les applications au compute shader peuvent être :

– Le traitement d’images
– La construction procédurale de modèles
– La physique et l’animation
– Le rendu
– L’intelligence artificielle
– Les effets de particules
– Les algorithmes de tri
– L’encodage vidéo
– Le calcul scientifique
– Des effets graphiques tels que : le depth of field, HDR bloom, le motion blur, etc…

Utilisation des std::array en C++11

arrays

Intro :

L’objet de la classe std::array permet de représenter en mémoire un tableau statique dont le nombre d’éléments est connue à la compilation et est fixé à l’avance.

Prérequis :

– Savoir lire du C++

Explications :

On peut l’initialiser d’emblée :

std::array<int, 5> a = {1, 2, 3, 4, 5};

Voilà une déclaration supplémentaire :

#include <array>
 
std::array<int, 3> UnArray;

Les données de ce type de tableau sont stockées de manière contigu en mémoire.

Résumé :

A l’opposé des tableaux classiques, les tableaux déclarés avec la classe std::array stocke aussi le nombre d’éléments du tableau en mémoire.

Utilisation de la librairie Zlib pour gérer des flux de compression

zlib3d-b1

Intro :

Il est très utile de pouvoir compresser des données en mémoire ou des fichiers sur disque ou mémoire de masse. Pour cela, on va utiliser la fameuse librairie C open-source nommée Zlib !

Prérequis :

– Savoir lire du C ou du C++

– Savoir configurer l’importation d’une librairie sous Visual C++

Explications :

Elle implémente l’algorithme de compression deflate et peut créer des fichiers au format gzip.
La bibliothèque est aussi multi-plateforme.

Résumé :

Nous avons présenté une classe C++ encapsulant les fonctions de compression de la librairie Zlib ; cette classe permet de compresser des fichiers ou des données en mémoire.

Références :

– http://www.zlib.net/manual.html

– http://www.zlib.net/zlib_how.html

– http://www.zlib.net/zlib_faq.html

Apprendre à utiliser TinyXML pour charger vos fichiers XML

tinyxml

Intro :

Parfois dans un programme ou un jeu vidéo, il est nécessaire de charger des données sous format XML. Par exemple pour un éditeur de scène / monde, on peut vouloir charger les positions des entités directement depuis un fichier Scene.xml par exemple.

Prérequis :

– Savoir lire du C++

– Savoir lire un fichier XML

– Savoir importer des fichiers sources dans Visual C++

Explications :

TinyXML est une toute petite librairie C++ permettant de charger et d’analyser les données stockées dans un fichier XML. Elle est sous la licence Zlib (cela signifie que vous pouvez l’utiliser pour n’importe quoi !)

D’abord il vous faut télécharger les sources de TinyXML. La dernière version à la date d’écriture de cet article est la version 2.

Deux choix s’offrent à vous : soit d’utiliser la version 1 ; soit d’utiliser la version 2.

Les avantages de choisir la 2ème : meilleur gestion de la mémoire ; lecture plus rapide du fichier XML ; pas d’utilisation de la STL.

TinyXML charge complètement un fichier XML avec la méthode LoadFile()… Il vous reste ensuite le soin d’analyser votre fichier XML en fonction de son contenu en terme de nœuds.

On précise que XML est l’abréviation de « eXtensible Markup Language ». XML est un format pour stocker des données, pas un langage de programmation !
Celles-ci sont écrites entre des balises ou sous forme d’attributs, et l’ensemble est écrit sous forme d’un arbre.

Voici la structure d’un fichier XML :

&lt;?xml version=&quot;1.0&quot; ?&gt;
&lt;root&gt;
    &lt;UnElement attribut1 =&quot;une valeur&quot; /&gt;
    &lt;UnAutreElement attribut2 = &quot;2&quot; attribute3 = &quot;3&quot;&gt;
        &lt;Element3 attribut4=&quot;4&quot; /&gt;
        Du Texte !
    &lt;/UnAutreElement&gt;
&lt;/root&gt;

 

Il vous faut importer dans votre projet Visual C++ la librairie directement par l’intermédiaire de ces fichiers :

– tinystr.cpp
– tinyxmlerror.cpp
– tinystr.h
– tinyxml.h
– tinyxml.cpp
– tinyxmlparser.cpp

 

Dans votre fichier source .cpp utilisant TinyXML, il vous faudra écrire #include « tinyxml.h » en tant que fichier d’en-tête !

Vous pouvez définir le define #define TIXML_USE_STL pour activer l’utilisation de la STL dans TinyXml.

Sachez que TinyXML reconnaît automatiquement l’encodage utilisé par le fichier à charger (il n’est pas nécessaire de renseigner l’attribut « encoding »).

Commençons l’analyse d’un fichier quelconque :

TiXmlDocument doc;
if(!doc.LoadFile(&quot;Test.xml&quot;))
{
    std::cerr &lt;&lt; doc.ErrorDesc() &lt;&lt; std::endl;
    return FAILURE;
}

 

Le fichier « doc » contient toutes les données d’analyse du fichier XML chargé.

TiXmlElement* pRoot = doc.FirstChildElement();
if (pRoot == nullptr)
{
    std::cerr &lt;&lt; &quot;Echec chargement du fichier. Aucun élément racine.&quot; &lt;&lt; std::endl;
    doc.Clear();

    return FAILURE;
}

 

La méthode FirstChildElement() renvoie un pointeur vers un objet TinyXmlElement qui représente le premier nœud du fichier XML en question. FirstChildElement() prend un paramètre une chaîne de caractères représentant le nom du nœud en question.

Une boucle permet d’afficher tout les noms des éléments du fichier :

for (TiXmlElement* pElem = pRoot-&gt;FirstChildElement(); pElem != nullptr; pElem = pElem-&gt;NextSiblingElement())
{
    std::string elemName = pElem-&gt;Value();
}

 

Le principe est le suivant : on parcourt chaque nœud en extrayant de chaque nœud enfant les attributs qui nous intéressent !

...

[...La boucle...]

    const char* attr = nullptr;

    if (elemName == &quot;UnElement&quot;)
    {
        attr = elem-&gt;Attribute(&quot;attribut1&quot;);

        if (attr != nullptr)
        {
            // Faire le travail d'analyse ici
        }
    }

[...Fin de boucle...]

 

La variable attr représente la valeur de l’attribut en question. Si l’attribut ne comporte pas le nom spécifié. La méthode Attribute(…) renvoie nullptr.

On peut tester si le contenu d’un nœud est du texte avec la méthode elem->ToText(). Elle renvoie nullptr s’il n’y a pas de texte.

Il peut être utile d’utiliser une structure de données spécifique customisée afin de stocker les données lues depuis le fichier XML. Mais cela reste facultatif !

On fini l’analyse avec l’appel de Clear() : doc.Clear();

Pour faciliter le parcours des nœuds, on peut utiliser ce qu’on appelle des handles.
Exemple :

TiXmlHandle hdl(&amp;doc);
TiXmlElement *elem = hdl.FirstChildElement().FirstChildElement().Element();

 

La méthode QueryIntAttribute(…) permet d’obtenir la valeur d’un attribut sous forme de integer

Voici la liste des classes de TinyXML formatée par Doxygen : http://www.grinninglizard.com/tinyxmldocs/annotated.html

Résumé :

Simple et efficace, la petite librairie TinyXML facilitera vos chargement de données sous format XML.

Références :

– http://www.dinomage.com/2012/01/tutorial-using-tinyxml-part-1/

Comment calculer la matrice de rotation d’un Billboard

tag

Intro :

Qu’est-ce un billboard ? Un billboard est un objet 3D (en général d’apparence 2D) qui fait toujours face à la caméra.

Prérequis :

– Savoir ce qu’est une matrice

– Savoir lire une matrice

– Savoir en quoi consiste la matrice de vue

Explications :

Rappelez vous que la matrice de vue (celle de la caméra) est définie ainsi :

viewmatrix

En effet, cette matrice est construite à partir de trois vecteurs :

uprightlook

Avec une origine (ainsi nommée « Caméra ») représentant la position de celle-ci par rapport au repère commun à tous les nœuds de la scène, cette dernière concerne la matrice World (matrice de monde)

Une fois construite, la matrice de vue est une matrice qui doit être mise à jour constamment dans la boucle de rendu.

Décomposition de la matrice View :

\vec{LookAt}_x = \textbf{View}_{02}
\vec{LookAt}_y = \textbf{View}_{12}
\vec{LookAt}_y = \textbf{View}_{22}

\vec{Right}_x = \textbf{View}_{00}
\vec{Right}_y = \textbf{View}_{10}
\vec{Right}_y = \textbf{View}_{20}

\vec{Up}_x = \textbf{View}_{01}
\vec{Up}_y = \textbf{View}_{11}
\vec{Up}_y = \textbf{View}_{21}

 

On normalise ensuite ces trois derniers vecteurs :

{\vec{LookAt}}_{norm}=\frac{{\vec{LookAt}}}{\|{\vec{LookAt}}\|}
{\vec{Right}}_{norm}=\frac{{\vec{Right}}}{\|{\vec{Right}}\|}
{\vec{Up}}_{norm}=\frac{{\vec{Up}}}{\|{\vec{Up}}\|}

 

A noter que l’on peut retrouver un vecteur connaissant les deux autres. Par exemple, l’on veut retrouver le vecteur {\vec{Up}} connaissant {\vec{Right}} et {\vec{LookAt}} . On procède ainsi  :

{\vec{Up}} = {\vec{LookAt}}\times{\vec{Right}}

 

Maintenant pour construire notre Billboard il nous faudra créer une matrice de rotation \textbf{R} . C’est avec cette matrice de rotation que seront multipliés toutes les vertices du Billboard de façon à ce qu’ils soient toujours en face de la caméra !

On calcule la matrice \textbf{R} de la façon suivante :

\textbf{R}_{0*} = {\vec{Right}}
\textbf{R}_{1*} = {\vec{Up}}
\textbf{R}_{2*} = {\vec{LookAt}}

 

C’est-à-dire de manière plus détaillée :

\textbf{R}_{01} = \vec{Right_x}
\textbf{R}_{02} = \vec{Right_y}
\textbf{R}_{03} = \vec{Right_z}

\textbf{R}_{11} = \vec{Up_x}
\textbf{R}_{12} = \vec{Up_y}
\textbf{R}_{13} = \vec{Up_z}

\textbf{R}_{21} = \vec{LookAt_x}
\textbf{R}_{22} = \vec{LookAt_y}
\textbf{R}_{23} = \vec{LookAt_z}

 

Voici le Vertex Shader qui applique le calcul en question :

PS_INPUT VS(VS_INPUT input)
{
    PS_INPUT output = (PS_INPUT)0;
    
    // Extraction du LookAt vector
    float3 look;
    look.x = View[0][2];
    look.y = View[1][2];
    look.z = View[2][2];
    look = normalize(look);    

    // Extraction du Right vector
    float3 right;
    right.x = View[0][0];
    right.y = View[1][0];
    right.z = View[2][0];
    right = normalize(right);
    
    // Détermination du Up vector
    float3 up = cross(look, right);
    up = normalize(up);

    // Matrice de rotation qui fait face à la caméra
    float3x3 R;
    R[0] = right;
    R[1] = up;
    R[2] = look;
    
    float3 posW = mul(input.Pos, R);

    output.Pos = mul( float4(posW, 1.0f), World );
    output.Pos = mul( output.Pos, View );
    output.Pos = mul( output.Pos, Projection );
    
    output.Color = input.Color;    
    output.Page = input.Page;    
    output.Tex = input.Tex;        
    
    return output;
}

 

Voici un Billboard affiché qui représente une quête disponible :

 

billboard

 

Résumé :

Nous avons expliqué comment créer un Billboard en construisant une matrice de rotation spéciale qui tourne et qui fait toujours face à la caméra.

Références :

– Des sujets issus de quelques forums Internet comme celui de www.gamedev.net

La bibliothèque standard du C++ (la STL du C++11)

cce_clean

Intro :

Dans un programme C++, il est nécessaire d’utiliser certaines structures de données comme les tableaux, les liste chaînées, les arbres, etc…

La librairie standard du C++ offre trois types d’objets prêts à être utilisés : les conténaires, les algorithmes et les itérateurs.

En l’occurence la STL est une bibliothèque de classes et de fonctions standardisées selon la norme en vigueur du C++.

Elle fournie des outils pour faciliter la programmation de vos programmes :

– pour les chaînes de caractères
– pour la gestion des flux (fichiers, entrée et sortie standard)
– des conteneurs qui facilitent la manipulation de certains objets
– une panoplie d’algorithmes de tri

Prérequis :

– Savoir lire du C++

– Savoir quelques rudiments à propos des structures de données

Explications :

On peut utiliser les fichiers d’en-tête du langage C en C++. Ils sont inclus sous un nom différent, ils sont obtenus en retirant le suffixe ‘.h’ et en ajoutant un ‘c’ au début. Par exemple l’header <stdio.h> devient <cstdio>.

Voici la liste des principaux objets les plus utilisés de la STL :

std::array ; std::dequeue ; std::list ; std::map ; std::queue ; std::set ; std::vector

std::mutex ; std::thread ; std::string ; std::exception ; std::function ; std::tuple

Dans cette catégorie d’articles, nous allons étudier et expliquer en détails à quoi servent toutes ces entités / objets.

Résumé :

La STL présente et fournie beaucoup d’objets facilitant votre programme tout en embellissant votre code.

Références :

– http://www.cplusplus.com/reference/

– https://fr.wikipedia.org/wiki/Biblioth%C3%A8que_standard_du_C%2B%2B

Une classe SceneNode pour représenter vos objets et entités dans la scène

graphe

Intro :

Dans tout moteur 3D il est représenté un objet SceneNode (noeud de scène).
Cet objet sert à identifier une entité sur la scène avec les composantes suivantes : position, rotation, et mise à l’échelle.

Prérequis :

– Savoir initialiser DirectX 10.1

– Savoir qu’il existe une autre classe liée : SceneManager

Explications :

Voici le fichier SceneNode.h :

#ifndef SCENE_NODE_H
#define SCENE_NODE_H

struct AlphaSceneNode
{
    MeshSceneNode* m_pNode;
    D3DXMATRIX m_concat;
    float m_ScreenZ;

    // Pour le tri de liste de la STL
    bool const operator <(AlphaSceneNode const &other)
    {
        return m_ScreenZ < other.m_ScreenZ;
    }
};

class SceneNode
{
    friend class SceneManager;

public:
    SceneNode(SceneNode* pParent, const D3DXVECTOR3& position, const D3DXVECTOR3& rotation, const D3DXVECTOR3& scale);

    virtual ~SceneNode();

    std::string& GetName();
    void SetName(const std::string& sName);

    bool IsVisible();
    void SetVisible(bool bVisible);

    unsigned int GetID();
    void SetID(unsigned int iID);

    void AddChild(SceneNode* pChild);
    void RemoveChild(SceneNode* pChild);

    void RemoveAll();

    D3DXVECTOR3& GetScale();
    void SetScale(float x, float y, float z);

    D3DXVECTOR3& GetRotation();
    void SetRotation(float x, float y, float z);

    D3DXVECTOR3& GetPosition();

    void SetPosition(const D3DXVECTOR3& position);
    void SetPosition(float x, float y, float z);

    std::vector<SceneNode*> GetChildren();

    void SetParent(SceneNode* pSceneNode);
    SceneNode* GetParent();
    void RemoveParent();

    void Remove();

    D3DMATRIX& GetAbsoluteTransformation();
    D3DXMATRIX& GetRelativeTransformation();

    bool IsReallyVisible() { return true; }

    bool IsTransparent();
    void SetTransparent(bool bTransparent);

    void UpdateRelativeTransformation();
    void UpdateAbsoluteTransformation();

    virtual void OnRegisterSceneNode() {}
    virtual void OnAnimate(float fTimeSinceLastFrame) {}

    D3DXVECTOR3 GetAbsolutePosition();

    virtual bool OnUpdate(float fTimeSinceLastFrame) { return true; }

protected:
    virtual bool OnPreRender(float fTimeSinceLastFrame);
    virtual bool OnRender(float fTimeSinceLastFrame) { return true; }
    virtual bool OnPostRender(float fTimeSinceLastFrame);

protected:
    D3DXVECTOR3 m_RelativeTranslation;
    D3DXVECTOR3 m_RelativeRotation;
    D3DXVECTOR3 m_RelativeScale;

private:
    std::string m_sName;

    D3DXMATRIX m_AbsoluteTransformation;
    D3DXMATRIX m_RelativeTransformation;

    uint32 m_iID;

    bool m_bVisible;

    SceneNode* m_pParent;
    std::vector<SceneNode*> m_children;

    bool m_bTransparent;
};

#endif

 

Voici le fichier SceneNode.cpp :


SceneNode::SceneNode(SceneNode* pParent,
                     const D3DXVECTOR3& position,
                     const D3DXVECTOR3& rotation,
                     const D3DXVECTOR3& scale) :
m_bTransparent(false),
m_bVisible(true),
m_pParent(nullptr),
m_iID(0)
{
    m_RelativeTranslation = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
    m_RelativeRotation = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
    m_RelativeScale = D3DXVECTOR3(1.0f, 1.0f, 1.0f);

    D3DXMatrixIdentity(&m_AbsoluteTransformation);
    D3DXMatrixIdentity(&m_RelativeTransformation);

    UpdateAbsoluteTransformation();
}

SceneNode::~SceneNode()
{
    RemoveAll();
}

unsigned int SceneNode::GetID()
{
    return m_iID;
}

void SceneNode::SetID(unsigned int iID)
{
    m_iID = iID;
}

bool SceneNode::IsVisible()
{
    return m_bVisible;
}

void SceneNode::SetVisible(bool bVisible)
{
    m_bVisible = bVisible;
}

bool SceneNode::OnPreRender(float fTimeSinceLastFrame)
{
    UpdateAbsoluteTransformation();

    SCENE_MANAGER->PushAndSetMatrix(m_AbsoluteTransformation);

    return true;
}

bool SceneNode::OnPostRender(float fTimeSinceLastFrame)
{
    SCENE_MANAGER->PopMatrix();

    return true;
}

D3DXVECTOR3& SceneNode::GetPosition()
{
    return m_RelativeTranslation;
}

void SceneNode::SetPosition(const D3DXVECTOR3& position)
{
    m_RelativeTranslation = position;

    UpdateRelativeTransformation();
}

void SceneNode::SetPosition(float x, float y, float z)
{
    m_RelativeTranslation = D3DXVECTOR3(x, y, z);

    UpdateRelativeTransformation();
}

bool SceneNode::IsTransparent()
{
    return m_bTransparent;
}

void SceneNode::SetTransparent(bool bTransparent)
{
    m_bTransparent = bTransparent;
}

void SceneNode::UpdateRelativeTransformation()
{
    D3DXMATRIX matTrans;
    D3DXMatrixTranslation(&matTrans, m_RelativeTranslation.x, m_RelativeTranslation.y, m_RelativeTranslation.z);

    D3DXMATRIX matRotX;
    D3DXMatrixRotationX(&matRotX, m_RelativeRotation.x);

    D3DXMATRIX matRotY;
    D3DXMatrixRotationY(&matRotY, m_RelativeRotation.y);

    D3DXMATRIX matRotZ;
    D3DXMatrixRotationZ(&matRotZ, m_RelativeRotation.z);

    D3DXMATRIX matRotation = matRotX * matRotY * matRotZ;

    D3DXMATRIX matScale;
    D3DXMatrixScaling(&matScale, m_RelativeScale.x, m_RelativeScale.y, m_RelativeScale.z);

    // Ordre de la multiplication : S*R*T
    m_RelativeTransformation = matScale * matRotation * matTrans;
}

void SceneNode::UpdateAbsoluteTransformation()
{
    if (m_pParent)
    {
        D3DXMATRIX AbsoluteTrans = m_pParent->GetAbsoluteTransformation();
        D3DXMATRIX RelativeTrans = GetRelativeTransformation();
    
        D3DXMatrixMultiply(&m_AbsoluteTransformation, &AbsoluteTrans, &RelativeTrans);
    }
    else
    {
        m_AbsoluteTransformation = GetRelativeTransformation();
    }
}

D3DMATRIX& SceneNode::GetAbsoluteTransformation()
{
    UpdateAbsoluteTransformation();

    return m_AbsoluteTransformation;
}

D3DXMATRIX& SceneNode::GetRelativeTransformation()
{
    UpdateRelativeTransformation();

    return m_RelativeTransformation;
}

std::string& SceneNode::GetName()
{
    return m_sName;
}

void SceneNode::SetName(const std::string& sName)
{
    m_sName = sName;
}

D3DXVECTOR3& SceneNode::GetScale()
{
    return m_RelativeScale;
}

void SceneNode::SetScale(float x, float y, float z)
{
    m_RelativeScale = D3DXVECTOR3(x, y, z);

    UpdateRelativeTransformation();
}

D3DXVECTOR3& SceneNode::GetRotation()
{
    return m_RelativeRotation;
}

void SceneNode::SetRotation(float x, float y, float z)
{
    m_RelativeRotation = D3DXVECTOR3(x, y, z);

    UpdateRelativeTransformation();
}

void SceneNode::AddChild(SceneNode* pChild)
{
    if (pChild && (pChild != this))
    {
        pChild->RemoveParent();
        m_children.push_back(pChild);
        pChild->SetParent(this);

        MeshSceneNode* pChildSceneNode = dynamic_cast<MeshSceneNode*>(pChild);

        if (typeid(pChildSceneNode) == typeid(MeshSceneNode*))
        {
            SCENE_MANAGER->AddSceneNode(pChildSceneNode);
        }
    }
}

void SceneNode::RemoveChild(SceneNode* pChild)
{
    for (auto it = m_children.begin(); it != m_children.end(); it++)
    {
        SceneNode* pCurrentChild = *it;

        if (pCurrentChild == pChild)
        {
            pCurrentChild->RemoveParent();

            m_children.erase(it);
        }
    }
}

void SceneNode::RemoveAll()
{
    for (auto it = m_children.begin(); it != m_children.end(); it++)
    {
        SceneNode* pCurrentChild = *it;
        m_children.erase(it);
    }

    m_children.clear();
}

void SceneNode::SetParent(SceneNode* pSceneNode)
{
    Remove();
    m_pParent = pSceneNode;
}

SceneNode* SceneNode::GetParent()
{
    return m_pParent;
}

void SceneNode::RemoveParent()
{
    m_pParent = nullptr;
}

void SceneNode::Remove()
{
    if (m_pParent)
    {
        m_pParent->RemoveChild(this);
    }
}

std::vector<SceneNode*> SceneNode::GetChildren()
{
    return m_children;
}

D3DXVECTOR3 SceneNode::GetAbsolutePosition()
{
    D3DXVECTOR3 absolutePosition;

    absolutePosition.x = m_AbsoluteTransformation._12;
    absolutePosition.y = m_AbsoluteTransformation._13;
    absolutePosition.z = m_AbsoluteTransformation._14;

    return absolutePosition;
}

 

Résumé :

Nous avons présenté une classe pour gérer les positions, rotations et d’agrandissement de vos entités.

Références :

– Code source d’Irrlicht 3D

Ajouter un fichier ressource Win32 dans votre exécutable

file

Intro :

Dans un exécutable Windows, on peut stocker plusieurs fichiers. Ces fichiers en question s’appellent des « ressources ».

Prérequis :

– Savoir lire du C++

– Savoir se servir de Visual Studio C++

Explications :

Le fichier à intégrer peut-être de n’importe quel format !

On déclare ainsi dans le fichier de déclaration de ressource, la ressource en question, avec le patron de schéma suivant :

nameID typeID filename

Ce contenu doit être mis dans un fichier ressource.h

Le nom du fichier « filename » spécifie le nom d’un fichier contenant des données binaires de la ressource. Le contenu du fichier est intégré dans la ressource spécifiée. A noter que les données doivent être proprement alignées pour récupération sur une autre architecture.

Les données peuvent être définies directement entre les accolades de la manière suivante :

nameID typeID { raw-data }

18 MYRES2
{
   "Here is an ANSI string\0",    // explicitly null-terminated
}


Paramètres :

nameID : un unique nom ou une valeur de 16 bits en tout (unsigned integer) qui identifie la ressource

typeID : un unique nom (chaîne de caractères) ou un entier de 16 bits (unsigned integer) qui identifie le type de la ressource. Si un nombre est spécifié, il doit être supérieur à 255, les autres nombres sont réservés

filename : nom (chaîne de caractères) du fichier qui contient les données de la ressource. Ce nom doit correspondre à un fichier valide et existant, le nom du fichier doit être un chemin complet s’il n’est pas dans le répertoire courant

raw-data : données brutes consistant en plusieurs entiers ou chaînes de caractères

 

Résumé :

Nous avons présentée une méthode permettant d’intégrer des fichiers au sein de votre application C++. Il est aisé de récupérer leur contenu avec la fonction présentée ci-dessus.

Références :

– http://stackoverflow.com/questions/2933295/embed-text-file-in-a-resource-in-a-native-windows-application

– https://msdn.microsoft.com/en-us/library/aa381054(VS.85).aspx

Utilisez Doxygen pour documenter vos programme C++ en HTML

Doxygen_logo

Intro :

La documentation d’un programme est une étape fondamentale dans sa compréhension.

Le programme Doxygen permet la générer automatiquement sous couvert d’alimenter le programme en commentaires de code !

Prérequis :

Explications :

Voici les tags à utiliser dans vos programmes en tant que commentaires :

\struct pour documenter une structure C.
\union pour documenter une union C.
\enum pour documenter un type énuméré.
\fn pour documenter une fonction.
\var pour documenter une variable / un typedef / un énuméré.
\def pour documenter un #define.
\typedef pour documenter la définition d’un type.
\file pour documenter un fichier.
\namespace pour documenter un namespace.
\package pour documenter un package Java.
\interface pour documenter une interface IDL.
\brief pour donner une description courte.
\class pour documenter une classe.
\param pour documenter un paramètre de fonction/méthode.
\warning pour attirer l’attention.
\author pour donner le nom de l’auteur.
\return pour documenter les valeurs de retour d’une méthode/fonction.
\see pour renvoyer le lecteur vers quelque chose (une fonction, une classe, un fichier…).
\throws pour documenter les exceptions possiblement levées.
\version pour donner le numéro de version.
\since pour faire une note de version (ex : disponible depuis…).
\exception pour documenter une exception.
\deprecated pour spécifier qu’une fonction/méthode/variable… n’est plus utilisée.
\li pour faire une puce.
\todo pour indiquer une opération restant « à faire ».
\fixme pour indiquer un code défectueux, « à réparer ».

Résumé :

Nous avons présenté le programme Doxygen qui vous permettra de documenter à la volée tout votre code !

Références :

– https://fr.wikipedia.org/wiki/Doxygen

– http://franckh.developpez.com/tutoriels/outils/doxygen/

– http://axiomcafe.fr/tutoriel-documenter-un-code-avec-doxygen

Charger vos fichiers shaders .fx en tant que ressources d’exécutable

shader

Intro :

Il est méconnu que l’on puisse stocker des fichiers à l’intérieur d’un exécutable Win32. Ces fichiers, en l’occurrence, sont appelés ressources.

Prérequis :

– Savoir lire du C++

– Savoir utiliser la classe ShaderTechnique

– Comprendre comment intégrer une ressource à un exécutable

Explications :

Pour cela il faut utiliser la fonction DirectX 10.1 D3DX10CreateEffectFromResource(…)

Résumé :

Références :

Convertissez rapidement vos chaînes de caractères avec swscanf

ascii

Intro :

Parfois dans un programme on veut retranscrire rapidement un morceau de chaîne de caractères (en entrée) vers un autre type (en arrivée) que l’on souhaite.

Prérequis :

– Savoir lire du C++

– Être curieux !

Explications :

Pour se faire, on utilisera la fonction définie dans l’en-tête C++ <cwchar> nommée swscanf. Cette fonction lit les données d’une chaîne de caractère étendue et l’analyse selon un format spécifié. Le résultat est stocké dans un endroit spécifié dans les paramètres additionnels.

Le format de cette fonction est le suivant :

int swscanf (const wchar_t* ws, const wchar_t* format, ...);


Paramètres :

ws : une chaîne de caractère étendue / large (sur 32 bits) que la fonction se sert pour recevoir les données à traiter.

format : contient une chaîne de caractères spécifiant le schéma / patron de la chaîne à analyser ; le format spécifié suit le même schéma que la fonction scanf.

: il s’agit des paramètres additionnels qui vont servir à stocker les valeurs sortantes.

Valeur de retour :

La valeur de retour 0 indique qu’aucun champ n’a été assigné
– Retourne le nombre de champs qui sont correctement convertis et assignés

Exemple :

wchar_t sentence [] = L"Clément a 28 ans";
wchar_t str [20];
int i;

swscanf (sentence,L"%ls %*s %d",str,&i);
wprintf (L"%ls -> %d\n",str,i);


Affiche
: Clément -> 28.

Résumé :

Nous avons présenté une méthode pour convertir une chaîne formatée en entrée.

Références :

– http://www.cplusplus.com/reference/cwchar/swscanf/

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

Les exceptions

exception

Intro :

Les exceptions, en C++, sont un moyen de gérer les erreurs dans les programmes.

Prérequis :

– Savoir lire du C++

Explications :

Pour expliquer rapidement, une exception est une situation donnée dans laquelle un programme, qui dans une circonstance particulière, lance une erreur.

Chaque exception lancée dans le programme doit être traitée, sinon le programme se termine et génère une erreur fatale.

L’apparition d’une exception interrompt l’exécution normale du programme et provoque sa reprise dans le gestionnaire d’exception le plus proche.

Elles sont déclenchées grâce à l’utilisation du mot-clé throw.

Il est conseillé de dériver chaque exception par celle std::exception  (en tête <exception>).

Cette classe dispose d’une fonction membre what() qui renvoie une description de l’exception.

On rattrape les exception par référence, on les envoie par valeur.

Exemple d’exception :

void UneFonction()
{
[...]
    throw std::exception("Une erreur s'est lancée !");
[...]
}

try
{
    UneFonction();
}
catch(std::exception& e )
{
    std::cout << e.what() << std::endl;
}
catch ( ... ) // traite toutes les autres exceptions
{
    std::cerr << "Erreur inconnue.\n";
}

Il existe une fonction qui permet de personnaliser la fonction d’appel lors d’une exception non gérée ou traitée : set_terminate(…)

A noter qu’on peut coupler le concept du RAII avec les exceptions (voir l’article)

Résumé :

Les exceptions offre un moyen très rapide et élégant de gérer les erreurs dans les programmes.

Références :

– http://guillaume.belz.free.fr/doku.php?id=pourquoi_le_raii_est_fondamental_en_c

– http://cpp.developpez.com/faq/cpp/?page=Utilisation-des-exceptions#Qu-est-ce-qu-une-exception

Une classe TextureScreenQuad pour afficher vos effets shaders de Post-Traitement

fig-1-war-and-peace

Intro :

Dans un jeu, nous avons besoin d’afficher une texture / image qui prend la place de tout l’écran. Cette image peut être modifiée pour appliquer un rendu de post-effet avec un fichier shader .fx.

Explications :

Nous nous servons d’un tampon de vertices formant un carré ou d’un quad.

Voici le fichier TextureScreenQuad.h :

#ifndef TEXTURE_QUAD_H
#define TEXTURE_QUAD_H

#include <d3dx10.h>
#include <string>

#include "VertexDeclarations.h"
#include "Renderable.h"
#include "RenderTarget.h"
#include "MeshSceneNode.h"

class TextureScreenQuad : public MeshSceneNode
{
public:
    TextureScreenQuad();
    virtual ~TextureScreenQuad();

    bool Initialize();
    virtual void OnRender(float fTimeSinceLastFrame) override;

    void SetTransparency(float fValue);
    void SetColor(const D3DXCOLOR& color);
    void SetQuadSize(uint32 iWidth, uint32 iHeight);

    RenderTarget* GetRT();

    virtual bool SetShaderTechnique(ShaderTechnique* pShader) override;

protected:
    bool Update();

private:
    int m_iScreenWidth;
    int m_iScreenHeight;

    ID3D10ShaderResourceView* m_pTextureRessourceView;

    float m_fAlpha;

    PTVertex m_aQuadVertices[6];

    RenderTarget* m_pRT;
    D3DXCOLOR m_Color;
};

#endif

 

Voici le fichier TextureScreenQuad.cpp :

#include "TextureScreenQuad.h"
#include "Defines.h"
#include "D3D10Renderer.h"
#include "ShaderTechnique.h"
#include "ShaderTechnique_Declarations.h"

TextureScreenQuad::TextureScreenQuad() :
MeshSceneNode("TextureScreenQuad"),
m_fAlpha(1.0f),
m_pRT(nullptr)
{
    ZeroMemory(&m_aQuadVertices, sizeof(PTVertex) * 6);
    SetDrawMethod(DRAW_INDEXED);
    SetVertexType(VertexLayoutType::PT_VERTEX);
}

TextureScreenQuad::~TextureScreenQuad()
{
    SAFE_RELEASE(m_pTextureRessourceView);
}

bool TextureScreenQuad::Initialize()
{
    m_iScreenWidth = D3D10_RENDERER->GetViewportWidth();
    m_iScreenHeight = D3D10_RENDERER->GetViewportHeight();

    if (!Update())
    {
        MessageBoxA(nullptr, "Erreur d'initialisation d'un TextureScreenQuad !", "Erreur", MB_ICONHAND | MB_OK);
    }

    return true;
}

void TextureScreenQuad::SetTransparency(float fValue)
{
    m_Color.a = fValue;

    if (GetShaderTechnique() != nullptr)
    {
        GetShaderTechnique()->SetColor("Color", m_Color);
    }
}

void TextureScreenQuad::SetColor(const D3DXCOLOR& color)
{
    m_Color = color;
    
    if (GetShaderTechnique() != nullptr)
    {
        GetShaderTechnique()->SetColor("Color", m_Color);
    }
}

void TextureScreenQuad::OnRender(float fTimeSinceLastFrame)
{
    if (IsVisible())
    {
        D3D10_RENDERER->EnableZBuffer(false);

            Renderable::OnRender(fTimeSinceLastFrame);

        D3D10_RENDERER->EnableZBuffer(true);
    }
}

bool TextureScreenQuad::Update()
{
    float left, right, top, bottom;

    left = (float) -m_iScreenWidth / 2;
    right = left + (float) m_iScreenWidth;

    top = (float) -m_iScreenHeight / 2;
    bottom = top + (float) m_iScreenHeight;

    // Premier triangle
    m_aQuadVertices[0].pos = D3DXVECTOR3(left, top, 0.0f);  // Top left.
    m_aQuadVertices[0].texture = D3DXVECTOR2(0.0f, 0.0f);

    m_aQuadVertices[1].pos = D3DXVECTOR3(right, bottom, 0.0f);  // Bottom right.
    m_aQuadVertices[1].texture = D3DXVECTOR2(1.0f, 1.0f);

    m_aQuadVertices[2].pos = D3DXVECTOR3(left, bottom, 0.0f);  // Bottom left.
    m_aQuadVertices[2].texture = D3DXVECTOR2(0.0f, 1.0f);

    // Deuxième triangle
    m_aQuadVertices[3].pos = D3DXVECTOR3(left, top, 0.0f);  // Top left.
    m_aQuadVertices[3].texture = D3DXVECTOR2(0.0f, 0.0f);

    m_aQuadVertices[4].pos = D3DXVECTOR3(right, top, 0.0f);  // Top right.
    m_aQuadVertices[4].texture = D3DXVECTOR2(1.0f, 0.0f);

    m_aQuadVertices[5].pos = D3DXVECTOR3(right, bottom, 0.0f);  // Bottom right.
    m_aQuadVertices[5].texture = D3DXVECTOR2(1.0f, 1.0f);

    // On déclare les tableaux
    std::vector<PTVertex> vertices;
    // On reset les emplacements
    vertices.resize(6);
    // On affecte les valeurs
    std::copy(m_aQuadVertices, m_aQuadVertices + 6, vertices.begin());

    unsigned short i[6] = { 0, 1, 2, 3, 4, 5 };

    // On déclare les tableaux
    std::vector<unsigned short> indices;
    // On reset les emplacements
    indices.resize(6);
    // On affecte les valeurs
    std::copy(i, i + 6, indices.begin());

    BuildMesh(vertices, indices);

    return true;
}

void TextureScreenQuad::SetQuadSize(uint32 iWidth, uint32 iHeight)
{
    m_iScreenWidth = iWidth;
    m_iScreenHeight = iHeight;

    Update();
}

RenderTarget* TextureScreenQuad::GetRT()
{
    return m_pRT;
}

bool TextureScreenQuad::SetShaderTechnique(ShaderTechnique* pShader)
{
    bool bSuccess = Renderable::SetShaderTechnique(pShader);

    if (m_pRT == nullptr)
    {
        m_pRT = new RenderTarget();
        m_pRT->Initialize(m_iScreenWidth, m_iScreenHeight);

        ShaderTechnique_FXAA* pFXAA = dynamic_cast<ShaderTechnique_FXAA*> (GetShaderTechnique());
        pFXAA->SetRenderTarget(m_pRT);

        SetColor(D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f));
    }

    return bSuccess;
}


Résumé :

Nous avons présenté une façon d’utiliser un Quad de de post-traitement afin de modifier les couleurs de rendu du front buffer.

Références :

– Stackoverflow.com

Une classe RenderTarget pour afficher un tampon d’image de rendu

rtt

Intro :

Nous avons parfois besoin de stocker le rendu 3D dans une texture que l’on pourra utiliser n’importe tout dans le programme !

Explications :

Voici le fichier RenderToTexture.h :

#ifndef RENDER_TO_TEXTURE_H
#define RENDER_TO_TEXTURE_H

#include <d3d10.h>
#include "Types.h"

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

    bool Initialize(uint32 iTextureWidth, uint32 iTextureHeight);

    void SetRenderTarget(ID3D10DepthStencilView* pDepthStencilView);
    void ClearRenderTarget(ID3D10DepthStencilView* pDepthStencilView);

    ID3D10ShaderResourceView* GetShaderResourceView();

private:
    ID3D10Texture2D* m_pRenderTargetTexture;
    ID3D10RenderTargetView* m_pRenderTargetView;
    ID3D10ShaderResourceView* m_pShaderResourceView;
};

#endif

 

Voici le fichier RenderToTexture.cpp :

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

RenderTarget::RenderTarget() :
m_pRenderTargetTexture(nullptr),
m_pRenderTargetView(nullptr),
m_pShaderResourceView(nullptr)
{
}

RenderTarget::~RenderTarget()
{
    SAFE_RELEASE(m_pRenderTargetTexture);
    SAFE_RELEASE(m_pRenderTargetView);
    SAFE_RELEASE(m_pShaderResourceView);
}

bool RenderTarget::Initialize(uint32 iTextureWidth, uint32 iTextureHeight)
{
    /* On créé les descriptions de texture 2D, de la render target et de la ressource du shader */
    D3D10_TEXTURE2D_DESC textureDesc;
    D3D10_RENDER_TARGET_VIEW_DESC renderTargetViewDesc;
    D3D10_SHADER_RESOURCE_VIEW_DESC shaderResourceViewDesc;

    HRESULT hr;
    // Initialize the render target texture description.
    ZeroMemory(&textureDesc, sizeof(textureDesc));

    // Setup the render target texture description.
    textureDesc.Width = iTextureWidth;
    textureDesc.Height = iTextureHeight;
    textureDesc.MipLevels = 1;
    textureDesc.ArraySize = 1;
    textureDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
    textureDesc.SampleDesc.Count = 1;
    textureDesc.Usage = D3D10_USAGE_DEFAULT;
    textureDesc.BindFlags = D3D10_BIND_RENDER_TARGET | D3D10_BIND_SHADER_RESOURCE;
    textureDesc.CPUAccessFlags = 0;
    textureDesc.MiscFlags = 0;

    // Create the render target texture.
    hr = D3D10_RENDERER->GetDevice()->CreateTexture2D(&textureDesc, NULL, &m_pRenderTargetTexture);
    if (FAILED(hr))
    {
        return false;
    }

    // Setup the description of the render target view.
    renderTargetViewDesc.Format = textureDesc.Format;
    renderTargetViewDesc.ViewDimension = D3D10_RTV_DIMENSION_TEXTURE2D;
    renderTargetViewDesc.Texture2D.MipSlice = 0;

    // Create the render target view.
    hr = D3D10_RENDERER->GetDevice()->CreateRenderTargetView(m_pRenderTargetTexture, &renderTargetViewDesc, &m_pRenderTargetView);
    if (FAILED(hr))
    {
        return false;
    }

    // Setup the description of the shader resource view.
    shaderResourceViewDesc.Format = textureDesc.Format;
    shaderResourceViewDesc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE2D;
    shaderResourceViewDesc.Texture2D.MostDetailedMip = 0;
    shaderResourceViewDesc.Texture2D.MipLevels = 1;

    // Create the shader resource view.
    hr = D3D10_RENDERER->GetDevice()->CreateShaderResourceView(m_pRenderTargetTexture, &shaderResourceViewDesc, &m_pShaderResourceView);
    if (FAILED(hr))
    {
        return false;
    }

    return true;
}

void RenderTarget::SetRenderTarget(ID3D10DepthStencilView* pDepthStencilView)
{
    D3D10_RENDERER->GetDevice()->OMSetRenderTargets(1, &m_pRenderTargetView, pDepthStencilView);
}

void RenderTarget::ClearRenderTarget(ID3D10DepthStencilView* pDepthStencilView)
{
    float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f };

    // On efface le back buffer
    D3D10_RENDERER->GetDevice()->ClearRenderTargetView(m_pRenderTargetView, color);

    // On efface le depth buffer
    D3D10_RENDERER->GetDevice()->ClearDepthStencilView(pDepthStencilView, D3D10_CLEAR_DEPTH, 1.0f, 0);
}

ID3D10ShaderResourceView* RenderTarget::GetShaderResourceView()
{
    return m_pShaderResourceView;
}

 

Comment l’utiliser ?

void D3D10Renderer::Render()
{
    [...]
    
    // Efface la surface de rendu
    m_pd3dDevice->ClearDepthStencilView(m_pDepthStencilView, D3D10_CLEAR_DEPTH, 1.0f, 0 );
    m_pd3dDevice->ClearRenderTargetView(m_pRenderTargetView, afClearColor);

    SCENE_MANAGER->DrawAll(fTimeSinceLastFrame);

    RenderToTexture(fTimeSinceLastFrame);

    m_pQuad->OnRender(fTimeSinceLastFrame);

    m_pSwapChain->Present(0, 0);
}

void D3D10Renderer::RenderToTexture(float fTimeSinceLastFrame)
{
    // Set the render target to be the render to texture.
    m_pQuad->GetRT()->SetRenderTarget(m_pDepthStencilView);

    // Clear the render to texture.
    m_pQuad->GetRT()->ClearRenderTarget(m_pDepthStencilView);

    // Render the scene now and it will draw to the render to texture instead of the back buffer.
    SCENE_MANAGER->DrawAll(fTimeSinceLastFrame);

    // Reset the render target back to the original back buffer and not the render to texture anymore.
    m_pd3dDevice->OMSetRenderTargets(1, &m_pRenderTargetView, m_pDepthStencilView);
}

 

Résumé :

Nous avons présenter la façon la plus optimale possible d’utiliser une RenderTarget !

Références :

L’anticrénelage FXAA – Shader de Post-Traitement

image.png

Intro:

Une image numérique est composée de pixels. Lorsqu’elle est redimensionnée, le bord des formes ayant un angle particulier prend la forme d’escalier : c’est le crénelage, ou aliasing.

Explication :

Voici une technique très efficace et très performante en ressource système afin d’effectuer du rendu anti-crénelage. Elle utilise une render target et un Quad de post-traitement.

Voici le fichier ShaderTechique_Declarations.h :

//------------------------------------------------------
// FXAA shader
//------------------------------------------------------
class ShaderTechnique_FXAA : public ShaderTechnique
{
public:
    ShaderTechnique_FXAA();
    virtual ~ShaderTechnique_FXAA();

    virtual bool Initialize() override;

    virtual void SetupShaderVariables();

    virtual void Update(float fTimeSinceLastFrame);

    void SetRenderTarget(RenderTarget* pRT);

    void SetEnabled(bool bEnabled);
    bool GetEnabled();

private:
    D3DXMATRIX m_WorldMatrix;
    D3DXMATRIX m_ViewMatrix;
    D3DXMATRIX m_OrthoMatrix;

    bool m_bEnabled;
};

 

Voici le fichier ShaderTechnique_FXAA.cpp :

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

ShaderTechnique_FXAA::ShaderTechnique_FXAA() :
ShaderTechnique("FXAA.fx", "Render", VertexLayoutType::PT_VERTEX),
m_bEnabled(true)
{
}

ShaderTechnique_FXAA::~ShaderTechnique_FXAA()
{
}

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

    SetupShaderVariables();

    return true;
}

void ShaderTechnique_FXAA::SetRenderTarget(RenderTarget* pRT)
{
    SetTextureRV("TextureDiffuse", pRT->GetShaderResourceView());
}

void ShaderTechnique_FXAA::SetupShaderVariables()
{
    RegisterMatrixVariable("World", ShaderVariableType::WORLD);
    RegisterMatrixVariable("View", ShaderVariableType::VIEW);
    RegisterMatrixVariable("Projection", ShaderVariableType::PROJECTION);
    RegisterTextureVariable("TextureDiffuse", ShaderVariableType::ANY);
    RegisterVectorVariable("FrameBufferSize", ShaderVariableType::ANY);
    RegisterVectorVariable("Color", ShaderVariableType::ANY);
    RegisterScalarVariable("Enabled", ShaderVariableType::ANY);

    D3DXMatrixIdentity(&m_WorldMatrix);
    D3DXMatrixIdentity(&m_ViewMatrix);
    D3DXMatrixIdentity(&m_OrthoMatrix);

    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);

    float fViewportWidth = (float)D3D10_RENDERER->GetViewportWidth();
    float fViewportHeigth = (float)D3D10_RENDERER->GetViewportHeight();

    D3DXMatrixOrthoLH(&m_OrthoMatrix, fViewportWidth, fViewportHeigth, 0.01f, 20.0f);

    SetVector("FrameBufferSize", D3DXVECTOR2(fViewportWidth, fViewportHeigth));
}

void ShaderTechnique_FXAA::Update(float fTimeSinceLastFrame)
{
    SetMatrix("World", &m_WorldMatrix);
    SetMatrix("View", &m_ViewMatrix);
    SetMatrix("Projection", &m_OrthoMatrix);

    SetAutoMatrix(ShaderVariableType::ANY);
}

void ShaderTechnique_FXAA::SetEnabled(bool bEnabled)
{
    SetScalar("Enabled", bEnabled);
    m_bEnabled = bEnabled;
}

bool ShaderTechnique_FXAA::GetEnabled()
{
    return m_bEnabled;
}

 

Voici les bouts de code pour initialiser le shader FXAA :


bool D3D10Renderer::Initialize(bool bFullscreen)
{
    [...]
    m_pQuad = new TextureScreenQuad();
    m_pQuad->Initialize();
    m_pQuad->SetShaderTechnique( SHADER_MANAGER->GetShader("FXAA") );
    [...]
}

void D3D10Renderer::Render()
{
    [...]
    // Efface la surface de rendu
    m_pd3dDevice->ClearDepthStencilView(m_pDepthStencilView, D3D10_CLEAR_DEPTH, 1.0f, 0 );
    m_pd3dDevice->ClearRenderTargetView(m_pRenderTargetView, afClearColor);

    SCENE_MANAGER->DrawAll(fTimeSinceLastFrame);

    DrawTextInfo();

    RenderToTexture(fTimeSinceLastFrame);

    UpdateWindowTitle();

    m_pQuad->OnRender(fTimeSinceLastFrame);

    m_pSwapChain->Present(0, 0);
    [...]
}

void D3D10Renderer::RenderToTexture(float fTimeSinceLastFrame)
{
    // Set the render target to be the render to texture.
    m_pQuad->GetRT()->SetRenderTarget(m_pDepthStencilView);

    // Clear the render to texture.
    m_pQuad->GetRT()->ClearRenderTarget(m_pDepthStencilView);

    // Render the scene now and it will draw to the render to texture instead of the back buffer.
    SCENE_MANAGER->DrawAll(fTimeSinceLastFrame);

    // Reset the render target back to the original back buffer and not the render to texture anymore.
    m_pd3dDevice->OMSetRenderTargets(1, &m_pRenderTargetView, m_pDepthStencilView);
}

 

Voici le fichier shader .fx :

SamplerState SamplerLinear
{
    Filter = ANISOTROPIC;
    AddressU = Wrap;
    AddressV = Wrap;
    MaxAnisotropy = 1;
};

matrix World;
matrix View;
matrix Projection;

Texture2D TextureDiffuse;

float2 FrameBufferSize;

float4 Color;

bool Enabled;

struct VS_INPUT
{
    float4 Pos : POSITION;
    float2 Tex : TEXCOORD;
};
 
struct PS_INPUT
{
    float4 Pos : SV_POSITION;
    float2 Tex : TEXCOORD0;
};

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;
    
    return output;
}    

static const float FXAA_SPAN_MAX = 4.0;
static const float FXAA_REDUCE_MUL = 1.0/8.0;
static const float FXAA_REDUCE_MIN = 1.0/128.0;
    
float4 PS(PS_INPUT input) : SV_Target
{
    input.Tex = 1.0f - input.Tex;
    
    float3 rgbNW = TextureDiffuse.Sample(SamplerLinear, input.Tex + (float2(-1.0,-1.0) / FrameBufferSize)).xyz;
    float3 rgbNE = TextureDiffuse.Sample(SamplerLinear, input.Tex + (float2(1.0,-1.0) / FrameBufferSize)).xyz;
    float3 rgbSW = TextureDiffuse.Sample(SamplerLinear, input.Tex + (float2(-1.0,1.0) / FrameBufferSize)).xyz;
    float3 rgbSE = TextureDiffuse.Sample(SamplerLinear, input.Tex + (float2(1.0,1.0) / FrameBufferSize)).xyz;
    float3 rgbM  = TextureDiffuse.Sample(SamplerLinear, input.Tex).xyz;
    
    float3 luma = float3(0.299, 0.587, 0.114);
    
    float lumaNW = dot(rgbNW, luma);
    float lumaNE = dot(rgbNE, luma);
    float lumaSW = dot(rgbSW, luma);
    float lumaSE = dot(rgbSE, luma);
    float lumaM  = dot(rgbM, luma);
    
    float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
    float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));
    
    float2 dir;
    dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
    dir.y =  ((lumaNW + lumaSW) - (lumaNE + lumaSE));
    
    float dirReduce = max(
          (lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL),
           FXAA_REDUCE_MIN);
      
    float rcpDirMin = 1.0/(min(abs(dir.x), abs(dir.y)) + dirReduce);
    
    dir = min(float2( FXAA_SPAN_MAX,  FXAA_SPAN_MAX),
              max(float2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),
              dir * rcpDirMin)) / FrameBufferSize;
            
    float3 rgbA = (1.0/2.0) * (
            TextureDiffuse.Sample(SamplerLinear, input.Tex + dir * (1.0/3.0 - 0.5)).xyz +
            TextureDiffuse.Sample(SamplerLinear, input.Tex + dir * (2.0/3.0 - 0.5)).xyz);
            
    float3 rgbB = rgbA * (1.0/2.0) + (1.0/4.0) * (
            TextureDiffuse.Sample(SamplerLinear, input.Tex + dir * (0.0/3.0 - 0.5)).xyz +
            TextureDiffuse.Sample(SamplerLinear, input.Tex + dir * (3.0/3.0 - 0.5)).xyz);
            
    float lumaB = dot(rgbB, luma);

    if (Enabled)
    {
        if ((lumaB < lumaMin) || (lumaB > lumaMax))
        {
            return float4(rgbA, 1.0f) * Color;
        }
        else
        {
            return float4(rgbB, 1.0f) * Color;
        }        
    }
    else
    {
        return TextureDiffuse.Sample(SamplerLinear, input.Tex);
    }
}

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

Résumé :

Nous avons présenté une technique FXAA par un fichier shader .fx afin d’effectuer le rendu d’anti-crénélage.

Références :

–  https://fr.wikipedia.org/wiki/Fast_approximate_anti-aliasing

– http://horde3d.org/wiki/index.php5?title=Shading_Technique_-_FXAA

Une classe DelayedTimerManager pour gérer les délais de temporisation

unnamed

Intro :

Parfois nous avons besoin, dans le code, d’appeler des fonctions / méthodes à un certain intervalle de temps !

Prérequis :

– Savoir lire du C++

Explications :

Voici le fichier DelayedTimerManager.h :

#ifndef DELAYED_TIMER_MANAGER_H
#define DELAYED_TIMER_MANAGER_H

#include "Singleton.h"

#include <map>

class DelayedTimerManager : public Singleton<DelayedTimerManager>
{
public:
    struct TimerData
    {
        TimerData() : iElapsedTime(0), iLastTime(0), iWaitingTime(0)
        {
        }

        uint64 iElapsedTime;
        uint64 iLastTime;
        uint32 iWaitingTime;
    };

    DelayedTimerManager();
    virtual ~DelayedTimerManager();

    bool TimerWait(const std::string& sTimerName);

    void RegisterTimer(const std::string& sTimerName, uint32 iWaitingTime);
    void UpdateTimer(const std::string& sTimerName, uint32 iWaitingTime);

    void ResetTimer(const std::string& sTimerName);

private:
    std::map<std::string, TimerData> m_timers;
};

#endif

 

Voici le contenu du fichier DelayedTimerManager.cpp :

#include "DelayedTimerManager.h"

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

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

DelayedTimerManager::DelayedTimerManager()
{
}

DelayedTimerManager::~DelayedTimerManager()
{
}

bool DelayedTimerManager::TimerWait(const std::string& sTimerName)
{
    TimerData* pTimerData = &m_timers[sTimerName];

    uint32 iElapsedTime = GetTickCount() - pTimerData->iLastTime;
    uint32 iWaitingTime = pTimerData->iWaitingTime;

    if (iElapsedTime > iWaitingTime)
    {
        pTimerData->iLastTime = GetTickCount();    

        return true;
    }

    return false;
}

void DelayedTimerManager::RegisterTimer(const std::string& sTimerName, uint32 iWaitingTime)
{
    TimerData timerData;
    timerData.iWaitingTime = iWaitingTime;

    m_timers[sTimerName] = timerData;
}

void DelayedTimerManager::ResetTimer(const std::string& sTimerName)
{
    TimerData* pTimerData = &m_timers[sTimerName];

    pTimerData->iLastTime = 0;
}

void DelayedTimerManager::UpdateTimer(const std::string& sTimerName, uint32 iWaitingTime)
{
    TimerData* pTimerData = &m_timers[sTimerName];

    pTimerData->iWaitingTime = iWaitingTime;
}

 

Résumé :

Nous avons présenter le code pour gérer un système de temporisation.

Une classe GUIDraggableWidget pour permettre de déplacer une GUIWidget

lnh5ycp0amwjyttfhg4x

Intro :

Parfois nous avons besoin qu’une GUIWidget puisse être déplaçable à volonté tout en restant appuyé dessus la souris.

Prérequis :

– Avoir compris la classe GUIWidget

Contenu :

Voici le contenu GUIDraggableWidget.h :

#ifndef GUI_DRAGGABLE_WIGET_H
#define GUI_DRAGGABLE_WIGET_H

#include <iostream>
#include <d3dx10math.h>

#include "GUIImage.h"
#include "GUIButton.h"

class GUIDraggableWiget : public GUIImage
{
public:
    enum DraggableBoxState
    {
        NON_ACTIVE,
        DRAGGING
    };

    GUIDraggableWiget(const std::string& sName, const std::string& sTextureFileName);
    virtual ~GUIDraggableWiget();

    virtual void Update(float fTimeSinceLastFrame);

    virtual void SetPosition(unsigned int x, unsigned int y);
    virtual void SetSize(unsigned int iWidth, unsigned int iHeight);

    void DisableDragging();
    void EnableDragging();

private:
    bool IsCollidingWithDraggableBox(int x, int y);
    bool IsCollidingWithCloseBox(int x, int y);

    void SetBoxesPositions(unsigned int x, unsigned int y);

    D3DXVECTOR2 GetDraggableBoxPosition();

    DraggableBoxState GetDraggableBoxState();
    void SetDraggableBoxState(DraggableBoxState state);

    void SetImageSize(unsigned int iWidth, unsigned int iHeight);

private:
    AABB m_draggableBoxAABB;
    AABB m_closeBoxAABB;

    DraggableBoxState m_state;

    int xPos;
    int yPos;

    int sx;
    int sy;

    bool m_bDragging;
    bool m_bDraggingEnabled;
};

#endif

 

Voici le fichier GUIDraggableWidget.cpp :

#include "GUIDraggableWidget.h"
#include "InputManager.h"
#include "GUIManager.h"

GUIDraggableWiget::GUIDraggableWiget(const std::string& sName, const std::string& sTextureFileName) :
GUIImage(sName, sTextureFileName),
xPos(0),
yPos(0),
sx(0),
sy(0),
m_bDragging(false),
m_bDraggingEnabled(true)
{
    ZeroMemory(&m_draggableBoxAABB, sizeof(AABB));
    ZeroMemory(&m_closeBoxAABB, sizeof(AABB));

    m_draggableBoxAABB.w = (float)GetImage()->GetWidth();
    m_draggableBoxAABB.h = (float)GetImage()->GetHeight() * 0.3f;    

    m_closeBoxAABB.w = 20;
    m_closeBoxAABB.h = 20;    
}

GUIDraggableWiget::~GUIDraggableWiget()
{
}

void GUIDraggableWiget::Update(float fTimeSinceLastFrame)
{
    if (!IsVisible())
    {
        return;
    }

    GUIImage::Update(fTimeSinceLastFrame);

    int absX, absY;
    INPUT_MANAGER->GetAbsMouseLocation(absX, absY);

    int dX = absX - sx;
    int dY = absY - sy;

    if (INPUT_MANAGER->IsMouseButtonDown(MOUSE_LEFT_BUTTON_DOWN)
        && GUI_MANAGER->DoesWidgetIsOnFront(this)
        && SYSTEM->HasFocus() && IsActive())
    {
        if (IsCollidingWithDraggableBox(absX, absY) && m_bDraggingEnabled)
        {
             m_bDragging = true;
        }

        if (IsCollidingWithCloseBox(absX, absY))
        {
            SetVisible(false);
        }

        if (m_bDragging)
        {
            SetDraggableBoxState(DraggableBoxState::DRAGGING);

            xPos += dX;
            yPos += dY;

            /* Contraintes de bords */
            if (yPos <= 0)
            {
                yPos = 0;
            }

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

            unsigned int iWidthOffset = D3D10_RENDERER->GetViewportWidth() - GetImage()->GetWidth();
            unsigned int iHeightOffset = D3D10_RENDERER->GetViewportHeight() - GetImage()->GetHeight();

            /* Contraintes de bords */
            if (xPos >= iWidthOffset)
            {
                xPos = iWidthOffset;
            }

            if (yPos >= iHeightOffset)
            {
                yPos = iHeightOffset;
            }    
            
            SetPosition(xPos, yPos);
        }
    }
    else
    {
        m_bDragging = false;
    }

    sx = absX;
    sy = absY;
}

bool GUIDraggableWiget::IsCollidingWithDraggableBox(int x, int y)
{
    return (x >= m_draggableBoxAABB.x) && (x < (m_draggableBoxAABB.x + m_draggableBoxAABB.w))
        && (y >= m_draggableBoxAABB.y) && (y < (m_draggableBoxAABB.y + m_draggableBoxAABB.h))
        && IsVisible();
}

bool GUIDraggableWiget::IsCollidingWithCloseBox(int x, int y)
{
    return (x >= m_closeBoxAABB.x) && (x < (m_closeBoxAABB.x + m_closeBoxAABB.w))
        && (y >= m_closeBoxAABB.y) && (y < (m_closeBoxAABB.y + m_closeBoxAABB.h))
        && IsVisible();
}

void GUIDraggableWiget::SetDraggableBoxState(DraggableBoxState state)
{
    m_state = state;
}
    
GUIDraggableWiget::DraggableBoxState GUIDraggableWiget::GetDraggableBoxState()
{
    return m_state;
}

void GUIDraggableWiget::SetBoxesPositions(unsigned int x, unsigned int y)
{  
    m_draggableBoxAABB.x = x;
    m_draggableBoxAABB.y = y;

    m_closeBoxAABB.x = (GetImage()->GetWidth() - m_closeBoxAABB.w) + x;
    m_closeBoxAABB.y = y;
}

D3DXVECTOR2 GUIDraggableWiget::GetDraggableBoxPosition()
{  
    return D3DXVECTOR2(m_draggableBoxAABB.x, m_draggableBoxAABB.y);
}

void GUIDraggableWiget::SetPosition(unsigned int x, unsigned int y)
{  
    SetBoxesPositions(x, y);

    xPos = x;
    yPos = y;

    GUIImage::SetPosition(x, y);
}

void GUIDraggableWiget::SetSize(unsigned int iWidth, unsigned int iHeight)
{    
    GUIImage::SetSize(iWidth, iHeight);

    m_draggableBoxAABB.w = (float)GetImage()->GetWidth();
    m_draggableBoxAABB.h = (float)GetImage()->GetHeight() * 0.3f;    
}

void GUIDraggableWiget::DisableDragging()
{
    m_bDraggingEnabled = false;
}

void GUIDraggableWiget::EnableDragging()
{
    m_bDraggingEnabled = true;
}

 

Résumé :

Nous avons décrit une classe qui représente un moyen d’afficher une GUIWidget déplaçable.

Une classe GUIButton pour représenter un bouton cliquable

8HcZT

Intro :

Parfois nous avons besoin d’afficher un bouton pour rendre l’interface utilisateur (GUI) plus dynamique. Le bouton peut afficher une autre image lorsqu’il est reste cliqué dessus !

Prérequis :

– Avoir lu la classe GUIWidget

Contenu :

Voici le fichier GUIButton.h :

#ifndef GUI_BUTTON_H
#define GUI_BUTTON_H

#include <d3dx10math.h>
#include <iostream>

#include "Sprite2D.h"
#include "GUIImage.h"
#include "Defines.h"

class GUIButton : public GUIImage
{
public:
    enum ButtonState
    {
        NON_ACTIVE,
        HOVER,
        CLICKED,
        PUSHED_OUT
    };

    GUIButton(const std::string& sName, const std::string& sTextureName, const std::string& sTextureClickedName = NONE,
        const std::string& sTextureHoverName = NONE);

    virtual ~GUIButton();

    virtual void Update(float fTimeSinceLastFrame);

    ButtonState GetState();
    void SetState(ButtonState state);

    void SetHoverOnImage();
    void SetClickedOnImage();
    void SetNormalImage();

    bool IsClickedOn();
    bool IsPushedOut();
    bool IsNonActive();

private:
    ButtonState m_state;

    std::string m_sNormalTextureFile;
    std::string m_sClickedOnTextureFile;
    std::string m_sHoverOnTextureFile;

    bool m_bClickedOn;
    bool m_bHover;
    bool m_bHasClickedOnImage;
    bool m_bHasHoverdOnImage;

    bool m_bMouseButtonReleased;
};

#endif

 

Voici le fichier GUIButton.cpp :

#include "GUIButton.h"
#include "InputManager.h"
#include "GUIManager.h"
#include "Utils.h"

GUIButton::GUIButton(const std::string& sName, const std::string& sTextureName, const std::string& sTextureClickedName,
                     const std::string& sTextureHoverName) :
GUIImage(sName, sTextureName),
m_state(NON_ACTIVE),
m_sNormalTextureFile(sTextureName),
m_sClickedOnTextureFile(sTextureClickedName),
m_sHoverOnTextureFile(sTextureHoverName),
m_bClickedOn(false),
m_bHover(false),
m_bMouseButtonReleased(false),
m_bHasClickedOnImage(false),
m_bHasHoverdOnImage(false)
{
    // Image quand on appuie
    if (Utils::IsFileReadable(sTextureClickedName))
    {
        m_bHasClickedOnImage = true;
    }

    // Image quand on survole
    if (Utils::IsFileReadable(sTextureHoverName))
    {
        m_bHasHoverdOnImage = true;
    }
}

GUIButton::~GUIButton()
{
}

void GUIButton::Update(float fTimeSinceLastFrame)
{
    if (!IsVisible())
    {
        return;
    }

    int x, y;
    INPUT_MANAGER->GetAbsMouseLocation(x, y);
    
    if (IsActive())
    {
        if (IsCollidingWithMousePointer(x, y)
            && GUI_MANAGER->IsWidgetFirstOverlapping(this))
        {
            SetState(ButtonState::HOVER);
            m_bHover = true;

            SetHoverOnImage();

            if (INPUT_MANAGER->IsMouseButtonDown(MOUSE_LEFT_BUTTON_DOWN))
            {
                m_bClickedOn = true;

                SetState(ButtonState::CLICKED);

                GUIParameters param;
                param << GetState();

                SendEventToListeners(param);

                SetClickedOnImage();
            }
            else if (m_bClickedOn)
            {
                SetNormalImage();    

                m_bClickedOn = false;

                SetState(ButtonState::PUSHED_OUT);
            }
        }
        else if (m_bHover)
        {
            SetState(ButtonState::NON_ACTIVE);

            SetNormalImage();

            m_bHover = false;
        }
        else
        {
            m_bClickedOn = false;
        }
    }

    GUIImage::Update(fTimeSinceLastFrame);
}

GUIButton::ButtonState GUIButton::GetState()
{
    return m_state;
}

void GUIButton::SetState(ButtonState state)
{
    m_state = state;
}

bool GUIButton::IsClickedOn()
{
    return GetState() == GUIButton::ButtonState::CLICKED && IsVisible();
}

bool GUIButton::IsPushedOut()
{
    return GetState() == GUIButton::ButtonState::PUSHED_OUT && IsVisible();
}

bool GUIButton::IsNonActive()
{
    return GetState() == GUIButton::ButtonState::NON_ACTIVE && IsVisible();
}

void GUIButton::SetClickedOnImage()
{
    if (m_bHasClickedOnImage)
    {
        SetTextureImage(m_sClickedOnTextureFile);
    }
}

void GUIButton::SetNormalImage()
{
    SetTextureImage(m_sNormalTextureFile);
}

void GUIButton::SetHoverOnImage()
{
    if (m_bHasHoverdOnImage)
    {
        SetTextureImage(m_sHoverOnTextureFile);
    }
}

 

Résumé :

Voici une classe qui permet d’afficher un bouton cliquable.

Une classe GUIImage pour afficher une image

Intro :

4NgHv

Parfois nous avons besoin d’afficher une image pour rendre l’interface utilisateur (GUI) plus jolie.

Prérequis :

– Avoir lu la classe GUIWidget

Contenu :

Voici le fichier GUIImage.h :

#ifndef GUI_IMAGE_H
#define GUI_IMAGE_H

#include "GUIWidget.h"

class GUIImage : public GUIWidget
{
public:
    GUIImage(const std::string& sName, const std::string& sTextureFileName);
    virtual ~GUIImage();

    virtual void SetTextureImage(const std::string& sTextureName);

    virtual void Update(float fTimeSinceLastFrame);

    virtual void SetPosition(unsigned int x, unsigned int y);

    virtual void SetSize(unsigned int iWidth, unsigned int iHeight);

    virtual void SetVisible(bool bVisible);

    Sprite2D* GetImage();

private:
    Sprite2D* m_pImage;
};

#endif

 

Voici le fichier GUIImage.cpp :

#include "GUIImage.h"
#include "Sprite2D.h"
#include "Defines.h"

GUIImage::GUIImage(const std::string& sName, const std::string& sTextureFileName) :
GUIWidget(sName),
m_pImage(nullptr)
{
    m_pImage = new Sprite2D(sTextureFileName);
    m_pImage->Initialize();

    unsigned int iImageWidth = m_pImage->GetWidth();
    unsigned int iImageHeight = m_pImage->GetHeight();

    SetSize(iImageWidth, iImageHeight);
}

GUIImage::~GUIImage()
{
    SAFE_DELETE(m_pImage);
}

void GUIImage::Update(float fTimeSinceLastFrame)
{
    if (IsVisible())
    {
        m_pImage->Render();
    }

    GUIWidget::Update(fTimeSinceLastFrame);
}

void GUIImage::SetTextureImage(const std::string& sTextureName)
{
    m_pImage->SetTextureImage(sTextureName);

    // On restaure les dimensions d'avant
    unsigned int iWidth = 0;
    unsigned int iHeight = 0;

    GetSize(iWidth, iHeight);

    m_pImage->SetImageSize(iWidth, iHeight);
}

void GUIImage::SetPosition(unsigned int x, unsigned int y)
{
    m_pImage->SetPosition(x, y);

    GUIWidget::SetPosition(x, y);
}

void GUIImage::SetSize(unsigned int iWidth, unsigned int iHeight)
{
    m_pImage->SetImageSize(iWidth, iHeight);

    GUIWidget::SetSize(iWidth, iHeight);
}

void GUIImage::SetVisible(bool bVisible)
{
    if (bVisible)
    {
        m_pImage->Show();
    }
    else
    {
        m_pImage->Hide();
    }

    GUIWidget::SetVisible(bVisible);
}

Sprite2D* GUIImage::GetImage()
{
    return m_pImage;
}

Résumé :

Une classe GUIWidget pour gérer les objets graphiques

Morrowind_UI

Intro :

Dans un jeu vidéo, nous avons besoin de créer des interfaces graphiques afin que le joueur puisse communiquer ou sélectionner des items, cliquer sur des boutons, déplacer une armure, etc…

Prérequis :

– Savoir initialiser DirectX 10

– Savoir lire du C++

– Connaître la classe Sprite2D

Contenu :

Voici le fichier GUIWidget.h  :

#ifndef GUI_WIDGET_H
#define GUI_WIDGET_H

#include <iostream>
#include <vector>

#include <d3dx10math.h>
#include "DataParameters.h"

class Sprite2D;

typedef DataParameters GUIParameters; 

struct AABB
{
    unsigned int x;
    unsigned int y;
    unsigned int w;
    unsigned int h;
};

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

    virtual void OnGUIEvent(GUIParameters& param) = 0;
};

class GUIWidget
{
public:
    GUIWidget(const std::string& sName);
    virtual ~GUIWidget();

    virtual void Update(float fTimeSinceLastFrame);

    D3DXVECTOR2 GetPosition();
    virtual void SetPosition(unsigned int x, unsigned int  y);
    void SetRelativePosition(unsigned int  x, unsigned int  y);

    bool IsCollidingWithMousePointer(unsigned int x, unsigned int y);

    void SetVisible(bool bVisible);
    bool IsVisible();

    void AddEventListener(GUIEventListener* pEventListener);

    virtual void SetSize(unsigned int iWidth, unsigned int iHeight);
    void GetSize(unsigned int& iWidth, unsigned int& iHeight);

    std::string GetName();

    void AddChild(GUIWidget* pChild);
    void SetParent(GUIWidget* pParent);

    bool HasParent();

    void SetCanBeOverlaped(bool bCanBeOverlaped);
    bool CanOverlap();

    void GetChildren(std::map<std::string, GUIWidget*>& children);
    GUIWidget* GetParent();

    bool HasChildren();

    virtual void SetActive(bool bActive);
    bool IsActive();

    bool IsChildrenLocked();
    void SetChildrenLocked(bool bActive);

protected:
    std::vector<GUIEventListener*> m_eventListeners;

    void SendEventToListeners(GUIParameters& param);

private:
    AABB m_aabox;

    bool m_bVisible;

    unsigned int m_iPosX;
    unsigned int m_iPosY;

    std::string m_sName;

    GUIWidget* m_pParent;

    std::map<std::string, GUIWidget*> m_children;

    bool m_bCanBeOverlaped;
    bool m_bActive;
    bool m_bIsChildrenLocked;
};

#endif

 

Voici le fichier GUIWidget.cpp :

#include "GUIWidget.h"

#include "Sprite2D.h"
#include "Defines.h"

#include "GUIManager.h"

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

GUIWidget::GUIWidget(const std::string& sName) :
m_iPosX(0),
m_iPosY(0),
m_sName(sName),
m_pParent(nullptr),
m_bCanBeOverlaped(true),
m_bVisible(true),
m_bActive(true),
m_bIsChildrenLocked(false)
{
    ZeroMemory(&m_aabox, sizeof(AABB));
}

GUIWidget::~GUIWidget()
{
}

void GUIWidget::Update(float fTimeSinceLastFrame)
{
}

void GUIWidget::SetCanBeOverlaped(bool bCanBeOverlaped)
{
    m_bCanBeOverlaped = bCanBeOverlaped;

    if (HasChildren())
    {
        for (auto it = m_children.begin(); it != m_children.end(); it++)
        {
            GUIWidget* pChild = it->second;

            pChild->SetCanBeOverlaped(bCanBeOverlaped);
        }
    }
}

bool GUIWidget::CanOverlap()
{
    return m_bCanBeOverlaped;
}

std::string GUIWidget::GetName()
{
    return m_sName;
}

D3DXVECTOR2 GUIWidget::GetPosition()
{
    return D3DXVECTOR2(m_iPosX, m_iPosY);
}

void GUIWidget::SetPosition(unsigned int x, unsigned int  y)
{
    m_iPosX = x;
    m_iPosY = y;

    m_aabox.x = x;
    m_aabox.y = y;
}

bool GUIWidget::IsCollidingWithMousePointer(unsigned int x, unsigned int  y)
{
    return (x >= m_aabox.x) && (x < (m_aabox.x + m_aabox.w))
        && (y >= m_aabox.y) && (y < (m_aabox.y + m_aabox.h));
}

void GUIWidget::SetVisible(bool bVisible)
{
    m_bVisible = bVisible;

    if (HasChildren())
    {
        for (auto it = m_children.begin(); it != m_children.end(); it++)
        {
            GUIWidget* pChild = it->second;

            pChild->SetVisible(bVisible);

            if (!bVisible)
            {
                SetActive(false);
            }
        }
    }
}

bool GUIWidget::IsVisible()
{
    return m_bVisible;
}

void GUIWidget::AddEventListener(GUIEventListener* pEventListener)
{
    m_eventListeners.push_back(pEventListener);
}

void GUIWidget::SendEventToListeners(GUIParameters& param)
{
    for (unsigned int i = 0; i < m_eventListeners.size(); i++)
    {
        GUIEventListener* pEventListener = m_eventListeners[i];
        pEventListener->OnGUIEvent(param);
    }
}

void GUIWidget::SetSize(unsigned int iWidth, unsigned int iHeight)
{
    m_aabox.w = iWidth;
    m_aabox.h = iHeight;
}

void GUIWidget::GetSize(unsigned int& iWidth, unsigned int& iHeight)
{
    iWidth = m_aabox.w;
    iHeight = m_aabox.h;
}

void GUIWidget::AddChild(GUIWidget* pChild)
{
    pChild->SetParent(this);

    m_children[pChild->GetName()] = pChild;
}

void GUIWidget::SetParent(GUIWidget* pParent)
{
    m_pParent = pParent;
}

void GUIWidget::GetChildren(std::map<std::string, GUIWidget*>& children)
{
    children = m_children;
}

GUIWidget* GUIWidget::GetParent()
{
    return m_pParent;
}

bool GUIWidget::HasParent()
{
    return m_pParent != nullptr;
}

void GUIWidget::SetRelativePosition(unsigned int  x, unsigned int  y)
{
    if (HasParent())
    {
        GUIWidget* pParent = GetParent();

        D3DXVECTOR2 pos = pParent->GetPosition();

        SetPosition(pos.x + x, pos.y + y);
    }
}

bool GUIWidget::HasChildren()
{
    return m_children.size() > 0;
}

void GUIWidget::SetActive(bool bActive)
{
    if (HasChildren())
    {
        for (auto it = m_children.begin(); it != m_children.end(); it++)
        {
            GUIWidget* pChild = it->second;

            pChild->SetActive(bActive);
        }
    }

    m_bActive = bActive;
}

bool GUIWidget::IsActive()
{
    return m_bActive;
}

bool GUIWidget::IsChildrenLocked()
{
    return m_bIsChildrenLocked;
}

void GUIWidget::SetChildrenLocked(bool bActive)
{
    m_bIsChildrenLocked = bActive;
}

Résumé :

Nous avons présenter la première classe nommée GUIWidget qui servira de base à toutes les autres classes graphiques (GUI).

Une simple classe D3D10Renderer

Intro :

1211804249

Pour utiliser DirectX, il nous faut une classe D3D10Renderer afin d’initialiser le rendu graphique 3D.

Prérequis :

– Aucun

Explications :

Voici le code du fichier D3D10Renderer.h :

#ifndef D3D10_RENDERER_H
#define D3D10_RENDERER_H

#include <windows.h>
#include <d3dx10math.h>
#include <DxErr.h>

#include "Defines.h"
#include "Singleton.h"
#include "Types.h"

#include "InputManager.h"

#pragma comment(lib, "d3d10.lib")
#pragma comment(lib, "d3dx10.lib")
#pragma comment(lib, "dxerr.lib")

class D3D10Renderer : public Singleton<D3D10Renderer>
{
public:
    D3D10Renderer(HWND hWnd);
    virtual ~D3D10Renderer();

    bool Initialize(bool bFullScreen);

    void Shutdown();

    void Render();

    ID3D10Device* GetDevice();

    uint32 GetViewportWidth();
    uint32 GetViewportHeight();
    
    void EnableZBuffer(bool bEnable);

    bool CreateSwapChain();
    bool CreateDepthStencilView();
    bool CreateRasterizerState();
    bool CreateDepthStencil();
    void CreateViewport();

    void UpdateWindowTitle();

    std::string Formater(const std::wstring& src);

    void SetFSAAMaximumLevel();

private:
    HWND m_hWnd;
    ID3D10Device* m_pd3dDevice;
    IDXGISwapChain* m_pSwapChain;        
    ID3D10RenderTargetView* m_pRenderTargetView;

    ID3D10Texture2D* m_pDepthStencil;
    ID3D10DepthStencilView* m_pDepthStencilView;
    ID3D10DepthStencilState* m_pDSState;
    ID3D10RasterizerState* m_pRasterizerState;

    uint32 m_iWidth;
    uint32 m_iHeight;

    bool m_bFullscreen;

    uint32 m_iFSAALevel;
};

#endif

 

Voici le code du fichier D3D10Renderer.cpp :

#include "D3D10Renderer.h"
#include "System.h"

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

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

D3D10Renderer::D3D10Renderer(HWND hWnd) :
m_hWnd(hWnd),
m_pd3dDevice(nullptr),
m_pSwapChain(nullptr),
m_pRenderTargetView(nullptr),
m_pDepthStencil(nullptr),
m_pDSState(nullptr),
m_pDepthStencilView(nullptr),
m_iWidth(0),
m_iHeight(0),
m_bFullscreen(false),
m_iFSAALevel(8)
{
}

D3D10Renderer::~D3D10Renderer()
{
    if (m_pd3dDevice)
    {
        m_pd3dDevice->ClearState();
    }

    SAFE_RELEASE(m_pRenderTargetView);
    SAFE_RELEASE(m_pSwapChain);
    SAFE_RELEASE(m_pd3dDevice);
}

bool D3D10Renderer::Initialize(bool bFullscreen)
{
    m_bFullscreen = bFullscreen;

    // Variable pour stocker l'état erreur / succès
    HRESULT hr = S_OK;;

    if (!CreateSwapChain())
    {
        return false;
    }

    // Créé le back buffer
    ID3D10Texture2D* pBackBuffer;
    hr = m_pSwapChain->GetBuffer(0, __uuidof(ID3D10Texture2D), (LPVOID*)&pBackBuffer);
    if (FAILED(hr))
    {
        return false;
    }

    // Créé la render target
    hr = m_pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, &m_pRenderTargetView);
    pBackBuffer->Release();
    if (FAILED(hr))
    {
        return false;
    }

    if (!CreateDepthStencilView())
    {
        return false;
    }

    if (!CreateRasterizerState())
    {
        return false;
    }

    if (!CreateDepthStencil())
    {
        return false;
    }

    CreateViewport();

    ShowCursor(false);

    return true;
}

void D3D10Renderer::Render()
{
    __int64 cntsPerSec = 0;
    QueryPerformanceFrequency((LARGE_INTEGER*)&cntsPerSec);

    __int64 currTimeStamp = 0;
    QueryPerformanceCounter((LARGE_INTEGER*)&currTimeStamp);

    static __int64 prevTimeStamp = currTimeStamp;

    float secsPerCnt = 1.0f / (float)cntsPerSec;   

    float fTimeSinceLastFrame = (currTimeStamp - prevTimeStamp) * secsPerCnt;

    static float afClearColor[4] = {0.85f, 1.0f, 1.0f, 1.0f}; 

    // Efface la surface de rendu
    m_pd3dDevice->ClearDepthStencilView(m_pDepthStencilView, D3D10_CLEAR_DEPTH, 1.0f, 0 );
    m_pd3dDevice->ClearRenderTargetView(m_pRenderTargetView, afClearColor);

    UpdateWindowTitle();

    m_pSwapChain->Present(0, 0);

    prevTimeStamp = currTimeStamp;
}

ID3D10Device* D3D10Renderer::GetDevice()
{
    return m_pd3dDevice;
}

uint32 D3D10Renderer::GetViewportWidth()
{
    DXGI_SWAP_CHAIN_DESC swapChainDesc;
    ZeroMemory(&swapChainDesc, sizeof(swapChainDesc));

    m_pSwapChain->GetDesc(&swapChainDesc);

    return swapChainDesc.BufferDesc.Width;
}

uint32 D3D10Renderer::GetViewportHeight()
{
    DXGI_SWAP_CHAIN_DESC swapChainDesc;
    ZeroMemory(&swapChainDesc, sizeof(swapChainDesc));

    m_pSwapChain->GetDesc(&swapChainDesc);

    return swapChainDesc.BufferDesc.Height;
}

void D3D10Renderer::EnableZBuffer(bool bEnable)
{
    D3D10_DEPTH_STENCIL_DESC pDesc;

    m_pDSState->GetDesc(&pDesc);
    pDesc.DepthEnable = bEnable;

    m_pd3dDevice->OMSetDepthStencilState(m_pDSState, 1);
}

void D3D10Renderer::CreateViewport()
{
    // Création du viewport
    D3D10_VIEWPORT vp;

    vp.Width = m_iWidth;
    vp.Height = m_iHeight;
    vp.MinDepth = 0.0f;
    vp.MaxDepth = 1.0f;
    vp.TopLeftX = 0;
    vp.TopLeftY = 0;

    m_pd3dDevice->RSSetViewports( 1, &vp );
}

bool D3D10Renderer::CreateSwapChain()
{
    // Rectangle 2D pour les dimensions de la fenêtre
    RECT rc;

    // On obtient les dimensions de la fenêtre courante
    GetClientRect(m_hWnd, &rc);
    // Largeur de la fenêtre
    m_iWidth = rc.right - rc.left;
    // Hauteur de la fenêtre
    m_iHeight = rc.bottom - rc.top;

    // Paramètres de création du device
    UINT createDeviceFlags = 0;

    // Permet d'afficher les éventuelles erreurs de la création du device
    createDeviceFlags |= D3D10_CREATE_DEVICE_DEBUG;

    // Création de la Swap Chain
    // c'est-à-dire création du front buffer et du back buffer
    DXGI_SWAP_CHAIN_DESC sd;
    ZeroMemory(&sd, sizeof(sd));
    sd.BufferCount = 1;
    // Taille de la surface en pixels
    sd.BufferDesc.Width = m_iWidth;
    sd.BufferDesc.Height = m_iHeight;
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    // Format des couleurs
    sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    sd.BufferDesc.RefreshRate.Numerator = 0;
    sd.BufferDesc.RefreshRate.Denominator = 1;
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    // L'handle de la fenêtre que l'on va afficher dessus
    sd.OutputWindow = m_hWnd;
    sd.SampleDesc.Count = m_iFSAALevel;
    sd.SampleDesc.Quality = 0;
    // Fenêtré ou non
    sd.Windowed = !m_bFullscreen;
    sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

    // On créé le device DirectX 10 et la Swap Chain
    HRESULT hr = D3D10CreateDeviceAndSwapChain(nullptr, D3D10_DRIVER_TYPE_HARDWARE, nullptr, createDeviceFlags,
                                               D3D10_SDK_VERSION, &sd, &m_pSwapChain, &m_pd3dDevice);
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    SetFSAAMaximumLevel();

    return true;
}

void D3D10Renderer::SetFSAAMaximumLevel()
{
    HRESULT hr = S_OK;
    UINT maxQualityLevel = 1;
    for (uint32 iSampleCount = 1; iSampleCount <= D3D10_MAX_MULTISAMPLE_SAMPLE_COUNT; iSampleCount++)
    {
        hr = m_pd3dDevice->CheckMultisampleQualityLevels(
            DXGI_FORMAT_R8G8B8A8_UNORM, iSampleCount, &maxQualityLevel);

        if (hr != S_OK)
        {
            fastprint(Formater(L"CheckMultisampleQualityLevels a échoué."));
        }
                                 
        if (maxQualityLevel > 0)
        {
            fastprint("MSAA " << iSampleCount << Formater(L"X supportée par la carte vidéo avec ") <<
                maxQualityLevel << Formater(L" niveau(x) de qualité."));
        }
    }
}

bool D3D10Renderer::CreateDepthStencilView()
{
    HRESULT hr = S_OK;

    D3D10_TEXTURE2D_DESC descDepth;
    ZeroMemory(&descDepth, sizeof(descDepth));
    descDepth.Width = m_iWidth;
    descDepth.Height = m_iHeight;
    descDepth.MipLevels = 1;
    descDepth.ArraySize = 1;
    descDepth.Format = DXGI_FORMAT_D32_FLOAT;
    descDepth.SampleDesc.Count = m_iFSAALevel;
    descDepth.SampleDesc.Quality = 0;
    descDepth.Usage = D3D10_USAGE_DEFAULT;
    descDepth.BindFlags = D3D10_BIND_DEPTH_STENCIL;
    descDepth.CPUAccessFlags = 0;
    descDepth.MiscFlags = 0;

    hr = m_pd3dDevice->CreateTexture2D(&descDepth, nullptr, &m_pDepthStencil);

    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }
        
    D3D10_DEPTH_STENCIL_VIEW_DESC descDSV;
    descDSV.Format = descDepth.Format;

    if (m_iFSAALevel > 1)
    {
        descDSV.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2DMS;
    }
    else
    {
        descDSV.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2D;
    }

    descDSV.Texture2D.MipSlice = 0;

    hr = m_pd3dDevice->CreateDepthStencilView(m_pDepthStencil, &descDSV, &m_pDepthStencilView);

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

    m_pd3dDevice->OMSetRenderTargets(1, &m_pRenderTargetView, m_pDepthStencilView);

    return true;
}

bool D3D10Renderer::CreateRasterizerState()
{
    D3D10_RASTERIZER_DESC rasterizerState;

    rasterizerState.CullMode = D3D10_CULL_NONE;
    rasterizerState.FillMode = D3D10_FILL_SOLID;
    rasterizerState.FrontCounterClockwise = true;
    rasterizerState.DepthBias = false;
    rasterizerState.DepthBiasClamp = 0;
    rasterizerState.SlopeScaledDepthBias = 0;
    rasterizerState.DepthClipEnable = true;
    rasterizerState.ScissorEnable = false;
    rasterizerState.MultisampleEnable = true;
    rasterizerState.AntialiasedLineEnable = true;

    HRESULT hr = m_pd3dDevice->CreateRasterizerState(&rasterizerState, &m_pRasterizerState);    

    m_pd3dDevice->RSSetState(m_pRasterizerState);

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

    return true;
}

bool D3D10Renderer::CreateDepthStencil()
{
    D3D10_DEPTH_STENCIL_DESC pDesc;

    pDesc.DepthEnable = true;
    pDesc.DepthWriteMask = D3D10_DEPTH_WRITE_MASK_ALL;
    pDesc.DepthFunc = D3D10_COMPARISON_LESS;
    pDesc.StencilEnable = true;
    pDesc.StencilReadMask = 0xFF;
    pDesc.StencilWriteMask = 0xFF;
    pDesc.FrontFace.StencilFailOp = D3D10_STENCIL_OP_KEEP;
    pDesc.FrontFace.StencilDepthFailOp = D3D10_STENCIL_OP_INCR;
    pDesc.FrontFace.StencilPassOp = D3D10_STENCIL_OP_KEEP;
    pDesc.FrontFace.StencilFunc = D3D10_COMPARISON_ALWAYS;
    pDesc.BackFace.StencilFailOp = D3D10_STENCIL_OP_KEEP;
    pDesc.BackFace.StencilDepthFailOp = D3D10_STENCIL_OP_DECR;
    pDesc.BackFace.StencilPassOp = D3D10_STENCIL_OP_KEEP;
    pDesc.BackFace.StencilFunc = D3D10_COMPARISON_ALWAYS;

    HRESULT hr = m_pd3dDevice->CreateDepthStencilState(&pDesc, &m_pDSState);

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

    m_pd3dDevice->OMSetDepthStencilState(m_pDSState, 1);

    return true;
}

FPSCamera* D3D10Renderer::GetCamera()
{
    return m_pCamera;
}

void D3D10Renderer::OnKeyPressed(const KeyEvent& arg)
{
    if (arg.keyCode == VK_ESCAPE)
    {
        SYSTEM->Quit();
    }
}

void D3D10Renderer::OnKeyReleased(const KeyEvent& arg)
{
}

void D3D10Renderer::UpdateWindowTitle()
{
    static char buffer[256];
    D3DXVECTOR3 camPos = D3DXVECTOR3(1.0f, 1.0f, 1.0f);

    sprintf(buffer, "Position de la caméra : X = %.2f Y = %.2f Z = %.2f", camPos.x, camPos.y, camPos.z);
 
    // On met à jour le titre de la fenêtre
    SetWindowTextA(m_hWnd, buffer);
}

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

 

Résumé :

Nous avons présenté les deux fichiers nécessaire à l’initialiser du rendu graphique de DirectX !

Le texture mapping

texturage

Intro :

Le texture mapping est une opération qui consiste à ajouter, avec une image, du détail de couleur à un modèle 3D.

Ce procédé est réalisé par l’étape Pixel Shader de la pipeline graphique du GPU.

Prérequis :

Explications :

texture

Résumé :

Références :

– https://takinginitiative.files.wordpress.com/2010/08/6.pdf

Les différents espaces de rendu graphique

transfor

Intro :

Avant d’effectuer un rendu en deux dimensions à l’écran (qui est une matrice de pixels), il est nécessaire d’effectuer certaines conversions de coordonnées entre des repères bien spécifiques.

DirectX a besoin de 3 matrices pour effectuer ce rendu.

Prérequis :

– Savoir ce qu’est une matrice

– Savoir ce qu’est un vecteur

– Savoir ce qu’est une transformation d’un système coordonnées dans un autre

Explications :

Tous les objets 3D sont composés d’un ensemble de triangles. Ces triangles sont composés de vertices. Ce qui fait que les objets 3D (appelés aussi meshes) sont représentés simplement une liste de vertices.

Model Space :

[image bonhomme avec axe model space]

Pour repérer correctement les vertices d’un modèle, nous avons besoin d’un repère ou d’un système de coordonnées.

Chaque modèle (ou mesh) a un système de coordonnée appelé en anglais « Space » .

L’artiste qui a créé ce modèle avec un logiciel de modélisation 3D (comme Maya, Blender ou 3D Studio Max)
a défini ses vertices dans cet espace (qui se nomme comme le Model Space).

World Space :

Notre scène a aussi un système de coordonnées (ou repère) qui se nomme le « World Space » et chaque modèle / objet a une position relative à ce dernier repère.

C’est dans cet espace que nous pouvons effectuer des translations, des rotations et des agrandissements.

View Space :

Nous avons besoin que la scène soit vue d’une certaine position dans celle-ci. Cette position s’appelle l’œil (the eye en anglais) ou la caméra.

Projection Space :

Nous avons besoin d’afficher le contenu de la scène en fonction de la position de la caméra à l’écran ou au framebuffer.

Résumé :

Références :

– https://fr.wikipedia.org/wiki/Rast%C3%A9risation

– https://en.wikipedia.org/wiki/Rasterisation

– https://takinginitiative.files.wordpress.com/2010/08/2.pdf

La technique du back-face culling

backface

Intro :

La technique du back-face culling permet d’ignorer le rendu des triangles qui ne sont pas visible par l’observateur ou la caméra.

Prérequis :

–  Savoir ce qu’est un produit scalaire

Explications :

Par exemple, les triangles de l’intérieur d’un modèle ou personnage ne seront pas rendu si l’on rentre et se positionne dedans !

Bacfaceculling

Dans DirectX, les vertices d’un triangle sont définis dans un certain ordre (il existe deux sens : le sens « counter clockwise » et le sens « closewise»). Cet ordre va définir dans quel sens est orienté la normale du triangle en question (voir l’article sur la normale).

clock_wise

Comment ça fonctionne ?

Soit le vecteur représentant la position de la caméra \vec{c} ; et le vecteur représentant la position du centre du polygone à tester \vec{p} .

On calcule donc le vecteur \vec{v} = \vec{p} - \vec{c}

On a aussi le vecteur normal à ce même polygone \vec{n}

Mathématiquement :

k = \vec{v} \cdot \vec{n}

 

Si le produit scalaire k est positif (en fait cela dépend du type de culling) alors cela signifie que le triangle est orienté dans le même sens que la vue (il nous tourne le dos), et il peut donc être éliminé du rendu.

Voilà c’est tout simple !

Il faut savoir aussi que cet algorithme est géré automatiquement par DirectX et la carte graphique.

On peut le configurer comme suit :

D3D10_RASTERIZER_DESC rasterizerState;

rasterizerState.CullMode = D3D10_CULL_NONE;
rasterizerState.FillMode = D3D10_FILL_SOLID;
rasterizerState.FrontCounterClockwise = true;

m_pd3dDevice->CreateRasterizerState(&rasterizerState, &m_pRasterizerState);    
m_pd3dDevice->RSSetState(m_pRasterizerState);

 

Résumé :

Pour conclure, le backface culling est l’élimination des faces qui « tournent le dos » à la caméra et que l’on ne voit donc pas.

Références :

– http://jeux.developpez.com/faq/3d/?page=culling

– http://stackoverflow.com/questions/3595087/opengl-newbie-question-what-is-back-face-culling

– http://www.gamedev.net/page/resources/_/technical/graphics-programming-and-theory/3d-backface-culling-r1088

– https://en.wikipedia.org/wiki/Back-face_culling

– https://fr.wikipedia.org/wiki/D%C3%A9termination_des_surfaces_cach%C3%A9es

Le Frustum de la caméra

frustum

Intro :

Appelé aussi « view frustum », ce volume décrit la façon dont la caméra effectue le rendu 2D.

En effet, pour effectuer du rendu 3D à l’écran, il faut définir une « camera » virtuelle qui pourra se mouvoir et se déplacer dans la scène.

Prérequis :

– Savoir initialiser DirectX 10.1

Explications :

camera_view_volume

Cette caméra spécifie quel volume, depuis l’observateur de la scène, elle peut observer.

L’espace compartimenté entre le « near clip plane » et le « far clip plane » définit le volume que la caméra peut voir. Cet espace s’appelle le « frustum » de la caméra, et comme on peut le voir c’est une sorte de pyramide tronquée.

Le clipping :

Les objets (ou les parties d’objets) qui ne sont pas à l’intérieur de ce volume doivent être ignorés avant d’être traités vers le rendu 2D. Ce procédé s’appelle le « clipping » (ou coupage en français).

Le « near clip plane » définit jusqu’à quelle distance les objets les plus proches de la caméra sont ignorés, en effet les objets situés entre le « near clip plane » et la caméra sont « coupés » (clipped).

Le « fear clip plane » définit jusqu’à quelle distance les objets les plus loins de la caméra sont ignorés, en effet les objets situés au delà du « far clip plane » sont « coupés » (clipped).

Les triangles situés en partie dans ce volume sont séparés en deux parties.

Les propriétés de ce volume « frustum » dépendent du FOV (Field of View) et des positions des near clip plane et fear clip plane.

La nature du frustum va modifier la façon dont le rendu sera effectué à l’écran 2D. C’est-à-dire depuis l’espace de la caméra 3D vers l’espace de projection 2D.

Composition :

Le frustum est composé de 6 plans formant une pyramide tronquée.

Pour savoir savoir si un point est dans le volume on utilise le produit scalaire sur ces 6 plans. Si les valeurs correspondent après avoir calculer les produits scalaire, le point est à l’intérieur du frustum.

Résumé :

Le frustum de la caméra représente le volume de vue que l’observateur peut voir dans la scène (graphe qui est formée de tous les modèles visibles).

Référence :

– https://msdn.microsoft.com/en-us/library/ff634570.aspx

– https://en.wikipedia.org/wiki/Viewing_frustum

– Shader Approach – Franck D.Luna

Kit basique d’initialisation DirectX 10.1

 

Kit

KIT_2

Intro :

Voici un kit formé d’un ensemble de fichiers qui pourra servir de base pour vos programmes graphiques ou essais 3D. Le kit comprend à peu près 4000 lignes de code.

Prérequis :

– Savoir lire du C++

Explications :

[rajouter : ]

[fullscreen switch]

[shadermanager]

Il comprend :

– le support de la transparence

– une caméra classique

– une caméra de type FPS (First Person Shooter) avec laquelle on peut voler partout

– une énumération de couleurs

– un gestionnaire D3D10Renderer quasi complet

– la possibilité de lancer l’application en plein-écran ou non

– la possibilité d’afficher du texte

– des gestionnaires en design-pattern Singleton (classe Singleton<class>)

– un ensemble de macros toutes utiles

– un gestionnaire InputManager pour gérer les entrées clavier et souris de l’utilisateur

– une classe SceneNode pour représenter vos entités sur la scène

– avec fonctions de translation, rotation et agrandissement

– un gestionnaire de fichier effet Shader avec gestion automatique des variables shader

– quelques définitions de shaders (ex : simple, lumière)

– une classe SkyDomeSceneNode (pour afficher un ciel avec une TextureCube)

– une classe SphereMeshSceneNode pour générer une sphère de façon procédurale

– une classe System

– une classe TextureManager pour charger les fichiers textures rapidement

– une liste complète de déclaration de vertex (avec les Vertex Input Layout)

– une classe Utils avec fonctions toutes très utiles

Résumé :

Voici l’archive contenant le kit et tous ses fichiers associés.

Références :

– stackoverflow.com

Utilisation du type TextureArray

TextureArray

Intro :

Souvent à l’étape pixel shader, on a besoin d’accéder à plusieurs textures en même temps.

Prérequis :

– Savoir lire du C++

– Savoir initialiser DirectX

Explications :

Voici une fonction qui créé un objet Texture2D :

ID3D10Texture2D* CreateTexture(const std::string& sImageFileName)
{
    ID3D10Texture2D* pLoadedTexture = nullptr;

    D3DX10_IMAGE_LOAD_INFO loadinfo;
    ZeroMemory(&loadinfo, sizeof(D3DX10_IMAGE_LOAD_INFO));

    loadinfo.Width = D3DX10_FROM_FILE;
    loadinfo.Height = D3DX10_FROM_FILE;
    loadinfo.Depth = D3DX10_FROM_FILE;

    loadinfo.FirstMipLevel = 0;
    loadinfo.MipLevels = D3DX10_FROM_FILE;
    loadinfo.Usage = D3D10_USAGE_STAGING;

    loadinfo.BindFlags = 0;
    loadinfo.CpuAccessFlags = D3D10_CPU_ACCESS_READ | D3D10_CPU_ACCESS_WRITE;
    loadinfo.MiscFlags = 0;

    loadinfo.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    loadinfo.Filter = D3DX10_FILTER_NONE;
    loadinfo.MipFilter = D3DX10_FILTER_NONE;
    loadinfo.pSrcInfo = 0;

    D3DX10CreateTextureFromFileA(D3D10_RENDERER->GetDevice(),
        sImageFileName.c_str(), &loadinfo, 0, (ID3D10Resource**)&pLoadedTexture, 0);

    return pLoadedTexture;
}


Voici une fonction qui créé un objet Texture2D assemblé et combiné de plusieurs Texture2D :


ID3D10Texture2D* CreateTextureArray(std::vector<ID3D10Texture2D*>& textures)
{
    if (textures.size() == 0)
    {
        return nullptr;
    }

    ID3D10Texture2D* pTexturesArray = nullptr;

    D3D10_TEXTURE2D_DESC textElementDesc;
    ZeroMemory(&textElementDesc, sizeof(D3D10_TEXTURE2D_DESC));

    textures[0]->GetDesc(&textElementDesc);

    D3D10_TEXTURE2D_DESC textArrayDesc;

    textArrayDesc.Width = textElementDesc.Width;
    textArrayDesc.Height = textElementDesc.Height;
    textArrayDesc.MipLevels = textElementDesc.MipLevels;
    textArrayDesc.ArraySize = textures.size();
    textArrayDesc.Format = textElementDesc.Format;
    textArrayDesc.SampleDesc.Count = 1;
    textArrayDesc.SampleDesc.Quality = 0;
    textArrayDesc.Usage = D3D10_USAGE_DEFAULT;
    textArrayDesc.BindFlags = D3D10_BIND_SHADER_RESOURCE;
    textArrayDesc.CPUAccessFlags = 0;
    textArrayDesc.MiscFlags = 0;

    D3D10_RENDERER->GetDevice()->CreateTexture2D(&textArrayDesc, 0, &pTexturesArray);

    // On copie toutes les textures dans le tableau !
    for (uint16 i = 0; i < textures.size(); i++)
    {
        for (uint16 j = 0; j < textElementDesc.MipLevels; j++)
        {
            D3D10_MAPPED_TEXTURE2D mappedTex2D;

            ID3D10Texture2D* pCurrentTexture = textures[i];

            pCurrentTexture->Map(j, D3D10_MAP_READ, 0, &mappedTex2D);

            uint32 c = D3D10CalcSubresource(j, i, textElementDesc.MipLevels);

            D3D10_RENDERER->GetDevice()->UpdateSubresource(pTexturesArray, c,
                0, mappedTex2D.pData, mappedTex2D.RowPitch, 0);

            pCurrentTexture->Unmap(j);
        }
    }

    return pTexturesArray;
}

 

Voici la fonction qui créée une texture ressource shader pour le programme HLSL :


ID3D10ShaderResourceView* FontTextManager::GetTextureRVArray(ID3D10Texture2D* pTexArray)
{
    if (pTexArray == nullptr)
    {
        return nullptr;
    }

    ID3D10ShaderResourceView* pArrayRV = nullptr;

    D3D10_SHADER_RESOURCE_VIEW_DESC viewDesc;
    ZeroMemory(&viewDesc, sizeof(D3D10_SHADER_RESOURCE_VIEW_DESC));

    D3D10_TEXTURE2D_DESC textArrayDesc;
    pTexArray->GetDesc(&textArrayDesc);

    viewDesc.Format = textArrayDesc.Format;
    viewDesc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE2DARRAY;
    viewDesc.Texture1DArray.MostDetailedMip = 0;
    viewDesc.Texture1DArray.MipLevels = textArrayDesc.MipLevels;
    viewDesc.Texture1DArray.FirstArraySlice = 0;
    viewDesc.Texture1DArray.ArraySize = textArrayDesc.ArraySize;

    D3D10_RENDERER->GetDevice()->CreateShaderResourceView(pTexArray, &viewDesc, &pArrayRV);

    return pArrayRV;
}

 

Utilisation : 

std::vector<ID3D10Texture2D*> textures;

textures.push_back( CreateTexture("UneImage.png") ) ;
textures.push_back( CreateTexture("UneAutreImage.png") ) ;
                                                   
ID3D10Texture2D* pTextureArray = CreateTextureArray(textures);

// On libère la mémoire
for (uint32 i = 0; i < textures.size(); i++)
{
    ID3D10Texture2D* pCurrentTexture = textures[i];
    SAFE_RELEASE(pCurrentTexture);
}

// Cette ressource est prête à être utilisée par un programme HLSL
ID3D10ShaderResourceView* pRVArray = GetTextureRVArray(pTextureArray);

SAFE_RELEASE(pTextureArray);

Résumé :

Nous avons présenté une façon d’accéder à une variable représentant un tableau de textures directement dans le shader !

Références :

– Franck D.Luna – DirectX 10

Générer une sphère MeshSceneNode

Sphere

Intro :

Il peut être utile de savoir générer une sphère à la volée par la définition de ses vertices et de ses indices.

Prérequis :

– Savoir lire du C++

– Savoir initialiser DirectX 10

– Savoir utiliser et comprendre un peu de trigonométrie

– Savoir utiliser la classe Renderable

– Savoir utiliser la classe MeshSceneNode

– Savoir utiliser la classe ShaderTechnique

Explications :

Voici l’implémentation de la classe shader :

//------------------------------------------------------
// Simple shader avec texturage
//------------------------------------------------------
class ShaderTechnique_BasicTexture : public ShaderTechnique
{
public:
    ShaderTechnique_BasicTexture(const std::string& sTextureFileName);
    virtual ~ShaderTechnique_BasicTexture();

    virtual bool Initialize();

    virtual void SetupShaderVariables();

    virtual void Update(float fTimeSinceLastFrame);

private:
    std::string m_sTextureFileName;
};

 

Voici le fichier ShaderTechnique_BasicTexture.cpp :

#include "ShaderTechnique_Declarations.h"

ShaderTechnique_BasicTexture::ShaderTechnique_BasicTexture(const std::string& sTextureFileName) :
ShaderTechnique("SimpleTextureEffect.fx", "Render", VertexLayoutType::PTN_VERTEX),
m_sTextureFileName(sTextureFileName)
{
}

ShaderTechnique_BasicTexture::~ShaderTechnique_BasicTexture()
{
}

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

    SetupShaderVariables();

    return true;
}

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

    RegisterTextureVariable("txDiffuse");

    AddTexture(m_sTextureFileName.c_str());

    SetTexture("txDiffuse", 0);
}

void ShaderTechnique_BasicTexture::Update(float fTimeSinceLastFrame)
{
    // On fait tourner la sphère / planète
    D3DXMATRIX mat;
    static float r = 0.0f;

    r += fTimeSinceLastFrame * 0.3f;

    D3DXMatrixRotationY(&mat, r);

    SetMatrix("World", &mat);

    SetAutoMatrix(ShaderVariableType::VIEW);
    SetAutoMatrix(ShaderVariableType::PROJECTION);
}

 

Voici le fichier SphereMeshSceneNode.h :

#ifndef SPHERE_MESH_SCENE_NODE_H
#define SPHERE_MESH_SCENE_NODE_H

#include "MeshSceneNode.h"
#include "Types.h"

class SphereMeshSceneNode : public MeshSceneNode
{
public:
    SphereMeshSceneNode();
    virtual ~SphereMeshSceneNode();

    void CreateSphere(uint16 iRings, uint16 iSectors, uint16 iRadius);

private:

};

#endif

 

Voici le fichier SphereMeshSceneNode.cpp :

#include <vector>

#include "SphereMeshSceneNode.h"
#include "Types.h"
#include "ShaderTechnique_Declarations.h"

SphereMeshSceneNode::SphereMeshSceneNode()
{
    // Chercher sur Google Image d'autres textures sphériques
    SetShaderTechnique( new ShaderTechnique_BasicTexture("earth.jpg") );
    CreateSphere(40, 40, 4);
}

SphereMeshSceneNode::~SphereMeshSceneNode()
{
}

// Voilà la méthode qui créer la sphère
void SphereMeshSceneNode::CreateSphere(uint16 iRings, uint16 iSectors, uint16 iRadius)
{
    std::vector<PTNVertex> vertices;
    std::vector<uint16> indices;
    
    float R = 1.f/(float)(iRings-1);
    float S = 1.f/(float)(iSectors-1);

    for (uint32 r = 0; r < iRings; r++)
    {
        for (uint32 s = 0; s < iSectors; s++)
        {
#define MATH_PI 3.141592653589793f
#define MATH_PI_2 MATH_PI / 2
            float const y = sin( -MATH_PI_2 + MATH_PI * r * R );
            float const x = cos(2*MATH_PI * s * S) * sin( MATH_PI * r * R );
            float const z = sin(2*MATH_PI * s * S) * sin( MATH_PI * r * R );

            PTNVertex v;

            v.position.x = x * iRadius;
            v.position.y = y * iRadius;
            v.position.z = z * iRadius;

            v.texture.x = 1 - s*S;
            v.texture.y = r*R;

            v.normal.x = -x;
            v.normal.y = -y;
            v.normal.z = -z;

            vertices.push_back(v);
        }
    }

    for (uint16 y = 0; y < iRings - 1; y++)
    {
        for (uint16 x = 0; x < iSectors - 1; x++)
        {
            indices.push_back( ((y + 0) * iSectors + x) );
            indices.push_back( ((y + 1) * iSectors + x) );
            indices.push_back( ((y + 1) * iSectors + x + 1) );
 
            indices.push_back( ((y + 1) * iSectors + x + 1) );
            indices.push_back( ((y + 0) * iSectors + x + 1) );
            indices.push_back( ((y + 0) * iSectors + x) );
        }
    }
    
    Build(vertices, indices);
}

 

Résumé :

Nous avons appris comment générer une sphère en 3D avec ses coordonnées de texture et normales.

Référénces :

– http://stackoverflow.com/questions/5988686/creating-a-3d-sphere-in-opengl-using-visual-c

Les processus légers (threads) avec std::thread (C++11)

Intro :

Les threads sont utilisés dans ce qu’on nomme  « la programmation concurrente », qui consiste en l’exécution des processus légers. Cela représente une séquence d’exécution du code d’un programme au sein d’un processus. Quelque part, les threads sont des processus légers qui sont exécutés « à l’intérieur » d’un processus.

Les threads d’un même processus partagent la même mémoire et leurs variables. La pile de chaque thread est cependant unique pour chacun d’entre-eux.

Un processus habituel possède un seul thread et ce dernier correspond tout simplement au processus lui-même.

Soit c’est le noyau du système d’exploitation qui s’occupe des threads, soit ils sont gérés dans l’espace utilisateur à travers un gestionnaire spécial !

Prérequis :

– Savoir lire du C++

Explications :

Exemple d’utilisation d’un std::thread :

#include <iostream>
#include <thread>

void f()
{
    std::cout << "Un thread C++11" << std::endl;
}

int main()
{
    std::thread t(f);

    t.join();

    return 0;
}

 

La méthode .join() permet d’assurer que le thread se termine avant de quitter le programme ou la portée du code. Cet appel est bloquant !

Un thread est dans l’état « joignable » s’il est est créé avec une tâche à réaliser.

Le type std::thread (C++11) a une méthode get_id() afin d’obtenir l’id du thread en question.

Il peut exister un problème de synchronisation entre deux threads ! On doit protéger l’accès à certaines ressources si deux threads distincts sont en train d’y accéder en écriture.

Un thread peut être aussi détaché :

#include <iostream>
#include <thread>

void f()
{
    std::cout<< "Un thread C++11" << std::endl;
}

int main()
{
    std::thread t(f);
    t.join();
    t.detach();

    return 0;
}

Qu’est-ce qu’un sémaphore ?

Il permet de synchroniser l’accès à une ressource partagée. Cette ressource peut aussi bien être un bout de code !

Les threads accèdent aux ressources par le biais de ce sémaphore.

Pendant que le thread exécute cette ressource partagée aucun autre thread ne peut y accéder. Dès qu’il a terminé son travail, il libère le sémaphore (qui est nommé aussi « moniteur »).


Qu’est-ce qu’un mutex ?

Le mot « mutex » provient de l’abréviation « Mutual Exclusion ». Il assure que les données partagées sont « mutuellement exclusives ». Il s’agit juste d’une variable qui peut être soit dans l’état « verrouillé » ou soit dans l’état « déverrouillé » !

Pour verrouiller un mutex on utilise la méthode lock().

Dès qu’un thread a fini de travailler avec une donnée / ressource partagée il doit libérer le mutex ! Il faut utiliser la méthode unlock().

On peut déclarer le mutex en même temps que la variable à protéger !

struct Data
{
    int var;
    std::mutex mutex;
};

 

Qu’est-ce qu’une condition ?

Une condition est nécessaire d’être utilisée si l’on veut qu’un thread patiente à l’événement d’un autre thread.

Lorsqu’un thread est en attente d’une condition, il reste bloqué jusqu’à ce que la condition soit réaliser par un autre thread.

Résumé :

Nous avons présenter l’utilisation des thread (processus léger) afin de répartir l’exécution des bouts de code d’un jeu-vidéo.

En effet, les programmes utilisant des threads sur des architectures de type multi-processeurs sont plus rapides que les programmes plus classiques !

Références :

– http://www.iro.umontreal.ca/~dift1169/cours/ift1169/communs/Cours/2P/C12_1169_2P.pdf

– https://fr.wikipedia.org/wiki/Thread_(informatique)

Transformations géométriques des objets par les matrices

1

Intro :

Les transformations géométriques opérées sur des objets 3D sont de 3 types : translation, rotation et mise à l’échelle (agrandissement). Grâce à ces trois transformations, on peut mettre un mouvement un ensemble de points (ou un modèle).

Mais de quoi s’agit-il ?

Une translation est un changement de position d’un point ou de plusieurs points.

Une rotation permet de faire pivoter un point (ou plusieurs points) autour d’un axe par un certain angle.

Une mise à l’échelle permet d’agrandir ou de diminuer la taille d’un objet.

Prérequis :

– Savoir ce qu’est un vecteur

– Savoir ce qu’est une matrice

Explications :

Il existe deux manières de représenter un système de coordonnées : de type gauche ou de type droit.

sys_coord

DirectX utilise un système de coordonnées de type droit tandis que OpenGL utilise un système de coordonnées de type gauche.

 


 

On peut se servir des matrices pour représenter une transformation d’un objet dans l’espace de coordonnées 3D.

L’intérêt de la représentation matricielle est principalement qu’on peut combiner à la suite n’importe quelle transformation en une et seule matrice de transformation finale.

Une transformation est une opération qui prend en entrée un vecteur et qui en sortie le converti autrement.

 

Transformation linéaire :

On précise que mathématiquement une transformation (en l’occurrence linéaire) représente :

Soit la fonction \tau(v) = \tau{(x , y , z)} = (x^{\prime}, y^{\prime}, z^{\prime})

respectant les propriétés suivantes :

\tau(u+v) = \tau(u) +\tau(v)    et   \tau(ku) = k\tau (u)

avec {u} et {v} deux vecteurs  à 3 composantes.

Ces transformations sont nommées « linéaires » car elles préservent l’addition de vecteurs et la multiplication par un scalaire.

Toutes les transformations linéaires peuvent être représentées par une matrice 3×3 (ou plus).

Pensez que les opérations de rotation ou d’agrandissement sont toutes des transformations linéaires respectant ce dernier principe.

Quant à la transformation de translation, elle n’est pas une transformation linéaire mais une transformation affine.
Une transformation affine est une transformation linéaire qui est suivie d’une translation, elle préserve la colinéarité des vecteurs (caractéristique des vecteurs qui ont la même direction). Ces dernières sont peuvent être représentées par une matrice 4×4.

On remarque que les matrices de rotation et d’agrandissement sont aussi, par quant à elles, des transformations affines !

Pour s’en servir, on multiplie un vecteur qui a besoin d’être transformé par une matrice 4×4 de translation. Ce dernier vecteur doit être en notation « homogeneous » (voir l’article sur les transformations de coordonnées) pour être multiplié par une matrice 4×4.

L’avantage de ces matrices affines, est qu’elles peuvent être combinées les une à la suite des autres en une seule matrice finale qui peut représenter la somme de ces transformations.

 


 

La translation :

[schéma / dessin]

Voici la matrice de transformation affine représentant une translation :

\textbf{T}(\textbf{t}) = \textbf{T}(t_{x}, t_{y},t_{z}) = \begin{pmatrix} 1 & 0 & 0 & t_{x} \\ 0 & 1 & 0 & t_{y} \\ 0 & 0 & 1 & t_{z} \\ 0 & 0 & 0 & 1 \end{pmatrix}

 

Le vecteur \textbf{t} représente la valeur de déplacement de la matrice.

Pour changer la position d’un vecteur p par cette matrice on fait :

Translation = \textbf{T}(\textbf{t})p

 

 

L’inverse de cette matrice (opération inverse) :

\textbf{T}^{-1}(\textbf{t}) = \textbf{T}^{-1}(t_{x}, t_{y},t_{z}) = \textbf{T}(-t_{x}, -t_{y},-t_{z}) = \begin{pmatrix} 1 & 0 & 0 & -t_{x} \\ 0 & 1 & 0 & -t_{y} \\ 0 & 0 & 1 & -t_{z} \\ 0 & 0 & 0 & 1 \end{pmatrix}

 

L’agrandissement :

Une matrice d’agrandissement permet de faire grossir ou de diminuer un modèle.

Voici la matrice de transformation affine représentant une mise à l’échelle :

\textbf{S}(\textbf{s}) = \textbf{S}(s_{x}, s_{y}, s_{z}) = \begin{pmatrix} s_{x} & 0 & 0 & 0 \\ 0 & s_{y} & 0 & 0 \\ 0 & 0 & s_{z} & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix}

 

Le vecteur \textbf{s} représente la grandeur d’agrandissement.

 

Pour agrandir ou minimiser un vecteur p par cette matrice on fait :

Agrandissement = \textbf{S}(\textbf{s})p

 

 

L’inverse de cette matrice (opération inverse) :

\textbf{S}^{-1}(\textbf{s}) = \textbf{S}(1/s_{x}, 1/s_{y}, 1/s_{z}) = \begin{pmatrix} 1/s_{x} & 0 & 0 & 0 \\ 0 & 1/s_{y} & 0 & 0 \\ 0 & 0 & 1/s_{z} & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix}

 

Si  s_{x} = s_{y} = s_{z} alors la matrice d’agrandissement est dite « uniforme ».

La rotation :

Cette transformation opère une rotation sur un vecteur avec un angle donné \theta et autour d’un axe donné i

La matrice de transformation affine sur l’axe des x :

\textbf{R}_{x}(\theta) = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos \theta & -\sin \theta & 0 \\ 0 & \sin \theta & \cos \theta & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix}

 

La matrice de transformation affine sur l’axe des y est :

\textbf{R}_{y}(\theta) = \begin{pmatrix} \cos \theta & 0 & \sin \theta & 0 \\ 0 & 1 & 0 & 0 \\ -\sin \theta & 0 & \cos \theta & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix}

 

La matrice de transformation affine sur l’axe des z est :

\textbf{R}_{z}(\theta) = \begin{pmatrix} \cos \theta & -\sin \theta & 0 & 0 \\ \sin \theta & \cos \theta & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix}

 

Pour faire pivoter un vecteur p par cette matrice on fait :

Rotation = R_{x/y/z}(\theta)p

 

L’inverse de cette matrice (opération inverse) :

\textbf{R}^{-1}(\theta) = \textbf{R}(-\theta)

 

Faire une rotation autour d’un point quelconque :

[image / schéma avec un bonhomme]

On remarque qu’une rotation autour d’un axe i d’un point p ne change en rien la position du point p sur l’axe i .

En utilisant ce principe, on déplace le point p de façon à ce qu’il coïncide autour de l’origine : la matrice de translation \textbf{T}(-p) le permet.
On opère ensuite la rotation sur l’axe i par la matrice de rotation \textbf{R}{_i}(\theta) .
Finalement le point p doit être remis à sa position initiale par la matrice de translation \textbf{T}(p) .

La transformation finale correspond à cette égalité :  \textbf{X} = \textbf{T}(p)\textbf{R}{_i}(\theta)\textbf{T}(-p)

 

Résumé :

Nous avons présenté les matrices de transformations affines qui permettent de mouvoir les modèles (ensemble de points / vecteurs d’un objet / modèle) à travers la translation, la rotation, et la mise à l’échelle.

Références :

– https://takinginitiative.files.wordpress.com/2010/08/2.pdf

– Shader Approach DirectX 9

– Real Time Rendering – Third edition

Récupérer le nombre de cœurs et d’autres informations de votre processeur

gulftowndie

Intro :

Il peut être utile de connaître le nombre de cœur de votre processeur afin d’optimiser vos applications utilisant des processus légers.

On peut obtenir aussi les information des caches associés au CPU.

Prérequis :

– Savoir lire du C++

Explications :

Voici le fichier d’en-tête CPUInfo.h :

#ifndef CPU_INFO_H
#define CPU_INFO_H

#include <Windows.h>
#include <iostream>

#include "Defines.h"
#include "Types.h"

typedef bool (WINAPI *LPFN_GLPI)(PSYSTEM_LOGICAL_PROCESSOR_INFORMATION, PDWORD);

class CPUInfo    
{
public:
    CPUInfo();
    virtual ~CPUInfo();
    
    bool Initialize();

    uint16 GetLogicalProcessorCount();
    uint16 GetPhysicalProcessorCount();

    uint16 GetSystemProcessorCount();

    uint16 GetL1CacheCount();
    uint16 GetL2CacheCount();
    uint16 GetL3CacheCount();

private:
    void ParseSystemProcessorCount();
    bool ParseProcessorData();
    uint32 CountBits(uint64 bitMask);

private:
    uint16 m_iPhysicalProcessorCount;
    uint16 m_iLogicalProcessorCount;
    uint16 m_iProcessorCoreCount;

    uint16 m_iProcessorL1CacheCount;
    uint16 m_iProcessorL2CacheCount;
    uint16 m_iProcessorL3CacheCount;

    size_t m_iProcessorL1CacheSize;
    size_t m_iProcessorL2CacheSize;
    size_t m_iProcessorL3CacheSize;
};

#endif

 

Voici le fichier source CPUInfo.cpp :

#include "CPUInfo.h"

CPUInfo::CPUInfo() :
m_iProcessorCoreCount(0),
m_iPhysicalProcessorCount(0),
m_iLogicalProcessorCount(0),
m_iProcessorL1CacheCount(0),
m_iProcessorL2CacheCount(0),
m_iProcessorL3CacheCount(0),
m_iProcessorL1CacheSize(0),
m_iProcessorL2CacheSize(0),
m_iProcessorL3CacheSize(0)
{
}

CPUInfo::~CPUInfo()
{
}

bool CPUInfo::Initialize()
{
    if (!ParseProcessorData())
    {
        return false;
    }

    ParseSystemProcessorCount();

    return true;
}

bool CPUInfo::ParseProcessorData()
{
    m_iLogicalProcessorCount = 0;
    m_iPhysicalProcessorCount = 0;

    LPFN_GLPI Glpi;

    Glpi = (LPFN_GLPI) GetProcAddress(GetModuleHandle(TEXT("kernel32")),
        "GetLogicalProcessorInformation");
    
    if (Glpi == nullptr)
    {
        /* La fonction qui permet d'obtenir les informations
           du CPU n'est pas supportée */
        return false;
    }

    bool bDone = false;

    PSYSTEM_LOGICAL_PROCESSOR_INFORMATION pBuffer = nullptr;
    DWORD iReturnLength = 0;

    while (!bDone)
    {
        bool bSucess = Glpi(pBuffer, &iReturnLength);

        if (!bSucess)
        {
            if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
            {
                if (pBuffer)
                {
                    free(pBuffer);
                }

                pBuffer = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION) malloc(iReturnLength);

                if (pBuffer == nullptr)
                {
                    // Echec de l'allocation
                    return false;
                }
            }
            else
            {
                // Erreur inconnu
                return false;
            }
        }
        else
        {
            bDone = true;
        }
    }

    DWORD iByteOffset = 0;
    PSYSTEM_LOGICAL_PROCESSOR_INFORMATION ptr = pBuffer;
    PCACHE_DESCRIPTOR pCache = nullptr;

    while (iByteOffset + sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION) <= iReturnLength)
    {
        switch (ptr->Relationship)
        {
            case RelationProcessorCore:
                {
                    // Nombre de processeurs physiques
                    m_iPhysicalProcessorCount++;

                    // Nombre de processeurs logiques (ceux utilisable par le système)
                    m_iLogicalProcessorCount += CountBits(ptr->ProcessorMask);

                    break;
                }
            case RelationCache:
                {
                    pCache = &ptr->Cache;

                    if (pCache->Level == 1)
                    {
                        m_iProcessorL1CacheCount++;

                        m_iProcessorL1CacheSize = pCache->Size;
                    }
                    else if (pCache->Level == 2)
                    {
                        m_iProcessorL2CacheCount++;

                        m_iProcessorL2CacheSize = pCache->Size;
                    }
                    else if (pCache->Level == 3)
                    {
                        m_iProcessorL3CacheCount++;

                        m_iProcessorL3CacheSize = pCache->Size;
                    }

                    break;
                }
        }

        iByteOffset += sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION);
        ptr++;
    }

    free (pBuffer);

    return true;
}

uint32 CPUInfo::CountBits(uint64 bitMask)
{
    DWORD c = 0;

    while (bitMask != 0)
    {
        c += (bitMask & 1L);
        bitMask >>= 1;
    }

    return c;
}

void CPUInfo::ParseSystemProcessorCount()
{
    SYSTEM_INFO si;
    GetSystemInfo(&si);

    m_iProcessorCoreCount = (uint16) si.dwNumberOfProcessors;
}

uint16 CPUInfo::GetSystemProcessorCount()
{
    return m_iProcessorCoreCount;
}

uint16 CPUInfo::GetPhysicalProcessorCount()
{
    return m_iPhysicalProcessorCount;
}

uint16 CPUInfo::GetLogicalProcessorCount()
{
    return m_iLogicalProcessorCount;
}

uint16 CPUInfo::GetL1CacheCount()
{
    return m_iProcessorL1CacheCount;
}

uint16 CPUInfo::GetL2CacheCount()
{
    return m_iProcessorL2CacheCount;
}

uint16 CPUInfo::GetL3CacheCount()
{
    return m_iProcessorL3CacheCount;
}

 

Résumé :

Nous avons présenter une méthode pour obtenir le nombre de cœur(s) de votre CPU.

Références :

– http://blogs.media-tips.com/bernard.opic/2007/09/03/compter-le-nombre-de-1-dans-la-forme-binaire-d-un-nombre/

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

Obtenir les informations de votre CG avec les interfaces DXGI*

gtx

Intro :

Il peut être utile d’obtenir le nom de la carte vidéo, sa mémoire vive maximum et tous ses modes d’affichage possibles pour un jeu vidéo.

Prérequis :

– Savoir initialiser DirectX 10

Explications :

Avant tout, voici deux petites macros affichant les erreurs tout en retournant false s’il y a en une !

#define ShowMessageBoxDXError(hr) char buf[2048]; \
       sprintf(buf, "Error : %s\nDescription : %s\n\nFile : %s\n\nLine : %d\n\nFunction : %s", DXGetErrorStringA(hr), DXGetErrorDescriptionA(hr), __FILE__, __LINE__, __FUNCTION__); \
       MessageBoxA(nullptr, buf, "Erreur", MB_ICONHAND | MB_OK);

#if defined(DEBUG) | defined(_DEBUG)
    #ifndef HR_FAILED_RETURN_FALSE
    #define HR_FAILED_RETURN_FALSE(x)                  \
    {                                                  \
        if (FAILED(x))                                \
        {                                              \
            ShowMessageBoxDXError(x)                  \
            return false;                              \
        }                                              \
    }
    #endif
 
#else
    #ifndef HR_FAILED_RETURN_FALSE
    #define HR_FAILED_RETURN_FALSE(x) x;
    #endif
#endif

 

Voici la fonction qui affiche les informations sur la carte graphique :


bool GetGPUInfo()
{
    HRESULT hr = S_OK;

    uint32 iNumModes;

    IDXGIFactory* pDXGIFactory = nullptr;
    IDXGIAdapter* pDXGIAdapter = nullptr;
    IDXGIOutput* pDXGIAdapterOutput = nullptr;

    DXGI_MODE_DESC* pDisplayModeList = nullptr;

    DXGI_ADAPTER_DESC adapterDesc;

    // Créée une interface métier "factory" DirectX
    hr = CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&pDXGIFactory);

    HR_FAILED_RETURN_FALSE(hr);

    // Utilise la "factory"
    hr = pDXGIFactory->EnumAdapters(0, &pDXGIAdapter);

    HR_FAILED_RETURN_FALSE(hr);

    // Enumère l'adpatateur principal de l'écran
    hr = pDXGIAdapter->EnumOutputs(0, &pDXGIAdapterOutput);

    HR_FAILED_RETURN_FALSE(hr);

    // Obtient le nombre de modes d'affichage qui corresppond à DXGI_FORMAT_R8G8B8A8_UNORM  
    hr = pDXGIAdapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &iNumModes, nullptr);

    HR_FAILED_RETURN_FALSE(hr);

    /* Créé une liste qui détient tous les modes d'affichage possibles pour cette carte graphique et
       le format d'affichage */
    pDisplayModeList = new DXGI_MODE_DESC[iNumModes];

    if (!pDisplayModeList)
    {
        return false;
    }

    // Remplie la précédente structure
    hr = pDXGIAdapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &iNumModes, pDisplayModeList);
    
    HR_FAILED_RETURN_FALSE(hr);

    /* On parcourt tous les modes d'affichages possibles
       et on trouve celui qui correspond à la taille spécifiée
       en paramètre ; puis on enregistre le numérateur et le dénominateur
       du taux de rafraichissement de l'écran
    */
    std::cout << "Modes d'affichage pour le format : DXGI_FORMAT_R8G8B8A8_UNORM" << std::endl << std::endl;

    for (uint32 i = 0; i < iNumModes; i++)
    {
        DXGI_MODE_DESC desc = pDisplayModeList[i];
        
        std::string sRefreshRate = std::to_string(desc.RefreshRate.Numerator / desc.RefreshRate.Denominator) + "Hz";

        std::cout << desc.Width << "x" << desc.Height << " " << sRefreshRate << std::endl;
    }

    std::cout << std::endl;

    hr = pDXGIAdapter->GetDesc(&adapterDesc);

    HR_FAILED_RETURN_FALSE(hr);

    // On enregistre la taille de la mémoire vidéo en mégabytes
    m_iVideoCardMemory = (adapterDesc.DedicatedVideoMemory / 1024 / 1024);

    size_t iStringLength = 0;
    // Convertie le nom la carte vidéo dans une chaîne de caractères
    int iError = wcstombs_s(&iStringLength, m_sVideoCardDescription, 128, adapterDesc.Description, 128);

    if (iError != 0)
    {
        return false;
    }

    std::cout << std::endl;

    std::cout << m_sVideoCardDescription << std::endl;

    std::cout << "Video memory = " << std::to_string(m_iVideoCardMemory) << " MB" << std::endl;

    std::cout << std::endl;

    SAFE_DELETE_ARRAY(pDisplayModeList);

    SAFE_RELEASE(pDXGIAdapterOutput);
    SAFE_RELEASE(pDXGIAdapter);
    SAFE_RELEASE(pDXGIFactory);

    return true;
}

 

Voici la fonction qui affiche les capacités de la carte graphique en matière d’anti-aliasing (déjà présentée dans les articles précédents) :


void PrintFSAAMaximumLevel()
{
    HRESULT hr = S_OK;
    UINT maxQualityLevel = 1;
    
    for (uint32 iSampleCount = 1; iSampleCount <= D3D10_MAX_MULTISAMPLE_SAMPLE_COUNT; iSampleCount++)
    {
        hr = m_pd3dDevice->CheckMultisampleQualityLevels(
            DXGI_FORMAT_R8G8B8A8_UNORM, iSampleCount, &maxQualityLevel);

        if (hr != S_OK)
        {
            std::cout << "CheckMultisampleQualityLevels a échoue." << std::endl;
        }
                                 
        if (maxQualityLevel > 0)
        {
            std::cout << "MSAA " << iSampleCount << "X supportee par la carte video avec " <<
                maxQualityLevel << " niveau(x) de qualite." << std::endl;
        }
    }
}

 

Voici la sortie de la console une fois affichés les modes d’affichage :

CG_specs

 

Résumé :

Nous avons appris comment obtenir les informations à partir de la carte graphique.

Introduction à DirectX 10 – Écrire du texte à l’écran (système de polices) – partie 7

directx9c

Hello

Utilisation de BMFont :

BMFont

 

Intro :

Pour afficher du texte dans une application DirectX il faut procéder comme suit.

Prérequis :

– Savoir initialiser DirectX 10

– Savoir comment afficher une image 2D à l’écran

Septième partie :

Afficher du texte à l’écran.

Explications :

Il vous faudra plusieurs images bitmap pour supporter les différentes résolutions de l’écran de l’utilisateur.

« Their source rect will form our texture coordinates, and their destination rect will form our vertexcoordinates. »

 

Voici le fichier FontTextManager.h :

#ifndef FONT_TEXT_MANAGER_H
#define FONT_TEXT_MANAGER_H

#include <d3dx10math.h>
#include <vector>
#include <map>
#include <string>
#include <fstream>

#include "Singleton.h"
#include "Defines.h"
#include "Types.h"

namespace FontSystem
{
    struct FontTextFileCharacter
    {
        FontTextFileCharacter() :
        iXPos(0),
        iYPos(0),
        iWidth(0),
        iHeight(0),
        iXOffset(0),
        iYOffset(0),
        iXAdvance(0),
        iPage(0)
        {
        }

        uint16 iXPos, iYPos;
        uint16 iWidth, iHeight;
        float iXOffset, iYOffset;
        float iXAdvance;
        uint16 iPage;
    };

    struct PTPVertex
    {
        D3DXVECTOR3 position;
        D3DXVECTOR2 texture;
        int page;
    };

    struct Font
    {
        std::string sFontTextName;
        uint16 iLineHeight;
        uint16 iBase;
        uint16 iWidth; // Taille en largeur de l'image de la police
        uint16 iHeight; // Taille en hauteur de l'image de la police
        uint16 iPagesCount;
        int16 iSize;

        // Un tableau de caractères constituants la police de font
        FontTextFileCharacter* aCharacters;
        // Un tableau contenant les fichiers shader ressources  (les pages !) en l'occurence les textures de la police de font
        ID3D10ShaderResourceView* texturesArray;
    };

    class Text
    {
    public:
        Text(const std::wstring m_sText, const D3DXCOLOR& color, float fPosX, float fPosY);
        virtual ~Text();
    
        void SetFont(Font* pFont);
        Font* GetFont();

        float GetPosX();
        float GetPosY();

        std::wstring GetText();
    
        void SetVertexBuffer(ID3D10Buffer* pBuffer);
        void SetIndexBuffer(ID3D10Buffer* pBuffer);

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

        D3DXCOLOR GetColor();

        size_t GetVertexCount();
        size_t GetIndexCount();

        void SetVertexCount(size_t iCount);
        void SetIndexCount(size_t iCount);

        void SetTransparency(float fAlpha);

    private:
        std::wstring m_sText;

        float m_fPosX;
        float m_fPosY;

        D3DXCOLOR m_color;

        Font* m_pFont;

        // Un vertex / index buffer par page
        ID3D10Buffer* m_pVertexBuffer;
        ID3D10Buffer* m_pIndexBuffer;

        size_t m_iVertexCount;
        size_t m_iIndexCount;
    };
}

using namespace FontSystem;

class FontTextManager : public Singleton<FontTextManager>
{
public : // todo : mettre des commentaires partout ; utiliser AlphaTransparencyPass
    FontTextManager();
    virtual ~FontTextManager();
    
    bool Initialize();

    void Render(Text* pText);

    bool LoadFontTextFile(const std::string& sFileName, const std::string& sFontTextName);
    bool IsFontTextLoaded(const std::string& sFontTextName);

    Text* CreateText(const std::wstring& sText, const std::string& sFontTextName, const float fPosX, const float fPosY,
        const D3DXCOLOR& color);

    float GetGlyphAspectRatio(const wchar_t* c, const std::string& sFontTextName);
    RECT GetGlyphTexCoords(const wchar_t* c, const std::string& sFontTextName);
    uint16 GetCharacterPage(const wchar_t* c, const std::string& sFontTextName);

private:
    void CreateVertexInputLayout();
    void SetupMatrix();
    bool BuildTextBuffers(Text* text);
    bool InitializeShader();

    ID3D10Texture2D* CreateTexture(const std::string& sBitmapFileName);
    ID3D10Texture2D* CreateTextureArray(std::vector<ID3D10Texture2D*>& textures);
    ID3D10ShaderResourceView* GetTextureRVArray(ID3D10Texture2D* pTexArray);

private:
    D3D10_INPUT_ELEMENT_DESC* m_pLayoutPT; // todo : enlever
    ID3D10InputLayout* m_pVertexLayout;

    std::map<std::string, Font*> m_fonts;

    ID3D10Effect* m_pEffect;
    ID3D10EffectTechnique* m_pTechnique;

    ID3D10EffectShaderResourceVariable* m_pBitmapVariable;

    ID3D10EffectMatrixVariable* m_pWorldVariable;
    ID3D10EffectMatrixVariable* m_pViewVariable;
    ID3D10EffectMatrixVariable* m_pProjVariable;
    ID3D10EffectVectorVariable* m_pColorVariable;

    D3DXMATRIX m_worldMatrix;
    D3DXMATRIX m_viewMatrix;
    D3DXMATRIX m_projMatrix;
};

#endif

 

Voici le fichier FontTextManager.cpp :

#include "FontTextManager.h"
#include "D3D10Renderer.h"
#include "AlphaPass.h"

Text::Text(const std::wstring sText, const D3DXCOLOR& color, float fPosX, float fPosY) :
m_sText(sText),
m_color(color),
m_fPosX(fPosX),
m_fPosY(fPosY),
m_pFont(nullptr),
m_pIndexBuffer(nullptr),
m_pVertexBuffer(nullptr),
m_iIndexCount(0),
m_iVertexCount(0)
{
}

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

void Text::SetFont(Font* pFont)
{
    m_pFont = pFont;
}

Font* Text::GetFont()
{
    return m_pFont;
}

std::wstring Text::GetText()
{
    return m_sText;
}

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

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

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

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

size_t Text::GetVertexCount()
{
    return m_iVertexCount;
}

size_t Text::GetIndexCount()
{
    return m_iIndexCount;
}

void Text::SetVertexCount(size_t iCount)
{
    m_iVertexCount = iCount;
}

void Text::SetIndexCount(size_t iCount)
{
    m_iIndexCount = iCount;
}

D3DXCOLOR Text::GetColor()
{
    return m_color;
}

float Text::GetPosX()
{
    return m_fPosX;
}

float Text::GetPosY()
{
    return m_fPosY;
}

void Text::SetTransparency(float fAlpha)
{
    m_color.a = fAlpha;
}

// ---------------------------------------------//

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

FontTextManager::FontTextManager() :
m_pLayoutPT(nullptr),
m_pBitmapVariable(nullptr),
m_pEffect(nullptr),
m_pTechnique(nullptr),
m_pVertexLayout(nullptr),
m_pWorldVariable(nullptr),
m_pViewVariable(nullptr),
m_pProjVariable(nullptr),
m_pColorVariable(nullptr)
{
}

FontTextManager::~FontTextManager()
{
    SAFE_DELETE(m_pLayoutPT);

    SAFE_RELEASE(m_pVertexLayout);
    SAFE_RELEASE(m_pEffect);
}

bool FontTextManager::Initialize()
{
    CreateVertexInputLayout();

    if (!InitializeShader())
    {
        return false;
    }

    SetupMatrix();

    return true;
}

bool FontTextManager::InitializeShader()
{
    HRESULT hr = S_OK;

    ID3D10Blob* pBlob = nullptr;
 
    hr = D3DX10CreateEffectFromFile(FONT_TEXT_FX_FILE_NAME, 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("FontTechnique");

    m_pWorldVariable = m_pEffect->GetVariableByName("World")->AsMatrix();
    m_pViewVariable = m_pEffect->GetVariableByName("View")->AsMatrix();
    m_pProjVariable = m_pEffect->GetVariableByName("Projection")->AsMatrix();
    m_pBitmapVariable = m_pEffect->GetVariableByName("gTextures")->AsShaderResource();
    m_pColorVariable = m_pEffect->GetVariableByName("BlendColor")->AsVector();

    D3D10_PASS_DESC PassDesc;
    m_pTechnique->GetPassByIndex(0)->GetDesc(&PassDesc);
    hr = D3D10_RENDERER->GetDevice()->CreateInputLayout(m_pLayoutPT, 3, PassDesc.pIAInputSignature,
                                         PassDesc.IAInputSignatureSize, &m_pVertexLayout);

    VR_RETURN(hr);

    return true;
}

void FontTextManager::SetupMatrix()
{
    D3DXMatrixIdentity(&m_worldMatrix);
 
    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);

    D3DXMatrixOrthoLH(&m_projMatrix, (float) D3D10_RENDERER->GetViewportWidth(), (float) D3D10_RENDERER->GetViewportHeight(),
        1.0f, 20.0f);
}

void FontTextManager::CreateVertexInputLayout()
{    
    m_pLayoutPT = new D3D10_INPUT_ELEMENT_DESC[3];

    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 },
        { "PAGE", 0, DXGI_FORMAT_R16_UINT, 0, 20, D3D10_INPUT_PER_VERTEX_DATA, 0 },
    };

    memcpy(m_pLayoutPT, layoutPT, sizeof(D3D10_INPUT_ELEMENT_DESC) * 3);
}

bool FontTextManager::IsFontTextLoaded(const std::string& sFontTextName)
{
    return m_fonts.count(sFontTextName) != 0;
}
/*
    http://www.angelcode.com/products/bmfont/doc/render_text.html
*/
bool FontTextManager::BuildTextBuffers(Text* pText)
{
    std::string sFontTextName = pText->GetFont()->sFontTextName;

    std::wstring sText = pText->GetText();
    float fPosX = pText->GetPosX();
    float fPosY = -pText->GetPosY();

    // Décalage en fonction de la taille de l'écran
    fPosX -= (float)((D3D10_RENDERER->GetViewportWidth() / 2 ));
    fPosY += (float)((D3D10_RENDERER->GetViewportHeight() / 2 ));

    // On vérifie si l'on a bien la police chargée en mémoire
    if (m_fonts.count(sFontTextName) == 0)
    {
        return false;
    }

    // On obtient la police spécifiée
    Font* pFont = m_fonts[sFontTextName];

    // Tableau de tous les caractères de la police de font
    FontTextFileCharacter* aCharacters = pFont->aCharacters;

    D3D10_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
    D3D10_SUBRESOURCE_DATA vertexData, indexData;

    HRESULT hr;

    std::vector<PTPVertex> vertices;

    for (uint32 i = 0; i < sText.length(); i++)
    {
        // Chaque lettre
        uint32 iLetter = (uint32)sText[i];

        FontTextFileCharacter character = pFont->aCharacters[iLetter];

        if (iLetter == SPACE_ASCII_CODE) // Espace !
        {
            fPosX += character.iXAdvance;

            continue;
        }

        if (iLetter == '\n')
        {
            fPosX = 0;
        }

        float x = 0;
        float y = 0;

        float tu = 0;
        float tv = 0;

        uint32 iCharX = character.iXPos; // Abscisse du caractère dans la texture
        uint32 iCharY = character.iYPos; // Ordonnée du caractère dans la texture
        uint32 iWidth = character.iWidth; // Largeur du caractère dans la texture
        uint32 iHeight = character.iHeight; // Hauteur du caractère dans la texture
        float iOffsetX = character.iXOffset;
        float iOffsetY = character.iYOffset;
        float iXAdvance = character.iXAdvance;
        uint16 iPage = character.iPage;

         //----------------- 1er triangle de la face du caractère -----------------//

        // ---- Bottom left ---- //
        iOffsetY = - iOffsetY - iHeight;
        //iOffsetY = (- iOffsetY - pFont->iLineHeight
        //iOffsetY -= pFont->iLineHeight;
        //iOffsetY -= pFont->iLineHeight;

        x = fPosX + iOffsetX;
        y = fPosY + iOffsetY;

        tu = (float) (iCharX) / pFont->iWidth;
        tv = (float) (iCharY + iHeight) / pFont->iHeight;

        PTPVertex vertex;

        vertex.position = D3DXVECTOR3(x, y, 0.0f);
        vertex.texture = D3DXVECTOR2(tu, tv);
        vertex.page = iPage;

        vertices.push_back(vertex);

        // ---- Top left ---- //

        x = fPosX + iOffsetX;
        y = fPosY + iOffsetY + iHeight;

        tu = (float) iCharX / pFont->iWidth;
        tv = (float) iCharY / pFont->iHeight;

        vertex.position = D3DXVECTOR3(x, y, 0.0f);  
        vertex.texture = D3DXVECTOR2(tu, tv);
        vertex.page = iPage;

        vertices.push_back(vertex);

        // ---- Top right ---- //

        x = fPosX + iOffsetX + iWidth;
        y = fPosY + iOffsetY + iHeight;

        tu = (float) (iCharX + iWidth) / pFont->iWidth;
        tv = (float) iCharY / pFont->iHeight;

        vertex.position = D3DXVECTOR3(x, y, 0.0f);  
        vertex.texture = D3DXVECTOR2(tu, tv);
        vertex.page = iPage;

        vertices.push_back(vertex);

        //----------------- 2ème triangle de la face du caractère -----------------//

        // ---- Top right ---- //

        x = fPosX + iOffsetX + iWidth;
        y = fPosY + iOffsetY + iHeight;

        tu = (float) (iCharX + iWidth) / pFont->iWidth;
        tv = (float) iCharY / pFont->iHeight;

        vertex.position = D3DXVECTOR3(x, y, 0.0f);  
        vertex.texture = D3DXVECTOR2(tu, tv);
        vertex.page = iPage;

        vertices.push_back(vertex);

        // ---- Bottom right ---- //

        x = fPosX + iOffsetX + iWidth;
        y = fPosY + iOffsetY;

        tu = (float) (iCharX + iWidth) / pFont->iWidth;
        tv = (float) (iCharY + iHeight) / pFont->iHeight;

        vertex.position = D3DXVECTOR3(x, y, 0.0f);  
        vertex.texture = D3DXVECTOR2(tu, tv);
        vertex.page = iPage;

        vertices.push_back(vertex);

        // ---- Bottom left ---- //

        x = fPosX + iOffsetX;
        y = fPosY + iOffsetY;

        tu = (float) (iCharX) / pFont->iWidth;
        tv = (float) (iCharY + iHeight) / pFont->iHeight;

        vertex.position = D3DXVECTOR3(x, y, 0.0f);  
        vertex.texture = D3DXVECTOR2(tu, tv);
        vertex.page = iPage;

        vertices.push_back(vertex);

        // On décale de quelques pixels vers la gauche après chaque lettre
        fPosX += iXAdvance;
    }

    pText->SetVertexCount(vertices.size());
    pText->SetIndexCount( pText->GetVertexCount() );

    std::vector<uint16> indices;
    for (uint16 i = 0; i < pText->GetIndexCount(); i++)
    {
        indices.push_back(i);
    }

    vertexBufferDesc.Usage = D3D10_USAGE_DYNAMIC;
    vertexBufferDesc.ByteWidth = sizeof(PTPVertex) * pText->GetVertexCount();
    vertexBufferDesc.BindFlags = D3D10_BIND_VERTEX_BUFFER;
    vertexBufferDesc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
    vertexBufferDesc.MiscFlags = 0;

    vertexData.pSysMem = vertices.data();

    ID3D10Buffer* pVB = nullptr;

    hr = D3D10_RENDERER->GetDevice()->CreateBuffer(&vertexBufferDesc, &vertexData, &pVB);
    VR_RETURN(hr);

    pText->SetVertexBuffer(pVB);
        
    indexBufferDesc.Usage = D3D10_USAGE_DEFAULT;
    indexBufferDesc.ByteWidth = sizeof(uint16) * pText->GetIndexCount();
    indexBufferDesc.BindFlags = D3D10_BIND_INDEX_BUFFER;
    indexBufferDesc.CPUAccessFlags = 0;
    indexBufferDesc.MiscFlags = 0;

    indexData.pSysMem = indices.data();

    ID3D10Buffer* pIB = nullptr;
    hr = D3D10_RENDERER->GetDevice()->CreateBuffer(&indexBufferDesc, &indexData, &pIB);
    VR_RETURN(hr);

    pText->SetIndexBuffer(pIB);

    return true;
}

void FontTextManager::Render(Text* pText)
{
    if (!pText)
    {
        return;
    } 

    uint32 iStride = sizeof(PTPVertex);
    uint32 iOffset = 0;

    D3D10_RENDERER->EnableZBuffer(false);

    m_pWorldVariable->SetMatrix((float*)&m_worldMatrix);
    m_pViewVariable->SetMatrix((float*)&m_viewMatrix);
    m_pProjVariable->SetMatrix((float*)&m_projMatrix);
    m_pColorVariable->SetFloatVector((float*)pText->GetColor());

    ID3D10Buffer* pVertexBuffer = pText->GetVertexBuffer();
    ID3D10Buffer* pIndexBuffer = pText->GetIndexBuffer();

    size_t iVertexCount = pText->GetVertexCount();
    size_t iIndexCount = pText->GetIndexCount();

    D3D10_RENDERER->GetDevice()->IASetVertexBuffers(0, 1, &pVertexBuffer, &iStride, &iOffset);
    D3D10_RENDERER->GetDevice()->IASetIndexBuffer(pIndexBuffer, DXGI_FORMAT_R16_UINT, 0);
    D3D10_RENDERER->GetDevice()->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    ID3D10ShaderResourceView* pRV = pText->GetFont()->texturesArray;

    if (pRV)
    {
        m_pBitmapVariable->SetResource(pRV);
    }

    D3D10_TECHNIQUE_DESC techniqueDesc;
    D3D10_RENDERER->GetDevice()->IASetInputLayout(m_pVertexLayout);

    m_pTechnique->GetDesc(&techniqueDesc);

    AlphaPass TransparencyPass;

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

        D3D10_RENDERER->GetDevice()->Draw(iVertexCount, 0);
    }

    D3D10_RENDERER->EnableZBuffer(true);
}

Text* FontTextManager::CreateText(const std::wstring& sText, const std::string& sFontTextName,
                                  const float fPosX, const float fPosY, const D3DXCOLOR& color)
{
    Text* pText = new Text(sText, color, fPosX, fPosY);

    if (!IsFontTextLoaded(sFontTextName)) // todo : add dummy text
    {
        PRINT_ERROR("Police de caractere non reconnu");

        return nullptr;
    }

    pText->SetFont(m_fonts[sFontTextName]);

    if (!BuildTextBuffers(pText))
    {
        PRINT_ERROR("Echec lors du la construction des buffers d'un texte speficie");

        return nullptr;
    }

    return pText;
}

/* Voir la documentation sur
    http://www.angelcode.com/products/bmfont/doc/file_format.html
*/
bool FontTextManager::LoadFontTextFile(const std::string& sFileName, const std::string& sFontTextName)
{
    std::vector<ID3D10Texture2D*> textures;

    std::string sLine;
    std::string sRead, sKey, sValue;

    std::ifstream file(sFileName);

    if (!file.is_open())
    {
        PRINT_ERROR("Impossible de charger un fichier font");

        return false;
    }

    std::string sBitmapFileName = "";

    FontTextFileCharacter* aCharacters = new FontTextFileCharacter[256];

    Font* pFont = new Font;
    pFont->sFontTextName = sFontTextName;
    pFont->aCharacters = aCharacters;

    while (!file.eof())
    {
        std::stringstream sLineStream;
        std::getline( file, sLine );
        sLineStream << sLine;

        // Lit le type de la ligne
        sLineStream >> sRead;

        if (sRead == "info")
        {
            while (!sLineStream.eof())
            {
                std::stringstream Converter;
                sLineStream >> sRead;

                uint32 i = sRead.find('=');
                sKey = sRead.substr(0, i);
                sValue = sRead.substr(i + 1);

                Converter << sValue;

                if (sKey == "size")
                {
                    Converter >> pFont->iSize;
                }
            }
        }
        else if (sRead == "common")
        {
            while (!sLineStream.eof())
            {
                std::stringstream Converter;
                sLineStream >> sRead;

                uint32 i = sRead.find('=');
                sKey = sRead.substr(0, i);
                sValue = sRead.substr(i + 1);

                Converter << sValue;

                if (sKey == "lineHeight")
                {
                    Converter >> pFont->iLineHeight;
                }
                else if (sKey == "base")
                {
                    Converter >> pFont->iBase;
                }
                else if (sKey == "scaleW")
                {
                    Converter >> pFont->iWidth;
                }
                else if (sKey == "scaleH")
                {
                    Converter >> pFont->iHeight;
                }
                else if (sKey == "pages")
                {
                    Converter >> pFont->iPagesCount;
                }
            }
        }
        else if (sRead == "page")
        {
            while (!sLineStream.eof())
            {
                std::stringstream Converter;
                sLineStream >> sRead;

                uint32 i = sRead.find('=');
                sKey = sRead.substr(0, i);
                sValue = sRead.substr(i + 1);

                Converter << sValue;
    
                if (sKey == "file")
                {
                    Converter >> sBitmapFileName;

                    // On supprime les guillemets entre le nom du fichier
                    sBitmapFileName.pop_back();    
                    sBitmapFileName.erase(0, 1);

                    fastprint("Chargement de " << sBitmapFileName << " pour " << pFont->sFontTextName);
                
                    ID3D10Texture2D* pTexture = CreateTexture(sBitmapFileName);

                    if (pTexture)
                    {
                        textures.push_back(pTexture);
                    }
                }

            }
        }
        else if (sRead == "char")
        {
            uint16 iCharID = 0;

            while (!sLineStream.eof())
            {
                std::stringstream Converter;
                sLineStream >> sRead;

                uint32 i = sRead.find('=');
                sKey = sRead.substr(0, i);
                sValue = sRead.substr(i + 1);
                Converter << sValue;

                if (sKey == "id")
                {
                    Converter >> iCharID;
                }
                else if (sKey == "x")
                {
                    Converter >> pFont->aCharacters[iCharID].iXPos;
                }
                else if (sKey == "y" )
                {
                    Converter >> pFont->aCharacters[iCharID].iYPos;
                }
                else if (sKey == "width")
                {
                    Converter >> pFont->aCharacters[iCharID].iWidth;
                }
                else if (sKey == "height")
                {
                    Converter >> pFont->aCharacters[iCharID].iHeight;
                }
                else if (sKey == "xoffset")
                {
                    Converter >> pFont->aCharacters[iCharID].iXOffset;
                }
                else if (sKey == "yoffset")
                {
                    Converter >> pFont->aCharacters[iCharID].iYOffset;
                }
                else if (sKey == "xadvance")
                {
                    Converter >> pFont->aCharacters[iCharID].iXAdvance;
                }
                else if (sKey == "page")
                {
                    Converter >> pFont->aCharacters[iCharID].iPage;
                }
            }
        }
    }
    
    m_fonts[sFontTextName] = pFont;

    debugprint(pFont->sFontTextName.c_str());
    debugprint(pFont->iBase);
    debugprint(pFont->iHeight);
    debugprint(pFont->iLineHeight);
    debugprint(pFont->iPagesCount);
    debugprint(pFont->iWidth);

    if (textures.size() == 0)
    {
        PRINT_ERROR("Aucune textures chargees");
    }

    fastprint("\n---------------------------\n");

    ID3D10Texture2D* pTextureArray = CreateTextureArray(textures);

    for (uint32 i = 0; i < textures.size(); i++)
    {
        ID3D10Texture2D* pCurrentTexture = textures[i];

        SAFE_RELEASE(pCurrentTexture);
    }

    ID3D10ShaderResourceView* pRVArray = GetTextureRVArray(pTextureArray);

    SAFE_RELEASE(pTextureArray);

    pFont->texturesArray = pRVArray;

    return true;
}

float FontTextManager::GetGlyphAspectRatio(const wchar_t* c, const std::string& sFontTextName)
{
    // On obtient la police spécifiée
    Font* pFont = m_fonts[sFontTextName];

    if (pFont)
    {
        return 0;
    }

    // Tableau de tous les caractères de la police de font
    FontTextFileCharacter* aCharacters = pFont->aCharacters;

    FontTextFileCharacter character = aCharacters[(uint32)c];

    uint32 iWidth = character.iWidth;
    uint32 iHeight = character.iHeight;

    return (float) (iWidth / iHeight);
}

RECT FontTextManager::GetGlyphTexCoords(const wchar_t* c, const std::string& sFontTextName)
{
    RECT rect;
    ZeroMemory(&rect, sizeof(RECT));

    // On obtient la police spécifiée
    Font* pFont = m_fonts[sFontTextName];

    if (!pFont)
    {
        return rect;
    }

    // Tableau de tous les caractères de la police de font
    FontTextFileCharacter* aCharacters = pFont->aCharacters;

    FontTextFileCharacter character = aCharacters[(uint32)c];

    uint32 iCharX = character.iXPos;
    uint32 iCharY = character.iYPos;
    uint32 iWidth = character.iWidth;
    uint32 iHeight = character.iHeight;

    rect.left = iCharX;
    rect.top = iCharY;
    rect.right = iWidth;
    rect.bottom = iHeight;

    return rect;
}

uint16 FontTextManager::GetCharacterPage(const wchar_t* c, const std::string& sFontTextName)
{
    // On obtient la police spécifiée
    Font* pFont = m_fonts[sFontTextName];

    if (!pFont)
    {
        return 0;
    }

    // Tableau de tous les caractères de la police de font
    FontTextFileCharacter* aCharacters = pFont->aCharacters;

    FontTextFileCharacter character = aCharacters[(uint32)c];

    return character.iPage;
}

ID3D10Texture2D* FontTextManager::CreateTexture(const std::string& sBitmapFileName)
{
    ID3D10Texture2D* pLoadedTexture = nullptr;

    D3DX10_IMAGE_LOAD_INFO loadinfo;
    ZeroMemory(&loadinfo, sizeof(D3DX10_IMAGE_LOAD_INFO));

    loadinfo.Width = D3DX10_FROM_FILE;
    loadinfo.Height = D3DX10_FROM_FILE;
    loadinfo.Depth = D3DX10_FROM_FILE;

    loadinfo.FirstMipLevel = 0;
    loadinfo.MipLevels = D3DX10_FROM_FILE;
    loadinfo.Usage = D3D10_USAGE_STAGING;

    loadinfo.BindFlags = 0;
    loadinfo.CpuAccessFlags = D3D10_CPU_ACCESS_READ | D3D10_CPU_ACCESS_WRITE;
    loadinfo.MiscFlags = 0;

    loadinfo.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    loadinfo.Filter = D3DX10_FILTER_NONE;
    loadinfo.MipFilter = D3DX10_FILTER_NONE;
    loadinfo.pSrcInfo = 0;

    D3DX10CreateTextureFromFileA(D3D10_RENDERER->GetDevice(),
        sBitmapFileName.c_str(), &loadinfo, 0, (ID3D10Resource**)&pLoadedTexture, 0);

    return pLoadedTexture;
}

ID3D10Texture2D* FontTextManager::CreateTextureArray(std::vector<ID3D10Texture2D*>& textures)
{
    if (textures.size() == 0)
    {
        return nullptr;
    }

    ID3D10Texture2D* pTexturesArray = nullptr;

    D3D10_TEXTURE2D_DESC textElementDesc;
    ZeroMemory(&textElementDesc, sizeof(D3D10_TEXTURE2D_DESC));

    textures[0]->GetDesc(&textElementDesc);

    D3D10_TEXTURE2D_DESC textArrayDesc;

    textArrayDesc.Width = textElementDesc.Width;
    textArrayDesc.Height = textElementDesc.Height;
    textArrayDesc.MipLevels = textElementDesc.MipLevels;
    textArrayDesc.ArraySize = textures.size();
    textArrayDesc.Format = textElementDesc.Format;
    textArrayDesc.SampleDesc.Count = 1;
    textArrayDesc.SampleDesc.Quality = 0;
    textArrayDesc.Usage = D3D10_USAGE_DEFAULT;
    textArrayDesc.BindFlags = D3D10_BIND_SHADER_RESOURCE;
    textArrayDesc.CPUAccessFlags = 0;
    textArrayDesc.MiscFlags = 0;

    D3D10_RENDERER->GetDevice()->CreateTexture2D(&textArrayDesc, 0, &pTexturesArray);

    // On copie toutes les textures dans le tableau !
    for (uint16 i = 0; i < textures.size(); i++)
    {
        for (uint16 j = 0; j < textElementDesc.MipLevels; j++)
        {
            D3D10_MAPPED_TEXTURE2D mappedTex2D;

            ID3D10Texture2D* pCurrentTexture = textures[i];

            pCurrentTexture->Map(j, D3D10_MAP_READ, 0, &mappedTex2D);

            uint32 c = D3D10CalcSubresource(j, i, textElementDesc.MipLevels);

            D3D10_RENDERER->GetDevice()->UpdateSubresource(pTexturesArray, c,
                0, mappedTex2D.pData, mappedTex2D.RowPitch, 0);

            pCurrentTexture->Unmap(j);
        }
    }

    return pTexturesArray;
}

ID3D10ShaderResourceView* FontTextManager::GetTextureRVArray(ID3D10Texture2D* pTexArray)
{
    if (pTexArray == nullptr)
    {
        return nullptr;
    }

    ID3D10ShaderResourceView* pArrayRV = nullptr;

    D3D10_SHADER_RESOURCE_VIEW_DESC viewDesc;
    ZeroMemory(&viewDesc, sizeof(D3D10_SHADER_RESOURCE_VIEW_DESC));

    D3D10_TEXTURE2D_DESC textArrayDesc;
    pTexArray->GetDesc(&textArrayDesc);

    viewDesc.Format = textArrayDesc.Format;
    viewDesc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE2DARRAY;
    viewDesc.Texture1DArray.MostDetailedMip = 0;
    viewDesc.Texture1DArray.MipLevels = textArrayDesc.MipLevels;
    viewDesc.Texture1DArray.FirstArraySlice = 0;
    viewDesc.Texture1DArray.ArraySize = textArrayDesc.ArraySize;

    D3D10_RENDERER->GetDevice()->CreateShaderResourceView(pTexArray, &viewDesc, &pArrayRV);

    return pArrayRV;
}

 

Résumé :

Nous avons présenter une méthode générique pour afficher du texte à l’écran ! Pas besoin d’utiliser D3DXFont !

Références :

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

– http://kvazars.com/littera/

– http://www.angelcode.com/products/bmfont/

– http://www.angelcode.com/products/bmfont/doc/file_format.html

– http://www.gamedev.net/topic/330742-quick-tutorial-variable-width-bitmap-fonts/

Apprendre à apprendre – méthode de travail

moreno-brain

Intro  :

La conception de jeu vidéo est un domaine vraiment dur à maitriser et à apprendre. Pour être compétent, il faut travailler régulièrement et sans relâche. Il faut donc être armé de méthodes pour y parvenir.

Prérequis : 

– Être motivé pour apprendre

Explications :

Voici quelques méthodes pour vous aider à progresser dans l’apprentissage de la conception de jeux vidéo ou celle du rendu 3D.

Méthodes :

1) Lors de la lecture d’un texte plus ou moins compliqué, il faut absolument noter tout les mots inconnus et prendre le temps de les définir avec un dictionnaire ou un lexique cherché par exemple sur un moteur de recherche.

2) Ne pas hésiter à essayer la méthode Coué pour suggestionner votre mental afin de faire émerger beaucoup d’idées dans votre esprit.

Répéter 15 fois 3 fois par jour : « Je suis très fort en programmation graphique et de jeux vidéo » à voix haute.

3) Ne faites qu’une chose à la fois ; apprenez qu’un seul thème à la fois. Par exemple « en ce moment j’apprend l’utilisation des quaternions ». Si vous n’avancez pas (ou si vous ne comprenez pas) un thème, passer au suivant.

4) Tenez à jour un mémo journalier où vous indiquez ce que vous apprenez petit à petit.
Il vous sera utile pour observer et identifier vos lacunes sur les sujets que vous n’avez pas assez travaillés ; à l’inverse, il vous sera aussi utile pour vous rappeler les sujets que vous maitrisez.

5) Travaillez par tranche de 1 heure 30 minimum sur un sujet, en effet mon expérience démontre qu’il n’est pas rentable de travailler moins d’une heure d’affilée. C’est pour cela que les cours à l’école durent plus de deux heures !

6) Comprendre avant d’apprendre. En effet il ne sert à rien d’apprendre par cœur des formules si vous ne savez pas à quoi elles servent.

7) Rejoignez un projet de jeu vidéo open source cherché sur le net. En effet le travail en équipe vous motivera et la communication avec les autres programmeurs enrichira vos connaissances.
C’est le même principe qu’avec un guitariste qui prendra peut-être plaisir à jouer seul, mais s’il jouait en groupe il progressera plus vite étant donné la motivation de travailler en groupe.

8) Faites des fiches en notant l’essentiel de chaque sujet où définissez vos mots inconnus.

9) Relisez vos fiches ou vos notes avant de vous coucher ou dans les transports en commun.

10) Ne lisez jamais du contenu que vous connaissez et maîtrisez déjà.

11) Lisez plus que vous n’écrivez !

12) Apprendre un langage de programmation est comme apprendre une langue étrangère : plus vous la pratiquez, mieux vous la comprenez.

13) Sélectionnez efficacement les documents / pages web que vous comptez lire, tout en évaluant la pertinence de ceux-ci.

En effet, le prix nobéliste de mathématiques C. Villani a dit : « […] maintenant, pour comprendre le monde, il ne s’agit plus d’avoir accès à l’information, mais bien de la trier ! »

14) Utilisez le principe de lecture « front to back and back to front ». Lisez d’abord de manière descendante et relisez de manière ascendante vos documents préférés.

15) Lisez de préférence du contenu qui vous semble facile. Lisez de préférence du contenu dont vous êtes susceptible de mieux retenir.

16) Je sais que c’est dur, mais essayer d’apprendre le plus rapidement possible !

Résumé :

Pour progresser dans l’étude de la programmation graphique, j’ai énuméré 4 idées :

Sur le net, il existe beaucoup de bonnes idées sur des blogs de développement personnel, n’hésitez pas à jeter un oeil !


Références : 

– https://fr.wikipedia.org/wiki/M%C3%A9thode_Cou%C3%A9

– Mon expérience

Introduction à DirectX 10 – Redimensionner la fenêtre de rendu – partie 6

directx9c

Intro :

Dans une application DirectX, l’utilisateur peut vouloir redimensionner la fenêtre à sa guise ou passer le jeu en mettre en mode plein écran.

Il faut implémenter et coder cette fonctionnalité.

Prérequis :

– Avoir suivi la cinquième partie de ce tutoriel.

Sixième partie :

Redimensionnement et plein écran de la fenêtre de rendu.

Explications :

Avant tout, juste pour énoncer un petit point  mis à part :

L’appel à D3D11DeviceContext::ClearState réinitialise par défaut toutes les configurations du contexte d’affichage (remet à zéro les shaders, les inputs layout, les viewports, etc..)

 


 

Avec les interfaces DXGI* on peut obtenir les informations sur la carte graphique :


bool D3D10Renderer::GetGPUInfo()
{
    HRESULT hr = S_OK;

    uint32 iNumModes;

    IDXGIFactory* pDXGIFactory = nullptr;
    IDXGIAdapter* pDXGIAdapter = nullptr;
    IDXGIOutput* pDXGIAdapterOutput = nullptr;

    DXGI_MODE_DESC* pDisplayModeList = nullptr;

    DXGI_ADAPTER_DESC adapterDesc;

    // Créée une interface métier "factory" DirectX
    hr = CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&pDXGIFactory);

    HR_FAILED_RETURN_FALSE(hr);

    // Utilise la "factory"
    hr = pDXGIFactory->EnumAdapters(0, &pDXGIAdapter);

    HR_FAILED_RETURN_FALSE(hr);

    // Enumère l'adpatateur principal de l'écran
    hr = pDXGIAdapter->EnumOutputs(0, &pDXGIAdapterOutput);

    HR_FAILED_RETURN_FALSE(hr);

    // Obtient le nombre de modes d'affichage qui corresppond à DXGI_FORMAT_R8G8B8A8_UNORM  
    hr = pDXGIAdapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &iNumModes, nullptr);

    HR_FAILED_RETURN_FALSE(hr);

    /* Créé une liste qui détient tous les modes d'affichage possibles pour cette carte graphique et
       le format d'affichage */
    pDisplayModeList = new DXGI_MODE_DESC[iNumModes];

    if (!pDisplayModeList)
    {
        return false;
    }

    // Remplie la précédente structure
    hr = pDXGIAdapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &iNumModes, pDisplayModeList);
    
    HR_FAILED_RETURN_FALSE(hr);

    /* On parcourt tous les modes d'affichages possibles
       et on trouve celui qui correspond à la taille spécifiée
       en paramètre ; puis on enregistre le numérateur et le dénominateur
       du taux de rafraichissement de l'écran
    */
    for (uint32 i = 0; i < iNumModes; i++)
    {
        if (pDisplayModeList[i].Width == (uint32)m_iWidth)
        {
            if (pDisplayModeList[i].Height == (uint32)m_iHeight)
            {
                m_iNumerator = pDisplayModeList[i].RefreshRate.Numerator;
                m_iDenominator = pDisplayModeList[i].RefreshRate.Denominator;
            }
        }

        DXGI_MODE_DESC desc = pDisplayModeList[i];

        // On vérifie que ce mode n'a pas déjà été ajouté
        bool bAlreadyExist = false;
        for (uint32 i = 0; i < m_modes.size(); i++)
        {
            if (m_modes[i].Width == desc.Width &&
                m_modes[i].Height == desc.Height)
            {
                bAlreadyExist = true;
                break;
            }
        }

        if (!bAlreadyExist)
        {
            m_modes.push_back(desc);
        }
    }

    hr = pDXGIAdapter->GetDesc(&adapterDesc);

    HR_FAILED_RETURN_FALSE(hr);

    // On enregistre la taille de la mémoire vidéo en mégabytes
    m_iVideoCardMemory = (adapterDesc.DedicatedVideoMemory / 1024 / 1024);

    size_t iStringLength = 0;
    // Convertie le nom la carte vidéo dans une chaîne de caractères
    int iError = wcstombs_s(&iStringLength, m_sVideoCardDescription, 128, adapterDesc.Description, 128);

    if (iError != 0)
    {
        return false;
    }

    // On supprime les objets dont a plus besoin !
    SAFE_DELETE_ARRAY(pDisplayModeList);

    SAFE_RELEASE(pDXGIAdapterOutput);
    SAFE_RELEASE(pDXGIAdapter);
    SAFE_RELEASE(pDXGIFactory);

    return true;
}

 

Voici le programme de cet article une fois compilé et lancé :

fullscreen

 

La fonction qui redimensionne les buffers :


bool D3D10Renderer::ResizeSwapChain(const size_t iWidth, const size_t iHeight)
{
    /* On décharge les précédents objets de rendu */
    SAFE_RELEASE(m_pRenderTargetView);
    SAFE_RELEASE(m_pDepthStencilView);

    HRESULT hr = S_OK;
    hr = m_pSwapChain->ResizeBuffers(2, iWidth, iHeight, DXGI_FORMAT_R8G8B8A8_UNORM, 0);

    HR_FAILED_RETURN_FALSE(hr);

    m_iWidth = iWidth;
    m_iHeight = iHeight;

    /* Puis on les recréer */
    CreateRenderTarget();
    CreateDepthStencilView();

    //m_pCube->SetLens()
    SetViewport();

    return true;
}

 

La fonction qui gère les entrées clavier :

void D3D10Renderer::OnKeyPressed(const KeyEvent& arg)
{
    if (arg.keyCode == VK_ESCAPE)
    {
        SYSTEM->Quit();
    }
    else if (arg.keyCode == VK_F1)
    {
        m_bFullscreen = !m_bFullscreen;

        if (m_bFullscreen)
        {
            ResizeSwapChain(m_iWidth, m_iHeight);
        }

        m_pSwapChain->SetFullscreenState(m_bFullscreen, nullptr);
    }
    else if (arg.keyCode == VK_F2)
    {
        static uint32 iMode = 0;

        // Largeur de la fenêtre
        uint32 iWidth = m_modes[iMode].Width;
        // Hauteur de la fenêtre
        uint32 iHeight = m_modes[iMode].Height;

        ResizeSwapChain(iWidth, iHeight);

        RECT rc;

        GetWindowRect(m_hWnd, &rc);

        SetWindowPos(m_hWnd, 0, rc.left, rc.top, iWidth, iHeight, SWP_SHOWWINDOW);

        iMode++;

        if (iMode >= m_modes.size())
        {
            iMode = 0;
        }
    }
}

 

Résumé :

Depuis DirectX 10 il est plus facile et aisé de changer de résolution et de passer en mode plein écran.

Voici les fichiers source – Partie 6

Références :

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

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

Introduction au moteur 3D Ogre3D – Afficher des modèles – partie 2

ogre3

Intro :

Voici la partie 2 du tutoriel sur l’introduction de Ogre3D.

Prérequis :

– Savoir lire du C++

– Avoir suivi la première partie de ce tutoriel.

Explications :

Avant tout quelques éléments de vocabulaire sur les objet qu’utilise Ogre3D :

SceneManager : gestionnaire qui gère l’organisation et le rendu d’une scène composée d’une collection de SceneNode. Tout ce qui est affiché à l’écran est géré par le SceneManager.

SceneNode : correspond à un objet que l’on relie au SceneManager. Possède les attributs de position, de rotation ou d’agrandissement. On peut relier plusieurs objets à une SceneNode de façon a ce que le SceneNode parent ait une position relative à son enfant.

Entity : représente un mesh (un modèle 3D composé de ses vertices et fait par un artiste) que l’on relie à une SceneNode.

1) Établissement d’une scène en affichant quelques modèles :

 

Résumé :

Références :

Introduction au moteur 3D Ogre3D – Installation – partie 1

ogre3

Intro :

Voici la partie 1 du tutoriel sur l’introduction de Ogre3D.

Ogre3D (Object-Oriented Graphics Rendering Engine) est un moteur 3D libre et multiplate-forme (compatible Linux, Win32, OS X, iOS, Android, Windows Phone 8)

Il peut utiliser les deux globales API de rendu 3D : OpenGL et DirectX 11 et 9

Prérequis :

– Savoir lire du C++

– Savoir configurer les dépendances (include / libs) dans Visual Studio C++ (voir ce lien)

– Avoir téléchargé et extrait le SDK de Ogre3D en fonction de la version de votre Visual Studio C++.

Explications :

Si vous n’avez pas le SDK (voir le lexique) il faut le télécharger sur le site de Ogre3D.

Installation de Ogre3D dans Visual Studio C++ :

Voici le répertoire du SDK une fois extrait :

sdk_rep

Il faut aller en suite dans les sous-répertoires pour voir où sont situées réellement les dépendances.

Configuration des fichiers d’en-têtes et bibliothèques :

1) Allez dans les propriétés de votre projet :

1

2) Allez dans la sous-partie « Répertoires VC++ » et ajoutez les chemins des fichiers de dépendances.

2

Il faut ne pas oublier d’ajouter les dépendances de boost.

Copiage des fichiers DLL :

Il faut copier toutes les DLLs requises depuis le repertoire bin\debug du SDK vers votre répertoire où est situé le fichier exécutable .exe (généralement : C:\Users\Yoann\Documents\Visual Studio 2010\Projects\Ogre3D – partie 1\Debug)

dll 

Configuration des fichiers d’entêtes précompilés :

Voir ce tutoriel

Copiage des fichiers .cfg :

Créez un fichier plugins_d.cfg dans le répertoire de votre exécutable :

# Defines plugins to load

# Define plugin folder
PluginFolder=.

# Define plugins
Plugin=RenderSystem_Direct3D9_d
Plugin=RenderSystem_Direct3D11_d
Plugin=RenderSystem_GL_d

 


Résumé :

L’installation du SDK de Ogre3D s’opère en plusieurs étapes qu’il faut suivre et respecter à la chaîne.

Références :

– http://bauland.developpez.com/tutoriel/ogre/install/

Lire un fichier ligne par ligne

file

Intro :

Parfois on a besoin de lire ligne par ligne un fichier, par exemple pour lire un fichier de modèle 3D ou un fichier de configuration.

Prerequis :

– Savoir lire du C++

Explications :

Voici le code pour obtenir le contenu d’un fichier :

#include <fstream>

std::ifstream inFile("MonFichier.txt");

if (inFile)
{
    std::string sLigne;

    // Tant qu'on n'est pas à la fin, on lit
    while (std::getline(inFile, sLigne))
    {
        std::cout << sLigne << std::endl;
    }
}
else
{
    std::cout << "Erreur de l'ouverture du fichier !" << std::endl;
}

 

Remarque : on utilise la classe ifstream (Input File) pour la lecture de fichier et ofstream (Output File stream) pour l’écriture de fichier.

Il faut noter que l’on peut lire sur le flux avec l’opérateur chevrons (>>) mot par mot. Et qu’on peut lire ligne par ligne avec la fonction std::getline().


Résumé :

Nous avons vu comment lire ligne par ligne un fichier par la fonction std::getline().

Références :

– Openclassroom.com

Introduction à DirectX 10 – Activer le super échantillonnage – partie 5

directx9c

Intro :

Dans une application graphique, les objets du rendu peuvent subir ce qu’on appelle un « crénelage » ou un « aliasing » en anglais.

Il apparaît comme un escalier autour de la ligne affichée.

La ligne du bas représente la correction de l’effet d’escalier de la ligne du haut.

crénélage

Prérequis :

– Savoir initialiser DirectX 10.1

Explications :

Cinquième partie :

Par défaut, DirectX 10.1 gère un type d’anticrénelage nommé Multisample anti-aliasing (MSAA)

1) L’anti-aliasing activé :

AA_on

1) L’anti-aliasing désactivé :

AA_off

 

Comment activer le multi-sampling dans le code ?

Lors de l’appel à D3D10CreateDeviceAndSwapChain il faut spécifier dans la structure DXGI_SWAP_CHAIN_DESC :

DXGI_SWAP_CHAIN_DESC sd;
ZeroMemory(&amp;sd, sizeof(sd));
sd.BufferCount = 1;
sd.BufferDesc.Width = m_iWidth;
sd.BufferDesc.Height = m_iHeight;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator = 0;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.OutputWindow = m_hWnd;

//----------------------------------//

/* On indique le nombre d'échantillons de l'anti-aliasing */
sd.SampleDesc.Count = m_iFSAASampleCount;
/* On indique le niveau de qualité de l'anti-aliasing */
sd.SampleDesc.Quality = 0;

//----------------------------------//

// Fenêtré ou non
sd.Windowed = !m_bFullscreen;
sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

 

warning_small

Il reste deux étapes à ne pas oublier d’effectuer :


1) Effectuer la même configuration que précédemment, lors la création de la texture du deph-stencil
:

Il faut spécifier la même configuration que précédemment, sinon cela ne fonctionnera pas !

D3D10_TEXTURE2D_DESC descDepth;
ZeroMemory(&amp;descDepth, sizeof(descDepth));
descDepth.Width = m_iWidth;
descDepth.Height = m_iHeight;
descDepth.MipLevels = 1;
descDepth.ArraySize = 1;
descDepth.Format = DXGI_FORMAT_D32_FLOAT;

//----------------------------------//

/* On indique le nombre d'échantillons de l'anti-aliasing */
descDepth.SampleDesc.Count = m_iFSAASampleCount;
/* On indique le niveau de qualité de l'anti-aliasing */
descDepth.SampleDesc.Quality = 0;

//----------------------------------//

descDepth.Usage = D3D10_USAGE_DEFAULT;
descDepth.BindFlags = D3D10_BIND_DEPTH_STENCIL;
descDepth.CPUAccessFlags = 0;
descDepth.MiscFlags = 0;

m_pd3dDevice-&gt;CreateTexture2D(&amp;descDepth, nullptr, &amp;m_pDepthStencil);


2) Effectuer une certaine configuration lors la création de la DepthStencilView
:

D3D10_DEPTH_STENCIL_VIEW_DESC descDSV;
descDSV.Format = descDepth.Format;

if (m_iFSAALevel &gt; 1)
{
     descDSV.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2DMS;
}
else
{
     descDSV.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2D;
}

descDSV.Texture2D.MipSlice = 0;

m_pd3dDevice-&gt;CreateDepthStencilView(m_pDepthStencil, &amp;descDSV, &amp;m_pDepthStencilView);

 


 

Voici une fonction qui permet d’afficher les capacités possibles de votre carte graphique en matière d’anti-aliasing.

Il faut obtenir le nombre d’échantillons m_iFSAASampleCount par ce bais :

void PrintFSAACapabilities()
{
    HRESULT hr = S_OK;
    UINT maxQualityLevel = 1;

    for (UINT iSampleCount = 1; iSampleCount &lt;= D3D10_MAX_MULTISAMPLE_SAMPLE_COUNT; iSampleCount++)
    {
        hr = m_pd3dDevice-&gt;CheckMultisampleQualityLevels(
            DXGI_FORMAT_R8G8B8A8_UNORM, iSampleCount, &amp;maxQualityLevel);

        if (hr != S_OK)
        {
            std::cout &lt;&lt; &quot;CheckMultisampleQualityLevels a échouee.&quot; &lt;&lt; std::endl;
        }
                                 
        if (maxQualityLevel &gt; 0)
        {
            std::cout &lt;&lt; &quot;MSAA &quot; &lt;&lt; iSampleCount &lt;&lt; (&quot;X supportee par la carte vidéo avec &quot;) &lt;&lt;
                maxQualityLevel &lt;&lt; &quot; niveau(x) de qualite.&quot; &lt;&lt; std::endl;
        }
    }
}

 

Résumé :

Nous avons présenté comment procéder pour afficher dans DirectX 10 les différents niveaux d’anticrénélages possibles par la carte vidéo et comment les activer.

Références :

– Windows DirectX Graphics Documentation (SDK June 2010)

Transformation d’un système de coordonnées dans un autre

small_22

Intro :

En mathématiques, on peut convertir un vecteur ou un point d’un système de coordonnées dans un autre.

Il existe deux façon de représenter un repère (ou système de coordonnées) en mathématiques.

En l’occurrence, DirectX utilise le système de coordonnées gauche. Voir l’article sur les transformations géométriques.

Prérequis :

– Savoir manipuler les vecteurs et les matrices

Explications :

Il faut noter que la notation \vec{v}={(x, y, z, w)} décrit un vecteur lorsque que w = 0 ou décrit un point lorsque w = 1.

Lorsqu’un vecteur a ces 4 composantes, comme décrit précédemment, on dit qu’il est en  « homogeneous coordinates ».

Soit un vecteur (ou un point) \vec{p}_{A}={(x, y, z, w)} d’un repère A et un autre vecteur (ou point) \vec{p}_{B}={(x^{\prime}, y^{\prime}, z^{\prime}, w)} d’un autre repère B.

Voici un schéma qui représente deux repères relatifs entre-eux et en dimension deux.

small

On obtient le vecteur \vec{p_{A}} correspondant dans le repère B (devenant le vecteur \vec{p_{B}} ) en faisant la multiplication matricielle suivante :

\vec{p_{A}}C = {[x, y, z, w]}\begin{bmatrix} {u_{x}} & {u_{y}} & {u_{z}} & 0 \\ {v_{x}} & {v_{y}} & {v_{z}} & 0 \\ {w_{x}} & {w_{y}} & {w_{z}} & 0 \\ {O_{x}} & {O_{y}} & {O_{z}} & 1 \end{bmatrix}={x\vec{u}} + {y\vec{v}} + {z\vec{w}} + {w\vec{O}}=\vec{p_{B}}

 

Les vecteurs \vec{O}   \vec{u}   \vec{v}   \vec{w} correspondent, respectivement aux positions : de l’origine, l’axe x, l’axe y, l’axe z du repère A relatifs au repère B.

Résumé :

Nous avons expliqué comment convertir un point d’un espace donné dans un autre repère en utilisant la multiplication matricielle de ce point.

Références :

– Introduction to DirectX 9.0c – A Shader Approach –  Franck D. Luna

Calculer le nombre d’images par seconde

Q3_HUD_gameplay

Intro : 

Dans les jeux ou les applications graphiques il peut être utile de savoir combien d’images par seconde sont affichées à l’écran pendant le rendu.

Prérequis :

– Savoir initialiser DirectX 10.1

Explications :

Voici le code pour calculer le temps de rafraichissement par seconde, cette méthode devra être appelée à chaque boucle de rendu.
Il est calculé aussi le temps que met, à peu près, le rendu pour afficher une image.


void CalculateFPS(float fTimeSinceLastFrame)
{
    /* Incrèmente le nombre d'images affichées.

       /!\ Doit être déclarée en tant que float et initialisée à 0 au début
       de l'application */
    m_fNumFrames += 1.0f;

   /* Ajoute combien de temps s'est passé depuis la dernière
     image de rendu

     /!\ Doit être déclarée en tant que float et initialisée à 0 au début
     de l'application */
    m_fTimeElapsed += fTimeSinceLastFrame;

    float iFPS = 0;
    float iMilliSecPerFrame = 0;

    // Lorsqu'une seconde s'est écoulée
    if (m_fTimeElapsed >= 1.0f)
    {
        iFPS = m_fNumFrames / m_fTimeElapsed;

        // Temps moyen, en milisecondes, pour qu'une seule image soit rendue
        iMilliSecPerFrame = 1000.0f / iFPS;

        /* Réinitialise le compteur d'images et le compteur de temps entre
           chaque image */
        m_fTimeElapsed = 0.0f;
        m_fNumFrames = 0.0f;

        /* On place les nombres dans une chaîne représentant les statistiques
           formatées */
        static char buffer[256];
        sprintf(buffer, "Images par seconde = %.2f | "
            "Millisecondes par image = %.4f", iFPS, iMilliSecPerFrame);

        // On met à jour le titre de la fenêtre
        SetWindowTextA(m_hWnd, buffer);
    }
}


Résumé :

Nous avons présenté comment calculer le FPS (Images par seconde) dans une application de rendu.

Références : 

Introduction to DirectX 9.0c – A shader approach – Franck D. Luna

Les principaux objets utilisés pour initialiser DirectX 11

objects

Intro :

Il est nécessaire de manipuler une bonne dizaine d’objets ou d’interfaces pour initialiser DirectX 11 avant de commencer tout affichage 3D.

Prérequis :

– Avoir déjà lu le code pour initialiser DirectX 10 ou 11

Explications :

Les-voici énumérés ci-dessous.

/* Objets nécessaires pour afficher du rendu 3D à l'écran */
IDXGISwapChain* m_pSwapChain = nullptr;
ID3D11Device* m_pDevice = nullptr;
ID3D11DeviceContext* m_pDeviceContext = nullptr;
ID3D11RenderTargetView* m_pRenderTargetView = nullptr;
ID3D11Texture2D* m_pDepthStencilBuffer = nullptr;
ID3D11DepthStencilState* m_pDepthStencilState = nullptr;
ID3D11DepthStencilView* m_pDepthStencilView = nullptr;
ID3D11RasterizerState* m_pRasterState = nullptr;

/* Objets pour effectuer des requêtes à la carte graphique */
IDXGIFactory* pDXGIFactory = nullptr;
IDXGIAdapter* pDXGIAdapter = nullptr;
IDXGIOutput* pDXGIAdapterOutput = nullptr;

ID3D11Texture2D* pBackBuffer = nullptr;

/* Objets de description de données pour les objets nécessaires au rendu */
DXGI_SWAP_CHAIN_DESC swapChainDesc;
D3D11_TEXTURE2D_DESC depthBufferDesc;
D3D11_DEPTH_STENCIL_DESC depthStencilDesc;
D3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc;
D3D11_RASTERIZER_DESC rasterDesc;
DXGI_ADAPTER_DESC adapterDesc;
DXGI_MODE_DESC DisplayModeList;
D3D11_VIEWPORT viewport;

 


 

Afficher du rendu 3D :

IDXGISwapChain : interface qui stocke une ou plusieurs surfaces antérieures de rendu avant d’effectuer l’affichage à l’écran.

Voici ses méthodes principales :

/* Accède à une surface de back buffer */
HRESULT GetBuffer(
  [in]       UINT Buffer,
  [in]       REFIID riid,
  [in, out]  void **ppSurface
);

/* Obtient les données descriptives de la swap chain */
HRESULT GetDesc(
  [out]  DXGI_SWAP_CHAIN_DESC *pDesc
);

/* Affiche une image à l'écran de l'utilisateur */
HRESULT Present(
  [in]  UINT SyncInterval,
  [in]  UINT Flags
);

 

ID3D11Device : interface qui représente un afficheur vidéo. Elle est utilisée soit pour afficher du rendu 3D soit pour créer des ressources.

Voici ses méthodes principales :

/* Créé un buffer quelconque qui peut être un vertex buffer,
   un index buffer ou un shader-constant buffer */
HRESULT CreateBuffer(
  [in]   const D3D11_BUFFER_DESC *pDesc,
  [in]   const D3D11_SUBRESOURCE_DATA *pInitialData,
  [out]  ID3D11Buffer **ppBuffer
);

/* Créé une "depth stencil view" pour accéder aux
   données des ressources */
HRESULT CreateDepthStencilView(
  [in]   ID3D11Resource *pResource,
  [in]   const D3D11_DEPTH_STENCIL_VIEW_DESC *pDesc,
  [out]  ID3D11DepthStencilView **ppDepthStencilView
);

/* Créé un objet "input-layout" qui servira à l'étape de rendu Input Assembler */
HRESULT CreateInputLayout(
  [in]   const D3D11_INPUT_ELEMENT_DESC *pInputElementDescs,
  [in]   UINT NumElements,
  [in]   const void *pShaderBytecodeWithInputSignature,
  [in]   SIZE_T BytecodeLength,
  [out]  ID3D11InputLayout **ppInputLayout
);

/* Créé un pixel shader */
HRESULT CreatePixelShader(
  [in]   const void *pShaderBytecode,
  [in]   SIZE_T BytecodeLength,
  [in]   ID3D11ClassLinkage *pClassLinkage,
  [out]  ID3D11PixelShader **ppPixelShader
);

/* Créé un vertex shader */
HRESULT CreateVertexShader(
  [in]   const void *pShaderBytecode,
  [in]   SIZE_T BytecodeLength,
  [in]   ID3D11ClassLinkage *pClassLinkage,
  [out]  ID3D11VertexShader **ppVertexShader
);

/* Créé les données descriptives sur comment doit réagir l'étape
   de rendu Razterizer */
HRESULT CreateRasterizerState(
  [in]   const D3D11_RASTERIZER_DESC *pRasterizerDesc,
  [out]  ID3D11RasterizerState **ppRasterizerState
);

/* Créé une render-target */
HRESULT CreateRenderTargetView(
  [in]   ID3D11Resource *pResource,
  [in]   const D3D11_RENDER_TARGET_VIEW_DESC *pDesc,
  [out]  ID3D11RenderTargetView **ppRTView
);

/* Créé un état depth-stencil  */
HRESULT CreateDepthStencilState(
  [in]   const D3D11_DEPTH_STENCIL_DESC *pDepthStencilDesc,
  [out]  ID3D11DepthStencilState **ppDepthStencilState
);

/* Créé une ressource shader */
HRESULT CreateShaderResourceView(
  [in]   ID3D11Resource *pResource,
  [in]   const D3D11_SHADER_RESOURCE_VIEW_DESC *pDesc,
  [out]  ID3D11ShaderResourceView **ppSRView
);

/* Créé une texture 2D */
HRESULT CreateTexture2D(
  [in]   const D3D11_TEXTURE2D_DESC *pDesc,
  [in]   const D3D11_SUBRESOURCE_DATA *pInitialData,
  [out]  ID3D11Texture2D **ppTexture2D
);

 

ID3D11DeviceContext : une interface qui représente un contexte d’affichage (device context) générant des commandes de rendu.

Voici ses méthodes principales :


/* Marque le début d'une série de commandes graphiques */
void Begin(
    ID3D11Asynchronous *pAsync
);

/* Efface le depth stencil */
void ClearDepthStencilView(
  [in]  ID3D11DepthStencilView *pDepthStencilView,
  [in]  UINT ClearFlags,
  [in]  FLOAT Depth,
  [in]  UINT8 Stencil
);

/* Reinitialise tous les élements de la render target à 1 */
void ClearRenderTargetView(
  [in]  ID3D11RenderTargetView *pRenderTargetView,
  [in]  const FLOAT ColorRGBA
);

/* Affiche des primitives non indexées */
void Draw(
  [in]  UINT VertexCount,
  [in]  UINT StartVertexLocation
);

/* Affiche des primitives indexées */
void DrawIndexed(
  [in]  UINT IndexCount,
  [in]  UINT StartIndexLocation,
  [in]  INT BaseVertexLocation
);

/* Relie un index buffer à l'étape de rendu Input Assembler */
void IASetIndexBuffer(
  [in]  ID3D11Buffer *pIndexBuffer,
  [in]  DXGI_FORMAT Format,
  [in]  UINT Offset
);

/* Relie un Vertex Input Layout à l'étape de rendu Input Assembler */
void IASetInputLayout(
  [in]  ID3D11InputLayout *pInputLayout
);

/* Relie le type de primitive à l'étape de rendu Input Assembler */
void IASetPrimitiveTopology(
  [in]  D3D11_PRIMITIVE_TOPOLOGY Topology
);

/* Obtient un pointeur sur la ressource dans la mémoire du GPU,
   ceci empeche la CG d'accéder à la ressource */
HRESULT Map(
    ID3D11Resource *pResource,
    UINT Subresource,
    D3D11_MAP MapType,
    UINT MapFlags,
    D3D11_MAPPED_SUBRESOURCE *pMappedResource
);

/* Invalide le pointeur de la ressource dans la mémoire du GPU,
   redonne l'accès à la ressource par la CG */
void Unmap(
    ID3D11Resource *pResource,
    UINT Subresource
);

/* Configure l'état du depth-stencil à l'étape de rendu Output Merger*/
void OMSetDepthStencilState(
  [in]  ID3D11DepthStencilState *pDepthStencilState,
  [in]  UINT StencilRef
);

/* Relie une ou plusieurs render target automatiquement et
   en même temps le buffer depth-stencil à l'étape de rendu Output Merger */
void OMSetRenderTargets(
  [in]  UINT NumViews,
  [in]  ID3D11RenderTargetView *const **ppRenderTargetViews,
  [in]  ID3D11DepthStencilView *pDepthStencilView
);

/* Configure l'étape de rendu Rasterizer */
void RSSetState(
  [in]  ID3D11RasterizerState *pRasterizerState
);

/* Relie un tablea de viewport à l'étape de rendu Rasterizer */
void RSSetViewports(
  [in]  UINT NumViewports,
  [in]  const D3D11_VIEWPORT *pViewports
);

 

ID3D11RenderTargetView : une interface qui représente une render-target (voir lexique) qui peut être accédée lors du rendu.

Elle possède la méthode principale :

GetDesc(D3D11_RENDER_TARGET_VIEW_DESC *pDesc)

 

ID3D11Texture2D : une interface qui représente une texture constituée de données texel qui
sont stockées dans la mémoire.

Elle possède la méthode principale :

GetDesc(D3D11_TEXTURE2D_DESC *pDesc)

 

ID3D11DepthStencilState : une interface qui représente l’état du depth-stencil pour que celui-ci
opère des tests à l’étape output-merger.

Elle possède la méthode principale :

GetDesc(D3D11_DEPTH_STENCIL_DESC *pDesc)

 

ID3D11DepthStencilView : une interface qui peut accéder à une texture paramétrée durant le test du depth-stencil.

Elle possède la méthode principale :

GetDesc(D3D11_DEPTH_STENCIL_VIEW_DESC *pDesc)

 

Cette structure nommée D3D11_DEPTH_STENCIL_VIEW_DESC est décrite ci-dessous :

struct D3D11_DEPTH_STENCIL_VIEW_DESC {
  DXGI_FORMAT         Format;
  D3D11_DSV_DIMENSION ViewDimension;
  UINT                Flags;
  union {
    D3D11_TEX1D_DSV         Texture1D;
    D3D11_TEX1D_ARRAY_DSV   Texture1DArray;
    D3D11_TEX2D_DSV         Texture2D;
    D3D11_TEX2D_ARRAY_DSV   Texture2DArray;
    D3D11_TEX2DMS_DSV       Texture2DMS;
    D3D11_TEX2DMS_ARRAY_DSV Texture2DMSArray;
  } ;
}

 

ID3D11RasterizerState : une interface qui permet de configurer l’état du Rasterzizer pour l’étape de rendu Rasterzizer.

Elle possède la méthode principale GetDesc(D3D11_RASTERIZER_DESC *pDesc).
Cette structure nommée D3D11_RASTERIZER_DESC est décrite ci-dessous :

struct D3D11_RASTERIZER_DESC {
  D3D11_FILL_MODE FillMode;
  D3D11_CULL_MODE CullMode;
  BOOL            FrontCounterClockwise;
  INT             DepthBias;
  FLOAT           DepthBiasClamp;
  FLOAT           SlopeScaledDepthBias;
  BOOL            DepthClipEnable;
  BOOL            ScissorEnable;
  BOOL            MultisampleEnable;
  BOOL            AntialiasedLineEnable;
}

 

Objets de description de données :

Une structure nommée DXGI_SWAP_CHAIN_DESC est décrite ci-dessous :

typedef struct DXGI_SWAP_CHAIN_DESC {
  DXGI_MODE_DESC   BufferDesc; // Décrit le mode d'affiche du backbuffer
  DXGI_SAMPLE_DESC SampleDesc; // Spécifie le super-échantillonnage
  DXGI_USAGE       BufferUsage; // Décrit le mode d'utilisation du backbuffer
                                // par le CPU
  UINT             BufferCount; // Spécifie le nombre de surface de tampon
  HWND             OutputWindow; // Spécifie l'handle de la fenêtre
                                 // Windows à utiliser
  BOOL             Windowed;     // Mode fenêtré ou non
  DXGI_SWAP_EFFECT SwapEffect;   // Spécifie la façon dont le contenu est affiché
  UINT             Flags;      
} DXGI_SWAP_CHAIN_DESC;

 

Une structure nommée D3D11_TEXTURE2D_DESC est décrite ci-dessous :

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

 

Une structure nommée D3D11_VIEWPORT est décrite ci-dessous :

typedef struct D3D11_VIEWPORT {
  FLOAT TopLeftX;
  FLOAT TopLeftY;
  FLOAT Width;
  FLOAT Height;
  FLOAT MinDepth;
  FLOAT MaxDepth;
} D3D11_VIEWPORT;

 

Résumé :

L’initialisation de DirectX 11 est assez délicate à mettre en œuvre car elle nécessite la manipulation de nombreux d’objets et interfaces associés.

Références :

– Documentation – DirectX SDK – June 2010

Une simple grille en 3D

grid

Intro :

Savoir créer une grille en 3D peut être utile pour :

– un éditeur de map

– générer un terrain à partir d’une height map

– représenter la surface d’une eau en mouvement

– etc…

Prérequis :

– Savoir initialiser DirectX 10.1

Explications :

Le fichier Grid.h :

#ifndef GRID_H
#define GRID_H

#include <vector>

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

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

class Grid
{
public:
    Grid();
    virtual ~Grid();
 
    bool Initialize();
 
    void Render(float fTimeSinceLastFrame);

    void GenTriGrid(int iNumVertRows, int iNumVertCols,
        float fDx, float fDz, const D3DXVECTOR3& center,
        std::vector<D3DXVECTOR3>& verts, std::vector<uint32>& indices);

private:
    ID3DX10Mesh* m_pMesh;
 
    D3DXMATRIX m_worldMatrix;
    D3DXMATRIX m_viewMatrix;
    D3DXMATRIX m_projMatrix;

    ID3D10EffectMatrixVariable* m_pWorldVariable;
    ID3D10EffectMatrixVariable* m_pViewVariable;
    ID3D10EffectMatrixVariable* m_pProjVariable;
     
    ID3D10Effect* m_pEffect;
    ID3D10EffectTechnique* m_pTechnique;
     
    ID3D10InputLayout* m_pVertexLayout;
};
 
#endif

 

Le fichier Grid.cpp :

#include "Grid.h"

Grid::Grid() :
m_pMesh(nullptr),
m_pWorldVariable(nullptr),
m_pTechnique(nullptr),
m_pEffect(nullptr),
m_pVertexLayout(nullptr)
{
    D3DXMatrixIdentity(&m_worldMatrix);

    D3DXVECTOR3 Eye(0.0f, 22.0f, -46.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);
}
 
Grid::~Grid()
{
    SAFE_RELEASE(m_pMesh);
}
 
bool Grid::Initialize()
{
    std::vector<D3DXVECTOR3> vertices;
    std::vector<uint32> indices;
     
    GenTriGrid(12, 12, 5.0f, 5.0f, D3DXVECTOR3(0, 0, 0), vertices, indices);

    D3D10_INPUT_ELEMENT_DESC layout[] =
    {
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
    };

    HRESULT hr;
    hr = D3DX10CreateMesh(D3D10_RENDERER->GetDevice(), layout,  1, "POSITION", vertices.size(), indices.size()/3, D3DX10_MESH_32_BIT, &m_pMesh);
 
    hr = m_pMesh->SetVertexData(0, vertices.front());
 
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }
 
    hr = m_pMesh->SetIndexData(indices.data(), indices.size());
 
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }
 
    hr = m_pMesh->CommitToDevice();
 
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }
 
    ID3D10Blob* pBlob = nullptr;
 
    hr = D3DX10CreateEffectFromFile(L"Grid.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();
 
    D3D10_PASS_DESC PassDesc;
    m_pTechnique->GetPassByIndex(0)->GetDesc(&PassDesc);
    hr = D3D10_RENDERER->GetDevice()->CreateInputLayout(layout, 1, PassDesc.pIAInputSignature,
                                         PassDesc.IAInputSignatureSize, &m_pVertexLayout);
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

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

    // Pour faire tourner la grille 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);

    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);
    }
}

void Grid::GenTriGrid(int iNumVertRows, int iNumVertCols,
        float fDx, float fDz, const D3DXVECTOR3& center,
        std::vector<D3DXVECTOR3>& verts, std::vector<uint32>& indices)
{
    int numVertices = iNumVertRows * iNumVertCols;
    int numCellRows = iNumVertRows - 1;
    int numCellCols = iNumVertCols - 1;

    int numTris = numCellRows*numCellCols*2;

    float width = (float) numCellCols * fDx;
    float depth = (float) numCellRows * fDz;

    /* Construction des vertices */

    /* On construit d'abord la grille centrée  à l'origine */

    verts.resize( numVertices );

    /* Valeurs de décalage pour pour déplacer la grille
       dans le centre du système de coordonnées */
    float xOffset = -width * 0.5f;
    float zOffset =  depth * 0.5f;

    int k = 0;
    for (float i = 0; i < iNumVertRows; ++i)
    {
        for (float j = 0; j < iNumVertCols; ++j)
        {
            verts[k].x =  j * fDx + xOffset;
            verts[k].z = -i * fDz + zOffset;
            verts[k].y =  0.0f;

            /* Opère une translation afin que le centre de la grille
               soit positionné en fonction du centre spécifié en paramètre */
            D3DXMATRIX T;
            D3DXMatrixTranslation(&T, center.x, center.y, center.z);
            D3DXVec3TransformCoord(&verts[k], &verts[k], &T);
            
            ++k;  // Prochain vertex
        }
    }

    /* Construction des indices */ 

    indices.resize(numTris * 3);
    
    // Créé les indices pour chaque face
    k = 0;
    for (DWORD i = 0; i < (DWORD)numCellRows; ++i)
    {
        for (DWORD j = 0; j < (DWORD)numCellCols; ++j)
        {
            indices[k]     =   i   * iNumVertCols + j;
            indices[k + 1] =   i   * iNumVertCols + j + 1;
            indices[k + 2] = (i+1) * iNumVertCols + j;
                    
            indices[k + 3] = (i+1) * iNumVertCols + j;
            indices[k + 4] =   i   * iNumVertCols + j + 1;
            indices[k + 5] = (i+1) * iNumVertCols + j + 1;

            // Prochaine face
            k += 6;
        }
    }
}

 

Résumé :

Nous avons expliqué comment générer une grille en 3D avec le paramétrage de sa longueur et de sa largeur puis la dimensions de ses faces.

Voici les fichiers sources : Simple Grille.zip

Références :

– Introduction to DirectX 9.0c – Frank D. Luna

La tessellation – Application (DirectX 11)

tessellation

Intro :

Nous allons voir comment mettre en pratique la « tessellation » de DirectX 11.

Prérequis :

– Avoir suivi la première partie de ce tutoriel concernant la tessellation.

Explications :

Le principe est simple : on ajoute de nouvelles vertices à un modèle puis on déplace ces vertices en suivant les données correspondantes d’une « height map». Il s’agit en l’occurrence du procédé de « Displacement Mapping ».

Résumé :

Références :