Tutoriel concis sur le rendu de volume [Les bases de NeRF]

Le rendu volumique est un sujet presque aussi vaste et aussi complexe que le rendu de surface dure. Il a son propre ensemble d'équations qui sont en fait à peu près une généralisation des équations utilisées pour décrire comment la lumière interagit avec la matière dure. Pour les lecteurs qui ne sont pas nécessairement familiarisés avec des formules mathématiques aussi complexes, elles peuvent être accablantes.

insérez la description de l'image ici

Recommandation : Utilisez NSDT Designer pour créer rapidement des scènes 3D programmables.

Comme pour la façon dont nous enseignons sur Scratchapixel, nous avons choisi une approche "ascendante" pour relever le défi de l'enseignement du rendu en volume. Ou en d'autres termes, c'est une approche pratique. Au lieu de les creuser en commençant par les équations, nous allons écrire du code pour rendre une sphère volumétrique simple et expliquer l'ensemble du processus d'une manière, espérons-le, intuitive. Ensuite, nous conclurons et formaliserons tout ce que nous avons appris jusqu'à présent à la fin du cours.

Quelques leçons seront consacrées au rendu volumique (qui est un vaste sujet). Dans ce cours d'introduction, nous apprendrons les bases du rendu de volume et de la marche des rayons. Les cours suivants seront consacrés aux autres possibilités de rendu des volumes, à l'illumination globale appliquée aux médias participants, à la diffusion multiple, aux formats de stockage des données volumiques (comme OpenVDB, etc.).

1. Introduction au rendu de volume

L'objectif des deux premiers chapitres de ce cours est d'apprendre à rendre un volume en forme de sphère éclairé par une seule source lumineuse sur un fond de couleur uniforme. Cela nous aidera d'abord à comprendre ce que sont les volumes et à introduire l'algorithme de marche des rayons que nous utiliserons pour les rendre.

Dans ce chapitre, nous allons juste rendre un volume normal avec une densité uniforme. Nous ignorerons les ombres projetées par des objets de l'extérieur ou de l'intérieur du volume, et comment les volumes sont rendus à différentes densités. Celles-ci seront étudiées dans les prochains chapitres.

Au lieu de fournir beaucoup d'informations détaillées sur ce que sont les volumes et les équations utilisées pour les rendre, nous plongeons directement dans l'implémentation et acquérons une compréhension plus formelle du rendu de volume à partir de là.
insérez la description de l'image ici

2. Transmission interne, capacité d'absorption, densité des particules et loi de Beer

La lumière qui atteint nos yeux lorsqu'elle est réfléchie par un objet ou émise par une source lumineuse est susceptible d'être absorbée lorsqu'elle traverse un volume d'espace rempli de certaines particules. Plus il y a de particules dans le volume, plus le volume est opaque. De cette simple observation, on peut déduire quelques concepts de base liés au rendu volumique : l'absorption, la transmission, et la relation entre l'opacité d'un volume et la densité des particules qu'il contient. Pour l'instant, nous considérerons la densité de particules contenues dans le volume comme uniforme.
insérez la description de l'image ici

