TD5 - Calcul des normales et éclairage

L'objectif de ce TP est de mettre en place le modèle d'éclairage direct de Blinn-Phong (similaire au TD1) en GLSL. Dans un premier temps nous supposerons que les normales aux sommets sont inclues dans le fichier du maillage. Nous verrons dans un second temps comment les calculer pour les maillages ne disposant pas de cette information.

1. Calcul de l'éclairage

Pour cette première étape, vous pouvez rester avec votre scène du système solaire en mettant à jour le fichier sphere.obj de manière à avoir des normales lisses. Les étapes sont :

  1. Si ce n'est pas déjà fait, vous devez mettre à jour la fonction Mesh::draw pour transmettre les normales des sommets aux shaders (comme vu au TD3 pour les attributs de couleurs).

  2. Dans le vertex shader, mettez en œuvre une fonction GLSL vec3 blinn(vec3 n, vec3 v, vec3 l, vec3 dCol, vec3 sCol, float s) évaluant le modèle d'éclairage de Blinn-Phong pour une normale n, une direction de vue v, une direction de lumière l, une couleur diffuse, une couleur spéculaire et un exposant spéculaire s. Pour rappel, le modèle de Blonn-Phong est composé d'un terme diffus $\cos(\theta) = \max(\vx{n} \cdot \vx{l}, 0)$ et d'un terme spéculaire $\cos^s(\beta) = (\max(\vx{n} \cdot \vx{h}, 0))^s$, pondérés par les couleurs de l'objet et de la lumière. Ici h est le half vecteur, bissecteur entre v et l.

    eclairagemonkey
  3. Pour pouvoir évaluer la fonction blinn précédente en un sommet donné, nous devons calculer et exprimer les trois premiers vecteurs dans le même repère. Remarquez que le vecteur de vue v peut être très facilement obtenu après transformation de la position du sommet dans le repère caméra. Il nous reste donc à exprimer les deux autres vecteurs (normale et lumière) dans ce repère. Comme au TD1, nous allons utiliser une lumière directionnelle dont les coordonnées dans le repère caméra peuvent être calculées une seule fois par image avant de les passer aux shaders. En revanche, les normales transmises aux shaders sont définies en espace objet, et elles doivent être transformées dans le repère caméra dans le vertex shader.

    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 (bloc 3x3 supérieure gauche) de la matrice permettant de passer du repère objet au repère caméra (résultant de la multiplication de la matrice de vue avec la matrice objet).

    Ajoutez une variable uniform mat3 normal_mat 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 et de la caméra. En effet, les normales des faces visibles sont forcement orientées vers la caméra, elles ont donc une coordonnée z positive (bleu).

  4. Passez d'un éclairage par sommet à un éclairage par fragment en déplaçant le calcul de l'éclairage du vertex au fragment shader. Observez le gain en qualité des reflets.

  5. 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
  6. Vous pouvez aussi tester avec le modèle monkey2.obj.

2. Calcul des normales

Mettez en œuvre la méthode Mesh::computeNormals() (re-)calculant les normales de chacun des sommets d'un maillage. Rappel : la normale en un sommet peut être définie comme la moyenne des normales des faces adjacentes pondérées par l'aire de la face. Comme la structure de maillage en tableau de sommets + tableau de faces ne permet pas de trouver rapidement les faces voisines d'un sommet donné, nous allons inverser les boucles de calcul :

  1. Mettre à zéro les normales des sommets
  2. Pour chaque face, calculer sa normale (sans la normaliser) et l'ajouter à la normales des sommets de la face
  3. Normaliser les normales des sommets
Testez avec le modèle lemming.off. Pensez à appeler la fonction computeNormals() au bon endroit.