M2 - GamaGora / UE Rendu
Année 2007-2008

Shadow map et applications






objectifs : éclairer "correctement" une scène 3D.

Les premiers shaders permettent de donner un aspect plus ou moins détaillé aux objets affichés, mais il manque encore un aspect important : les ombres et bien sur l'éclairement indirect.

Les premières parties du sujet présentent les shadow maps et leur réalisation. Les parties suivantes se concentrent sur une approximation courante de l'éclairement indirect : l'Ambient Occlusion.
Deux variantes sont présentées : la plus simple ne stocke qu'une constante (le terme ambient), alors que l'autre construit une représentation directionnelle plus précise qui permet de calculer le terme ambient.


Partie 1 : introduction aux Shadow maps

    L'ojectif est de déterminer pour chaque pixel s'il éclairé par une source de lumière, ou à l'ombre. Le principe est simple : un point à la surface d'un objet est éclairé lorsqu'il "voit" directement la source de lumière. Dans l'autre cas, un autre objet bloque la lumière, ce qui "produit" une ombre.

    On peut exprimer cette condition différement :
    si le point est le plus proche de la source de lumière, il est éclairé, sinon, il est à l'ombre.

    Le schèma suivant illustre le principe :




    Comment réaliser ce test ?
    indication : une carte graphique traite les sommets et les fragments / pixels indépendemment les uns des autres, il n'est donc pas possible de savoir directement si un objet se trouve entre la source de lumière et l'objet en cours d'affichage.

       La solution "clasique" consiste à afficher deux fois la scène : une fois vue de la source de lumière afin de stocker les objets éclairés (et leur distance à la source), et une autre fois depuis la caméra. Pour afficher la scène du point de vue de la source de lumière, il faudra construire une caméra qui permet de visualiser tous les objets projettant des ombres sur les objets visibles de la caméra.

    Dans la deuxième étape, lors de l'affichage depuis la caméra, il suffit de recalculer la position de chaque fragment dans le repère (projectif) de la caméra associée à la source de lumière et de comparer cette distance à celle stockée dans la première étape. Si la distance est inférieure ou égale à celle stockée : le fragment est visible de la source, il est donc éclairé. Dans l'autre cas, le fragment n'est pas le plus proche de la source et il est donc à l'ombre.

Partie 2 : réalisation

    En résumé :

    Quelques précisions supplémentaires :

Partie 3 : application à l'Ambient Occlusion,
ou, le terme ambiant comme approximation de l'éclairement indirect


 


    Les shadow maps permettent de calculer l'énergie directe qui éclaire l'objet et le terme ambiant représente le reste (cf. CM Lumière et Matières pour un rappel). Calculer réellement le terme ambiant pour chaque sommet ou chaque fragment est beaucoup trop long : on le remplace par un pré-calcul approximatif. Une approximation "classique" consiste à compter, pour chaque sommet d'un objet, le nombre de sources de lumières visibles (cf. illustration ci-dessus, les sources sont représentées par les points jaunes).

Pré-calcul

    L'idée est d'éclairer chaque objet par un grand nombre de sources de lumière disposées tout autour de lui et de compter, pour chaque sommet, le nombre de sources visibles. Le terme ambient correspond à la fraction de sources visibles sur le nombre total de sources.

    En utilisant une shadow map, il est même possible de faire plus précis : compter le nombre de sources visibles pour chaque élément de la surface de l'objet, ou pour chaque texel de la texture de l'objet.

    Dans le calcul classique d'ombre, on dessine l'objet en 3D : pour chaque fragment, on compare sa distance dans le repère projectif de la source de lumière à celle stockée dans la première étape, pour déterminer si le fragment est éclairé, ou à l'ombre. Plus précisement : on projette la position 3D de chaque sommet dans le repère projectif de la source de lumière, puis pour chaque fragment, on utilise la position 3D du fragment pour déterminer s'il est visible de la source.

    Dans le calcul du terme ambient, on veut calculer, pour chaque texel de la surface de l'objet, s'il est visible de la source de lumière. On dessine alors l'objet en 2D, dans l'espace de la texture, en lui associant sa position dans l'espace 3D. On projette la position 3D dans le repère projectif de la source de lumière, puis pour chaque fragment, on utilise la position 3D du fragment pour déterminer s'il est visible de la source.

    Relisez attentivement ces 2 derniers paragraphes : d'habitude on dessine en 3D, en associant aux positions 3D une position 2D dans l'espace de la texture. Ce qui signifie que chaque point 3D a un point 2D correspondant dans la texture.

    On veut faire différement : on dessine en 2D dans l'espace de la texture et on associe aux positions 2D une position 3D dans l'espace de l'objet. Ce qui signifie que chaque point 2D de la texture a un point 3D correspondant dans le repère de l'objet.

    Illustrations dans l'espace 2D de la surface de l'objet, et ombre correspondante (la source est à la verticale au dessus de bigguy) :

  

    Il ne reste plus qu'à recommencer pour plusieurs sources de lumières et calculer le nombre de sources de lumière visibles pour chaque texel. On peut améliorer la qualité du résultat, en tenant compte de la direction de la source et de la normale (à droite).

  


