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 :
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 :
représentant la tangente.
représentant la binormale.
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/

