Introduction

Tout d'abord pourquoi parler de ça sur ce blog? :reflechi:

Comme vous avez surement pu le constater, je ne suis plus très actif. Sachez que ce n'est pas uniquement un manque de temps mais surtout de sujets à aborder.

En effet, au fils des années j'ai commencé à travailler sur des problématiques de plus en plus complexes et de moins en moins génériques. Il y avait donc très peu d’intérêt d'aborder les sujets ici.

J'ai un certain nombre de billets que j'ai commencé sans jamais les finir car les sujets abordés n'étaient soit pas intéressants (mon point du vue sur la vague des "logiciels de rendu") sois trop spécifiques (il n'est pas évident d'aborder les technologies maisons des studios :cayMal: ).

Bref, ce n'est pas pour autant que j'ai chaumé et j'ai emmagasiné un bon paquet d'infos, de connaissances que je commence à maitriser, en tous cas, suffisamment pour en parler. :laClasse:

L'idée m'est donc venue de vous parler de différentes publications (papers en anglais) dont vous avez surement déjà dû voir ou entendre parler, leur sortie étant très florissante pendant la période du Siggraph. :jdicajdirien:

Maintenant, pourquoi cette publication là en particulier?

Et bien parce que depuis quelques années l'idée que le full raytracing "va tout simplifier" s'est généralisée. Le maitre incontesté de cette philosophie est sans conteste Arnold à qui on pourrait presque reprocher son approche quasi religieuse, mais le virage abrupt de Renderman vers du full raytrace tend à lui donner raison. :pasClasse:

Et pourtant. Non, tout n'est pas rose au pays du full raytrace. Car si de nouvelles choses sont possibles, de nouvelles problématiques apparaissent et oui, il faut "tricher". Le rayswitch devient la norme et tout moyen visant à gagner du temps est mis à profit, car c'est bien le rapport temps/qualité qui détermine l'efficacité d'un moteur, et la course à la puissance a aussi ses limites. :papi:

Cette publication est intéressante car elle permet, de par la problématique qu'elle aborde (GI raytrace) de comprendre les défis et limites que peuvent/vont rencontrer les moteurs de rendu modernes.

Avant propos

Avant de commencer je voudrais préciser quelques points techniques importants. Ces notions ne sont pas forcément très connu des graphistes, pourtant je pense qu'elle ont beaucoup de sens, même à notre échelle.

Rayon cohérent/incohérent

Un terme fréquemment utilisé quand on parle d'optimisation du raytracing est cohérent/incohérent mais doit se comprendre comme qui varie peu/qui varie beaucoup/presque aléatoire.

coherent_incoherent_rays.jpg

Ces images sont tirées du très instructif article de Intel expliquant le fonctionnement de Embree: Embree: Photo-Realistic Ray Tracing Kernels. Pdf ici.

  • A gauche: Ce sont des rayons cohérents. Ils partent d'un même point et vont dans une direction particulière (même si cette direction est plus ou moins "ouverte"). Le nombre d'objets que vont toucher ces rayons est faible.
  • A droite: Ce sont des rayons incohérents. Ils vont et viennent de toutes les directions. Bien entendu, le nombre d'objets que vont toucher ces rayons est important.

Vous allez très vite comprendre le pourquoi du comment. :sourit:

Rays et Shading Points

En raytracing on fait souvent la distinction entre le lancer de rayons en lui même (savoir quelle géométrie le rayon va toucher dans la scène) et le shading du point (le fait de calculer la couleur, la reflection, tout ce qui est lié au matérial sur ladite géométrie).

Ce sont deux étapes très différentes:

schema_shading_point.png

Out-of-core

Le terme out-of-core est un terme que l'on rencontre souvent dans les calculs massivement distribués. La page Wikipedia explique bien le principe mais en gros, core est un ancien terme pour désigner la mémoire vive. Out-of-core fait donc référence à des calculs sur des données qui ne sont pas stockées dans la mémoire vive mais directement sur le disque.

Pour être efficace, un calcul out-of-core doit lire le minimum d'information sur le disque, et donc, les informations les plus pertinentes.

