Programmation Graphique Haute Performance ´Eclairage
Transcription
Programmation Graphique Haute Performance ´Eclairage
Programmation Graphique Haute Performance Éclairage local March 4, 2015 Objectifs pédagogiques : calcul des normales aux sommets d’un maillage, calcul de l’éclairage local à l’aide de différentes BRDF et méthodes d’interpolation. Objectifs applicatifs : rendu d’une scène 3D à l’aide d’un modèle d’éclairage local, et de sources lumineuses directionnelles ou ponctuelles. 1 Normales Pour pouvoir calculer l’éclairage, nous avons besoin de connaı̂tre l’orientation de la surface. Cette information est représentée par les normales (vecteurs 3D) que nous noterons n. Les normales sont transmises par la classe Mesh aux shaders en tant qu’attributs vec3 vtx_normal. 1. Modifiez le vertex shader simple.vert pour qu’il récupère l’attribut vtx_normal en entrée et qu’il le transmette au fragment shader. Modifiez ensuite le fragment shader de façon à afficher les normales sous forme de couleurs. Attention, les coordonnées des normales sont comprises dans l’intervalle [−1, 1], il faut les transformer dans l’intervalle [0, 1] pour les afficher comme des couleurs. Testez avec le maillage cow.obj, dont les normales sont pré-calculées et stockées dans le fichier. 2. Les modèles 3D ne sont pas toujours fournis avec leurs normales. Modifiez la méthode Mesh::load (ligne 44) pour qu’elle calcule les normales en fonction de la position des sommets, si le maillage chargé ne possède pas de normales (normalId.empty() == true). Testez avec le modèle tw.off. Rappels : La normale d’un sommet est obtenue en faisant la moyenne pondérée des normales des faces adjacentes à ce sommet, sachant que la normale nf d’une face de sommets (v1 , v2 , v3 ) est : nf = (v2 − v1 ) × (v3 − v1 ) où × représente le produit vectoriel. Il s’avère que knf k = 2∆, où ∆ est l’aire du triangle (v1 , v2 , v3 ). Par conséquent, en calculant directement la moyenne des normales nf des faces adjacentes à un sommet, nous obtenons une moyenne pondérée par les aires des triangles adjacents. Cette pondération est fréquemment utilisée car elle produit de bons résultats tout en étant rapide à calculer. Indices : Si votre implémentation utilise des boucles imbriquées ou une structure de donnée temporaire, vous faites fausse route. L’algorithme consiste à calculer les normales en trois passes : (1) initialiser toutes les normales au vecteur nul, (2) calculer les normales des faces et les ajouter à leurs trois sommets incidents au fur à mesure, et (3) normaliser la normale de chaque sommet. 3. 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 viewMatrix 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. Cependant la transformation qui s’applique aux normales n’est pas la même que celle des sommets, une normale étant une direction et pas une position 1 (cf. cours et http://www.lighthouse3d.com/tutorials/glsl-tutorial/the-normal-matrix/). La matrice de transformation des normales peut néanmoins se déduire de la matrice de vue grâce à la formule suivante : N = (L−1 )> où L est la partie linéaire de la matrice de vue (sous-matrice 3 × 3 supérieure gauche). Ajoutez une variable uniform mat3 normalMatrix au vertex shader, et initialisez la correctement dans votre code C++ (glm vous permet d’effectuer les calculs matriciels nécessaires). 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 Calcul de l’éclairage Vous allez utiliser les BRDF de Phong puis de Blinn-Phong pour calculer l’éclairage local. Ce sont des modèles empiriques qui sont efficaces à calculer et produisent un résultat plausible, mais qui ne sont pas physiquement réalistes. Vous allez également comparer l’interpolation de Gouraud (calcul de l’éclairage par sommet) et de Phong (calcul par fragment). 1. Implémentez les shaders phong.vert/.frag qui doivent calculer l’éclairage avec la BRDF de Phong et l’interpolation de Gouraud pour une source lumineuse directionnelle. Voici le prototype de la fonction principale : /** * n : la normale de la surface * l : un vecteur pointant vers la source de lumière * v : un vecteur pointant vers la caméra * diffuse_color : la couleur diffusée par l’objet * specular_color : la couleur du reflet spéculaire * exponent : le coefficient speculaire de la BRDF de Phong * light_color : la couleur de la lumière */ vec3 phong(vec3 n, vec3 l, vec3 v, vec3 diffuse_color, vec3 specular_color, float exponent, vec3 light_color) { return vec3(.5); } Procédez par étape : a. Commencez l’implémentation de la fonction phong de façon à calculer uniquement la partie diffuse de la BRDF. Rappelons qu’il s’agit d’une constante ρd , qui correspond ici à la variable diffuse_color. N’oubliez pas de prendre en compte le terme en cosinus (produit scalaire entre la normale et la direction vers la lumière) et la couleur de la lumière. Pour tester, fixez dans votre shader la direction de la lumière l à une valeur arbitraire, par exemple normalize(vec3(1.)). Dans un premier temps, utilisez une couleur uniforme pour l’objet, par exemple vec3(.5) ; vous pourrez ensuite tester avec vtx_color ou une texture. De même pour la couleur de la lumière, fixez d’abord sa valeur à vec3(1.). Appelez la fonction phong avec ces paramètres pour obtenir la couleur de sortie, et transmettez-là au fragment shader. b. Déplacez la caméra. La lumière est-elle fixée à la scène ou à la caméra ? Pourquoi ? Faites en sorte d’obtenir l’effet inverse. c. Rajoutez la partie spéculaire de l’éclairage, qui simule un matériau brillant. Celle-ci s’obtient par la formule ρs (r · n)n , où r est la réflexion du vecteur l par rapport à la normale n, ρs correspond au paramètre specular_color et n correspond au paramètre exponent. Testez avec specular_color = vec(1.) et avec un exponent assez élevé, par exemple 128. Que pensez-vous du résultat ? 2 2. Modifiez les shaders pour implémenter une interpolation de Phong, c’est-à-dire pour calculer l’éclairage par fragment. Vous devez évaluer la fonction phong dans le fragment shader et donc lui passer les vecteurs l, n et v. Ces vecteurs doivent être normalisés, mais où doit être réalisée cette normalisation ? Quel(s) changement(s) dans l’illumination constatez-vous ? Que pensez-vous du surcoût qu’implique cette interpolation ? 3. Créez de nouveaux shaders blinn.vert/.frag afin d’implémenter la BRDF de Blinn-Phong. Pour cette variante, la composante spéculaire s’obtient avec la formule : ρs (h · n)n , où h est le demi-vecteur entre le vecteur de vue v et celui de la source lumineuse l. Vérifiez que vous obtenez bien un résultat équivalent à la BRDF de Phong en multipliant par quatre le coefficient spéculaire ρs . 4. On souhaite pouvoir utiliser des sources lumineuses ponctuelles. Dans le shader blinn.frag, ajoutez une variable uniform vec3 light_view correspondant à la position de la lumière en espace vue. Modifiez ensuite la méthode render dans main.cpp de façon à ajouter un paramètre correspondant à la position de la lumière en espace vue, et transmettez ses coordonnées au shaders. Finalement, modifiez le shader blinn.frag pour gérer une source de lumière ponctuelle. Cela modifie la façon de calculer le vecteur l et implique de prendre en compte l’atténuation liée à la distance entre la source et la surface. 3 Système solaire (bonus) Vous pouvez continuer à améliorer votre système solaire en considérant le soleil comme une source lumineuse et en calculant l’éclairage des autres astres (en modulant l’intensité de leur texture). Pour aller encore plus loin, vous pouvez utiliser le multi-texturing pour mélanger une texture de la terre vue de jour avec une texture vue de nuit en fonction de l’éclairage. 3