Pendant des années, la majorité des ombres dans les jeux vidéo étaient précalculées et statiques. De nos jours, les environnements se voulant de plus en plus interactifs et animés, le nombre d’objets statiques a fortement diminués. De plus, les ombres statiques demandant beaucoup de mémoires pour être stockés, elles ne sont pas adaptées à des niveaux gigantesques comme le propose les open worlds modernes. Pour ces raisons, beaucoup de jeux récents comme Battlefield 1, The Legend Of Zelda: Tears of The Kingdom, Horizon Zero Dawn, … n’ont quasiment que des ombres dynamiques.

Chaque jeu gère les ombres différemment selon la puissance de la console et l’objectif de réalisme visé. Il existe donc de nombreuses façons de rendre des ombres en temps réel. Dans cet article, nous allons voir les méthodes les plus utilisées et les plus prometteuses.

Les types de lumières

Dans les moteurs de jeux, il existe 4 principaux types de lumière.

  • Directional light : Éclaire de façon uniforme tout le niveau dans une direction, souvent utilisé pour simuler la lumière du soleil.
  • Spot light : Éclaire une zone conique.
  • Point light : Éclaire dans toutes les directions comme une ampoule.
  • Area light : Zone éclairant comme un écran ou un tube néon.

Il est possible de générer des ombres dynamiques avec tous les types de lumières, mais certaines sont plus complexes que d’autres. Beaucoup de jeux se limitent aux moins couteuses, les directional lights et les spots lights. Les méthodes expliquées juste après s’appliquent à ces types d’éclairages et nécessitent parfois quelques modifications pour fonctionner avec les areas et les points lights.

I – Générer une ombre (Hard Shadows)

La première étape dans la génération des ombres est de détecter si un pixel rendu à l’écran est éclairé ou non.

Shadow Map

Une des méthodes les plus anciennes est la shadow map. L’idée est de faire un rendu de la scène depuis le point de vue de la lumière. Ce rendu va stocker dans chaque texel (~pixel) d’une texture la distance entre la lumière et l’objet le plus proche. Ce que l’on obtient s’appelle une depth map. À partir de cette simple texture, il est possible de savoir si ce que la caméra principale voit est à l’ombre ou non.

Pour savoir si un pixel est ombragé, il faut transposer la position du pixel visible vers sa position dans l’espace de la lumière pour en déduire ses coordonnées dans la depth map. Il suffit alors de comparer la distance entre la lumière et le pixel visible par la caméra avec la distance stockée dans la depth map. Si le pixel que l’on voit est plus proche ou à distance égale du pixel de la depth map, alors il est dans la lumière. Sinon, c’est qu’il est derrière l’objet vu par la lumière donc il est dans l’ombre. La shadow map est à la base de nombreuses techniques modernes.

 La shadow map à plusieurs défauts importants. La qualité des ombres dépend de la taille de la texture utilisée et de la taille de la zone que l’on souhaite couvrir. Plus la zone est grande plus la texture devra être grande pour stocker un maximum d’informations et limiter l’effet pixélisé de l’ombre. Un autre défaut est le fait que l’ombre d’un objet soit détaché de sa position réelle (petter-panning). Cela est causé par le fait de devoir corriger la shadow acné. La shadow acné est due à l’imprécision des distances stockées dans la depth map. Lorsque l’on va comparer les distances des pixels vus par la caméra avec celles stockées dans la texture il risque d’y avoir des faux négatifs, où un pixel éclairé est compris comme étant dans l’ombre. Pour corriger cela, un biais est introduit. Si la distance entre les 2 points est inférieur à ce biais, alors le pixel est éclairé. Grâce à cela, moins de faux négatifs mais l’ombre d’un objet au sol se retrouve alors légèrement décalée (plus ou moins loin selon la valeur de biais choisit).

Shadow Cascades

La technique la plus utilisée de nos jours pour le rendu d’ombres temps réel est une évolution de la shadow map. C’est la méthode des shadow cascades (Unity URP & HDRP, The Witcher 3, Horizon Zero Dawn, The Legend Of Zelda: Tears Of The Kingdom, …). L’idée va être de faire plusieurs rendus des ombres de plus en plus grands en fonction de la distance à la caméra. Cela permet d’avoir des ombres proches détaillées avec un aliasing (pixelisation) limité et des ombres de plus basses qualités au loin.

