TD7 : Normal Mapping

L'objectif de ce TP est de mettre en place la technique de normal mapping. Le principe de cette approche est de stocker dans une texture une carte des normales détaillées qui seront utilisées pour perturber le calcul d'éclairage. Vous procéderez en trois étapes : d'abord vous implémenterez le modèle d'éclairage de Phong du TD2 en GLSL, puis vous appliquerez le normal mapping sur un plan dont les normales perturbées seront exprimées en espace objet, puis sur une sphère dont les normales seront définies en espace tangent.

1 - Calcul de l'éclairage

1. Les normales transmises aux shaders sont définies en espace objet. Or, pour le calcul de l'éclairage, nous avons besoin de connaître l'orientation de la normale par rapport à la direction de la lumière incidente et la direction de vue. Pour cela, il faut exprimer tous ces vecteurs dans le même repère. La matrice de vue utilisée dans le vertex shader transforme la position des sommets dans le repère de la vue. Cette position correspond directement au vecteur pointant de la surface vers la caméra (qui est, par définition, placée à l'origine du repère). Il faut donc appliquer une transformation aux normales de façon à les exprimer elles aussi dans le repère de la vue.

La matrice de transformation des normales n'est cependant pas la même que celle des sommets, mais elle peut se déduire de celle-ci grâce à la formule suivante : $\vx{N} = (\vx{L}^{-1})^\top$ où $\vx{L}$ est la partie linéaire de la matrice de vue (matrice 3x3 supérieure gauche).

Ajoutez une variable uniform mat3 normalMatrix au vertex shader, et initialisez la correctement dans votre code C++ avec Eigen. Vérifiez que les normales en espace vue affichées sous forme de couleurs produisent un résultat bleuté quel que soit l'orientation du modèle. En effet, les normales des faces visibles sont forcement orientées vers la caméra, elles ont donc une coordonnée z positive.

2. Passez les normales transformées au fragment shader et calculez alors l'éclairage avec le modèle de Phong et une source de lumière directionnelle, comme au TD2. Pour rappel, La couleur du fragment est obtenue comme la somme d'un terme ambiant constant, d'un terme diffus $\cos(\theta) = \max(\vx{n} \cdot \vx{l}, 0)$ et d'un terme spéculaire $\cos^s(\alpha) = (\max(\vx{r} \cdot \vx{v}, 0))^s$, pondérés par la couleur de l'objet et de la lumière.

eclairage

Afin de bien comprendre comment est formée l'image finale, affichez tour à tour les images constituées uniquement de l'éclairement ambiant, puis diffus, et enfin spéculaire :

gouraud  =  gouraud  +  gouraud  +  gouraud
modèle de Phong ambiant diffus spéculaire

2 - Normal mapping sur un plan

1. Comme son nom l'indique, une carte de normales contient des normales, encodées sous forme de couleurs. Téléchargez l'archive textures.zip et chargez la carte de normales "normals.png" à l'aide de SOIL2 (comme une texture classique) et passez-la à un sampler de texture dans le fragment shader. Utilisez la valeur lue dans cette texture à la place des normales interpolées passées par le vertex shader pour calculer l'éclairage. Testez avec un simple plan (classe Quad). Pour bien visualiser la perturbation de l'éclairage, affichez d'abord le calcul de l'illumination en niveau de gris sans la texture de couleur.

Attention : les textures étant faites pour contenir des couleurs, leurs valeurs sont comprises dans l'intervalle $[0,1]$ alors que les normales sont comprises dans l'intervalle $[-1,1]$. Pensez à effectuer la transformation appropriée !

2. Chargez la texture de couleurs "diffuse.png". Passez-la à un second sampler de texture et utilisez-la pour définir la couleur diffuse de l'objet.

normalMap normalMap normalMap normalMap
texture diffuse carte de normales perturbation de l'éclairage résultat final

3. Appliquez une rotation à l'objet, que constatez-vous ? Que se passe-t-il si vous testez sur autre chose qu'un plan ?

3 - Normal mapping sur une sphère

Le plan est placé de façon à ce que les normales contenues dans la carte de normales correspondent à l'espace objet. Ce n'est généralement pas le cas : la carte de normales est en réalité exprimée dans le repère tangent à la surface de l'objet. Il est défini par trois vecteurs : la normale $\vx{n}$, la tangente $\vx{t}$, la bitangente $\vx{b}$.

1. Chargez la texture "earth3.png" dans une nouvelle unité de texture. Vous pouvez dans un premier temps visualiser directement cette texture à la surface de la sphère.

2. Les normales fournies par la texture sont exprimées dans un repère tangent à la surface de l'objet. Pour les utiliser, il est donc nécessaire d'exprimer les vecteurs de lumière et de vue dans ce repère ($l_t$ et $v_t$). Ajoutez au vertex shader l'attribut vec3 vtx_tangent pour avoir accès à la tangente en chaque sommet, puis construisez ce repère en suivant les étapes suivantes :

  1. transformez les vecteurs tangent et normal du repère Model au repère View puis normalisez les pour obtenir les vecteurs $\vx{t_v}$ et $\vx{n_v}$ respectivement,
  2. calculez le vecteur binormale $\vx{b_v}$ par produit vectoriel (fonction cross) des vecteurs $\vx{t_v}$ et $\vx{n_v}$ (Attention, le produit vectoriel n'est pas commutatif !),
  3. créez la matrice de transformation 3x3 tbnvMatrix dont chaque ligne est constituée des 3 composantes des vecteurs $\vx{t_v}$, $\vx{b_v}$ et $\vx{n_v}$. Si v_1 = vec2(a,b) et v_2 = vec2(c,d) alors m = mat2(v_1,v_2) représente la matrice 2x2 dont la première ligne sera (a,c) et la deuxième (b,d)
  4. les vecteurs $\vx{l_t}$ et $\vx{v_t}$ sont obtenus en multipliant la matrice tbnvMatrix par $\vx{l_v}$ et $\vx{v_v}$ respectivement.

Utilisez la normale lue dans la normale map ainsi que $l_t$ et $v_t$ pour calculer les coefficients diffus et spéculaires dans le fragment shader.

bump bump bump
rendu original perturbation de l'éclairage résultat final

4 - Calcul du repère tangent (bonus)

Le repère tangent peut être calculé analytiquement pour une sphère paramétrique, mais pas pour un maillage quelconque. Comme pour le calcul des normales lisses au TD4, il faut d'abord calculer la tangente et la bitangente par triangle, en utilisant ses coordonnées de textures (cf. cours), puis moyenner ces vecteurs pour obtenir un repère par sommet.

Dans un premier temps, pour chaque face, calculez sa tangente et sa bitangente, et accumulez-les dans les trois sommets de la face. Dans un second temps, pour chaque sommet, appliquez l'orthonormalisation de Gram-Schmidt afin de garantir que le triplet $[\vx{t},\vx{b},\vx{n}]$ forme un repère orthonormal.