M2 Image

TP3 - lancer de rayon




Partie 1 : lancer de rayons !

exercice 1 : chargez la scene data/cornell.obj et construisez une image en lancer de rayon. vous pouvez reprendre les étapes de la doc en ligne "principes du lancer de rayons".

indication : utilisez data/cornell_orbiter.txt  comme camera.
indication : vous pouvez gagner du temps en partant du code de base, cf tuto_rayons.cpp

le programme principal aura toujours la même structure :

charger une scène
charger une camera

créer une image
récupérer les transformations pour générer les rayons

pour chaque pixel de l'image
    générer l'origine et l'extremité du rayon
    si intersection valide avec les objets / triangles de la scène
        récupérer les infos sur l'objet touché par le rayon
        calculer la couleur du pixel...

enregistrer l'image


exercice 2 : restructurez un peu le code, c'est une bonne idée d'écrire une fonction pour calculer les intersections d'un rayon avec les triangles de la scène, par exemple :

Hit intersect( const Point& o, const Vector& d, const float tmax );


Partie 2 : et avec de la lumière, c'est mieux ?

pour calculer une image avec des ombres, des pénombres et des reflets, il faut comprendre 2 choses : comment formuler ce que l'on veut calculer, et comment l'estimer avec Monte Carlo. mais bien sur, il y a quelques détails à régler avant d'obtenir une bonne solution. les exercices suivants décomposent une solution.

exercice 1 : application directe de l'équation de rendu.
l'équation de rendu permet de formuler la solution du problème : quelle est la lumière réfléchie vers la camera / l'origine du rayon. mais pour calculer ça, on commence par regarder dans toutes les directions d'ou peut venir la lumière.

relisez cette partie du cours, et appliquez-la dans un cas simple : on suppose que c'est un ciel uniforme qui éclaire la scène. ce qui se traduit par : \( L_i(p, \vec{l_k}) \)  est constant pour toutes les directions (au dessus de l'horizon...). il ne reste plus qu'à écrire l'estimateur Monte Carlo, et, bien sur, à choisir des échantillons et leur densité de proba. on va utiliser une densité constante.

donc on calcule, avec l'équation de rendu :
\[
    L_r(p, \vec{o})= \int_\Omega L_i(p, \vec{l}) V(p, \vec{l}) \cos \theta dl
\]

et on définit la lumière incidente, en fonction de la direction \(\vec{l}\) :
\[
    L_i(p, \vec{l}) = 1 \mbox{ si } \vec{l} \cdot \vec{Y} > 0
\]
ie le ciel n'éclaire que pour les directions au dessus de l'horizon... pour les directions en dessous, c'est 0.

on écrit ensuite l'estimateur Monte Carlo :
\[
    L_r(p, \vec{o})= \frac{1}{N} \sum_{k=1}^N L_i(p, \vec{l_k}) V(p, \vec{l_k}) \cos \theta \frac{1}{pdf(\vec{l_k})}
\]

il ne reste plus qu'à générer N directions et on a tous les ingrédients pour faire le calcul.
manque la recette pour \( pdf(\vec{l}) \)... on va utiliser une densité constante qui permet de construire des directions aléatoires avec 2 nombres aléatoires entre 0 et 1, \(u_1\) et \(u_2\) :
\[
    \begin{eqnarray*}
        pdf(\vec{l}) &= & \frac{1}{2\pi}\\
        \cos \theta &= & u_1\\
        \sin \theta &= & \sqrt{1 - \cos^2 \theta}\\
        \phi &= & 2\pi u_2\\
        \vec{l}& = & ( \cos \phi \sin \theta, \cos \theta, \sin \theta \sin \phi )\\
    \end{eqnarray*}
\]
ces directions sont générées directement dans le repère de la scène et elles sont toutes au dessus de l'horizon.

le code de l'estimateur aura toujours la même structure : générer une direction, évaluer sa pdf, évaluer la fonction, et recommencer pour calculer la moyenne (l'esperance) de N réalisations :

std::random_device hwseed;

