Étude graphique : DOOM (2016)

Adrian Courreges

Traducteur

Alexandre Laurent

En 1993, DOOM a apporté des changements fondamentaux dans le game design et les mécaniques de jeu. Cela a été un phénomène qui a propulsé à la célébrité des figures emblématiques comme John Carmack et John Romero…

23 ans plus tard, id Software appartient maintenant à Zenimax et tous les fondateurs d'origine sont partis. Toutefois cela n'a pas empêché l'équipe chez id de nous montrer son talent en nous offrant un superbe jeu.

Le nouveau DOOM est un plus parfait pour la franchise. Il utilise le nouveau moteur id Tech 6, pour lequel Tiago Sousa, un ex-membre de Crytek a assumé le rôle de programmeur rendu principal après le départ de John Carmack.
Historiquement, id Software est connu pour la publication, après quelques années, de leur moteur en open source. Cela a permis la création de remakes sympa et d'analyses. Il reste à voir si cela sera toujours le cas avec id Tech 6, mais nous n'avons pas nécessairement besoin du code source pour apprécier les techniques graphiques implémentées dans le moteur.

Le contenu dynamique ne peut pas être affiché dans ce support, veuillez consulter la page en ligne.

Commentez

- Titre : Étude graphique : DOOM (2016)

- Auteur : Adrian Courreges

- Traducteur : Alexandre Laurent

- Parution : 30 mars 2017

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2017 Alexandre Laurent. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Comment une image est dessinée

- I-A. Mise à jour des mégatextures

- I-B. Atlas des textures d'ombrage

- I-C. Prépasse de profondeur

- I-D. Requêtes d'occlusion

- I-E. Rendu direct par cluster des objets opaques

- I-F. Particules GPU

- I-G. Occlusion ambiante en espace écran

- I-H. Réflexions en espace écran

- I-I. Cubemap de réflexions statiques

- I-J. Fusionner les textures ensemble

- I-K. Éclairage des particules

- I-L. Réduction et flou

- I-M. Objets transparents

- I-N. Texture de distorsion

- I-O. Interface utilisateur

- I-P. Anti-aliasing temporel et flou de mouvement

- I-Q. Luminosité de la scène

- I-R. Flou lumineux

- I-S. Post-processing final

- I-T. Interface utilisateur et grain de pellicule

- II. Bonus

- II-A. Gros plan sur les vitres

- II-B. Champ de profondeur

- III. En savoir plus

- IV. Remerciements

I - Comment une image est dessinée

Nous allons examiner la scène ci-dessous dans laquelle le joueur attaque un Gore Nest défendu par quelques ennemis possédés, juste après avoir obtenu l'armure Praetor au début du jeu.

Contrairement à la majorité des jeux Windows commercialisés ces derniers temps, DOOM n'utilise pas Direct3D, mais OpenGL et Vulkan.
Vulkan est la dernière technologie à la mode et Baldur Karlsson a récemment ajouté son support dans RenderDoc. Il était dur de résister à la tentation d'étudier les entrailles de DOOM. Les observations suivantes reposent sur l'exécution du jeu avec Vulkan sur une GTX 980 avec tous les paramètres à Ultra. Certaines observations ne sont que des hypothèses et d'autres s'inspirent de la présentation de Tiago Sousa et Jean Geffroy à Siggraph.

I-A - Mise à jour des mégatextures