Le défaut des shadow cascades est le nombre et la taille des rendus nécessaires. Pour limiter ces rendus, il est possible d’utiliser une méthode de cache comme le propose le CryEngine (la série Crysis, Prey…). Les cascades les plus éloignées ont un rendu centré autour du joueur. Tant que le joueur reste dans une zone restreinte, pas besoin de rendre à nouveau ces cascades. Elles seront rendues uniquement s’il s’éloigne trop du centre de la shadow map.

Unreal Engine 5 propose le virtual shadow mapping. Un mélange entre le virtual texturing et les shadow cascades. Le virtual texturing consiste à utiliser des textures très grandes (supérieur à la 8k) et à envoyer sur le GPU uniquement les parties visibles de cette texture pour limiter l’impact mémoire de son utilisation. Avec le virtual shadow mapping, le monde est séparé en zones ayant chacune leur shadow map. Elles sont plus ou moins grandes selon la distance avec le joueur. Les shadow maps sont stockés dans une grande texture commune. Pour limiter les rendus de ces zones, les ombres sont mises à jour seulement si un objet bouge dans la zone ou si la lumière se déplace. Cette technique permet d’avoir des ombres très détaillées et de bonnes performances.

Shadow Volumes – Doom 3

Certaines méthodes sont moins répandues mais peuvent être utile dans des cas précis. C’est le cas de la méthode des shadow volumes notamment utilisée sur Doom 3. L’idée ici va être d’allonger les triangles des meshs dans la direction opposée à la lumière pour détecter les pixels ombragés. Si un pixel se trouve entre deux triangles allongés alors il est à l’ombre.

Pour détecter les pixels ombragés, il faut faire une passe de rendu uniquement avec les triangles allongés. Dans ce rendu, à chaque fois que l’on rend une face avant d’un triangle on ajoute +1, lors du rendu d’une face arrière -1. Lors du rendu principal, pour chaque pixel il suffit de regarder la valeur stockée dans le rendu précédent pour savoir si on est à l’ombre. Si la valeur est différente de 0 alors on est dans l’ombre sinon la face est éclairée. Les valeurs peuvent être stockées dans le stencil buffer. C’est le cas dans Doom 3 par exemple, la méthode est alors parfois appelée stencil shadowing.

Cette méthode génère des ombres avec une qualité parfaite. Pas de pixélisation et une emprunte mémoire fixe égale à la qualité de l’écran. Le problème c’est l’allongement des triangles qui crée un surcoût important pour le GPU dans les scènes complexes. De plus, la technique se limite à des ombres fortes et est complexe à adapter pour avoir des ombres douces. Si le sujet vous intéresse, cet article paru dans GPU Gems explique en détail l’algorithme, ces avantages et inconvénients.

II – Adoucir l’ombre (Soft Shadows)

Avec les shadow maps nous avons une ombre forte sans pénombre. Les jeux ne cherchant pas le réalisme se suffisent parfois de ça mais pour les autres il faut trouver un moyen d’adoucir les ombres. Dans la réalité, l’ombre d’un objet est plus ou moins diffuse selon sa distance au sol. Le pied d’un arbre aura une ombre nette alors que ses branches et feuilles créeront une ombre plus diffuse. Cet effet, en plus d’ajouter du réalisme, permet d’avoir une meilleure compréhension des distances.

Générer une pénombre fixe

La façon la plus simple d’adoucir les bords d’une ombre est le Percentage-Closer Filtering (PCF). L’idée est de regarder pour chaque pixel rendu à l’écran, pas un seul texel correspondant dans la shadow map mais aussi ceux qui l’entourent. La fonction décrivant la zone couverte et le poids utilisés pour chaque texel s’appelle un kernel ou un filtre. Avec ce kernel on va faire la moyenne de la valeur des texels et en déduire l’intensité de l’ombre. Grâce à cela, si un pixel à rendre est sur le bord d’un objet dans la depth map, alors l’intensité de l’ombre obtenu sera faible. Ainsi, on obtient des objets aux bords doux. Plus on veut une ombre douce, plus il faudra un kernel couvrant une large zone. L’inconvénient de cette technique est que les objets ont tous une ombre avec des bords identiques quelle que soit leur distance par rapport à la source. Mais elle à l’avantage d’avoir un coût de calcul fixe. Cette technique simple suffit à beaucoup de jeux.

