Texel
OpenGL | Index | Contact

Les Shadow Volumes



Le principe des Shadow Volumes est de calculer le volume d'ombre généré par un objet occultant une source de lumière. Une fois ce calcul effectué, la sélection de seulement certaines parties de ce volume permet l'affichage de l'ombre projetée. Cette technique est très intéressante, car bien implémentée, elle peut être utilisée dans de nombreuses applications. Les objets peuvent projeter leurs ombres sur eux-mêmes, contrairement à des techniques comme la projection plane ou la projection de texture.


Ici, un volume d'ombre représente les polygones constituant la surface du volume créer par l'extrusion des arrêtes des triangles des objets occultant (Ex: Un cône pour une sphere). Les techniques présentées ici ne fonctionnent qu'avec des objets polygonaux fermés: Les arrêtes de tous les triangles doivent être utilisés par exactement 2 triangles (Ex: Les murs doivent avoir une épaisseur).


I. La technique de base: Zpass

La technique de base pour le rendu des Shadow Volumes est communément appelée méthode Zpass. Ce nom provient de l'utilisation de la fonction Zpass lors de la modification du tampon de stencil (ou stencil buffer).

I.1. Les étapes

Voici les grandes étapes qui permettent d'afficher une scène polygonale avec ses ombres en utilisant la technique des Shadow Volumes. Les détailles nécessaires à leur implémentation sont décrits dans la suite de ce tutorial. Nous verons aussi plus tard comment calculer les coordonnées d'un volume d'ombre.

1ère étape :

On affiche notre scène dans son intégralité mais sans ombre. Ce qui est important c'est que cet affichage met à jour le tampon de profondeur qui sera utilisé dans l 'étape 3.

2ème étape :

On désactive la mise à jour du Tampon de profondeur et du tampon de couleur. On active le tampon de stencil avec une fonction de comparaison qui laisse passer tous les fragments.

glDepthMask(GL_FALSE);
glColorMask(0,0,0,0);

glEnable(GL_STENCIL_TEST);
glClear(GL_STENCIL_BUFFER_BIT);
glStencilFunc(GL_ALWAYS,128, 0xffffffff);








3ème étape :

On affiche le volume d 'ombre créé par les objets éclairés en deux passes :

Lors de la 1ère passe, on affiche les faces avants du volume d'ombre en incrémentant le stencil buffer lorsque le test de stencil et de profondeur est réussi.

glFrontFace(GL_CW);
glStencilOp(GL_KEEP,GL_KEEP,GL_INCR);
AfficheShadowVolume(Scene,Light);






Lors de la 2ième passe, on affiche les faces arrières du volume d'ombre en décrémentant le stencil buffer lorsque le test de stencil et de profondeur est réussi.

glFrontFace(GL_CCW);  
glStencilOp(GL_KEEP,GL_KEEP,GL_DECR);
AfficheShadowVolume(Scene,Light);






Une fois ce rendu effectué, seul les zones d'ombres de la scène on une valeur différente de la valeur de référence dans le tampon de stencil.

Note : Le tampon de couleur étant désactivé, il ne sera pas mis à jour et rien ne sera affiché. De même pour le tampon de profondeur qui sera néanmoins testé lors de l'affichage des volumes dans le tampon de stencil.

L'image ci-dessous est une représentation d'un volume d'ombre traversant le sol d'une scène (le damier). Dans cet exemple, ce volume a la forme d'un pavé (2eme image), créé par la face supérieure (le quad de la 1ère image). L'ombre crée est constituée par la base du pavé tronqué par le sol.


On voit très bien que du point de vue de l'observateur, seule une face est orientée de façon frontale (au premier plan)(on ne compte pas le quad). Cette face incrémentent le stencil buffer, les autres faces le décrémentent. En l'absence de sol dans la scène, les faces avants et arrière devraient s'annuler pour laisser une valeur nulle dans stencil buffer. Mais ici, la présence du sol cache les faces arrières au niveau de la zone qui constitue l'ombre projetée (les faces arrières, en noir sur la 4eme image, ne s'affiche pas devant la zone d'ombre en vert). Ce n'est pas le cas pour les faces avant qui sont les seules à modifier le stencil buffer (dans la zone de l'ombre projetée). Ce déséquilibre est à l'origine de formation de l'ombre.

Note: La décrémentation peut être faite avant l'incrémentation. L'ordre n'a pas d'importance.

4ème étape :

