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 \frac{c}{\pi}
V(p, \vec{l}) L_i(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
\frac{c}{\pi} V(p, \vec{l_k}) L_i(p, \vec{l_k}) \cos \theta_k
\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
\frac{c}{\pi} V(p, \vec{l}) L_i(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 \frac{c}{\pi}
V(p, q) L_e(q, \vec{qp}) \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
\frac{c}{\pi} V(p, q_k) L_e(q_k, \vec{q_kp}) \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 :