Skip to content

Comment les moteurs modernes réduisent le suraffichage du feuillage

Par Oleg Sidorkin, CTO et cofondateur de Cinevva

Canopée de forêt dense composée de cartes de feuilles superposées les unes derrière les autres devant un soleil couchant, suggérant un important suraffichage

Une forêt est l’une des pires choses que l’on puisse demander à un GPU d’afficher. Chaque feuille est un quadrilatère texturé, découpé par un masque alpha. Des dizaines de ces quadrilatères se superposent le long de chaque rayon de vue. Le rastériseur n’a aucun moyen de savoir à l’avance quels fragments réussiront le test alpha, si bien que l’optimisation standard d’early-Z qui accélère les scènes opaques est en grande partie désactivée. Résultat : un seul pixel à l’écran peut exécuter le shader de feuille complet 10 ou 15 fois avant que l’image ne soit finalisée. C’est le suraffichage du feuillage, et il s’agit de la partie la plus coûteuse d’une image de monde ouvert dans tous les jeux sortis au cours de la dernière décennie.

La bonne nouvelle, c’est que le problème est résolu. Non pas au sens où quelqu’un aurait trouvé une astuce unique, mais parce qu’il existe un ensemble de sept ou huit techniques qui, combinées, font passer le suraffichage effectif de 8-15x dans une forêt dense à 1-2x. Tous les moteurs modernes intègrent une version de cet ensemble. Voici les techniques qui le composent, la raison d’être de chacune et leurs références incontournables.

1. Pourquoi le suraffichage du feuillage est si coûteux

Dans une scène opaque classique, le GPU effectue un rejet anticipé selon la profondeur : avant même l’exécution du shader de pixels, le matériel consulte le tampon de profondeur existant et ignore les fragments qui se trouvent déjà derrière quelque chose. Cette opération est pratiquement gratuite, et c’est elle qui maintient le coût des géométries denses à un niveau raisonnable.

Le feuillage à test alpha perturbe ce mécanisme. Le shader de fragments doit réellement s’exécuter pour évaluer la texture alpha et appeler discard (ou clip) sur les pixels exclus par le masque. Le matériel ne peut pas savoir si un fragment sera éliminé avant l’exécution du shader. Sur la plupart des GPU, l’utilisation de discard n’importe où dans un shader désactive donc entièrement l’early-Z pour cet appel de rendu. Sur les GPU à tuiles (mobiles, puces de série M et certaines consoles), elle peut désactiver le Hi-Z et la compression de profondeur pour toute l’image. Le shader de pixels s’exécute une fois pour chaque triangle couvrant un pixel, et la plupart de ces évaluations se terminent par un rejet.

Superposez 10 quadrilatères de feuilles devant un seul pixel à l’écran, et le shader de feuille s’exécutera 10 fois. Multipliez cela par 4 millions de pixels, et le coût devient énorme. L’article de Marco Salvi, Early-Z ou pas Early-Z, constitue l’introduction la plus accessible aux raisons matérielles de ce phénomène. C’est le meilleur point de départ si vous ne vous êtes jamais penché sur le sujet.

Vue latérale du rayon d’un seul pixel à l’écran traversant douze cartes de feuilles superposées, chacune étant mise en évidence pour montrer l’empilement des fragments soumis au test alpha

2. La prépasse de profondeur pour la géométrie masquée

Le gain le plus important à lui seul — et la technique intégrée à tous les moteurs modernes — consiste à diviser le rendu du feuillage en deux passes. La première passe écrit uniquement la profondeur, à l’aide d’un shader minimal qui effectue le test alpha, élimine les pixels masqués et n’écrit rien d’autre. La seconde passe affiche le matériau complet avec le test de profondeur réglé sur « égal » et l’écriture de profondeur désactivée. Chaque pixel visible ne calcule désormais la BRDF complète qu’une seule fois, quel que soit le nombre de quadrilatères de feuilles empilés derrière lui.

Cela peut sembler représenter deux fois plus de travail, puisque les mêmes triangles sont traités deux fois. Mais le shader de prépasse est si peu coûteux — un échantillonnage de texture, un rejet et une écriture de profondeur — que les économies réalisées lors de la passe principale dépassent largement ce surcoût. Dans une forêt dense, la passe principale n’exécute plus le shader de feuille complet 10 à 15 fois par pixel, mais exactement une seule fois.

Unreal, Frostbite, Decima ainsi que les branches modernes de Rage et Dunia utilisent tous cette technique. C’est également la principale raison pour laquelle les matériaux masqués restent moins coûteux que les matériaux translucides dans chacun de ces moteurs.

