Lesson 2 : Lighting

Au cours de cet atelier, vous implémenterez l’illumination directe en OpenGL. Vous apprendrez à modéliser en GLSL les types de lampes les plus courants. La seconde partie de l’atelier consiste à réimplémenter l’illumination en utilisant la technique du Deferred Shading, vous apprendrez ensuite à optimiser cet algorithme.

Consultez cette page pour récupérer le code de base, ou partez du code écrit pour la leçon précédente.

Forward shading

Forward Shading

On parle de Forward Shading par opposition au Deferred Shading, c’est le pipeline temps-réel traditionnel de rasterization et de shading. Un draw call est émis via l’API OpenGL, les vertices sont transformés par le vertex shader, les primitives sont rasterizées, et le fragment shader calcule le shading sur les echantillons générés.

  1. Quelle est la différence entre un pixel et un fragment?
  2. Définissez la notion d’Early-Z Rejection.
  3. Définissez la notion d’illumination directe.

Blinn Phong

Implementez un modèle d’llumination de type Blinn-Phong normalis

Blinn_Vectors

  1. Citez d’autres modèles d’illumination.
  2. Définissez la notion de BRDF

1) Construisez une scène similaire à cette image.

1_1

2) Définissez un vecteur \vec{L} arbitraire dans le fragment shader.

3) On définit \L_o(v) comme l’énergie véhiculée le long de \vec{L}  et réfléchie vers le point de vue selon cette équation.

L_o(v) = (\frac{c_{diff}}{\pi} + (\vec{n}\cdot\vec{h})^{\alpha_p} c_{spec}*\frac{\alpha_p+2}{8\pi})(\vec{n}\cdot\vec{l})

c_{diff} est la couleur diffuse, c_{spec} la couleur spéculaire, 0.1 < \alpha_p < 100.0 le coefficient spéculaire, et \vec{h} = \parallel \vec{l} + \vec{v} \parallel.

Point Light

pointlight

1) Illuminez la scène avec une point light.

On peut arbitrairement définir une point light ou lumière ponctuelle, en utilisant une position, une couleur, et une intensité. Utilisez les uniform pour passer ces informations au fragment shader.

To compute the illumination you need to compute the vector \vec{l}, from the surface to the light. We modify the normalized Blinn-Phong equation to look like this.

Pour calculer l’illumination vous avez besoin de définir le vecteur \vec{l} de la surface vers la position de la lumière. Modifiez l’équation précédente pour y injecter le calcul de l’illumination incidente.

L_o(v) = c_{light} * l_{i} * \pi * (\frac{c_{diff}}{\pi} + \frac{(\vec{n}\cdot\vec{h})^{\alpha_p} c_{spec}}{\frac{\alpha_p+8}{8\pi} })(\vec{n}\cdot\vec{l})

Dans quel espace géométrique avez vous défini ces données ?

3) Animez les valeurs passées en uniform, rendez les accessible via l’interface graphique.

4) Faites en sorte que la lumière s’atténue sur la distance par rapport à la surface.

On implémente une atténuation quadratique en multipliant la quantité de lumière reçue par la surface au coefficient  \frac{1.0}{d^2} avec d la distance lumière/surface.

On implémente usuellement les atténuation linéaires, quadratiques, et cubique. Quelle atténuation est physiquement juste?

1_2

5) Ajoutez une seconde lumière du même type, avec des paramètres différents.

Il est judicieux d’encapsuler le calcul de l’illumination dans une fonction.

1_3

Directional Light

directionallight

1) Illuminez la scène avec une  directional light

On peut arbitrairement définir une directional light ou lumière directionnelle, en utilisant une direction, une couleur, et une intensité.  Le calcul du vecteur l est simplifié.

1_4

Spot Light

spotlight

1) Illuminez la scène avec un  spot light

On peut arbitrairement définir une spot light ou projecteur, en utilisant une position,  une direction, un angle, une couleur, et une intensité.

La principale différence entre la lumière ponctuelle et le projecteur  est que ce dernier n’illumine pas la surface si l’angle entre le vecteur de direction du projecteur et -l  est supérieur au demi angle du projecteur.

2) Implémentez une transition progressive entre lumière et ombre