// init d'un générateur de nombre de aleatoire std
std::default_random_engine rng( hwseed() );
std::uniform_real_distribution<float> uniform(0, 1);

Color color;
for(int i= 0; i < N; i++)

{
    // genere u1 et u2
    float u1= uniform( rng );
    float u2= uniform( rng );
   

    // construit la direction l et évalue sa pdf
    Vector l= { ... };
    float pdf= { ... };

    // evalue la fonction
    float V= { ... }; // 0 ou 1, selon les intersections
    float cos_theta= { ... };
    Color emission= Color(1);
   

    // moyenne
    color= color + emission * V * cos_theta / pdf;
}
color= color / float(N);


attention : il faudra aussi penser à décoller l'origine de ce rayon, cf doc en ligne "precision et lancer de rayon"

indication : n'oubliez pas la doc en ligne, plus complète : cf "Monte Carlo et équation de rendu" + "Monte Carlo et éclairage direct"

et hop !
 


exercice 2 : et avec les vraies sources de lumière ?
on peut faire exactement le même calcul, mais en remplacant l'émission du ciel par celle de la source de lumière placée sur le plafond de la cornell box. comment ?

il suffit d'adapter la définition de \( L_i(p, \vec{l_k}) \) :
\[
    L_i(p, \vec{l}) = L_e(q,- \vec{l}) \mbox{ avec q visible depuis p dans la direction } \vec{l}
\]

pourquoi ça marche ? la matière des triangles de la scène émet de la lumière, ou pas. \( L_e(q) \) est nulle pour les triangles classiques et > 0 pour les sources de lumière. il suffit de récupérer cette information dans la description de la matière du triangle visible dans la direction \(\vec{l}\)...

ce qui se code sans trop de problème :
if(Hit hit= intersect(p + epsilon*pn, l))
{
    const Material& material= mesh.triangle_material(hit.triangle_id);    // cf la doc de Mesh
    Color emission= material.emission;                                    // cf la doc de Material
   
    // utiliser l'emission du triangle touche par le rayon dans la direction l
    ...
}

le reste du code est identique, on a uniquement modifié la fonction intégrée... pour N= 256




et en couleur, c'est mieux non ? que faut-il faire pour récupérer la matière du triangle visible pour le rayon de chaque pixel de l'image ?




Partie 3 : éclairage direct efficace ?

la solution de la partie précédente n'est pas très efficace, il faut un grand nombre de directions / d'échantillons pour obtenir une image sans défauts. mais on peut aussi re-écrire l'équation de rendu en intégrant sur les surfaces des sources de lumières au lieu d'intégrer sur les directions de l'hémisphère. ce qui permet de viser directement les sources de lumières au lieu d'espérer les trouver en regardant dans quelques directions.

relisez monte carlo et équation de rendu, si nécessaire.

rappel, voici l'équation de rendu avec son intégrale sur les directions de l'hémisphère \( \Omega \) :
\[
    L_r(p, \vec{o})= \int_\Omega L_i(p, \vec{l}) V(p, \vec{l}) \cos \theta dl
\]
voici la version avec l'intégrale sur les points des surfaces \( A \) :
\[
    L_r(p, \vec{o})= \int_A L_e(q, \vec{qp}) V(p, q) \cos \theta_p \frac{\cos \theta_q}{|| \vec{pq}||^2} dq
\]
on peut maintenant écrire l'estimateur :
\[
    L_r(p, \vec{o})= \frac{1}{N} \sum_{k=1}^N L_e(q_k, \vec{q_kp}) V(p, q_k) \cos \theta_p \frac{\cos \theta_{q_k}}{|| \vec{pq_k}||^2} \frac{1}{pdf(\vec{q_k})}
\]

il ne reste plus qu'à choisir des points q sur les sources de lumières pour estimer le résultat. la cornell box est très simple : presque toutes les surfaces voient la source de lumière au plafond. la 1ere solution présentée dans Monte Carlo et éclairage direct fonctionnera très bien. par exemple pour N= 256 :