Archives pour la catégorie Mathématiques

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

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

La normale (vecteur)

normale

Intro :

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

Prérequis :

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

Explications :

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

Comment calculer la normale ?

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

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

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

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

 

Voici l’algorithme en C++ :

#include <d3dx10math.h>

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

    D3DXVec3Cross(out, &u, &v);

    D3DXVec3Normalize(out, out);
}

 

On peut la calculer autrement :

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

 

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

 

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

 

Comment la normaliser ?

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

 

Voici l’algorithme en C++ :

#include <d3dx10math.h>

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

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

 

A quoi sert la normale ?

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

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

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

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

[schéma / dessin]

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

Résumé :

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

Références :

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

Les matrices

matrice

Intro :

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

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

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

Voir aussi cet article traitant les transformations.

Prérequis :

– Savoir faire des opérations sur les vecteurs.

– Savoir appliquer le produit scalaire.

Explications :

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

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

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

Multiplication de deux matrices :

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

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

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

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

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

Voici un exemple :

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


Matrice carrée :

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

Voici une matrice carrée :

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

 

Ordre d’une matrice :

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

Matrice identité :

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

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

Voici une matrice identité d’ordre 3 :

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

 

Multiplication d’un vecteur par une matrice :

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

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

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

Transposée d’une matrice :

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

Une matrice MxN devient une matrice NxM.

Exemple :

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

Propriétés remarquables :

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

 

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

 

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

 

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

 

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

 

Ajouter deux matrices :

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

Soustraire deux matrices :

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

Multiplier une matrice par un réel :

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

Inverse d’une matrice :

Seul les matrices carrées sont inversibles.

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

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

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

Voici un schéma résumant ce principe :

img008

Déterminant d’une matrice :

Associativité :

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

 

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

 

Résumé :

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

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

Rotation autour d’un axe quelconque

Intro :

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

matrix_rotation

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

Prérequis :

– Savoir ce qu’est une matrice

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

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

Explications :

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

 


 

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

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

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

 

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

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

    D3DXVECTOR3 vect1 = v * cos(fAngle);

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

    vect2 *= sin(fAngle);

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

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

 

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

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

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

 

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

 

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

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

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

 


 

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

#include <d3dx10math.h>

D3DXMATRIX worldMatrix;
D3DXMatrixIdentity(&worldMatrix);

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

D3DXMatrixRotationAxis(&worldMatrix, &axis, r);

 

Résumé :

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

Références :

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

Calculer la tangente et la bitangente d’une face

NTBFromUVs_cadre

Intro :

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

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

Explications :

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

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

 

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

 

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

 

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

 

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

 

c = 1 / p

 

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

 

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

 

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

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

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

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

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

 

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

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

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

    D3DXVECTOR2 tuVector;
    D3DXVECTOR2 tvVector;

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

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

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

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

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

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

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

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

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

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

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

 

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

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

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

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

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

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

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

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

 

Voilà c’est tout pour les algorithmes !

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

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

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

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

 

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

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

 

Résumé :

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

Références :

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

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

Les vecteurs

vector_2d_coordinates

Intro :

Ceci constitue un petit cours sur les vecteurs que vous avez vus normalement au lycée.

Un vecteur est une entité mathématique qui désigne aussi bien une position qu’une direction dans un repère donné.
Un vecteur peut servir à représenter une force, une position, une vitesse, par exemple la direction à laquelle le joueur regarde, etc…

Dans cet article on notera un vecteur {v} plutôt que \vec{v} .

Prérequis :

– Avoir déjà lu et compris quelques rudiments sur les mathématiques des vecteurs.

Explications :

Un vecteur se note de cette façon : v=({x,y,z})

Opérations sur les vecteurs :

Obtenir la négation d’un vecteur :

-v=-({x,y,z}) = ({-x,-y,-z})

 

On peut ajouter deux vecteurs :

{v + w} = {({v_{x},v_{y},v_{z}}) + ({w_{x},w_{y},w_{z}})} = ({v_{x} + w_{x},v_{y} + w_{y},v_{z} + w_{z}})

 

Soustraire deux vecteurs :

{v - w} = {v + (-w)} = {({v_{x},v_{y},v_{z}}) + ({-w_{x},-w_{y},-w_{z}})} = ({v_{x} - w_{x},v_{y} - w_{y},v_{z} - w_{z}})

 

Multiplier un vecteur par un scalaire :

\textbf{k}v = ({\textbf{k}v_{x}, \textbf{k}v_{y}, \textbf{k}v_{z}})

 

On peut inverser la direction d’un vecteur dans le même genre :

{(\textbf{-1})}v = -v = ({-v_{x}, -v_{y}, -v_{z}})

 

On peut calculer la longueur d’un vecteur (en utilisant le théorème de Pythagore) :

\|v\| = \sqrt{{v}_x^2+{v}_y^2+{v}_z^2}

 

On peut calculer la distance entre deux vecteurs :

distance({a}, {b})=\|{b}-{a}\|=\sqrt{({b}_x-{a}_x)^{2}+({b}_y-{a}_y)^{2}+({b}_z-{a}_z)^{2}}

 

Normaliser un vecteur (rendre sa longueur de 1 unité) :

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

 

Il existe aussi deux autres opérations qui sont : le produit scalaire et le produit vectoriel.

L’opération du produit scalaire donne comme résultat un nombre (d’où le nom « scalaire ») :

{v}\cdot{w}={v}_{x}{w}_{x}+{v}_{y}{w}_{y}+{v}_{z}{w}_{z}

 

Il s’exprime aussi avec la fonction cosinus avec comme paramètre l’angle entre les deux vecteurs :

{v}\cdot{w}=\|{v}\|\|{w}\|\cos(\theta)

 

Le produit scalaire est une opération fondamentale dans le calcul des lumières dans le rendu graphique 3D.

On peut trouver l’angle entre les deux vecteurs ainsi :

\theta=\arccos\left(\frac{{a}\cdot{b}}{\|{a}\|\|{b}\|}\right)

 

La fonction arccos est tout simplement la fonction cos⁻¹ de votre calculatrice.

L’opération du produit vectoriel :

{w}={u}\times{v}

 

Le résultat du produit vectoriel est un vecteur perpendiculaire aux deux vecteurs (u et v) de l’opération ; le sens de ce vecteur dépend de l’ordre des opérandes dans la multiplication.

On le calcul d’après la formule suivante :

{w}={u}\times{v}=(({u}_{y}{v}_{z}-{u}_{z}{v}_{y}),({u}_{z}{v}_{x}-{u}_{x}{v}_{z}),({u}_{x} {v}_{y}-{u}_{y}{v}_{x}))


Résumé :

Nous avons vu la notion de vecteur ; objet mathématique fondamentale dans la réalisation d’un jeu vidéo en 2D ou 3D.