MOS8.5 - Centrale Lyon

TP4 - BVH et trop de triangles


Partie 1.  mais pourquoi ?




retour sur la perspective :
La construction des rayons est très simple, mais la perspective est assez brutale et déforme énormément les objets lorsqu'ils se rapprochent de la camera. Comment utiliser une ouverture plus petite que 90° ? L'image au dessus utilise une perspective plus naturelle avec une ouverture de 60°. Comment modifier la construction de l'extrémité des rayons ? Ou : comment zoomer dans l'image ?

pour les curieux : comment simuler une profondeur de champ ? ie comment rendre les objets flous s'ils ne sont pas à la distance du plan focal / du plan image ?


exercice 1 : beaucoup de triangles
Chargez une scène plus complexe que la cornell box, en utilisant read_meshio_data().

par exemple alley, vous pouvez utiliser la transformation suivante pour placer les objets devant la camera :
Translation( 180.638168, 109.031326, -1229.029297 ) * RotationX( 13 ) * RotationY( 275 );
   
Parcourez les triangles et conservez ceux dont l'aire est non nulle. oui, il y a souvent des triangles dégénérés (avec 2 sommets superposés).

rappel : scènes d'exemple, cf doc en ligne.

si vous preférez partir d'un code départ : cf scene_meshio.cpp


exercice 2 : BVH
Reprenez le principe de construction de l'arbre dans la doc en ligne.
Vous pouvez soit trier les triangles sur un axe, soit les répartir par rapport au centre de l'axe le plus étiré. Comparez votre solution avec votre voisin.

astuce : il est plus robuste de manipuler les centres des boites des triangles pour répartir.

pour les rapides : expérimentez avec une des constructions qui minimisent le coût de parcours du BVH, cf doc en ligne.

pour les curieux : peut-on déterminer que certains noeuds sont mauvais et qu'il faudrait répartir les triangles dans plus de fils / noeuds ? Que faudrait-il modifier ?
question : que pourrait-être la définition d'un "mauvais" noeud ?
indications : à partir de quelle taille / aire est-il plus intéressant de remplacer un fils par ses fils ? Ou : dans quel cas est-il intéressant de créer un noeud avec 3 fils, au lieu de 2...

si vous preférez partir d'un code départ : cf bvh_correction.cpp


exercice 3 : intersection rayon / BVH
Ecrivez le parcours du BVH permettant de visiter toutes les feuilles touchées par le rayon.


exercice 4 : intersection ordonnée
Adaptez l'ordre de parcours des fils d'un noeud en fonction de la direction du rayon. Pourquoi ? Constatez-vous une différence ?


exercice 5 : transformations BVH et rayons
Dans les tps précédents, il était probablement plus simple de pre-transformer les sommets des objets pour les placer devant la camera.
Que peut-on dire de la qualité d'un BVH construit avec des boites alignées sur les axes lorsqu'on applique une rotation à la géométrie ?

indication : sait-on estimer le nombre de droites visitant une région de la scène ? Si cette région est plus grande, comment évolue le nombre de droites ?
question : comment construire l'englobant d'une boite alignée après une rotation ? Comment évolue l'englobant ?

Si on ne souhaite pas pre-transformer les objets, il suffit de transformer l'origine et l'extrémité des rayons issus de la camera avant de calculer les intersections. Si on applique une transformation T pour passer du repère de l'objet au repère de la camera, quelle transformation appliquer à l'origine et l'extrémité du rayon pour passer du repère camera vers le repère de l'objet ?

Vérifiez que le BVH construit sur des objets non transformés est plus efficace que celui construit sur les objets pre-transformés dans le repère de la camera.






Partie 2 : estimateurs et réduction de variance

un peu de lecture pour changer !

exercice : on souhaite intégrer une somme pondérée de fonctions. on suppose que l'on connaît une distribution bien adaptée à l'intégration numérique de chaque fonction. Quelles sont les différentes stratégies pour construire un estimateur de Monte Carlo de la somme pondérée de fonctions ?

les détails sont dans :
"Optimally Combining Sampling Techniques for Monte Carlo Rendering", 1995, E. Veach, L. Guibas


exercice : utilisez "multiple importance sampling" présenté dans l'article ci-dessus pour estimer l'éclairage direct avec 2 "stratégies" : soit en intégrant sur les directions, soit en intégrant sur l'aire des sources. Comment pondérer les densités de probabilités des 2 stratégies pour construire l'estimateur "multi-sample" ? même question pour l'estimateur "one-sample" ?

attention : il faudra convertir une des deux densités, ie on va comparer les valeurs des densités, il serait assez malin qu'elles soient toutes exprimées soit sur les aires, soit sur les directions...

testez sur la scène emission.obj construite pour tester précisément les stratégies d'intégration de l'éclairage direct.


exercice : on pourrait aussi contourner le problème... en sélectionnant uniformément les sources de lumières dans une première étape afin de construire ensuite la variable aléatoire parfaite. Cette solution est présentée dans :
 "Importance Resampling for Global Illumination", 2005 J. Talbot,  D. Cline, P. Egbert