Si l’on prend tous les texels environnant naïvement, les ombres résultantes ont tendance à créer des artefacts de blocks et à rester partiellement aliasées. Avec cette approche, produire une ombre très douce demande beaucoup d’accès mémoire en couvrant une large zone. Pour améliorer les résultats, il existe différentes formes de kernels pour choisir les texels environnants dans la depth map. Parmi les plus utilisées, il y a le poisson-disk et le vogel-disk. En ajoutant une rotation aléatoire à ces fonctions, on obtient un paterne qui ne se répète pas et limite les artefacts visuels. Les méthodes de disques sont faites pour choisir des texels uniformément dans un cercle autour d’un point. Elles permettent avec peu de sampling de couvrir une large zone dans la shadow map et d’obtenir des soft shadows très correctes.

Générer une pénombre variable

Le percentage-closer filtering possède beaucoup d’évolutions différentes. L’une des plus utilisées est le Percentage-Closer Soft Shadows (PCSS). Avec cette méthode, la distance de l’objet créant l’ombre (bloqueur) par rapport à l’endroit recevant l’ombre (receveur) est prise en compte. Plus un bloqueur est loin du receveur, plus le kernel utilisé sera grand pour prendre en compte une large zone dans la shadow map.

Pour trouver les bloqueurs influençant le pixel à rendre, il faut faire une recherche dans la shadow map. La recherche se fait avec les mêmes types de kernel que décrit précédemment. Si plusieurs bloqueurs sont détectés, on prend la moyenne de leur distance pour déduire la taille du kernel à utiliser. Avec un kernel naïf, si un bloqueur est très éloigné du receveur, le nombre de texel pris en compte peut devenir très conséquent et augmenter le temps de calculs.

Pour éviter cela, il est commun d’utiliser des kernels comme le poisson-disk ou le vogel-disk et d’augmenter le radius du disque en fonction de la distance receveur/bloqueur. De cette façon on peut couvrir une large zone en ayant un nombre d’accès à la texture fixe. Le résultat est théoriquement moins précis, mais permet de garder des performances stables.

D’autres méthodes de soft shadows existent, mais semblent peu utilisées actuellement dans le jeu vidéo. Détailler le fonctionnement de chacune d’entre elles serait trop long, mais je vous encourage à vous y intéresser. Les plus importantes générant des ombres à pénombre fixes (comme le PCF) sont les Variances Shadow Maps, les Convolution Shadow Maps et les Exponential Shadow Maps. Un ingénieur de Nvidia détaille dans cette présentation les avantages et inconvénients de chaque méthode. Pour les ombres à pénombres variables, les autres méthodes régulièrement citées sont les Smoothies et les Penumbra-Wedge. Une méthode récente, celle des Moment Shadows semble très prometteuse et permettrait d’avoir une pénombre variable à un coût bien moindre que le PCSS. En plus de cela, elle supporte les objets transparents.

Analytical Soft Shadow – The Last Of Us

Dans le cas d’un éclairage très diffus, sans ombre marquée, l’utilisation des méthodes vues précédemment ne donnent pas de bons résultats. La solution est de simplifier la représentation des objets avec des sphères ou des capsules. Cela permet de faire des calculs complexes avec un coût raisonnable. Le rendu de la shadow map se fait depuis le point de vue de la caméra principale. L’idée de base est, depuis chaque pixel, de projeter un cône dans la direction inverse de la lumière. Plus une des formes analytiques prend une partie importante du cône, plus elle produit une ombre forte. Cette méthode permet un éclairage en intérieur de bâtiment très convaincant. Mais le coût est important et son usage se limite le plus souvent à quelques personnages.

Cette méthode est utilisée dans The Last of Us et est disponible dans Unreal Engine.

III – Améliorer les détails – Les méthodes plus avancées

Les méthodes précédentes se basent toutes sur le principe de shadow map et ne corrigent pas ou seulement partiellement ses défauts. D’autres méthodes plus avancées permettent de palier à ces problèmes, mais cela a un coût ou introduit d’autres limitations.