Pour approfondir :

3. LOD agressifs et imposteurs octaédriques

Le deuxième gain le plus important consiste à ne jamais afficher les feuilles lorsqu’il est possible de l’éviter. Les ressources de feuillage comportent plusieurs niveaux de LOD. Le plus proche utilise le maillage complet, avec des cartes pour chaque feuille. À moyenne distance, les feuilles sont regroupées dans des cartes composites plus denses : un bouquet de 30 feuilles devient une seule carte texturée présentant la même silhouette. Au-delà d’un certain seuil de distance, l’arbre entier devient un imposteur : un petit élément géométrique texturé avec des vues de l’arbre précalculées sous plusieurs angles.

Le format d’imposteur moderne est l’imposteur octaédrique : un élément géométrique à 8 faces, texturé avec un atlas de vues capturées depuis des points répartis sur une sphère au moyen d’une projection octaédrique. À l’exécution, le shader sélectionne les deux ou trois vues précalculées les plus proches selon la direction de la caméra, puis effectue une interpolation entre elles. On obtient ainsi un substitut composé de quelques triangles qui paraît tridimensionnel sous n’importe quel angle et peut bénéficier d’un éclairage correct, de normal maps et même d’une animation par le vent. L’implémentation de Ryan Brucks, initialement publiée sous forme de plugin communautaire et désormais intégrée à Unreal, fait référence. Les forêts comptant des milliards d’arbres de Microsoft Flight Simulator sont essentiellement constituées d’imposteurs octaédriques partout, sauf à proximité de la caméra.

Le gain structurel le plus important est qu’à distance, les imposteurs sont opaques ou presque opaques. La carte composite de 30 feuilles est un seul quadrilatère masqué au lieu de 30. L’imposteur représentant l’arbre entier ne comporte que quelques faces, au lieu de milliers. Le suraffichage à longue distance devient presque nul.

Pour approfondir :

4. Élimination par clusters pilotée par le GPU

Même avec une prépasse de profondeur, celle-ci a un coût : elle doit toujours traiter chaque triangle de chaque arbre visible ou potentiellement visible. Les moteurs modernes réduisent ce coût grâce à l’élimination de clusters pilotée par le GPU, qui écarte des groupes entiers de triangles avant même qu’ils n’atteignent le rastériseur.

Le pipeline fonctionne ainsi : chaque maillage est préalablement divisé en clusters de 64 ou 128 triangles, chacun doté d’une boîte englobante serrée et d’un cône de normales. Au moment du rendu, un compute shader parcourt la liste des instances, teste chaque instance par rapport au frustum, puis chaque cluster de chaque instance visible, avant de soumettre chaque cluster restant à un test d’occlusion Hi-Z fondé sur la pyramide de profondeur de l’image précédente. Des branches entières cachées derrière une colline ou masquées par un autre arbre sont éliminées avant même l’exécution du moindre shader de sommets. Le résultat est une liste compacte d’arguments « afficher ces clusters », transmise directement à un unique appel DrawIndirect.

C’est ce qui permet d’afficher une forêt de 10 000 arbres en quelques millisecondes plutôt qu’en quelques secondes. La présentation d’Ubisoft sur Assassin’s Creed Unity a introduit ce pipeline sous une forme exploitable en production : 20 à 40 % des triangles éliminés, 30 à 80 % des triangles d’ombre écartés et 10 fois plus d’instances à l’écran que sur la génération précédente. La présentation de Wihlidal sur Frostbite est allée encore plus loin. Nanite dans UE5 est l’aboutissement visible de cette évolution : une élimination par clusters jusqu’au niveau du pixel.

Pour approfondir :

5. Tri des instances et des clusters de l’avant vers l’arrière

Une fois que la prépasse remplit son rôle, l’ordre commence à compter. La prépasse écrit la profondeur, mais uniquement pour les fragments qui réussissent le test alpha. Si vous affichez d’abord l’arrière de la forêt et terminez par l’avant, chaque fragment de premier plan écrase un fragment d’arrière-plan, tandis que le shader de prépasse s’exécute quand même pour ce dernier. Si vous procédez de l’avant vers l’arrière, chaque appel de rendu successif remplit davantage le tampon de profondeur avec des valeurs plus petites, et le Hi-Z rejette de plus en plus de fragments d’arrière-plan avant l’exécution du shader.