On réactive le tampon de couleur. On affiche un rectangle semi-transparent qui couvre la totalité de l'écran par-dessus notre scène. Ce rectangle est de la couleur de l'ombre (donc gris). On limite l'affichage de ce rectangle dans les zones de l'écran où les valeurs du tampon de stencil sont égales à la valeur de référence. Notre ombre est dessinée !

A gauche la scène sans ombre. Au milieu le volume d'ombre. A droite la scène finale


Note : Pour donner plus de réalisme à la scène, lors de l'affichage de la 1ère étape, il est préférable de ne pas attribuer aux matériaux de la scène leurs propriétés dépendantes de la lumière. Dans le cas contraire, la composante diffuse de la lumière (par exemple) va être appliquée aux matériaux de la scène, aussi bien dans les zones éclairé, que dans les zones d'ombre (ce qui n'est pas naturelle, puisque ces zones sont à l'abri de la lumière).Ces composantes pourront être ajoutées lors de la dernière étape.


II. Le calcul du volume d'ombre.

Pour calculer les coordonnées des vertices qui constitue le volume d'ombre créé par un objet, on commence par chercher la silhouette visible de l'objet vue par la lumière. Cette silhouette une fois extrudée sur une distance assez importante constituera le volume d'ombre.

II.1. Détermination de la silhouette du volume d'ombre à extruder.

Une première méthode pour déterminer la silhouette de l'objet pour l'extrusion du volume d'ombre, on utilise l'algorithme suivant :

Pour chaque face :
	Si elle est éclairée
		Pour chaque arrête :
			On détermine si cette arrête est partagée par une face voisine éclairée
			Si ce n'est pas le cas :
				Cette arrête fait partie de la silhouette.
			Fin Si
		Fin Pour
	Fin Si
Fin Pour












Les indices des vertices de chaque face permettent de déterminer leurs voisines. Ce calcule peu être fait lors du démarrage de l'application puisque la géométrie des objets ne change pas.

Note : On détermine si une face est éclairée par un produit scalaire entre la normale de la face et le vecteur direction de la lumière (que l'on peu calculer pour chaque vertex dans le cas d'une lumière omnidirectionnelle). Ce calcule doit être fait à chaque déplacement de la lumière ou de l'objet.

II.2. L'extrusion

Pour extruder notre silhouette, on projette les vertices des arrêtes de la silhouette pour créer les rectangles (ou deux triangles) qui constitueront la surface du volume d'ombre. Cette projection se fait suivant la direction de la lumière sur les vertices. Il faut bien sûr faire attention au sens de la déclaration des vertices de ces polygones.

VertexProjeté1 =  (VertexArrête1 - Lumière) * Infini;
VertexProjeté2 =  (VertexArrête2 - Lumière) * Infini;





Note: Cette projection peut être faite au niveau du CPU ou au niveau du GPU de la carte graphique par l'intermédiaire des vertex shaders.


III. Les problèmes à régler

La technique décrite précédemment ne fonctionne malheureusement pas dans toutes les situations. Les problèmes suivants peuvent très souvent se poser :

III.1. Fermeture du volume d'ombre (capping)

Dans les paragraphes précédents nous avons extrudé les arrêtes des silhouettes des objets créateurs d'ombres pour créer les volumes d'ombre. Mais nous n'avons pas fermé ces volumes. Ils ressemblent à des tunnels et cela va poser un problème dans les scènes en extérieure.

En effet, dans ce type de scène, les volumes vont pouvoir être extrudés vers des zones de l'espace ne contenant aucun obstacle (le ciel par exemple). Si la caméra est orientée dans la direction vers laquelle un volume est extrudé, l'observateur pourra voir une ombre s'afficher sur un obstacle imaginaire (voir photo ci-contre). La raison de ce phénomène est simple: Les parois du tunnel du volume d'ombre vont incrémenter le tampon de stencil mais en l'absence de bouchons au fond de ce tunnel, certaines zones (précisément celles correspondants au bouchon) ne pourront pas annuler cette incrémentation par des faces arrières.

Le second problème se situe au niveau des faces non éclairées des objets: Les parois des tunnels d'ombre vont ajouter des ombres sur ces faces. Mais les lumières gérées par OpenGL ont déjà fait le traivail (et bien mieux que nous). Ces faces auront donc trops d'ombres.

Dans cette situation la fermeture du volume d'ombre est donc indispensable. Le premier bouchon (cap near) est constitué par les faces non éclairées de l'objet. Ce bouchon permet d'éliminer l'affichage d'une ombre sur les faces arrières.