Screen Space Shadows (Contact Shadows)

Parmi les défauts des shadow maps, figurent le fait d’avoir une qualité limitée et du petter-panning (détachement de l’ombre). La méthode des Screen Space Shadows règle ces deux problèmes. L’idée ici va être de déduire l’ombre de chaque pixel à partir de ce qui est visible à l’écran. Pour ça, il nous faut une texture contenant les normales visibles à l’écran et une texture de profondeur. Deux textures disponibles lors d’un rendu différé (pré-rendu de la couleur, des normales et de la profondeur dans des textures différentes. Pour ensuite calculer l’éclairage et les effets uniquement sur les pixels visibles).

À partir de la texture de profondeur, on peut déduire la position du pixel dans le monde. La normale nous permet de savoir dans quel sens regarde le triangle auquel ce pixel appartient. Si le triangle est dirigé dans une direction opposée à la lumière, alors il est ombragé. Sinon, on simule un lancer de rayon depuis le pixel vers la lumière. On va alors progresser pas à pas le long de ce rayon (raymarching) et le projeter dans l’espace 2D de la texture de profondeur. Si le rayon passe derrière un objet visible, alors on considère qu’il y a un obstacle. Si on atteint le bord de la texture sans trouver d’obstacle, alors l’objet est considéré comme éclairé.

L’inconvénient majeur de cette technique est qu’un objet non visible à l’écran ne créera pas d’ombre. Si un mur est derrière la caméra et devrait projeter son ombre devant elle, alors cela ne sera pas visible. Mais sa force est de prendre en compte des objets très lointains ou petits. Permettant de générer des ombres pour des objets qu’une shadow map à cause de sa limite de qualité aurait pu manquer. Un autre de ses défauts est son manque de précision pour détecter des bloqueurs loin du pixel observé, cela peut générer des fausses ombres. Pour limiter cela, il est courant de limiter la distance de vérification pour se concentrer sur les objets très proches du pixel actuel. Cette méthode est souvent appelée Contact Shadows. On risque alors de rater des ombres mais ce n’est pas un problème lorsque cela est utilisé en complément d’une autre méthode de génération d’ombre.

Day’s Gone utilise des contact shadows en complément des shadows cascades. L’effet est particulièrement visibles sur les petits objets et les ombres éloignés. Cela donne plus de profondeur à la forêt et aux reliefs.

L’inconvénient majeur de cette technique est qu’un objet non visible à l’écran ne créera pas d’ombre. Si un mur est derrière la caméra et devrait projeter son ombre devant elle, alors cela ne sera pas visible. Mais sa force est de prendre en compte des objets très lointains ou petits. Permettant de générer des ombres pour des objets qu’une shadow map à cause de sa limite de qualité aurait pu manquer. Un autre de ses défauts est son manque de précision pour détecter des bloqueurs loin du pixel observé, cela peut générer des fausses ombres. Pour limiter cela, il est courant de limiter la distance de vérification pour se concentrer sur les objets très proches du pixel actuel. Cette méthode est souvent appelée Contact Shadows. On risque alors de rater des ombres mais ce n’est pas un problème lorsque cela est utilisé en complément d’une autre méthode de génération d’ombre.

Day’s Gone utilise des contact shadows en complément des shadows cascades. L’effet est particulièrement visible sur les petits objets et les ombres éloignés. Cela donne plus de profondeur à la forêt et aux reliefs.

Frustum Tracing Shadows

Pour palier au problème de précision des shadow maps, il existe la méthode des Frustum Tracing Shadows (FTS). Comme pour les shadow map il faut faire un rendu depuis le point de vue de la lumière. Cette fois, au lieu de stocker une profondeur pour chaque pixel, on va stocker un frustum pour chaque triangle visible par la lumière. Un frustum est une forme pyramidale qui va permettre de connaitre la zone couverte par le triangle. Dans le rendu principal, pour savoir si un pixel est à l’ombre il faut regarder s’il est dans un des frustums créés précédemment. Cette méthode demande beaucoup de calculs, mais elle génère des ombres nettes ayant une qualité parfaite.