Si vous comprenez ces notions, la suite va vous sembler évidente. :dentcasse:

Traduction

Toute bonne publication commence par la partie la plus courte et la plus simple à comprendre: Abstract. Même si ce genre de lecture ne vous fait pas rêver, je vous encourage à la lire si vous vous intéressez un temps soit peu à la technique.

Le principe est de résumer, aussi simplement que possible, en quelques phrases, ce que présente la publication.

Abstract

L'illumination globale (GI) en lancé de rayon se généralise dans le rendu de production mais l'incohérence des rayons secondaires limite son utilisation à des scènes qui peuvent être placées intégralement en mémoire.

L'incohérence du shading entraine également d'importants soucis de performance lors de l'utilisation de nombreuses (et lourdes) textures qui oblige le moteur de rendu à recourir aux caches d'irradiance, de radiosité, et autres moyens pour diminuer le cout du shading.

Malheureusement, cette mise en cache:

  • Complique le travail du graphiste
  • Est difficile à paralléliser efficacement
  • Occupe une précieuse mémoire.
  • Et pire, ces caches impliquent une approximations qui altère la qualité.

Dans cette publication, nous présentons une nouvelle structure de path tracing qui évite ces compromis. Nous organisons les rayons en gros paquets, potentiellement hors du core, pour assurer la cohérence des rayon lancés.

Nous retardons le shading des points touchés par les rayons jusqu'à ce que nous les ayons trié, ce qui permet un shading cohérent et évite d'avoir recours à des caches.

Et voilà pour Abstract!

Avouez que ce n'est pas non plus la misère à comprendre. :seSentCon:

On enchaine avec l'introduction qui résume grosso modo ce que présente Abstract.

Introduction

La GI en path-tracing offre de nombreux avantage en rendu de production: Un éclairage plus riche, plus plausible avec moins de lights et évite la lourde gestion des données liées aux point clouds (PTC) et aux deep map shadows.

Bien que cette approche offre une meilleure image et une productivité accrue, les performances se dégrade de manière importante à mesure que la géométrie et les textures dépassent la capacité mémoire disponible.

Ainsi, les moteurs de rendu de production existants s'évertuent à lancer le moins de rayons possible et à calculer le moins de points de shading possible, privilégiant l'utilisation de caches d'éclairage/de shading et de géométrie instanciées. Mais ces mesures gênent le travail des graphistes.

A mesure que la recherche améliore l'intersection rayon/géométrie, le calcul de shading points incohérent devient un problème de plus en plus conséquent.

En particulier, l'accès incohérent à d'énormes textures de production entraine de fréquents cache-misses (à traduire: "Pas dispo dans le cache de texture? S'pas grave, on reload!") provoquant des pauses importantes due à la latence d'accès mémoire/disque/réseaux durant le calcul.

Les caches d'irradiance et de radiosité tentent de minimiser cela, bien que leur non-directionalité inhérente ne les rendent efficace que pour des surfaces parfaitement diffuses. (ndt: Par "non-directionnalité", comprenez qu'on ne peut stocker l'irradiance d'une surface réfléchissante, qui "varie en fonction de la direction", miroir etc... Le chercheur semble toutefois oublier qu'une irradiance est tout à fait "stockable" sous la forme d'une spherical harmonique, ce qui permet de palier à ce problème).

Par conséquent, il est d'usage de séparer les techniques en fonction du type de rayon à calculer (diffus, spéculaires et caustiques) ce qui complique encore le travail des graphistes.

Pire encore, ces approches sont difficiles à adapter à des architectures multi-core à cause de la synchronisation, l'effet NUMA (le fait d'aller chercher des informations un peu partout dans la mémoire diminue drastiquement ces performances) et l'équilibrage de la charge (load-balancing).

De fait, il est préférable d'éviter les caches et privilégier un shading cohérent.

Dans cette publication, nous présentons un ray-tracer continu capable d'effectuer plusieurs rebonds de GI dans une scène de production sans recourir à des caches de shading ou des instances.

Pour y parvenir, nous introduisons une nouvelle approche de tri de rayon en deux étapes.