Vous pouvez  calculer un coefficient d’atténuation angulaire falloff  f = \frac{cos(\theta)-cos(\Phi)}{cos(\Phi)-cos(\phi)}^4 entre 0 et 1 avec \theta l’angle entre l et la direction du spot, \Phi l’angle du spot, et \phi l’angle de départ du falloff avec \phi > \Phi .  Passez la valeur de \phi en uniform.

1_5

3) Implémentez l’atténuation quadratique sur le projecteur

4) Illuminez la scène avec les trois types de lumières.

1_6

Deferred shading

Deferred shading

Le deferred shading est une technique d’illumination en deux passes consistant à découpler la rasterization et le calcul de l’illumination. Cette technique est très largement employée dans les moteurs de jeux depuis le début de la précédente génération de consoles.

On commence par rendre la scène dans un G-Buffer (G pour Geometry), le G-Buffer est un ensemble de textures dans lesquelles sont enregistrées en espace écran les propriétés géométriques -normale, profondeur/depth- et de matériaux -couleur diffuse, coefficient spéculaire- des objets rendus.

Au cours de la seconde passe, on rend un rectangle pour chaque lumière, pour chaque fragment on reconstruit les propriétés du matériaux et de l’objet à illuminer à l’aide des données présentes dans les textures du G-Buffer. On calcule ensuite l’illumination en espace écran, puis on répète le processus pour chaque lumière en utilisant un blending additif.

Le deferred shading a pour avantages de réduire le nombre d’appels au fragment shader, notamment dans les cas ou l’overdraw est important. Cette technique permet de plus ne tenir en compte que des lumières contribuant directement à illuminer les objets affichés.

La technique présente deux inconvénients majeurs, d’une part elle rend plus complexe l’implémentation de matériaux très différents, d’autre part elle est difficilement compatible avec les techniques d’antialiasing géométriques comme le MSAA.

Les deux passes de l’algorithme sont très largement optimisables, en effet si la technique permet de réduire le nombre d’appels au fragment shader, elle peut être très lourde en bande passante de texture. Il est donc nécessaire de compresser au maximum le G-Buffer, et de réduire au minimum la taille du rectangle utilisé pour chaque lumière.

Première passe : G-Buffer

La construction du G-Buffer se fait en utilisant les Framebuffer Objects OpenGL. Jusqu’ici vous avez utilisé le framebuffer fourni par le système de fenêtrage. Dans cet exercice vous utiliserez votre propre framebuffer, configuré pour rendre la scène dans les textures du G-buffer.

1) Construisez une scène ressemblant à cette image.

1_1

2) Affichez successivement les normales, la couleur diffuse, le coefficient spéculaire et la profondeur.
Vous pouvez utiliser la variable builtin gl_FragCoord.z pour lire la profondeur du fragment dans un fragment shader.

3) Rendez la scène directement dans un Framebuffer Object

Le framebuffer utilisé pour rendre le G-Buffer contiendra trois textures, une texture de couleur, une texture de normale, et une texture de profondeur. Il vous faudra modifier l’initialisation, la boucle de rendu ainsi que votre fragment shader.

Construisez les objets  OpenGL nécessaires.

Initialisez la  texture de couleur de la taille de la fenêtre.

La texture de normale en RGBA32F.

La texture de depth.

On crée ensuite le framebuffer en lui indiquant qu’on utilisera deux DrawBuffers.

On attache ensuite les trois textures au framebuffer.

OpenGL utilise une fonction particulière pour tester le statut des framebuffer objects .

Attention à partir du moment ou un FBO est bound, le rendu ne s’effectue plus dans le framebuffer par défaut.

Il vous faut maintenant modifier la boucle de rendu pour rendre la scène à l’intérieur du framebuffer que vous venez de créer. N’oubliez pas que l’opération de glClear doit être effectuée pour chaque framebuffer, y compris le framebuffer par défaut.

Vérifiez que l’affichage est maintenant noir, mais que l’interface graphique est toujours présente.
Modifiez les variables de sortie du fragment shader.

Modifiez votre shader pour écrire dans Color, la couleur diffuse en rgb et la couleur spéculaire dans l’alpha et dans Normal, la valeur de normale en rgb et le SpecularPower en alpha.

Pour vérifiez vos résultats vous aurez besoin de l’exercice suivant.

Blit

Le shader de blit a pour but d’afficher une image sur la totalité du viewport courant.

1) Affichez la texture de couleur du G-Buffer en overlay.

2_1

Créez un shader à partir du vertex shader et du fragment shader suivants.