The Division propose sur PC un paramètre d’Hybrid Frustum Tracing Shadows (HTFS). Le frustum tracing est alors utilisé uniquement pour les objets proches. Les ombres lointaines sont gérées avec des shadows cascades normales. Le frustum tracing permet uniquement la gestion d’ombre forte, pour ajouter les ombres douces, ils utilisent du Percentage-Closer Soft Shadows (PCSS) comme décrit plus tôt.

Deep Shadow Maps

Les nombreuses méthodes vu précédemment ont toutes un défaut commun. Elles ne gèrent que les objets bloquants totalement la lumière. Elles ne prennent pas en compte les objets volumétriques (nuages, poussière…), les objets transparents et gèrent mal les ombres des objets denses ayant une épaisseur parfois inférieure à un pixel (cheveux, poils…). Une des méthodes permettant de régler cela est celle des Deep Shadow Maps popularisé par Pixar en 2000. Comme pour les shadow maps, il faut faire un rendu depuis le point de vue de la lumière. Mais au lieu de stocker une profondeur, on va stocker une fonction décrivant l’intensité lumineuse en fonction de la distance. Pour faire cela, depuis chaque pixel on va lancer un rayon vers l’avant. À intervalles réguliers, on regarde à quel point la lumière est absorbé. Une fois la distance maximale atteinte, on utilise les informations collectées et on les approxime sous la forme d’une fonction.

Aucun jeu récent n’a communiqué sur l’utilisation des Deep Shadow Maps (ou du moins je n’en ai pas trouvé) et Unity et Unreal Engine 5 ne le propose pas. Cependant, des implémentations temps réel existent et laissent penser que l’on pourrait la voir utiliser tôt ou tard dans l’industrie.

Conclusion

Bien choisir sa façon de gérer les ombres est une étape importante qui peut beaucoup influencer le résultat visuel et les performances d’un jeu. La majorité des jeux récents utilisent des shadow cascades avec une gestion de la pénombre via PCF ou PCSS. Et, de plus en plus de AAA complètent leurs ombres avec des contacts shadows. Mais le rendu des ombres dynamiques temps réel est encore un sujet actif de recherche où de nouvelles techniques sont régulièrement publiées. Ici, j’ai simplement évoqué le fonctionnement général des techniques me semblant les plus importantes. J’ai volontairement omis les ombres avec raytracing. J’en parlerai dans un prochain article dédié au raytracing.

Des questions ? Des corrections à proposer ? Des suggestions ? Laissez un commentaire.

Cet article fait partie de la série d'articles Tech Temps Réel  dédiée à la vulgarisation des méthodes de rendues et de simulation temps réel utilisées dans les jeux vidéo. Pour ne pas rater les prochains articles :  Twitter / Mastodon / Bluesky.

Sources et plus :

🗣️ Conférence | 👁️‍🗨️ PPT | 👨‍💻 Code | 📄 Papier

🗣️ Advanced Geometrically Correct Shadows for modern games engine, Chris Wyman, Nvidia, 2016 – The Division

🗣️ Moment Shadow Mapping, Christoph Peters, 2016

👁️‍🗨️ Inside Bend: Screen Space Shadows, Graham Aldridge, Bend Studio, 2023 – Day’s Gone

👁️‍🗨️ The Last of Us Lighting, Michał Iwanicki, Naughty Dog, 2013

👁️‍🗨️ Advanced Soft Shadow Mapping Techniques, Nvidia, 2008

👨‍💻 Integrating Realistic Soft Shadows into Your Game Engine (PCSS Implementation), Nvidia, 2008

👨‍💻 ShadowFX – Open Source Library, AMD, 2018

📄 Shadow Silhouette Maps, Stanford University, 2003

📄 Rendering Fake Soft Shadows with Smoothies, Eric Chan and Fredo Durand, MIT, 2003

📄 Percentage-Closer Soft Shadows, Randima Fernando, Nvidia, 2005

📄 Deep Shadow Maps, Tom Lokovic and Eric Veach, Pixar, 2000

📄 Realistic Soft Shadows by Penumbra-Wedges Blending, Vincent Forest, Toulouse University, 2006

📄 Volumetric Shadow Mapping, Pascal Gautron, Jean-Eudes Marvie and Guillaume François, 2009