Vous pouvez tester sur la scène précédente, mais elle est presque trop simple. Essayez plutôt sur bistro/exterior.obj et ses 15000 triangles émissifs distribués dans toute la scène (ie les ampoules des guirlandes sont triangulées et chaque triangle est émissif... idem pour les lampes de la terrasse du bar) ou sur les ampoules du manège de carnival.obj...





Une autre application du même principe permet d'intégrer l'éclairage direct sur des objets dont les matières sont très réfléchissantes. Pourquoi le type de matière poserait-il un problème ? Peut-on appliquer le principe de réduction de variance dans ce cas ? Quelle densité de probabilité serait (approximativement...) proportionnelle à la fonction intégrée ?


exemple d'intégration d'un reflet bien concentré.
>1000 échantillons par pixel ne suffisent pas à converger sur cette scène simpliste...



exemple d'intégration avec "multiple importance sampling", 256 échantillons,
utilise la génération de directions présentée en annexe.


à cogiter pour la suite : à partir du moment ou l'on commence à jouer avec les distributions des échantillons, il faut être préparé aux défauts introduits dans certaines situations... on a peut être grandement réduit la variance dans la majorité des cas, mais il reste sans doute des situations mal gérées. Ces situations mal échantillonnées, avec une distribution peu adaptée à la fonction intégrée peuvent introduire des évaluations avec des pondérations aberrantes qui cassent complètement la convergence de l'estimateur. Il y a deux familles de solutions, soit toujours utiliser une stratégie de défense, ie uniforme, dans les estimateurs "multiple importance sampling" au prix de la vitesse de convergence, soit réduire a posteriori l'impact de ces pondérations aberrantes, soit ignorer le problème...

L'article ci-dessous illustre très bien le problème de pondération aberrante et propose de construire plusieurs estimateurs au lieu d'un seul et d'utiliser leur médiane comme estimateur final. C'est plutôt élégant, mais peu adapté lorsque l'on se limite à un faible nombre d'échantillons.
"Firefly removal in Monte Carlo rendering with adaptive Median of meaNs", J. Buisine, S. Delepoulle, C. Renaud, 2021


pour les curieux : l'utilisation des textures comporte également une intégration, une solution alternative est présentée dans :
"Filtering After Shading With Stochastic Texture Filtering", M. Pharr, B. Wronski, M. Salvi, M. Fajardo, I3D 2024.





Annexe : génération de directions pour le modèle Blinn Phong

Le modèle de matière se présente comme une mixture de 2 termes, une réflexion diffuse ainsi qu'une réflexion spéculaire :

\[
    f_r( \vec{d}, \vec{l} )= \frac{diffuse}{\pi} + specular \times \frac{8+\alpha}{8 \pi} ( \cos \theta_h )^\alpha \\
\]

rappel : la construction du modèle se trouve sur la doc en ligne.

En pratique, dans l'équation de rendu, le modèle de matière est toujours pondéré par le cosinus de l'angle entre la normale en p et la direction \( \vec{l} \) :
\[
    L_r(p, \vec{o})= L_e(p, \vec{o}) + \int_{\Omega} L_i(p, \vec{l}) \, f_r( \vec{o}, \vec{l} ) \cos \theta dl
\]
On peut ré-ecrire l'équation de rendu explicitement en décomposant les 2 termes du modèle de matière :
\[
    L_r(p, \vec{o})= L_e(p, \vec{o}) + \int_{\Omega} L_i(p, \vec{l}) \, \frac{diffuse}{\pi} \, \cos \theta dl + \int_{\Omega} L_i(p, \vec{l}) \, \frac{8+\alpha}{8 \pi} ( \cos \theta_h )^\alpha \, \cos \theta dl
\]

On peut écrire directement un estimateur utilisant une distribution de directions proportionnelle à \( \cos \theta \) pour l'intégration de la partie diffuse, par contre, il va falloir cogiter un peu pour construire une distribution proportionnelle à \( (\cos \theta_h)^\alpha \cos \theta \) pour la partie spéculaire.

En consultant le recueil de distributions GI Compendium, P. Dutre, 2003, il est assez direct d'identifier une distribution de directions proportionnelle à \( \cos \theta \) présentée en eq 35 :
\[
\begin{eqnarray*}
    p( \vec{v} ) & = & \frac{\cos \theta}{\pi} \\
    \cos \theta & = & \sqrt{u_1} \\
    \sin \theta & = & \sqrt{1 - (\cos \theta)^2} \\
    \phi & = & 2\pi u_2
\end{eqnarray*}
\]


On peut également trouver, cf eq 36, une distribution adaptée à \( (\cos \theta)^n \) :
\[
\begin{eqnarray*}
    p( \vec{v} ) & = & \frac{n+1}{2\pi} (\cos \theta)^n\\
    \cos \theta & = & u_1^{\frac{1}{n+1}} \\
    \sin \theta & = & \sqrt{1 - (\cos \theta)^2} \\
    \phi & = & 2\pi u_2
\end{eqnarray*}
\]