C’est pourquoi presque tous les moteurs modernes trient les instances de feuillage selon leur distance à la caméra avant de lancer la prépasse. Le tri est peu coûteux — quelques centaines de milliers d’instances sur le GPU au moyen d’un tri radix — et transforme la prépasse elle-même en opération d’auto-élimination. Pour la même raison, l’élimination au niveau des clusters effectue le tri à la granularité des meshlets. La prépasse de profondeur et l’ordonnancement de l’avant vers l’arrière forment le genre de duo où chaque technique est efficace séparément, mais où leur combinaison l’est encore davantage.

Pour approfondir :

6. Transitions de LOD tramées et alpha haché

L’autre grand piège est le fondu. La méthode naïve pour effectuer une transition entre deux LOD — ou faire apparaître ou disparaître progressivement une instance à mesure que la caméra s’en approche — consiste à utiliser le mélange alpha. Mais une géométrie mélangée ne peut pas écrire dans le tampon de profondeur, ce qui fait basculer chaque arbre en transition vers le lent pipeline translucide et rend la prépasse inopérante. La solution consiste à maintenir la géométrie dans le pipeline masqué et à effectuer le fondu au sein même du test alpha.

Deux techniques principales :

  • Les transitions de LOD tramées échantillonnent une matrice de Bayer 4x4 ou 8x8 — ou une texture de bruit bleu en espace écran — et l’utilisent comme modificateur de seuil par pixel. Un arbre à 50 % de transition conserve un damier de pixels ; les pixels manquants sont remplis par le damier complémentaire du LOD suivant. Le TAA lisse le damier en une transition fluide sur deux ou trois images. La technique est peu coûteuse, stable et s’intègre à tout le reste du moteur.
  • Le test alpha haché (Wyman et McGuire, I3D 2017) remplace le seuil alpha fixe de 0,5 par un seuil haché par pixel appartenant à [0,1). Les géométries alpha éloignées qui disparaîtraient normalement entièrement — parce que leur alpha après mipmapping passe sous 0,5 — conservent une dispersion stable de pixels visibles. Là encore, le TAA se charge du nettoyage.

Ces deux techniques maintiennent le feuillage dans le pipeline opaque/masqué où la prépasse de profondeur fonctionne. Vous évitez ainsi de payer le coût complet du rendu translucide simplement pour faire apparaître progressivement un élément. L’alpha-to-coverage est l’équivalent de cette idée à l’époque du MSAA : convertir l’alpha en masque de couverture sous-pixel afin d’obtenir une transparence partielle sans quitter le pipeline masqué. Son inconvénient est que l’A2C n’est réellement efficace qu’avec le MSAA, que la plupart des moteurs modernes à rendu différé n’utilisent plus.

Pour approfondir :

7. Réduction du coût d’ombrage des pixels masqués

Même avec une prépasse parfaite et une élimination parfaite, chaque pixel visible du feuillage doit toujours être ombré une fois. Les moteurs réduisent également ce coût :

  • BRDF moins coûteuse. Le feuillage est mat et n’a pas vraiment besoin d’un chemin spéculaire Cook-Torrance complet. Une diffuse de Lambert enveloppée et une approximation spéculaire en une ligne suffisent amplement.
  • Normal maps à plus basse fréquence. Les feuilles sont déjà visuellement chargées. Une normal map de 256x256 produit le même rendu qu’une version de 1024x1024 aux distances d’observation habituelles, tout en économisant de la bande passante.
  • Ni parallaxe, ni anisotropie, ni vernis. Les fonctionnalités PBR correspondantes sont désactivées pour les feuilles.
  • Transmission mince double face au lieu d’une diffusion sous-surfacique complète. Les feuilles transmettent la lumière venant de derrière, mais on peut la simuler avec un simple produit scalaire d’éclairage arrière.
  • Ombrage en demi-résolution dans certains moteurs. Le feuillage est ombré à 1/4 ou 1/2 du taux de pixels, puis mis à l’échelle. Le bruit stochastique du TAA masque le rééchantillonnage.
  • Pas de textures de détail ni de décalcomanies par défaut sur les matériaux masqués.

Chacune de ces optimisations apporte individuellement un petit gain. Ensemble, elles permettent aux shaders de feuillage masqué de s’exécuter 2 à 3 fois plus vite que le matériau opaque équivalent.

Pour approfondir :

8. Buffers de visibilité et Nanite pour les matériaux masqués

La solution la plus propre au suraffichage consiste à découpler entièrement l’ombrage de la rastérisation. Un buffer de visibilité rastérise la géométrie dans un buffer compact (uniquement l’identifiant du triangle et celui de l’instance pour chaque pixel), puis exécute le matériau complet dans une passe différée qui lit ce buffer et n’ombre chaque pixel qu’une seule fois. Par construction, il n’y a aucun suraffichage à l’étape d’ombrage. L’article de Burns et Hunt paru en 2013 a introduit cette technique ; Nanite d’UE5 en est l’implémentation prête pour la production, y compris pour le feuillage masqué depuis UE 5.5.

