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é :
- étape 1
- construire une caméra placée au niveau de la
source de lumière et choisir sa pyramide de vision (son frustum)
pour visualiser les objets qui projettent des ombres sur les objets
visibles de la caméra de la scène.
- activer un render target avec un zbuffer pour stocker la
profondeur de chaque fragment visible de la source
- dessiner la scène dans le render target
- désactiver le render target pour dessiner dans la fenetre
- étape 2
- activer une unité de texture avec le zbuffer de
l'étape 1
- activer le vertex shader responsable de calculer la position
des sommets dans le repère de la caméra et dans le
repère de la source de lumière ( ... dans le
repère de la caméra utilisé à
l'étape 1)
- activer un pixel shader responsable de tester la position du
fragment par rapport à la source de lumière et de
calculer sa couleur : éclairé ou à l'ombre
- dessiner la scène
Quelques précisions supplémentaires :
- la projection d'un sommet produit des coordonnées
homogènes dans le repère projectif unitaire de la
caméra, les coordonnées sont donc entre -1 et 1. Pour
trouver les coordonnées du fragment dans le zbuffer, il faut
calculer les coordonnées du fragment dans l'espace de la texture
: entre 0 et 1. C'est encore un changement de repère.
- documentation directX :
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é :
- pour le pré-calcul, on place une grille dans la
scène et pour chaque sommet de la grille on calcule un "cube".
Pour chaque face de chaque cube, on affiche la scène.On
considère que chaque pixel de chaque face est une source de
lumière. Donc, pour
chaque pixel de la face, on calcule la direction correspondante (par
rapport au centre du cube) et la quantié de lumière qui
arrive le long de cette direction (c'est la couleur du pixel). Enfin,
on accumule le résultat sur la normale de la face du cube (en
tenant compte de la direction associée à chaque pixel).
- pour l'affichage de l'objet dans la scène : on trouve les
cubes entourant l'objet, on interpole l'énergie arrivant le long
des 6 directions et la direction moyenne correspondante. On utilise ces
2 résultats pour finir le calcul du terme ambiant.
- ou : on calcule un terme ambient pour les 6 directions.
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"