Ombres en temps-réel Nicolas Holzschuch Cours d’Option Majeure 2 [email protected] Ombres en temps-réel • Pourquoi faire ? • Les ombres • Shadow maps • Shadow volumes • Ombres douces Les ombres : pourquoi ? • Réalisme accru • Positionnement spatial • Information sur les objets • Informations sur le système graphique : – Comment ça marche, pourquoi,… – Nombreuses extensions, additions,… Exemples + Vidéo Ombres dures/ombres douces • Vidéos Techniques • 2 méthodes : – Shadow mapping • Basé image – Shadow volumes • Basé objet – Démos Shadow mapping • Source lumineuse ponctuelle • Principe : – Carte de profondeur de la scène – Vue depuis la source lumineuse • Pour chaque pixel de l’image – – – – Calculer position par rapport à la source Calculer profondeur par rapport à la source Comparer à la profondeur stockée Égal : lumière, plus grand : ombre Shadow volume • Source lumineuse ponctuelle • Principe : – Silhouette des objets vus depuis la source – Plans infinis s’appuyant sur la source et sur chaque arête – Définit « volume d’ombre » • Pour chaque pixel de l’image : – Compter le nombre de plans entrants et sortants – Positif : ombre, nul : lumière Et maintenant, les détails Shadow mapping • Source lumineuse ponctuelle • Principe : – Carte de profondeur de la scène – Vue depuis la source lumineuse • Pour chaque pixel de l’image – – – – Calculer position par rapport à la source Calculer profondeur par rapport à la source Comparer à la profondeur stockée Égal : lumière, plus grand : ombre Shadow mapping • A < B : ombre depth map image plane depth map Z = A light source eye position eye view image plane, aka the frame buffer fragment’s light Z = B Shadow mapping • A≈B : lumière depth map image plane depth map Z = A light source eye position eye view image plane, aka the frame buffer fragment’s light Z = B Carte de profondeur • Comment la générer ? – Pourquoi c’est compliqué ? – back-buffer – pbuffers • Précision/coût – En xy – En z Pourquoi c’est compliqué ? Disque dur Mémoire Mémoire CPU Processeur graphique Carte-mère Écran Carte graphique Comment faire ? • Le CPU ne peut pas faire le travail : – Trop lent – Transfert trop lent • C’est le processeur graphique qui travaille – Comment faire pour dessiner la scène sans l’afficher ? – Deux solutions : back-buffer et pbuffers Double-buffering • L’affichage peut être lent • L’utilisateur voit la scène s’afficher morceau par morceau – Gênant • Idée : double-buffer – – – – Deux buffers On affiche le front-buffer On dessine dans le back-buffer Quand on est prêt : glutSwapBuffers(); Double-buffering • Suppose que la carte soit équipée : – Coût mémoire supplémentaire (léger) – Automatique de nos jours • À demander à la création du contexte OpenGL glutInitDisplayMode(GLUT_DEPTH|GLUT_RGB|GLUT_DOUBLE); • Ne pas oublier d’échanger les buffers… Application aux ombres • On a un endroit pour dessiner ! • On dessine la scène une première fois : – Avec la matrice de projection de la lumière – Directement dans le back-buffer – Ensuite, transfert en mémoire • On dessine la scène une deuxième fois : – Avec la matrice de projection de la caméra – Toujours dans le back-buffer – Échange des buffers Problème • Résolution du back-buffer limitée : – À la résolution de la fenêtre – Problèmes d’aliasing • Si je veux d’avantage de résolution : – pbuffers – Possibilité de rendu sur la carte, par le processeur, dans une zone mémoire spécifique – Résolution plus grande que celle de la fenêtre – Mais pas illimitée – Pas toujours possible, dépend de la carte Pour chaque pixel • Génération de coordonnées de texture – – – – – Matrice de projection de la lampe + conversion Résultat : (r,s,t) coordonnées de texture r distance à la source (s,t) coordonnées dans la carte de profondeur Comparaison r / carteProfondeur(s,t) • Extension OpenGL : – GL_ARB_SHADOW ou GL_SGIX_SHADOW Extensions OpenGL • OpenGL : – Spécifications (www.opengl.org) – Liste de fonctionnalités (glBegin, glEnd…) – Architecture Review Board (ARB) • Extensions : – Nouvelles fonctionnalités – Décision par l’ARB (meetings) – Extensions « officielles » : • • • • http://oss.sgi.com/projects/ogl-sample/registry/ Spécifications approuvées, publiques Nom et prototypes de fonctions publics Différents niveaux d’intégration : – GL_ARB_EXTENSION, GL_EXT_EXTENSION, GL_CONSTRUCTEUR_EXTENSION Extensions OpenGL • Comment savoir si une extension est présente ? – glxinfo – http://www.delphi3d.net/hardware/index.php (liste cartes+drivers = extensions) – glutExtensionSupported("GL_SGIX_shadow"); • On y reviendra au prochain cours GL_SGIX_SHADOW glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_SGIX, GL_TRUE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_OPERATOR_SGIX, GL_TEXTURE_LEQUAL_R_SGIX); • Implémentation très simple Algorithme • Désactiver l’affichage des polygones • Dessiner la scène • Transférer le Z-buffer en mémoire • Ré-activer l’affichage des polygones • Affecter la carte de profondeur comme texture • Activer la shadow-map • Dessiner la scène • Échanger les buffers Algorithme • Désactiver l’affichage des polygones : – glColorMask(0,0,0,0); – glDisable(GL_LIGHTING); • Permet de gagner du temps – La carte graphique travaille moins • Dessiner la scène Algorithme • Récupérer le Z-buffer : glCopyTexImage2D(GL_TEXTURE_2D,0, GL_DEPTH_COMPONENT16_SGIX, 0,0,width,height,0); • Alternative : glReadPixels(0, 0, width, height, GL_DEPTH_COMPONENT, taille, pointeur); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16_SGIX, width, height, 0, GL_DEPTH_COMPONENT, taille, pointeur); • Double transfert CPU/carte graphique ! Algorithme • Ré-activer l’affichage des polygones : glEnable(GL_LIGHTING); glColorMask(1,1,1,1); glViewport(0, 0, winWidth, winHeight); • Activer la shadow map • Dessiner la scène • Échanger les buffers Shadow mapping • Avantages : – Très simple à implémenter – Code compact – Marche toujours (scène quelconque) • Inconvénients : – Problèmes d’échantillonnage (xy et z) – Deux passes de rendu (vitesse divisée par deux) • Ne pas regénérer systématiquement la shadow map, seulement si la source se déplace – Besoin d’extensions OpenGL (disponibles ?) Échantillonnage • Principal inconvénient • Système discrétisé • Double discrétisation : caméra et source lum. • Conflit de précision Précision en xy • La plus visible • Solution : augmenter la résolution de la carte – Limite liée à la carte • Pas toujours suffisant : – – – – Projection de la texture depuis la source Pixels après projection déformés et agrandis Cas idéal : source proche de la caméra Cas le pire : source opposée à la caméra • Animal dans les phares (pas bon pour lui) Cas idéal : lampe de spéléo Caméra Source La couleur représente l’aire projetée d’un élément de surface Le fantôme représente l’ombre de l’objet Cas le pire : source opposée Caméra Source Source opposée Résolution en xy • Principale source d’erreur • Solutions : – Augmenter la résolution – Déformer la shadow map pour augmenter sa résolution près de l’œil – Résolution adaptative • Pas de solution idéale si la source est face à l’œil Shadow mapping • A < B : ombre depth map image plane depth map Z = A light source eye position eye view image plane, aka the frame buffer fragment’s light Z = B Shadow mapping • A ≈ B : lumière depth map image plane depth map Z = A light source eye position eye view image plane, aka the frame buffer fragment’s light Z = B Problèmes de précision Problème de précision • La carte de profondeur est aussi discrétisée en z • Besoin de précision : 16 bits, 24 bits… • Problèmes avec z voisins : – Auto-ombrage des surfaces • Solution : – Déplacer la carte de profondeur (bias) – Trouver la valeur idéale : • Trop peu : les surfaces s’ombrent elles-mêmes • Trop : les ombres disparaissent – glPolygonOffset(); Variantes : ID-buffer • Pour éviter les problèmes d’auto-ombrage • Une couleur par objet • Objet = ? – Quelque chose qui ne peut pas s’ombrer – Convexes • Ombrage si ID objet ≠ ID dans buffer • Pas de problème de précision – Mais besoin nombreuses ID : 16 bits • Problème si objets proches les uns des autres Précision • La résolution effective dépend de la pyramide de vue de la lampe – Large cône de vue : résolution gaspillée • Plus la pyramide est proche des objets, plus on est précis • Rapprocher la pyramide de vue – En xy : faible angle d’ouverture – En z : front plane et far plane rapprochés Shadow Mapping : résumé • Avantages : – – – – Très simple à implémenter, code compact Marche toujours (scène quelconque) Prix indépendant de la complexité de la scène Nombreuses variantes pour améliorer la qualité • Inconvénients : – – – – Problèmes d’échantillonnage (xy et z) Deux passes de rendu Artefacts visibles Sources omni-directionnelles ? Shadow volume • Source lumineuse ponctuelle • Principe : – Silhouette des objets vus depuis la source – Plans infinis s’appuyant sur la source et sur chaque arête – Définit « volume d’ombre » • Pour chaque pixel de l’image : – Compter le nombre de plans entrants et sortants – Positif : ombre, nul : lumière Shadow volume Silhouette des objets • Travail sur le modèle • Pour chaque arête du modèle : – Identifier polygones qu’elle relie – Produit scalaire normale / vecteur vers la source – Si produits scalaires de signe différent : arête de silhouette – Besoin structure de données sur le maillage • Sur-ensemble de la silhouette des objets Volume d’ombre • Plans définis par (arête + source) • Définit volume d’ombre : – En fait, plusieurs volumes imbriqués – On est à l’ombre si on est à l’intérieur d’au moins un volume • Principe : pour chaque pixel, on compte les plans, de l’œil jusqu’à la surface affichée – Entrée/sortie dans le volume – Nombre total de plans croisés Compter les plans • Stencil buffer : – Autre fonctionnalité OpenGL – Buffer auxiliaire, jamais affiché – Opérations possibles : • Incrémenter/décrémenter le stencil buffer • Conditions sur le stencil buffer, actions sur l’écran – Multiples utilisations : • Ombres, réflexions, fenêtres… • Rendu conditionnel • Début de programmation de la carte Utilisation du stencil buffer • Premier rendu de la scène – Initialise le Z-buffer • Rendu du volume d’ombre – Pour chaque plan positif : glStencilOp(GL_KEEP,GL_KEEP,GL_INCR); – Pour chaque plan négatif : glStencilOp(GL_KEEP,GL_KEEP,GL_DECR); • Deuxième rendu de la scène : – glStencilFunc(GL_EQUAL, 0, ~0); • Pour la partie éclairée Algorithme : tracé du volume glDisable(GL_LIGHTING); drawScene(); /* La scène, écl. ambiant glDepthMask(0); /* Ne plus écrire ds Z-buffer glStencilFunc(GL_ALWAYS, 0, ~0); glEnable(GL_STENCIL_TEST); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); glColorMask(0,0,0,0); /* pas modifier framebuffer draw_shadow_volume(); /* plans positifs glCullFace(GL_FRONT); glStencilOp(GL_KEEP, GL_KEEP, GL_DECR); draw_shadow_volume(); /* plans négatifs glColorMask(1,1,1,1); glDepthMask(1); /* On peut écrire ds Z-buffer */ */ */ */ */ */ Algorithme : rendu de la scène glStencilFunc(GL_EQUAL, 0, ~0); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); glEnable(GL_STENCIL_TEST); glDepthFunc(GL_EQUAL); glEnable(GL_LIGHTING); drawScene(); Shadow volume • Avantages : – Ombres précises – Positions quelconques lumière/caméra • Inconvénients : – – – – Calcul de la silhouette (sur CPU, év. long) Besoin de modèles fermés, formés de convexes Deux rendus de la scène, plus rendu du volume fill-rate : tracé de nombreux polygones, qui couvrent l’écran. – Carte limitée en nb. pixels/seconde Mauvais cas pour le fill-rate Shadow volume : améliorations • Et si la caméra est dans le volume d’ombre ? – Le compte des plans n’est plus bon • Système général : – – – – Prendre un point hors du volume d’ombre Compter les plans entre ce point et la surface Exemple de points hors du volume : l’infini Méthode zfail zfail/zpass zfail glDepthMask(0); glStencilFunc(GL_ALWAYS, 0, ~0); glEnable(GL_STENCIL_TEST); glEnable(GL_CULL_FACE); glCullFace(GL_FRONT); glStencilOp(GL_KEEP, GL_INCR, GL_KEEP); glColorMask(0,0,0,0); draw_shadow_volume(); glCullFace(GL_BACK); glStencilOp(GL_KEEP, GL_DECR, GL_KEEP); draw_shadow_volume(); glColorMask(1,1,1,1); glDisable(GL_CULL_FACE); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); glDepthMask(1); Limites du volume d’ombre • Le volume d’ombre est défini par des plans • Les plans vont de l’arête à l’infini • L’infini est difficile à gérer – En pratique, on coupe à une certaine distance – Que se passe t-il si on voit le volume d’ombre à cet endroit ? – Et si la source est proche de la caméra ? • Il faut que le volume d’ombre soit fermé : – Si on coupe, on ajoute des polygones de fermeture Limites du volume d’ombre • Applications : limiter le fill-rate • Plus le volume est petit, plus le fill-rate est bas • Couper les plans : – far clipping plane – Et fermer le volume : • Sur les arêtes, par des plans • À l’infini, par des plans – Ça marche encore – Pyramide de vue de la source Dark cap/light cap Limitations du volume d’ombre Extensions OpenGL •GL_EXT_stencil_two_side – Pour faire les deux faces du volume d’ombre en une seule passe •GL_NV_depth_clamp – Pour avoir des plans qui vont vraiment à l’infini •GL_EXT_depth_bounds_test – Pour ne rasteriser que les primitives proches de la source Shadow volume • Avantages : – Ombres précises – Positions quelconques lumière/caméra – Si bien programmé, robuste • Inconvénients : – Calcul de la silhouette (sur CPU, év. long) – Scènes spécifiques : modèles fermés, formés de convexes – Deux rendus de la scène, plus rendu du volume – fill-rate limité Ombres douces • Algorithmiquement plus compliqué • Problème de visibilité point-surface – Au lieu de point-point – Silhouette ? • Ombre de la somme ≠ somme des ombres • Plusieurs algorithmes approximatifs Ombre/pénombre Combinaison d’ombres Problèmes de silhouette Ombres douces • Accumulation d’ombres : – – – – – Calculer plusieurs ombres ponctuelles Additionner les résultats, moyenne Accumulation buffer Nombre d’échantillons élevés Temps de calcul multiplié par # échantillons Accumulation 4 échantillons 1024 échantillons Ombres douces • Recherche de voisins : – Shadow map normale – Pour chaque pixel dans la shadow map • Rechercher frontière de l’ombre la plus proche • Donne position + distance (source, récepteur) • Coefficient d’atténuation fonction du rapport des distances – Lent (recherche sur r2 pixels) – Limiter r : taille de la pénombre limitée Ombres douces • Volume d’ombres douces : – Shadow volume normal – Pour chaque arête de la silhouette : • Calculer volume englobant la pénombre • Pour chaque pixel dans ce volume – Calculer coefficient d’atténuation – Beau, réaliste – Problèmes de fill-rate multipliés par 2 Résumé : OpenGL • OpenGL : – – – – – – Z-buffer Double-buffer Pbuffers Extensions Stencil buffer Accumulation buffer • Cartes graphiques : – – – – Rendu en plusieurs passes Programmables (en un sens) utilisées pour faire des choses complexes (ombres) Ce n’est que le début Résumé : ombres • Shadow maps : – Stable, robuste, facile, rapide, aliasage • Shadow volumes : – Beau, difficile, complexité algorithmique • Ombres douces – Complexe, lent, beau