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 :