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

Documents pareils