La première étape est la mise à jour des mégatextures, une technique déjà présente dans id Tech 5 et utilisée dans RAGE. Elle est maintenant aussi utilisée dans DOOM.
En voici une explication très basique ; l'idée est d'allouer quelques textures gigantesques (d'une taille de 16 k x 8 k pour DOOM) dans la mémoire GPU et chacune regroupe une collection de tuiles de 128 pixels sur 128.

Toutes ces tuiles sont supposées représenter l'ensemble idéal de textures au niveau approprié de mipmap dont les pixels shaders ont besoin pour dessiner la scène que vous observez.
Lorsque le pixel shader lit à partir d'une « texture virtuelle », il finit simplement par lire une de ces tuiles physiques de 128 pixels par 128.
Bien sûr, suivant où le joueur regarde, l'ensemble va changer : de nouveaux modèles vont apparaître à l'écran, référençant d'autres textures virtuelles, de nouvelles tuiles doivent être transférées vers le GPU et les anciennes déchargées du GPU…
Donc au début du rendu d'une image, DOOM met à jour quelques tuiles avec vkCmdCopyBufferToImage() pour copier les données de textures sur la mémoire GPU.

Plus d'informations à propos des mégatextures ici et là.

I-B - Atlas des textures d'ombrage

Une texture de profondeur (« depth map ») est générée pour chaque lumière produisant une ombre. Celle-ci est sauvegardée dans une tuile d'un atlas de textures géant (8 k x 8 k). Par contre, toutes les textures de profondeur ne sont pas calculées à chaque image : DOOM réutilise intensivement le résultat de l'image précédente et ne régénère que les textures de profondeur qui doivent être mises à jour.

Lorsqu'une lumière est statique et ne produit que des ombres sur des objets statiques, il est évident de simplement conserver sa texture de profondeur au lieu de refaire des calculs inutiles. Si des ennemis passent devant la lumière alors, la texture de profondeur doit être régénérée.
Les tailles des textures de profondeur peuvent varier selon la distance de la lumière par rapport à la caméra. Aussi, les textures de profondeur régénérées ne sont pas nécessairement toujours dans la même tuile de l'atlas.
DOOM possède des optimisations spécifiques comme la mise en cache de portion statique de la texture de profondeur, ne recalculant alors que la projection des modèles dynamiques. Le résultat sera alors une composition de la partie statique et de la partie dynamique.

I-C - Prépasse de profondeur

Nous dessinons les modèles opaques. La profondeur est inscrite dans une texture de profondeur. Premièrement l'arme du joueur, puis les géométries statiques et finalement les géométries dynamiques.

Progression du rendu de la texture de profondeur

Mais la profondeur n'est pas la seule information inscrite lors de la prépasse pour la profondeur.
Alors que les objets dynamiques (les possédés, câbles, l'arme du joueur) ont été dessinés dans la texture de profondeur, leur vélocité par pixel a aussi été calculée et écrite dans un autre tampon pour créer une texture de vélocité. Pour cela, le vertex shader calcule la différence de position de chaque sommet entre l'image précédente et l'image actuelle.

Texture de vélocité

Seuls deux canaux suffisent pour stocker la vélocité : le rouge correspond à la vitesse horizontale et le vert correspond à la vitesse sur l'axe vertical.
Le possédé se déplace rapidement en direction du joueur (vert) alors que l'arme ne se déplace presque pas (noir).
Quid des zones jaunes (rouge et vert tous deux égaux à 1) ? En réalité, le jaune est la couleur par défaut du tampon indiquant donc qu'aucun modèle dynamique ne l'a modifié : ce sont les « zones des modèles statiques ».
Pourquoi DOOM ignore-t-il le calcul de la vélocité pour les modèles statiques ? Parce que la vélocité d'un pixel statique peut simplement être déduite de sa profondeur et du nouvel état de la caméra du joueur depuis l'image précédente. Aucun besoin de la calculer au niveau des modèles.
La carte de vélocité sera utile par la suite pour appliquer le flou de mouvement (« motion blur »).

I-D - Requêtes d'occlusion

Nous souhaitons demander au GPU de dessiner aussi peu de géométrie que possible. La meilleure méthode pour ce faire est d'éliminer tous les modèles qui ne sont pas directement visibles par le joueur. Le middleware Umbra réalise la plus grande partie de ce filtrage dans DOOM, mais le moteur effectue des requêtes GPU d'occlusion (« occlusion queries ») supplémentaires pour affiner l'ensemble visible.

Test de la boîteRouge : la partie cachéeVert : la partie visible

Donc quelle est l'idée derrière les requêtes GPU d'occlusions ?
La première étape est de regrouper plusieurs modèles du monde dans une boîte virtuelle les englobant. Ensuite, on demande au GPU de dessiner cette boîte et de comparer les valeurs de profondeur obtenues avec le tampon de profondeur actuel. Si aucun des pixels dessinés ne passe le test de profondeur, cela veut dire que la boîte est complètement cachée et que tous les objets du monde contenus dans cette boîte peuvent être sereinement ignorés lors du rendu.
Par contre, il est à noter que les résultats de ces requêtes d'occlusion ne sont pas immédiatement disponibles : vous ne souhaitez pas que le pipeline GPU cale sur une requête. Habituellement, la lecture des résultats est reportée aux images suivantes, donc il est nécessaire d'avoir un algorithme quelque peu conservateur pour éviter que les objets apparaissent subitement.

I-E - Rendu direct par cluster des objets opaques

Nous dessinons les géométries opaques et les projections. Les informations sur l'éclairage sont stockées dans un tampon de nombres à virgule flottante.

Progression du rendu de la lumière

La fonction de test de la profondeur est définie à EQUAL afin d'éviter de calculer les pixels recouverts. Grâce à la prépasse de profondeur précédente, nous connaissons exactement la valeur de profondeur que chaque pixel est supposé avoir.

Cela semble déjà bien, mais nous n'avons toujours pas de matériau transparent comme une vitre ou des particules et il n'y a pas de reflets de l'environnement.

Quelques mots sur cette passe : elle utilise un rendu direct par cluster (« clustered forward rendering ») inspiré des travaux de Emil Person et Ola Olsson.
Historiquement, une des faiblesses du rendu direct est son incapacité de gérer un grand nombre de lumières. Une chose bien plus facile dans le rendu différé.
Comment fonctionne le rendu par cluster ?
Premièrement vous divisez votre viewport en tuiles : DOOM crée une sous-division de 16 tuiles par 8. Certains moteurs s'arrêtent ici et calculent une liste de lumières par tuile ce qui aide à réduire le nombre de calculs pour la lumière, mais ont toujours quelques lacunes particulières.

Le contenu dynamique ne peut pas être affiché dans ce support, veuillez consulter la page en ligne.

Le rendu par cluster pousse ce concept un peu plus loin, de la 2D à la 3D : au lieu de s'arrêter à une subdivision du viewport en 2D, il effectue une subdivision 3D de l'intégralité du frustum de la caméra en créant des tranches suivant l'axe Z.

Chaque « bloc » est appelé un « cluster », vous pouvez aussi les appeler voxel « dont la forme est déterminée par le frustum » (« frustum-shaped »). Sur la droite vous voyez une visualisation d'une subdivision 3 x 2 du viewport, avec 5 morceaux sur la profondeur donnant ainsi 40 clusters.

Dans DOOM le frustum est divisé en 3072 clusters (une subdivision de 16 x 8 x 24), les morceaux en profondeur ont une position déterminée par une échelle logarithmique suivant l'axe Z.

Avec un rendu par cluster, le processus typique serait :

- premièrement le CPU calcule la liste des objets influençant l'éclairage dans chaque cluster : lumière, projection (« decalc ») et cubemaps…
Pour ce faire tous les objets sont « voxelisé » afin que l'intersection entre leur zone d'influence et les clusters puisse être testée. Les données sont stockées dans une liste indexée dans les tampons GPU afin que les shaders puissent y accéder. Chaque cluster peut contenir jusqu'à 256 lumières, 256 projections et 256 cubemaps ;

- lorsque le GPU dessine un pixel :

- à partir des coordonnées et de la profondeur du pixel, le cluster le contenant est déterminé,

- la liste des projections/lumières de ce cluster est récupérée. Cela implique une indirection et un calcul d'index comme montré ci-dessous,

- le code boucle sur toutes les projections/lumières du cluster, calculant et ajoutant leur contribution à l'image.

Voici exactement comment le pixel shader peut récupérer la liste des lumières et projections au cours de cette passe :

Processus d'application des lumières et projections

Il y a aussi la liste de sondes (non montré dans le diagramme ci-dessus) qui peut être accédée exactement de la même manière, mais qui n'est pas utilisée dans cette passe donc nous en reparlerons plus tard.
Le surcoût CPU de la génération de la liste des objets par cluster est avantageux sachant que la technique réduit considérablement la complexité des calculs GPU pour le rendu par la suite.
Récemment, le rendu direct par cluster attire l'attention : il possède la propriété de gérer plus de lumières que le rendu direct basique tout en étant plus rapide que le rendu différé qui doit écrire/lire à partir de plusieurs G-Buffers.

Mais il y a un point que je n'ai pas encore mentionné : cette passe que nous venons d'étudier n'est pas simplement un rendu direct écrivant dans un tampon de lumières, elle a aussi généré deux petits G-Buffers grâce au rendu dans plusieurs textures (Multiple Render Targets).

Texture des normales

Texture des spéculaires

La texture des normales est stockée dans un format R16G16 de nombres flottants. La texture des spéculaires est dans un format R8G8B8A8 où le canal alpha contient un facteur indiquant le lissage.
DOOM mélange intelligemment le rendu direct et le rendu différé grâce à cette approche hybride. Ces G-Buffers supplémentaires seront utiles pour l'application d'effets comme les réflexions.

Finalement, j'ai omis une dernière chose : un tampon feedback de 160x120 a aussi été généré pour le système de mégatextures. Il contient les informations pour indiquer au système de streaming quelles textures et à quel niveau de mipmap doivent être transférées au GPU.
Le moteur de mégatextures travaille de manière réactive : le moteur ne charge les textures que si la passe de rendu indique qu'elles sont manquantes.

I-F - Particules GPU

Un compute shader est exécuté pour mettre à jour la simulation des particules : position, vélocité et temps de vie.
Il lit l'état actuel des particules, ainsi que les tampons de normales et de profondeur (pour la détection de collisions), joue l'animation et stocke les nouveaux états dans les tampons.

I-G - Occlusion ambiante en espace écran

Texture d'occlusion ambiante

Cette étape génère la texture d'occlusion ambiante en espace écran (Screen Space Ambiant Occlusion (SSAO)).

Son but est d'assombrir la couleur autour des rabats, pli…
Elle est aussi utilisée pour appliquer l'occlusion spéculaire pour éviter les artefacts de lumière forte apparaissant sur les modèles qui ont été cachés.

La texture est calculée, dans un pixel shader, avec une taille correspondant à la moitié de la résolution de base. Le pixel shader lit à partir du tampon de profondeur, de normales et de spéculaire.
Le premier résultat obtenu est bruité.

I-H - Réflexions en espace écran

Un pixel shader génère la texture des réflexions en espace écran (« Screen space reflections » (SSR)). Il lance des rayons en utilisant seulement les informations présentes à l'écran et fait rebondir les rayons sur chaque pixel du viewport, lisant la couleur du pixel touché par ceux-ci.

Texture de profondeur

Texture des normales

Texture des spéculaires

Image précédente

Texture des réflexions

Le shader reçoit comme entrée la texture de profondeur (pour calculer la position en espace monde du pixel) la texture des normales (pour savoir comment les rayons rebondissent), la texture spéculaire (pour savoir la « valeur » de la réflexion) et l'image précédemment rendue (avant l'étape d'égalisation des tons (« tonemaping »), mais après l'application des transparences, pour obtenir des informations sur les couleurs). La configuration de la caméra pour l'image précédente est aussi fournie au pixel shader pour garder une trace des changements de position des fragments.

La réflexion en espace écran est une technique sympa, et pas trop coûteuse. Elle permet d'obtenir des réflexions dynamiques en temps réel à un coût constant. Cela aide vraiment à l'immersion et au réalisme.
Mais la technique possède ses propres artefacts liés au fait qu'elle ne fonctionne qu'en espace écran et dus au manque d'information « globale ». Vous pouvez voir de belles réflexions dans une scène, mais si vous commencez à regarder le sol, le nombre de réflexions diminue jusqu'à ce qu'il n'y ait plus aucune réflexion lorsque vous regardez vos pieds. Je trouve que les réflexions en espace écran dans DOOM sont bien intégrées, elles augmentent la qualité visuelle tout en étant assez subtiles pour que vous ne remarquiez pas leur disparition sauf si vous vous concentrez dessus.

I-I - Cubemap de réflexions statiques

Après toutes les réflexions dynamiques de la passe précédente (et leurs limitations) arrivent maintenant les réflexions statiques grâce à l'éclairage basé sur une image (Image-based lighting (IBL)).

La technique est basée sur des cubemaps prégénérées de 128 pixels sur 128 représentant les informations d'éclairage de l'environnement à différents points de la carte. Elles sont aussi appelées « sondes environnementales ». Tout comme pour les lumières et les projections que nous avons vues précédemment au cours du découpage en cluster du frustum, les sondes ont aussi été indexées pour chaque cluster.
Toutes les cubemaps du niveau sont stockées dans un tableau. Il y a plusieurs douzaines d'entre elles, mais voici les cinq contributions les plus importantes dans cette scène (les cubemaps de cette salle) :

Un pixel shader lit à partir des tampons de profondeur, normale et spéculaire, regarde dans la structure de cluster quelles cubemaps influencent le pixel (plus la cubemap est proche, plus forte est son influence) et génère une texture des réflexions statiques :

Texture des réflexions statiques

I-J - Fusionner les textures ensemble

Dans cette étape un compute shader fusionne toutes les textures qui ont été générées précédemment.
Il lit les textures de profondeur et de spéculaire et fusionne l'éclairage de la passe de rendu direct avec :

- les informations SSAO ;

- le SSR lorsqu'il est disponible pour ce pixel ;

- lorsque le SSR est manquant, les données de la texture de réflexion statique sont utilisées ;

- quelques effets de fumée qui ont été calculés précédemment.

Fusion + brouillard : avant/après

I-K - Éclairage des particules

Nous avons quelques particules de fumée dans cette scène et l'éclairage est calculé par sprite. Chaque sprite est dessiné comme s'il était en espace monde : grâce à sa position, une liste de lumières est récupérée ainsi que les textures d'ombrage associées. La lumière du carré est calculée. Le résultat est stocké dans une tuile de l'atlas 4 k. Les tuiles peuvent être d'une résolution différente suivant la distance entre la particule et la caméra, des paramètres utilisateur… L'atlas a des régions dédiées pour les sprites de la même résolution. Voici un aperçu de sprites 64x64 :

Atlas d'éclairage des particules

Seule l'information d'éclairage est stockée avec une si faible résolution. Plus tard lorsque la particule sera dessinée, la texture haute résolution est utilisée et le carré d'éclairage est agrandi avant sa fusion.
C'est à ce moment que DOOM découple les calculs d'éclairage de particules du rendu du jeu. Peu importe la résolution dans laquelle vous jouez (720 p, 1080 p, 4 K…), l'éclairage des particules est toujours calculé et stocké dans ces petites tuiles à taille fixe.

I-L - Réduction et flou

La scène est réduite plusieurs fois, jusqu'à une taille de 40 pixels. L'image de plus faible résolution est floutée en utilisant des passes horizontales et verticales séparées.

Pourquoi le flou est-il appliqué si tôt ? Un tel processus est habituellement effectué à la fin, au cours du post-process, pour ajouter un effet de flou lumineux sur les zones lumineuses.
Mais ici tous les niveaux de flou vont être utiles pour la prochaine passe lors du dessin des réfractions de la vitre.

I-M - Objets transparents

Tous les objets transparents (vitres, particules) sont dessinés au-dessus la scène :

Objets transparents : avant/après

Le dessin des vitres est très joli dans DOOM et plus spécifiquement pour les vitres givrées ou sales : les projections sont utilisées pour altérer seulement quelques parties de la vitre afin de rendre le reflet plus ou moins flou.
Le pixel shader calcule le « niveau de flou » de la réflexion et sélectionne les deux textures les plus proches de ce niveau parmi les textures générées précédemment. Le shader lit ces deux textures et effectue une interpolation linéaire de ces deux valeurs pour obtenir la couleur floutée que la réflexion doit avoir. Grâce à cette technique, les vitres peuvent avoir une jolie réflexion à différents niveaux de flou et ce, pour chaque pixel.

I-N - Texture de distorsion

Texture de distorsion

Les zones très chaudes peuvent créer une distorsion de l'image. Voici le « Gore Nest » distordant légèrement l'image.

Les distorsions sont dessinées en utilisant le tampon de profondeur. Le résultat est conservé dans une texture basse résolution.
Les canaux rouge et vert représentent le niveau de distorsion suivante les axes horizontal et vertical. Le canal bleu contient la quantité de flou à appliquer.

L'effet est appliqué plus tard dans le post-process en utilisant la texture de distorsion afin de connaître quels pixels doivent être déplacés.
Toutefois cette scène n'a qu'une petite distorsion presque imperceptible.

I-O - Interface utilisateur

Interface utilisateur

L'interface utilisateur est dessinée dans une autre cible de rendu, dans un format où l'alpha est prémultiplié.

L'avantage d'avoir toute l'interface utilisateur dans un tampon séparé, contrairement à son affichage directement au-dessus de l'image finale, est de pouvoir appliquer quelques filtres/effets de post-process telles les aberrations chromatiques ou la distorsion visuelle, et cela avec une unique passe pour tous les widgets.

Le moteur n'utilise aucune technique de batching en particulier. Il dessine les éléments de l'interface utilisateur un par un, grâce à 120 appels de dessin environ.
Dans les passes suivantes, le tampon de l'interface utilisateur est appliqué au-dessus de l'image du jeu.

I-P - Anti-aliasing temporel et flou de mouvement

L'anti-aliasing temporel (Temporal Anti-Aliasing (TAA)) et le flou de mouvement sont appliqués grâce à la texture de vélocité et le résultat du rendu des images précédentes.
Les fragments peuvent être rétroprojetés afin de permettre au pixel shader de savoir où le pixel en cours de traitement était positionné sur l'image précédente. Le rendu décale légèrement la projection des modèles d'une moitié de pixel une image sur deux : cela aide à supprimer les artefacts de l'aliasing au niveau du sous-pixel.

Anti-aliasing temporel et flou de mouvement : avant/après

Le résultat est très joli, non seulement les bordures des modèles deviennent lisses, mais l'aliasing spéculaire (où un pixel clair serait visible pour une image) est aussi éliminé. La qualité est bien meilleure que ce qui aurait été possible d'obtenir avec des méthodes de post-process comme FXAA.

I-Q - Luminosité de la scène

Cette étape calcule la luminance moyenne de la scène, afin de l'utiliser comme paramètre pour la passe d'égalisation des tons.

Le tampon d'éclairage HDR est réduit de moitié dans une boucle jusqu'à ce qu'il devienne une texture 2 x 2. Chaque itération calcule la couleur du pixel en effectuant la moyenne des quatre pixels parents dans la texture de résolution supérieure.

I-R - Flou lumineux

Flou lumineux

Un filtre passe-haut est appliqué pour réduire les zones les plus sombres de la scène.

Le résultat du filtre est réduit dans une boucle et flouté grâce à un processus similaire à ce que nous avons vu précédemment.

Les couches sont floutées avec un flou gaussien dont le passage vertical et horizontal sont séparés. Le shader calcule une moyenne pondérée suivant une direction.

Les couches floutées sont combinées pour créer le flou lumineux. Il est stocké dans une texture HDR ayant un quart de la résolution de base.

I-S - Post-processing final

Les étapes suivantes sont effectuées dans un seul pixel shader :

- la distorsion causée par la chaleur est appliquée grâce aux données de la texture de distorsion ;

- la texture du flou lumineux est ajoutée par-dessus le tampon d'éclairage HDR ;

- les effets comme le vignettage, la saleté/les reflets de lentilles sont effectués ;

- la luminosité moyenne est récupérée en échantillonnant le centre de la texture 2x2 de luminosité et grâce aux paramètres d'exposition. L'égalisation des tons et l'étalonnage des couleurs (« color grading ») sont effectués.

Égalisation des tons : avant/après

L'égalisation des tons prend le tampon d'éclairage HDR contenant les couleurs variant sur une grande échelle de luminosité et les passent en 8 bits par composant (LDR) afin que l'image puisse être affichée sur un écran.
Un opérateur d'égalisation des tons pour donner un aspect film basé sur l'équation (x(Ax+BC)+DE) / (x(Ax+B)+DF) - (E/F) est utilisé. C'est le même opérateur que dans Uncharted 2 et aussi dans GTA V.

Notez que toutes les teintes rouges proviennent de la correction des couleurs.

I-T - Interface utilisateur et grain de pellicule

Finalement, l'interface utilisateur est fusionnée par-dessus l'image du jeu tout en appliquant un fin grain de pellicule.

Grain de pellicule : avant/après

Pfiou ! Nous avons fini avec l'image et elle peut maintenant être envoyée au moniteur pour l'affichage. Cela fait beaucoup de calculs, mais tout se passe en moins de 16 ms.
DOOM réussit à produire une grande qualité visuelle très rapidement, car il réutilise intelligemment les données calculées dans les images précédentes. Au total, il y a eu 1331 appels de dessin, 132 textures et 50 cibles de rendu.

II - Bonus

II-A - Gros plan sur les vitres

Le rendu des vitres est très joli et il est réalisé avec des étapes relativement simples comme nous l'avons vu précédemment :

- préparation de plusieurs niveaux de flou des modèles opaques dessinés ;

- affichage des objets transparents en partant du fond et en revenant vers le premier plan en rendu direct en appliquant les projections/l'éclairage/les sondes de réflexion, et en utilisant le rendu précédent pour obtenir différentes valeurs floutées de réfraction afin que chaque pixel ait sa propre valeur de réfraction.

Vitre : avant/après

II-B - Champ de profondeur

L'image étudiée précédemment ne montre pas vraiment de champ de profondeur. Prenons donc la scène suivante, avant et après que le champ de profondeur a été appliqué :

Champ de profondeur : avant/après

Tous les jeux n'implémentent pas le champ de profondeur correctement : l'approche naïve est souvent d'utiliser un flou gaussien et de faire tout le floutage en une passe grâce à la profondeur du pixel. L'approche est simple et économique, mais possède plusieurs problématiques :

- bien que le flou gaussien soit bon pour le flou lumineux, il n'est pas correct pour créer un flou artistique (« bokeh ») : vous avez vraiment besoin d'un noyau plat pour rendre la lumière d'un pixel clair tout autour d'un disque ou d'une forme hexagonale… Un flou gaussien ne peut pas créer de formes jolies pour le flou artistique ;

- effectuer le champ de profondeur en un coup de pixel shader peut facilement apporter d'importants artefacts.

DOOM effectue le champ de profondeur correctement et l'approche choisie est, d'après mon expérience, une de celles qui donnent les meilleurs résultats.

Les images de l'arrière-plan et du premier plan sont créées : la sélection des pixels dépend de leur profondeur et des paramètres du champ de profondeur.

- Le premier plan peut être fortement flouté, plus il va se fondre dans les pixels derrière lui, mieux ce sera.

- L'arrière-plan est aussi flouté, mais ne lit aucun pixel dans le focus / proche de la zone de champ. Il évite donc tous les problèmes avec les objets en premier plan qui seraient malencontreusement fusionnés dans l'arrière-plan.

Progression du floutage

Pour créer le flou artistique, DOOM travaille à la moitié de la résolution et applique un flou radial avec 64 échantillons de textures pour chaque pixel traité. Chaque échantillon possède le même poids donc la luminosité va vraiment se propager contrairement au flou gaussien.
Le diamètre du disque peut varier pour chaque pixel suivant la valeur du cercle de confusion du pixel.

Ensuite, il étend encore plus le flou avec 16 tailles de flou, mais cette fois, il ne calcule pas de moyenne pondérée, il accumule simplement les valeurs échantillonnées et garde la valeur la plus haute des tailles voisines. Du coup, il n'agrandit pas seulement le premier floutage, mais aussi corrige les artefacts (espace dans l'échantillonnage) de la première passe. Cette dernière partie s'inspire du travail de McIntosh.
Une telle technique itérative sur plusieurs passes peut produire de très belles et grandes zones de flou tout en restant efficace en termes de performance. Le nombre d'échantillons de texture lus par pixel reste bas au regard du large rayon du flou final obtenu.

Les premier et arrière-plans sont finalement assemblés par-dessus la scène originale avec une fusion alpha pour créer l'effet final de champ de profondeur. Cette passe est effectuée juste avant d'appliquer le flou de mouvement.

III - En savoir plus

Si vous souhaitez aller encore plus loin dans les technologies de idTech 6, il y a heureusement de nombreuses ressources disponibles :

- The devil is in the details: idTech 666 (Siggraph 2016) par Tiago Sousa et Jean Geffroy

- Tech Interview: Doom par Digital Foundry

- id Software Tech Interview par DSOGaming

- QuakeCon 2016: Doom Uncapped - Part1 et Part 2

- Doom: The definitive interview par VentureBeat

- Graphics Gems CryEngine 3 (Siggraph 2013), de nombreuses techniques de post-process sont utilisées dans idTech 6.

IV - Remerciements

Cet article est une traduction autorisée dont le texte original peut être trouvé sur le blog d'Adrian Courrèges.