La particularité de Nanite est d’y parvenir grâce à une géométrie virtualisée au niveau des clusters : le rastériseur utilise donc lui-même un chemin logiciel pour les triangles plus petits qu’un pixel et maintient le suraffichage sous contrôle. Dans Nanite, les matériaux masqués ont besoin de la fonctionnalité « programmable raster » : le test alpha s’exécute pendant la passe du buffer de visibilité, mais l’ombrage du matériau n’a toujours lieu qu’une seule fois par pixel visible lors de la résolution différée. Ainsi, le feuillage Nanite très dense, autrefois réputé lent avec les matériaux masqués, rivalise désormais avec les matériaux opaques, voire coûte moins cher, car le suraffichage est nul au moment de l’ombrage. Il y a toutefois un compromis : il est souvent préférable de faire traiter par Nanite une géométrie d’arbre opaque et très détaillée plutôt que de conserver des arbres peu détaillés composés de cartes masquées, car le chemin masqué ajoute le coût de la rastérisation programmable.

Pour approfondir :

9. Représentations d’ombres distinctes pour le feuillage

Les shadow maps du feuillage avec test alpha constituent le deuxième problème d’ombres le plus coûteux dans une frame de monde ouvert, juste après les grandes cascades. Chaque cascade nécessite sa propre prépasse de profondeur, chaque prépasse exécute le test alpha, et le coût s’accumule rapidement avec 4 cascades et des dizaines de frustums lumineux. La plupart des moteurs ne rendent donc pas les ombres du feuillage de la même manière que sa couleur.

Substitutions courantes :

  • Ombres par champs de distance de maillage (UE Lumen, moteurs personnalisés). Chaque maillage possède un champ de distance signé précalculé. Un court lancer de cône à travers le SDF produit une ombre douce sans jamais toucher au maillage avec test alpha. Cette technique convient particulièrement bien aux arbres, car le SDF représente la silhouette de la canopée comme une seule masse solide et ignore les détails de chaque feuille.
  • Cascades de plus basse résolution pour le feuillage. Les ombres du feuillage sont rendues dans une tranche en demi-résolution, puis agrandies avec un filtrage adaptatif aux contours. L’œil ne remarque pas la baisse de résolution, car les ombres sont déjà douces.
  • Shadow maps avec conversion alpha-vers-couverture et MSAA. Dans les moteurs qui conservent le MSAA dans leur chemin d’ombres, l’A2C produit des ombres de feuillage aux contours lisses sans supporter le coût complet du test alpha.
  • Ombres par capsules pour le tronc et les grosses branches, champ de distance pour la canopée, test alpha complet uniquement dans la cascade la plus proche. Différentes représentations sont utilisées selon la distance, puis fusionnées dans la passe d’éclairage.
  • Distance de désactivation du WPO. Le World Position Offset piloté par le vent est désactivé au-delà d’un certain seuil afin que les données d’ombre mises en cache restent valides d’une frame à l’autre. Les Virtual Shadow Maps d’UE reposent largement sur cette technique.

Pour approfondir :

10. Vent, animation et mise en cache des ombres

Un problème connexe plus subtil : la plupart des feuillages bougent. L’animation des sommets pilotée par le vent (World Position Offset dans UE, ou son équivalent dans Frostbite et Decima) rend la géométrie du feuillage instable d’une frame à l’autre, ce qui compromet la mise en cache et la reprojection des ombres. Les moteurs modernes luttent contre ce phénomène de deux façons :

  • Limiter le WPO à distance. Au-delà d’un certain seuil, l’amplitude de l’animation due au vent diminue progressivement jusqu’à zéro. De toute façon, l’œil ne distingue pas ce mouvement à une telle distance, et les caches d’ombres restent valides.
  • Intégrer le vent aux volumes englobants des clusters. Les boîtes englobantes des clusters sont agrandies en fonction du décalage WPO maximal, de façon à conserver un culling prudent sans devoir les renvoyer à chaque frame.
  • Décalages de phase par instance. Les arbres identiques utilisent une graine aléatoire propre à chaque instance pour décaler la phase du vent. Ainsi, toute la forêt ne se balance pas à l’unisson, sans qu’il soit nécessaire de calculer une animation unique pour chaque arbre.

C’est le genre de détail qui n’apparaît pas dans les listes de techniques, mais qui, en pratique, fait toute la différence entre une forêt à 3 ms et une forêt à 9 ms.