Lorsque la lumière traverse un volume en direction de nos yeux (c'est ainsi que se forment dans nos yeux les images des objets que nous voyons), une partie de cette lumière est absorbée par le volume lorsqu'elle le traverse. Ce phénomène s'appelle l'absorption. Nous sommes (actuellement) intéressés par la quantité de lumière transmise à travers le volume depuis l'arrière-plan. On parle de transmission interne (la quantité de lumière absorbée par un volume lorsqu'il le traverse). La transmission interne peut être considérée comme une valeur comprise entre 0 (le volume bloque toute la lumière) et 1 (enfin, c'est un vide, donc toute la lumière est transmise).

La quantité de lumière transmise à travers ce volume est régie par la loi de Beer-Lambert (ou loi de Beer en abrégé). Dans la loi de Beer-Lambert, la notion de densité est exprimée en termes de coefficients d'absorption (et de coefficients de diffusion, mais nous reviendrons sur les coefficients de diffusion plus loin dans ce chapitre). Il peut être lu comme "plus le volume est dense, plus le coefficient d'absorption est élevé" ; comme vous pouvez le deviner intuitivement, plus le coefficient d'absorption augmente, plus le volume devient opaque. L'équation de la loi de Beer-Lambert ressemble à ceci :
insérez la description de l'image ici

Cette loi stipule qu'il existe une dépendance exponentielle entre la transmission interne, la lumière à travers le volume (T) multipliée par le coefficient d'absorption volumique (sigma_a) et la distance parcourue par la lumière à travers le matériau (c'est-à-dire la longueur du trajet).

Les unités de ces coefficients sont l'inverse de la distance ou l'inverse de la longueur, comme cm^-1 ou m^-1, ce qui est important car il aide à comprendre intuitivement quelles informations ces coefficients contiennent. Si vous voulez qu'un événement aléatoire se produise à un point/distance donné (comme un photon absorbé ou diffusé), vous pouvez considérer le coefficient d'absorption (et le coefficient de diffusion, que nous verrons plus tard) comme une probabilité ou probabilité.

On dit que les coefficients d'absorption et de diffusion représentent des densités de probabilité (si vous voulez faire plus de recherches sur ce sujet). Cependant, comme il s'agit d'une probabilité qui ne doit pas dépasser 1, elle dépend de l'unité de mesure. Par exemple, si vous utilisez des millimètres, vous pourriez obtenir 0,2 pour un support donné. Mais en centimètres et en mètres, ils sont respectivement 2 et 20. Ainsi, en pratique, rien ne vous empêche d'utiliser une valeur supérieure à 1.

Relation entre les coefficients et le libre parcours moyen

Le fait que les unités des coefficients d'absorption et de diffusion soient l'inverse de la longueur est important car si vous prenez l'inverse du coefficient (1 divisé par l'absorption du coefficient de diffusion), vous obtenez la distance. Cette distance est appelée libre parcours moyen et représente la distance moyenne sur laquelle se produisent les événements aléatoires :
insérez la description de l'image ici

Cette valeur joue un rôle important dans la modélisation de la diffusion multiple des médias participants. Consultez les cours sur la diffusion souterraine et le rendu de volume avancé pour en savoir plus sur ces sujets vraiment intéressants.

insérez la description de l'image ici

Figure 1 : Plus la distance est grande ou plus la densité est grande, plus la valeur de transmission interne est faible.

Plus le coefficient d'absorption ou la distance est grand, plus le T est petit. L'équation de Beer-Lambert renvoie un nombre compris entre 0 et 1. Si la distance ou le coefficient d'absorption est 0, l'équation renvoie 1. Pour de très grandes distances ou densités, T tend vers 0. Pour une distance fixe, T diminue lorsque le coefficient d'absorption augmente. Pour un coefficient d'absorption fixe, T diminue avec la distance. Plus la lumière voyage loin dans un volume, plus elle est absorbée. Plus il y a de particules dans le volume, plus la lumière est absorbée. simple. Vous pouvez voir cet effet sur la figure 1.

3. Rendu des volumes sur un fond uniforme

Il est facile de commencer ici. Imaginez que nous ayons une dalle de volume d'épaisseur et de densité connues. Dites 10 et 0,1 respectivement. Ainsi, si la couleur d'arrière-plan (par exemple, la lumière réfléchie par le mur que nous regardons) est (xr, xg, xb), la quantité de couleur d'arrière-plan que nous voyons à travers le volume est :

vec3 background_color {xr, xg, xb};
float sigma_a = 0.1; // absorption coefficient
float distance = 10;
float T = exp(-distance * sigma_a);
vec3 background_color_through_volume = T * background_color;

Rien de plus simple.

4. Diffusion

Notez que jusqu'à présent, nous avons supposé que nos volumes étaient noirs. En d'autres termes, nous assombrissons simplement la couleur de fond partout où se trouve notre tableau. Mais le volume n'a pas à être comme ça. Les volumes comme les solides reflètent également (ou plus précisément dispersent) la lumière. C'est pourquoi, lorsque vous regardez un nuage par une journée ensoleillée, vous pouvez voir la forme du nuage presque comme s'il s'agissait d'un nuage solide. Les blocs peuvent également émettre de la lumière (pensez aux flammes des bougies), nous le mentionnons juste pour être complet, mais nous ignorerons la lueur dans ce chapitre.

Par conséquent, nous supposons que nos plaques de volume ont une couleur spécifique (yr, yg, yb). Ignorons la source de cette couleur pour le moment ; nous l'expliquerons plus tard dans ce chapitre. Nous pouvons seulement dire que jusque-là, notre volume a une certaine couleur car l'objet volume "réfléchit" la lumière (en fait non, mais utilisons le concept de "réflexion" comme un objet solide), l'éclairant comme un objet solide. Alors notre code devient :

vec3 background_color {xr, xg, xb}; 
float sigma_a= 0.1; 
float distance = 10; 
float T = exp(-distance * sigma_a); 
vec3 volume_color {yr, yg, yb}; 
vec3 background_color_through_volume = T * background_color + (1 - T) * volume_color;

Considérez-le comme le processus de mélange d'images (A+B) dans Photoshop, par exemple en utilisant le mélange alpha. Supposons que vous souhaitiez ajouter l'image B au-dessus de A, où A est l'image d'arrière-plan (notre mur bleu) et B est l'image d'un disque rouge avec un canal de transparence. La formule pour combiner ces deux images est :

insérez la description de l'image ici

La transparence ici est 1 - transmission (alias opacité), B est la couleur de l'objet volume (la lumière qui est "réfléchie" par le volume et se dirige vers nos yeux/caméra). Nous y reviendrons lorsque nous discuterons de l'algorithme de marche des rayons ; pour l'instant, gardez cela à l'esprit.

5. Rendre notre première boule volumétrique

Nous avons tout ce dont nous avons besoin pour rendre notre première image 3D. Nous utiliserons ce que nous avons appris jusqu'à présent pour rendre une sphère que nous supposons remplie de particules. Supposons que nous rendions une sphère sur un arrière-plan.

Le principe est simple. Nous commençons par vérifier l'intersection entre le rayon de la caméra et la sphère. S'il n'y a pas d'intersection, nous renvoyons simplement la couleur de fond. S'il y a une intersection, nous calculons les points sur la surface de la sphère où le rayon entre et sort de la sphère. À partir de là, nous pouvons calculer la distance parcourue par la lumière à travers la sphère et appliquer la loi de Beer pour calculer la quantité de lumière qui traverse la sphère. Nous supposons maintenant que la lumière "réfléchie" (diffusée) par la sphère est uniforme. Nous parlerons de l'éclairage plus tard.
insérez la description de l'image ici

Figure 2 : Rayons de la caméra traversant un volume.
insérez la description de l'image ici

Figure 3 : Nous utilisons l'intersection du rayon de la caméra et de l'objet volume pour calculer l'opacité de l'objet volume le long du rayon de la caméra.

class Sphere : public Object 
{ 
public: 
    bool intersect(vec3, vec3, float, float) const { /* compute ray-sphere intersection */ } 
    float sigma_a{ 0.1 }; 
    vec3 scatter{ 0.8, 0.1, 0.5 }; 
    vec3 center{ 0, 0, -4 }; 
    float radius{ 1 }; 
}; 
 
void traceScene(vec3 ray_origin, vec3d ray_direction, const Sphere *sphere) 
{ 
    float t0, t1; 
    vec3 background_color { 0.572, 0.772, 0.921 }; 
    if (sphere->intersect(rayOrigin, rayDirection, t0, t1)) { 
        vec3 p1 = ray_origin + ray_direction * t0; 
        vec3 p2 = ray_origin + ray_direction * t1; 
        float distance = (p2 - p1).length();  // though you could simply do t1 - t0 
        float tranmission = exp(-distance * sphere->sigma_a); 
        return background_color * transmission + sphere->scatter * (1 - transmission); 
    } 
    else 
       return background_color; 
} 
 
void renderImage() 
{ 
    Sphere *sphere = new Sphere; 
    for (each row in the image) 
        for (each column in the image) 
            vec3 ray_dir = computeRay(col, row); 
            pixel_color = traceScene(ray_orig, ray_dir, sphere); 
            image_buffer[...] = pixel_color;  // store pixel color in image buffer 
 
    saveImage(image_buffer); 
    ... 
} 

Logiquement, à mesure que la densité augmente, la transmittance se rapproche progressivement de 0, ce qui signifie que la couleur de la boule de volume est plus dominante que la couleur du fond.
insérez la description de l'image ici

Vous pouvez voir dans l'image ci-dessus que le volume devient plus opaque vers le centre de la sphère (où la lumière se déplace le plus loin à travers la sphère. Vous pouvez également voir qu'à mesure que la densité augmente (à mesure que sigma_a augmente), la sphère devient globalement plus opaque.

6. Ajoutons de la lumière ! diffusion interne

Jusqu'à présent, nous avons une belle image de sphère volumétrique, mais qu'en est-il de l'éclairage ? Si nous braquons une lumière sur l'objet volume, nous pouvons voir que les parties du volume qui sont plus directement exposées à la lumière sont plus lumineuses que les parties qui sont dans l'ombre. Les volumes sont également éclairés par des luminaires. Comment expliquons-nous cela ?

Le principe est simple. Imaginons le devenir de la lumière émise par la source lumineuse traversant le volume. Au fur et à mesure qu'il traverse le volume, sa résistance diminue en raison de l'absorption. Sans surprise, la quantité d'énergie lumineuse restante après avoir parcouru une certaine distance dans un volume est régie par la loi de Beer. En d'autres termes, si nous connaissons la distance parcourue par la lumière à travers le volume, l'intensité à cette distance est :

float light_intensity = 10; // just a number, it could be anything 
float T = exp(-distance_travelled_by_light * volume->absorption_coefficient); 
light_intensity_attenuation = T * ligth_intensity;

Tout d'abord, selon la loi de Beer, l'énergie lumineuse diminue lorsqu'elle traverse un volume. C'est très logique. Mais quelque chose d'autre se produit : la lumière de la source lumineuse ne frappe pas l'œil initialement, mais peut également y être redirigée (au moins une partie de ce que nous verrons) en raison de ce que nous appelons l'effet de diffusion.

On parle du cas particulier de l'inscattering. La diffusion se produit lorsque la lumière traverse un volume et est redirigée vers l'œil en raison d'événements de diffusion. Cet effet est illustré à la figure 4. Les événements de diffusion sont le résultat d'interactions entre les photons et les particules/atomes qui composent le milieu/volume. Les atomes ne sont ni absorbés ni réfléchis (ce qui peut arriver aussi), mais "crachent" simplement le photon dans une direction différente de sa direction incidente. Nous reviendrons plus en détail sur ce phénomène dans les chapitres suivants.

insérez la description de l'image ici

Figure 4 : La lumière que nous voyons à travers le volume provient de l'objet d'arrière-plan (bleu ici) ainsi que de la source lumineuse. Bien que le faisceau lumineux de la source lumineuse ne se déplace pas vers l'œil, en raison des effets de diffusion, une certaine quantité de lumière est redirigée vers l'œil lorsqu'elle traverse le volume.

Si vous regardez la figure 4, notez que la lumière qui atteint l'œil (le long du rayon œil/caméra spécifique dessiné en bleu dans le diagramme) est une combinaison de la lumière de l'arrière-plan (notre arrière-plan bleu) et de la lumière de la source lumineuse qui se disperse vers l'œil en raison de la diffusion (lumière jaune).

Alors, comment comptabiliser la contribution des sources lumineuses ? Nous devons "mesurer" la lumière diffusée vers l'œil (et la lumière de la caméra) en tant qu'effet d'inscattering. Le problème est que nous devons tenir compte de cet effet sur toute la portion du rayon de la caméra qui coupe la sphère (Fig.5). Nous devons "intégrer" la lumière diffusée le long du rayon de la caméra dans la plage t0-t1.

insérez la description de l'image ici

Figure 5 : Nous devons intégrer la lumière redirigée vers l'œil en raison de la diffusion le long de la portion de lumière traversant le volume.

Pour résoudre ce problème, nous divisons la partie du rayon de la caméra traversant le volume en un certain nombre de fragments (notre exemple, si vous voulez) et calculons la quantité de lumière atteignant le centre de chaque fragment (exemple) en utilisant la procédure suivante (voir la figure 6 pour une intuition sur le concept) :

  • Nous tirons un rayon depuis ce point d'échantillonnage (appelons-le X) vers la source de lumière pour calculer la distance entre le point d'échantillonnage et la limite de la sphère (appelons-le point P). Notez que X est toujours à l'intérieur de la sphère (notre volume), et P est toujours un point sur la surface de la sphère.
  • La loi de Beer est ensuite appliquée pour voir combien d'énergie lumineuse est atténuée lorsqu'elle se déplace de P (le point où le rayon pénètre dans la sphère) à X (le point le long du rayon oculaire où le rayon se disperse vers l'observateur).
    insérez la description de l'image ici

Figure 6 : Les sommes de Riemann sont utilisées pour estimer les intégrales suivant un rayon dans les étapes conventionnelles.
insérez la description de l'image ici

Figure 7 : Nous pouvons utiliser la somme de Riemann pour estimer l'aire sous la courbe représentant la quantité de lumière diffusée le long de la caméra. L'idée est de décomposer l'aire sous la courbe en une somme de petits rectangles. La hauteur de chaque rectangle est donnée par Li(x) et la largeur dx est définie par l'utilisateur.

Pour comprendre le type de problème que nous abordons ici, nous devons examiner les figures 6 et 7. La figure 6 montre la lumière incidente arrivant le long du rayon de la caméra, qui, comme vous pouvez le voir au bas de la figure, est une fonction continue. Nous appelons cette fonction Li(x), où x est un point arbitraire le long du rayon de la caméra contenu dans la plage t0-t1. Ce que nous devons calculer, c'est la "zone" sous la courbe. En mathématiques, c'est une intégrale et on peut écrire :

insérez la description de l'image ici

Comme nous venons de le dire, le résultat (nombre) de l'intégration est défini comme l'aire (signée nette) sous la courbe (la fonction Li(x)), comme le montre la figure 6. Le problème dans notre cas est que nous ne pouvons pas calculer cette aire en utilisant une solution analytique. Mais nous pouvons utiliser une astuce pour approximer cette aire en la décomposant en formes plus simples où nous connaissons l'aire du rectangle (comme le montre la figure 7). Nous échantillonnons Li(x) à intervalles réguliers le long de la courbe, nous connaissons la largeur de (dx), puis nous pouvons calculer l'aire du rectangle résultant comme Li(x) fois dx (x est au milieu de l'intervalle). Nous pouvons obtenir une approximation de l'aire sous la courbe en additionnant les aires de tous les rectangles. attend et regarde! Cette technique est connue sous le nom de sommation de Riemann (l'idée d'utiliser une zone que nous connaissons pour approximer la forme d'une zone inconnue remonte aux Grecs).

Alors, comment cela se traduit-il en code ? Nous introduisons la prochaine fois.


Lien d'origine : Tutoriel concis sur le rendu de volume - BimAnt

Je suppose que tu aimes

Origine blog.csdn.net/shebao3333/article/details/131888400
conseillé
Classement