Une solution assez simple consiste à générer la direction \( \vec{h} \) avec cette distribution et ensuite à construire la direction \( \vec{l} \) qui doit être symétrique de \( \vec{o} \) par rapport à \( \vec{h} \).
rappel : \( \vec{h} \) doit être la direction bissectrice de  \( \vec{o} \) et \( \vec{l} \) dans le modèle Blinn-Phong.
\[
    \vec{l} = 2(\vec{o} \cdot \vec{h}) \vec{h} - \vec{o}
\]

Mais bien sur, comme on transforme \( \vec{h} \), la direction générée, pour obtenir \( \vec{l} \), il faut aussi évaluer le changement de mesure pour déterminer la densité de probabilité de \( \vec{l} \) :
\[
    p( \vec{l} ) = p( \vec{h} ) \frac{1}{4 \cos \angle( \vec{o}, \vec{h} )} = p( \vec{h} ) \frac{1}{4 \cos \angle( \vec{l}, \vec{h} )}
\]

c'est sans doute plus lisible en utilisant directement le produit scalaire des directions :
\[
    p( \vec{l} ) = p( \vec{h} ) \frac{1}{4(\vec{o} \cdot \vec{h})} = p( \vec{h} ) \frac{1}{4(\vec{l} \cdot \vec{h})}
\]

pour les curieux : les détails du calcul du changement de variable entre \( \vec{h} \) et \( \vec{l} \) sont dans Pbrt, chapitre 9, par exemple.

Dernier "détail", à cause de la transformation de \( \vec{h} \) vers \( \vec{l} \), \( \vec{l} \) peut assez souvent se trouver sous la surface, ie \( \vec{n} \cdot \vec{l} < 0 \), dans ce cas, la densité de proba doit être nulle et le modèle de matière noir.


Bilan on vient de construire une distribution de directions proportionnelle au terme\(  ( \cos \theta_h )^\alpha \) du modèle Blinn-Phong. La génération n'est pas parfaite, ie certaines directions générées sont aberrantes et on ne tient pas compte du \( \cos \theta \). Mais malgré ces défauts cette solution est largement préférable à une distribution uniforme ou simplement proportionnelle à \( \cos \theta \) et devrait permettre de très fortement réduire la variance dans l'exemple précédent, ie l'image avec la boule rouge. A vérifier !


exercice : calculez l'albedo / la reflectance hémisphérique de vos matières afin de vérifier le code calcul que vous venez d'écrire et de vérifier que votre modèle de matière est correctement normalisé. La définition se trouve dans GI Compendium eq 64, ou Pbrt, chaptire 4  :
\[
    \rho= \frac{1}{\pi} \int_\Omega \int_\Omega f_r( \vec{o}, \vec{l} ) \cos \theta_o \cos \theta_l \, dl do \mbox{ avec } \theta_o : \angle(\vec{n}, \vec{o}) \mbox{ et } \theta_l : \angle(\vec{n}, \vec{l})
\]

ou pour une seule direction \( \vec{o} \) :
\[
    \rho( \vec{o} ) = \int_\Omega f_r( \vec{o}, \vec{l} ) \cos \theta_l \, dl \mbox{ avec } \theta_l : \angle(\vec{n}, \vec{l})
\]

vérifiez que vos estimations convergent bien vers la même valeur quelque soit la distribution utilisée pour générer les directions, ie uniforme, \( \cos \) ou \( \cos^n \). Ces estimations devraient être inférieures ou égales à 1 dans tous les cas.

indications : écrivez systématiquement un couple de fonctions pour chaque distribution, une pour générer une direction, l'autre pour évaluer sa densité de probabilité. Ce sera plus simple pour utiliser les combinaisons d'estimateurs de "multiple importance sampling" ou "importance resampling" ou pour tester plusieurs distributions, tout simplement. par exemple :
Vector sample_cos( const float u1, const float u2 ) { ... }
float pdf_cos( const Vector& v ) { ... }
Vector sample_uniform( const float u1, const float u2 ) { ... }
float pdf_uniform( const Vector& v ) { ... }


exercice : pour intégrer tout le modèle, ie les 2 termes, diffus et spéculaire, comment construire une seule distribution correcte / adaptée ?
indication : la distribution sera très probablement une mixture, comment écrire l'estimateur Monte Carlo dans ce cas ?


exercice : comment utiliser "importance resampling" pour construire une distribution proportionnelle à \( (\cos \theta_h)^\alpha \cos \theta \) ? Même question pour une distribution proportionelle à \( f_r( \vec{o}, \vec{l} ) \cos \theta \) ?


pour les curieux : pourrait-on utiliser un estimateur différent dans ce cas ? Quel autre estimateur Monte Carlo permet de normaliser la densité de probabilité a posteriori ? (ce n'est pas celui utilisé par "importance resampling"...)