Pour le deuxième bouchon (cap far), on projette vers l'infinie ces mêmes faces tout comme on a projeté les arrêtes des silhouettes d'ombres. Les polygones de ces bouchons seront utilisés dans le tampon de stencil pour régler le problème des ombres dans le ciel.

III.2. Le plan de clipping

Lorsque la source de lumière est trop proche de l'objet dont il doit projeter l'ombre, il se peut que l'ombre ne soit pas projetée assez loin pour atteindre sa cible (un mur par exemple). Pour éviter ce cas de figure, il est préférable de projeter l'ombre le plus loin possible (proche de l'infini).

Mais attention : Si l'ombre est projetée vers l'infini, le volume va être clippé par le plan de clipping éloigné (far) de la camera. Les bouchons de nos volumes d'ombre ne seront donc pas affichés et testés par le tampon de stencil. Pour éviter ce problème, il faut utiliser une matrice spéciale de projection en perspective qui va repousser ce plan de clipping vers l'infini. La fonction glFrustum d'OpenGL, utilise par exemple la matrice de projection en perspective conique ci-contre :

Ici, n et f représentent la distance entre le point de vue et les plans de clipping proches et éloignés (near et far). l, r, b et t définissent la taille du plan de clipping proche.



Cette matrice devra être remplacée par la matrice P' ci-dessus (où l'on a fait tendre f vers l'infini). On utilise P'' en cas d'imprécision des calculs du matériel dans la normalisation des coordonnées z (mais des extensions OpenGL telle que GL_NV_depth_clamp permettent d'éviter l'utilisation de ces matrices).

III.3. Lorsque la camera est dans le volume d'ombre.

Lorsque la camera est dans le volume d'ombre, des problèmes d'affichage apparaissent avec la technique présentée jusqu'ici. En effet, dans cette situation, le tampon de stencil n'est pas rempli correctement puisque chaque face du volume d'ombre a alors la même orientation par rapport à la camera (face arrière) Il faut alors utiliser le stencil d'une façon différente (Méthode Zfail).



Sur l'image de gauche, la camera est à l'extérieur du volume d'ombre. Sur l'image de droite, la lumière s'est légèrement déplacée et la caméra est donc entré dans le volume d'ombre. Le problème est visible.


IV. Une amélioration du Shadow Volume: La méthode ZFail

Comme nous venons de le voir, la technique du Shadow Volume pose de graves problèmes d'affichage lorsque la caméra est située dans le volume de l'ombre.

C'est en l'an 2000 que le célèbre John Carmack (développeur des jeux Wolfenstein, Doom et quack) présente une technique permettant de résoudre ce problème. Elle consiste en un test de stencil différent: L'incrémentation et la décrémentation du tampon de stencil n'est effectué qu'en cas d'échec du test de profondeur. D'où son nom " ZFail ". Cette méthode d'ombrage est dite robuste car elle est utilisable quelle que soit la position de la caméra ou de la lumière, que se soit pour une scène en intérieur ou en extérieure.


IV.1. Les étapes

1ère étape :

On affiche notre scène dans son intégralité mais sans ombre. Cet affichage met à jour le tampon de profondeur qui sera utilisé dans l 'étape 3.

2ème étape :

On désactive la mise à jour du Tampon de profondeur et du tampon de couleur. On active le tampon de stencil avec une fonction de comparaison qui laisse passer tous les fragments.

3ème étape :

On affiche le volume d 'ombre créé par les objets éclairés en deux passes :

Lors de la 1ère passe, on affiche les faces arrière du volume d'ombre en incrémentant le stencil buffer lorsque le test de stencil échoue.

glFrontFace(GL_CW);
glStencilOp(GL_KEEP, GL_INCR, GL_KEEP);
AfficheShadowVolume(Scene,Light);






Lors de la 2nde passe, on affiche les faces avant du volume d'ombre en décrémentant le stencil buffer lorsque le test de stencil échoue.

glFrontFace(GL_CCW);
glStencilOp(GL_KEEP, GL_DECR, GL_KEEP);
AfficheShadowVolume(Scene,Light);






Une fois ce rendu effectué, seul les zones d'ombres de la scène on une valeur différente de la valeur de référence dans le tampon de stencil.

4ème étape :

Comme dans la méthode classique, on réactive le tampon de couleur. On affiche un rectangle semi-transparent qui couvre la totalité de l'écran par-dessus notre scène. Ce rectangle est de la couleur de l'ombre (donc noir). On affiche tous les pixels du rectangle où la valeur de stencil est différente de la valeur de référence c'est-à-dire les parties non ombrés.

Si on reprend l'exemple du volume d'ombre qui nous à permis d'illustrer la méthode Zpass, on comprend vite que le principe général est le même. Mais cette fois, ce sont les faces arrières, caché par le sol qui modifie la valeur du stencil buffer pour créer l'ombre.

Important: Avec cette méthode, la fermeture du volume d'ombre à ses deux extrémités est indispensable que ce soit dans une scène en intérieur ou extérieure.


Sur la 1ère image de gauche, l'absence de bouchon donne un rendu catastrophique.

Sur la 2ème image, seuls les bouchons lointains sont affichés. On peut donc voir l'intérieur des volumes d'ombre à travers les objets. Ceci explique que tous les objets soit sombres.

Sur la 3ème image, seuls les bouchons proches sont affichés. La source du problème d'affichage est similaire à celui observé dans le ciel avec la méthode Zpass vu précédemment. Le test de stencil étant de type Zfail, c'est l'inverse qui ce produit: l'extrémité lointaine des volumes est visible à travers les objets.

Sur la dernière images, en présence des deux types de bouchon, l'image est parfaite.


V. Les optimisations

V.1. Eviter certain calcules

Des optimisations sont possibles pour accélérer l'affichage des scènes ombrées. La première optimisation consiste à détecter la présence de la camera à l'intérieur du volume d'ombre pour pouvoir déterminer s'il est nécessaire ou non de fermer les volumes.

Noter aussi, qu'il n'est pas nécessaire de recalculer en permanence les silhouettes et les volumes d'ombre lorsque les lumières et les objets de la scène sont fixes.

V.2. Utiliser les extensions des OpenGL

Des extensions OpenGL permettent d'accélérer le rendu. GL_EXT_stencil_two_side permet de ne rendre les faces avants et arrières une seule fois dans le stencil buffer. GL_EXT_stencil_wrap évite la saturation du stencil buffer. Comme nous l'avons dis précédemment, l'extension GL_NV_depth_clamp peut éviter d'avoir à calculer une matrice de projection infinie. Nvidia vient aussi de créer la technologie Ultra Shadow qui permet de limiter la zone de rendu des volumes d'ombre. Malheureusement, ces extensions ne sont pas disponibles sur les cartes graphiques de tous les constructeurs. Mais d'autres extension peuvent servir. La liste et la description des extensions disponibles sont consultables sur le site officiel d'OpenGL (www.opengl.org).

V.3. Utilisation des shaders

L'extrusion des arrêtes du Shadow Volume peut être aussi effectué par grâce au vertex shaders, que ce soit sous OpenGL. De même pour le calcul des silhouettes des ombres.


Conclusion

Contrairement à la technique du shadow mapping, cette technique ne nécessite pas l'utilisation d'extensions particulières et incompatibles avec certaines cartes graphiques. Mais cela ne l'empêche pas de pouvoir aussi utiliser des extensions pour accélérer le rendu. La source de lumière peut être uni ou multi directionnel. L'utilisation d'un spot de lumière ou d'une lumière locale à un volume est aussi possible grâce à un traitement avec le stencil buffer et le test de profondeur (on utilise alors des volumes de lumière avant d'utiliser des volumes d'ombre). D'autres techniques peuvent s'ajouter au shadow volumes pour générer des ombres douces (comme par exemple le shadow mapping) mais le plus simple (et malheureusement le moins rapide) est d'utiliser des area lights (superposer les ombres de plusieurs sources de lumière).

Exemple avec code source


Références

Eric Lengyel, " The Mechanics of Robust Stencil Shadows ", Octobre 2002, Gamasutra

Cass Everitt et Mark J. Kilgard, " Practical and Robust Stenciled Shadow Volumes for Hardware-Accelerated Rendering ", Nvidia

Banu Octavian (Choko) & Brett Porter, Jeff Molofee, NeHe - Lesson 27 (nehe.gamedev.net)

Michael D. McCool, " Shadow Volume Reconstitution from Depth Maps ", Université de Waterloo (http://www.cgl.uwaterloo.ca/Projects/rendering/)




Version originale: Février 2004
Dernière mise à jour: Février 2004
Par Grégory Smialek
www.texel.fr.fm