utilisation du terme ambiant pré-calculé :

    Le résultat de l'étape précédente est une texture, il suffit de l'appliquer à l'objet et d'ajouter sa couleur à celle calculée pour l'éclairement direct.

    On peut également filtrer la texture et récupérer une couleur pour chaque sommet de l'objet.


Partie 4 : ambient occlusion directionnel

    Une amélioration est utilisée par Valve dans la série "Half-Life". Au lieu de n'utiliser qu'une valeur constante, ils conservent 3 valeurs associées à des orientations différentes de la surface de l'objet. C'est particulièrement interressant lorsque l'on utilise une normal map pour "ajouter" des détails à l'objet.

    Le calcul est le même, sauf qu'il faut le faire pour 3 directions exprimées dans le repère tangent de chaque face de l'objet.
    Lors de l'affichage de l'objet, la normal map fournit la normale détaillée pour le calcul de l'éclairement direct et le terme ambiant est tout simplement interpolé à partir des 3 valeurs pré-calculées. Les poids de l'interpolation sont calculés par produit scalaire de la normale détaillée et des 3 directions utilisées lors du pré-calcul.

    référence : Valve "Source Engine 2006" cf. la partie "Basis for Radiosity Normal Mapping"
    référence : Valve "Source Engine 2004"

Partie 5 : pré-calculs pour l'ambiant directionnel

    Il manque une dernière étape pour obtenir un rendu cohérent. En effet, les pré-calculs du terme ambiant placent arbitrairement des sources de lumières tout autour de chaque objet, ce qui est rarement le cas dans la scène complète.

    Il faut donc trouver un moyen d'adapter la position et le nombre de sources de lumières utilisées pour le pré-calcul en fonction de la scène complète. Si l'objet est fixe dans la scène, pas de problème particulier, il suffit d'utiliser les mêmes sources de lumière. Par contre, lorsque l'objet peut se déplacer, il faut trouver un moyen d'adapter le pré-calcul à toutes les conditions possibles d'éclairage de l'objet dans la scène ... sans stocker trop d'informations.

    On utilise encore une approximation (pas très subtile) : on ne considère que les 6 directions correspondants aux normales d'un cube placé autour de l'objet. Le pré-calcul du terme ambiant n'est pas modifié, mais on va réaliser un pré-calcul supplémentaire qui stocke la quantité de lumière arrivant le long de ces 6 directions pour chaque point de l'espace de la scène !

    On peut utiliser cette information de 2 manières différentes : soit on interpole une seule direction pour calculer l'éclairement indirect, soit on calcule l'éclairement indirect pour les 6 directions.

    Lors de l'affichage de l'objet, il faut trouver les "cubes" les plus proches de l'objet pour obtenir la quantité de lumière arrivant le long des 6 directions.


    En résumé :

    référence : Valve "Source Engine 2006" cf. la partie "Ambient Cube Basis"
    référence : Valve "Source Engine 2004"

    utilitaires : D3DX ID3DXRenderToEnvMap, version cubeMap

    remarque : le même pré-calcul effectué sur les sommets des objets de la scène s'appelle 'radiosité'. C'est encore une fois une approximation, puisque les calculs ne 'simulent' que l'éclairement indirect du à 1 seul rebond de l'énergie (de la source vers un réflecteur puis vers le centre du "cube"). Pour obtenir la "vraie" radiosité, il faut faire le pré-calcul dans l'espace de la texture de la scène (cf. section précédente) et considérer que chaque triangle peut re-émettre la lumière qu'il reçoit des autres triangles / sources de lumières.
   
    références supplémentaires : P. Nettle, "Radiosity in english", part 1, part 2, demo + source
                                                       H. Elias "Radiosity"

Partie Subsidiaire : amélioration de la qualité des shadow maps

    Une méthode de filtrage simple et élégante permet de réduire les "gros carrés" : 'Variance Shadow Maps'.

    Une autre méthode interressante pour choisir le "réglage" de la caméra associée à la source de lumière : 'Parallel Split Shadow Maps'. Particulièrement utile pour les scènes d'extérieurs.

    Autre technique simple pour améliorer la qualité des shadow maps et limiter les problèmes de précisions du zbuffer : 'Linear Zbuffer'.



Partie Subsidiaire : pénombres ou ombres douces - "Soft Shadows"