Dans un premier temps, nous organisons les rayons en gros paquets, potentiellement hors du core (stocké sur disque donc), pour s'assurer de leur cohérence.

Travailler avec de gros paquets est essentiel pour extraire les groupes de rayons cohérent d'une scène complexe.

Dans un second temps, nous trions les rayons ayants touchés (les fameux ray hits) pour calculer les shading des points en utilisant les textures hors du core.

Pour chaque lot, nous réalisons un shading parfaitement cohérent avec des lectures de textures séquentielles, ce qui élimine la nécessité d'un cache de texture.

Notre approche est simple à implémenter et compatible avec la plupart des techniques de casting de scène.

Nous démontrons l'efficacité de notre tri en deux étapes combiné au paquets de rayons hors du core et comparons nos résultats avec les deux principaux ray-tracers de production: PhotoRealistic RenderMan (hum... :trollface: ) de Pixar et Arnold de Solid Angle.

Je m’arrête là, pour plus d'explications, il faut lire la publication! :sauteJoie:

Explications

Ce qu'explique cette publication c'est que le fait d'avoir beaucoup de rayons incohérents va nécessiter un load de quasiment toute la scène pour calculer les intersections des rayons, puis, en parallèle, va nécessiter de loader un grand nombre de textures différentes pour calculer le shading de ces rayons. Tout ça juste pour calculer l'illumination d'un seul point.

schema_ray_coherent_incoherent.png

Et sur une scène plus dense:

schema_big.png Imaginez chaque zone comme un énorme asset avec beaucoup de géométrie et beaucoup de textures. On ne peut pas tout loader en RAM. Du-coup, pour calculer l'illumination global d'un point ça risque de faire tourner le cache inutilement.

La solution proposée consiste à rassembler les rayons allants dans des directions similaires et de les calculer en même temps, d'un seul bloc. La quantité de données à charger sera donc moins disparate/plus pertinente et permettra d'éviter de charger et décharger le cache, opération couteuse en temps.

Grossièrement cela donne ça:

schema_big002.png

Partant de là on peut imaginer pas mal d'approches:

  • Calculer quelques samples "à l'ancienne", pour sortir une "map" d'environnement, récupérer son importance et générer plus de rayons à lancer dans certaines directions plutôt que d'autres (si une direction renvoit majoritairement du noir, il n'y a pas de raison d'envoyer autant de rayons qu'une direction plus clair qui va avoir une influence plus importante sur la solution finale).
  • Comme on a une utilisation plus optimisé du cache, on pourrait imaginer que le calcul des intersections soit fait sur le GPU, après y avoir chargé un paquet de rayon. Les échanges RAM/VRAM étant le goulet d'étranglement traditionnel de cette approche, on peux espérer repousser ces limites. L'avantage en cas de multi GPU c'est de pouvoir lancer un paquet d'une direction sur un des GPU et un autre sur le second GPU. Une fois qu'un GPU a fini son calcul, on pourrait en tirer une importance et générer des paquets plus "intéressants" (importance driven).
  • Après une première "passe", on stocke les résultats des shading points par object (on doit pouvoir les "compresser" si ils ont la même couleur, en leur donnant un "facteur de présence"). Lors de la seconde "passe" le calcul d'intersection des objets éloigné/peu "importants" se fait sur une version allégé de la géométrie (plus rapide à intersecté) et le résultat du shading point est récupéré directement, de manière aléatoire, dans le tableau des valeurs de shading points stocké précédemment sur l'object. De cette façon, plus aucune raison de calculer les shading points lors de la seconde/troisième passe, faisant des économies de ressource indirect conséquent (cache de texture notamment). Même si cette approche est biased, dans le cas de rayons incohérents, elle pourrait se révéler très efficace.
  • On combine toutes ces approches en même temps! :baffed:

Conclusion

J'espère que ce petit billet vous aura intéressé et vous aura permis de comprendre un peu ce qui se passe dans un raytracer quand il doit parcourir une scène et tout particulièrement les limites qu'il peut y avoir durant cette étape.

A bientôt!

Dorian

:marioCours: