M2 Image

TP4 - pipeline graphique,
episode 2...


Partie 1 : et les ombres ?

vous pouvez utiliser robot_studio.obj ou robot_studio.gltf comme scène de test pour cette partie du tp, ce sera plus simple que de manipuler une scène gltf complète...


Résumé : comment ca marche les ombres avec openGL ?

réponse directe : ca ne marche pas, lorsque l'on dessine un triangle, on a pas d'information sur les autres triangles qui pourraient faire de l'ombre...


ah ? comment on fait alors ?

facile, mais il va falloir dessiner 2 fois la scène,

mais on "garde" les triangles visibles par la source de lumière, on sait qu'ils sont éclairés, c'est le résultat de la 1ere étape. pour chaque pixel de l'image dessinée pour le point de vue de la source on connait la distance entre la source de lumière et le triangle visible.

ensuite, lorsque l'on dessine la scène pour le point de vue de la camera, on transforme le point visible pour la camera dans le repère / le point de vue de la source. il ne reste plus qu'à comparer la distance entre le point transformé et la source avec la distance de la 1ere étape, qui correspond au triangle le plus proche de la source, ie celui qui est éclairé. si le point est à la bonne distance / sur le triangle éclairé, il est lui aussi éclairé, sinon sa distance est plus grande et il est à l'ombre.

euh ? et sinon ?

on fait le tp...


exercice 1 : dessinez la scène pour le point de vue de la source de lumière.

on commence par construire les transformations qui permettent d'observer la scène depuis la position de la source de lumière. il faut, comme d'habitude, définir les transformations model, view, projection et image.

le plus direct est d'utiliser une projection ortographique, cf Ortho() qui "projette" un cube [left .. right] x [bottom .. top] x [znear .. zfar] du repère camera vers le repère projectif.
des exemples complets sont dans la doc en ligne, cf "decals et projection de textures"


exercice 2 : création du framebuffer et des textures

étape suivante, il faut conserver les triangles visibles par la source de lumière, ils seront éclairés. ces triangles sont aussi les plus proches de la source de lumière (par construction), on veut conserver au minimum le zbuffer, ie la distance entre la source de lumière et le triangle visible / éclairé.

la création / configuration d'un framebuffer est assez directe, mais il y a bien sur quelques détails techniques à régler, cf "rendu multi-passes". vous pouvez utiliser les utilitaires de texture.h pour créer simplement les textures nécessaires, cf make_depth_texture() ou make_vec3_texture(), etc. elles sont faites exactement pour ça !

au final, votre code ressemblera à :

    GLuint shadow_map= make_depth_texture( /* unit */ 0, /* width */ 256, /* height */ 256 );

    GLuint framebuffer= 0;
    glGenFramebuffers(1, &framebuffer);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);

    glFramebufferTexture(GL_DRAW_FRAMEBUFFER, /* attachment */ GL_DEPTH_ATTACHMENT, shadow_map, /* mipmap */ 0);

    // verification de la configuration du framebuffer
    if(glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
        return "error";

    // nettoyage...
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);

pour dessiner dans le framebuffer, il ne reste plus qu'à le sélectionner (avant de dessiner la scene), mais attention aux paramètres implicites du draw() !
(par exemple les dimensions de l'image... cf glViewport())


exercice 3 : et maintenant les ombres !!

et voila, tout est pret !! il ne reste plus qu'à dessiner la scène depuis le point de vue de la camera et à écrire le test qui vérifie qu'un point (vu par la camera) est plus près ou plus loin que le triangle éclairé par la source de lumière. les détails de la projection et des transformations sont dans "decals et projection de textures" / section "finir le calcul : projetter le décal"

attention : dans le shader, la fonction texture(shadow_map, uv) renvoie un vec4, la distance stockée dans la texture se trouve dans la composante .x ou .r (et pas .z qui sera toujours 0...)


exercice 4 : et maintenant ? des ombres propres !!

et voila ça marche !


ou pas, c'est quand même moche, non ?

argh, mais pourquoi ??

le zbuffer stocke une distance par pixel :


mais cette profondeur est constante : tous les points du triangle qui se projettent sur le même pixel ont la même distance, et le triangle n'est plus vraiment une surface, mais une approximation :

et lorsque l'on dessine la scène depuis un autre point de vue, on utilise la distance d'un autre point du triangle, et ces distances ne sont pas tout à fait identiques... donc il va falloir modifier le test...




il y a plusieurs solutions : on peut ajouter une tolérance lors de la comparaison (comme pour le lancer de rayon, cf doc en ligne), on peut arrondir la position du point pour correspondre au point stocké, et recalculer sa distance avant de faire la comparaison, on peut aussi stocker (ou recalculer) la normale du triangle et interpoler la distance, etc. mais le test reste quand même fragile.

pour les curieux : arrondir la position du point et recalculer sa distance, cf "Adaptive Depth Bias for Shadow Maps", H. Dou, Y. Yan, E. Kerzner, Z.Dai, C.Wyman, 2014


les solutions récentes utilisent une autre solution : il faut se rendre compte que le problème n'apparait que sur les surfaces éclairées lorsque les distances sont proches, pas sur les surfaces bien séparées / différentes. il n'y a pas de problème sur l'ombre du sol (les surfaces sont éloignées), ils sont principalement visibles sur la tête du robot... unreal et unity par exemple, proposent des "screen space contact shadows" : c'est un lancer de rayon local qui est réalisé sur le zbuffer de la caméra lorsque les surfaces sont proches (sans intersecter les triangles, uniquement en comparant la profondeur d'un point sur le rayon à la profondeur du zbuffer, on ce type de lancer de rayon du ray marching, on teste plusieurs points le long du rayon pour trouver l'intersection), sinon la comparaison est faite directement.

un autre problème apparait lorsque la résolution de l'image camera est très différente de celle de la shadow map... par exemple, pour un objet loin de la camera et proche de la source... il faudrait tester tous les pixels de la shadow map qui se projettent dans un pixel de l'image, et c'est très lent, malheureusement on ne peut pas moyenner les profondeurs de la shadow map (ie construire les mipmaps) et faire un seul test sur la profondeur moyenne, le test n'est pas linéaire. mais bien sur, on peut construire une version linéaire de ce test, par exemple "Variance Shadow Maps" W. Donnelly, A. Lauritzen, il y a d'autres constructions possibles, cf Gpu Gems 3, chapitre 8 par exemple, et bien sur la version brutale, cf la méthode PCF.


Partie 2 : rendu direct et différé