N’oubliez pas de remplir l’uniform Texture.

Il faut maintenant une surface sur laquelle appliquer ce shader,  pour cela on crée un rectangle ou quad, cet objet n’a pas pour vocation d’être animé ou transformé, on peut donc le spécifier directement en screen space.

Observez la différence avec la façon dont les autres objets sont définis.
Pourquoi n’utilise-t-on pas de normales, de coordonnées de texture? Dans quelle dimension sont les positions?

Créez un vao spécifique à ce rectangle en vous aidant du code suivant.

On veut afficher maintenant afficher la texture de couleur du G-Buffer à des fins de debug, pour cela il faut pouvoir l’afficher en overlay, c’est à dire par dessus le reste du rendu, mais avant l’interface graphique. On désactive donc le depth test, n’oubliez pas de le réactiver au bon endroit.

Le shader de blit affiche la texture sur la totalité du viewport, pour ne pas recouvrir la totalité de la fenêtre, il faut définir un viewport de plus petite taille, tout en essayant de conserver le ratio original. N’oubliez pas de rétablir le viewport à la taille de la fenêtre.

Il suffit ensuite d’utiliser le shader de blit, de faire un bind de la texture à afficher, et de rendre le rectangle précédemment créé.

2) Affichez les autres buffers.
Modifiez le code précédent pour afficher simultanément les trois textures dans trois viewports différents et vérifiez vos résultats.

Seconde passe : Lighting

Le rendu de la seconde passe doit être effectué après la création du G-Buffer mais avant l’affichage des informations de debug.

1) Rendre une point light

Commencez par créer le shader qui servira à rendre la lumière ponctuelle en espace écran. Réutilisez le vertex shader de blit et utilisez le code suivant comme point de départ du fragment shader.

Dans le framebuffer par défaut, rendez un rectangle plein écran en utilisant le shader de point light.

Vérifiez que vous obtenez bien la couleur du fragment shader sur la totalité de l’écran.

Dans le shader il faut maintenant réussir à reconstruire les trois vecteurs v, n, l, et les propriétés des matériaux. On commence par lire les valeurs contenues dans les trois buffers.

On peut ensuite facilement reconstruire la couleur de diffuse, la couleur de spéculaire, le coefficient spéculaire et la normale.

Affichez ces quatre valeurs pour vérifier que tout fonctionne.

Pour calculer les vecteurs l et v, il faut calculer la position p de la géométrie correspondant au fragment courant. Cette opération consiste à convertir la position x,y,z du fragment dans l’espace choisi pour calculer l’illumination, ici l’espace monde. On commence par calculer la matrice de conversion espace écran à espace monde et à la passer en uniform au shader.

Dans le shader on utilise cette matrice pour reconstruire p.

Il ne reste plus qu’à passer la position de la lumière en uniform pour calculer l et la position de la caméra pour calculer v.

Vous avez maintenant toutes les données nécessaires pour calculer l’illumination par la lumière ponctuelle.

2) Rendre plusieurs lumières ponctuelles.
2_2

Pour afficher plusieurs lumières du même type, il suffit d’une modification très simple du code existant. Il suffit de dessiner un rectangle pour chaque lumière en blending additif. N’oubliez pas de désactiver le blending à la fin du calcul de l’illumination.

3) Rendre d’autres types de lumière.

all_lights_deferred
Créez deux autres shaders pour rendre les spots et les lumières directionelles. Encore une fois, une modification simple du code existant vous permet d’utiliser d’autres types de lumières.

 Optimisation

1) Réduisez la taille du G-Buffer.

Compression des normales :

http://aras-p.info/texts/CompactNormalStorage.html

http://c0de517e.blogspot.fr/2015/01/notes-on-g-buffer-normal-encodings.html

 

2) N’affichez un rectangle que pour les lumières visibles.

3) Réduisez la taille du rectangle à la zone d’influence de la lumière.

Student work

3 thoughts on “Lesson 2 : Lighting

  1. Dans la deuxième passe du deferred Shading, pour trouver les coordonnées en screen to world, pourquoi transposer ?
    Je pense que c’est plutôt la matrice VP que MVP (si la matrice est différente de l’identité).

  2. Effectivement ce n’est pas la peine de transposer, si on change l’ordre de la multiplication dans le fragment shader (j’ai mis à jour). A noter que le corrigé contenait déjà cette correction.

Leave a Reply