Pour approfondir :

11. Le calcul combiné

Aucune de ces techniques n’est une solution miracle. Ce qui devient intéressant, c’est ce qui se produit lorsqu’on les cumule :

  • Une passe naïve de feuillage masqué dans une scène de forêt dense affiche un suraffichage effectif de 8 à 15x. Chaque pixel visible exécute le shader de feuille entre 8 et 15 fois.
  • L’ajout d’une prépasse de profondeur ramène le suraffichage de la passe principale à environ 1x, mais la prépasse elle-même parcourt encore tout.
  • Avec un tri de l’avant vers l’arrière, la prépasse commence à s’élaguer elle-même.
  • Avec un culling GPU au niveau des clusters, la prépasse ne traite plus que ce qui pourrait réellement être visible.
  • Avec des chaînes de LOD et des imposteurs, le nombre de quadrilatères visibles chute d’un ordre de grandeur au-delà de 30 m.
  • Avec le chemin buffer de visibilité / Nanite, l’étape d’ombrage ne s’exécute réellement qu’une fois par pixel, même en cas de chevauchement dense.
  • Avec les ombres par champs de distance, le coût des ombres cesse d’évoluer avec le test alpha.

Le résultat principal, répété dans les conférences citées ci-dessus : le suraffichage effectif s’effondre de 8–15x à 1–2x, et le coût total du feuillage par frame est divisé par 4 à 6 dans une scène de forêt dense. C’est précisément ce qui permet aux jeux modernes en monde ouvert d’afficher des forêts à plus de 60 FPS sur du matériel grand public.

Schéma montrant le pipeline de rendu du feuillage sous la forme de cinq étapes empilées : culling GPU, prépasse triée, passe de couleur masquée avec profondeur égale, ombrage par buffer de visibilité et chemin d’ombres distinct par champs de distance

12. Ce que cela signifie pour le navigateur

L’essentiel de cette architecture se transpose sans difficulté à WebGPU. Nous avons déjà déployé le culling piloté par le GPU, le dispatch indirect, l’occlusion Hi-Z et les prépasses triées de l’avant vers l’arrière dans le moteur de monde ouvert pour navigateur. La prépasse de profondeur pour la géométrie masquée est simple à mettre en œuvre : WebGPU prend en charge le test de profondeur « égal » et discard dans les fragment shaders, avec les mêmes réserves concernant l’early-Z. Les imposteurs octaédriques se portent mécaniquement : les calculs se résument à déplier une sphère sur un octaèdre et à indexer un atlas.

Les éléments les plus difficiles sont les plus modernes. Un buffer de visibilité dans WebGPU implique d’écrire un identifiant de triangle 32 bits dans une cible de rendu, puis de résoudre les matériaux dans une passe de calcul plein écran ; les briques nécessaires existent, mais leur orchestration est complexe. Les ombres par champs de distance de maillage nécessitent une texture 3D par ressource et un court lancer de cône, deux opérations à la portée de WebGPU. L’alpha haché et les LOD tramés ne demandent chacun qu’une fonction de shader.

La voie à suivre pour le feuillage dans les navigateurs est la même que pour tout le reste de cette architecture : déployer d’abord les composants peu coûteux et robustes (prépasse, instances triées, LOD, imposteurs, alpha haché), puis ajouter les mécanismes plus lourds (buffer de visibilité, ombres par SDF de maillage). Le niveau matériel minimal des navigateurs est enfin assez élevé pour qu’aucune raison architecturale n’empêche une forêt WebGPU d’offrir le même rendu et les mêmes performances que sur console. Il ne reste que des problèmes d’ingénierie, et ce sont précisément ceux que nous aimons résoudre.

Pour approfondir l’ensemble de l’architecture

Si vous cherchez une source unique qui rassemble tous ces sujets, les archives SIGGRAPH « Advances in Real-Time Rendering in Games » (advances.realtimerendering.com) regroupent les conférences de référence sur le feuillage et le rendu piloté par le GPU depuis 2014. Les articles d’Adrian Courrèges sur le profilage GPU proposent des analyses frame par frame de GTA V et Horizon Zero Dawn qui montrent chaque passe abordée ici dans son ordre de production. Pour les calculs propres au test alpha, la page de recherche de Chris Wyman présente les articles consacrés à l’alpha haché et à la transparence stochastique, avec des shaders de référence. Enfin, les chapitres de Real-Time Rendering, 4e édition consacrés à la transparence, à l’échantillonnage et à la gestion de la profondeur restent le meilleur point de départ théorique.