M2 - Images

TP 2 - Méthodes de monte carlo
et lancer de rayons.




travail à rendre pour fin fevrier :
une archive complète de votre travail que vous expédirez avec le mail de l'université en précisant les noms des binomes.

Vous apporterez également un soin particulier à la rédaction du rapport expliquant les détails de l'utilisation des méthodes de Monte Carlo que vous avez utilisés.




mise à jour 7/12: tp2_intersection.tar.gz

quelques explications dans la doc (à générer avec Doxygen) sur les paramètres magiques des fonctions d'intersection de gk::BBox et gk::Triangle / PNTriangle.


mise à jour 18/11: tp2_camera.tar.gz

la classe gk::Camera peut maintenant enregistrer ses paramètres dans un fichier texte et le recharger plus tard pour reprendre la même position.
l'exemple mesh_viewer.cpp charge un objet et utilise openGL pour se déplacer autour... les touches C et V permettent de copier / coller la position de la camera.
utile pour regler le point de vue avant de lancer un calcul un peu long ... non ?




L'objectif de ce tp est de mettre en place les éléments nécessaires pour une simulation "correcte" de l'éclairage direct et indirect basé sur les méthodes de Monte Carlo.

Utilisez les scenes de test suivantes, elles sont composées de très peu de geométrie (triangles), ce qui permet d'éviter de construire une structure accélératrice pour limiter les calculs de visibilité (au moins dans un premier temps ...) :

Partie 1 : mise en jambe

exercice 1 : intégration numérique et Monte Carlo.



Commencez par utiliser des échantillons distribués uniformement, puis utilisez des échantillons distribués selon une densité de probabilité adaptée à la fonction f( ).

rappels : Comment générer des directions uniformes sur une (hemi-) sphère ?

Parcourez "Global Illumination Compendium" pour trouver les densités de probabilités adaptées et la méthode de tirage d'échantillons aléatoires distribués selon la densité de probabilité choisie.

Combien d'échantillons sont nécessaires dans chaque cas (vérifiez que le résultat de l'estimation converge vers la solution) ?


indications et exemple complet :
voila les résultats pour l'exemple vu en td, f( )= cos \theta

convergence de l'estimation  convergence

le programme de test et d'exemple est la : tp2.cpp
quelle méthode a généré la courbe de gauche ? celle de droite ? que peut-on dire de la convergence pour la méthode de droite ?


pour tracer la courbe, il est très simple d'utiliser gnuplot, par exemple. il suffit de copier la sortie du programme dans un fichier texte et de l'afficher ensuite avec gnuplot :

tp2 > mc.txt                    // copie la sortie du programme dans le fichier mc.txt
gnuplot                         // lance gnuplot
plot "mc.txt" with lines        // trace la courbe en lisant les valeurs dans le fichier texte
quit                            // quitte gnuplot



Partie 2 : lancer de rayons et visibilité

exercice 1 : visualisation par lancer de rayons

Pour chaque pixel de l'image résultat, déterminez les coordonnées de l'origine du rayon dans le repère de la scène, ainsi que celles de son extrémité.
Dans quel espace est-il le plus simple de calculer l'origine et l'extrémité du rayon ?

Calculez l'intersection du rayon avec tous les triangles de la scène (utilisez gk::MeshIO::read( ) pour la charger) et ne conservez que l'intersection valide la plus proche de l'origine du rayon.


remarques :

indications : votre programme devrait ressembler à :

main
    creer une image
    charger un ou plusieurs objets
   
    pour chaque pixel de l'image :
        générer un rayon

        // trouver le point visible pour le rayon
        pour chaque triangle de chaque objet :
            calculer l'intersection du rayon avec le triangle,
            conserver l'intersection si elle est valide et plus proche que la dernière trouvée

        si une intersection valide existe
            ecrire un pixel blanc dans l'image

    sauver l'image


il est également plus lisible d'écrire les calculs d'intersection dans une fonction séparée :
    bool intersection( const gk::Ray& ray, const gk::Mesh *mesh, gk::Hit& hit );

    qui renvoie vrai ou faux, selon le cas, et complète la structure gk::Hit en cas d'intersection.

dernière remarque : pour déterminer la position du point d'intersection à l'abscisse t, le plus simple est d'utiliser l'opérateur ( ) de gk::Ray. par exemple, gk::Point p= ray(t);

Partie 3 : éclairage direct


    éclairage direct, 1 échantillon    éclairage direct, 100 échantillons    éclairage direct, 500 échantillons


Identifez les sources de lumières présentes dans la scène, elles sont associées à une matière nommée "diffuseLuminaire1SG", dont l'émission est non nulle (cf. gk::MeshMaterial.emission)
Il suffit de parcourir l'ensemble de triangles des objets chargés et de récupérer leur matière avec gk::Mesh::triangleMaterial( ) pour construire l'ensemble des sources de lumières éclairant la scène.

indications :
    charger un objet, geometry.obj, par exemple,

    sources= ensemble vide
    pour chaque triangle de l'objet :
        si la matiere du triangle à une émission non nulle,
            insérer le triangle et son emission dans l'ensemble de sources.

    par exemple :

    // representation d'une source de lumiere
    struct Source
    {
        gk::Triangle triangle;
        gk::Energy emission;
    };

    // ensemble de sources de lumieres
    std::vector<Source> sources;

    // charger un objet
    gk::Mesh *mesh= gk::MeshIO::read("geometry.obj");
    assert(mesh != NULL);

    int n= mesh->triangleCount();
    for(int i= 0; i < n; i++)
    {
        // recuperer la matiere associe a chaque triangle de l'objet
        const gk::MeshMaterial& material= mesh->triangleMaterial(i);
        if(material.emission.r + material.emission.g + material.emission.b > 0.f)
        {
            // construire une source de lumiere
            Source source;
            source.emission= material.emission;
            source.triangle= mesh->getTriangle(i);

            // inserer la source de lumiere dans l'ensemble.
            sources.push_back(source);
        }
    }
   

exercice 1 : 1 source

Pour déterminer l'éclairage d'un point du à une source "étendue", il faut générer des points à la surface de la source.
Comment choisir uniformément des points à la surface d'un triangle émettant de la lumière ?

indications :
    connaissant un point p à la surface d'un objet de la scène, la direction de l'observateur et une source de lumière :
            si la source peut émettre de l'énergie vers p,
                générer un point q aléatoirement à la surface de la source ( cf. gk::Triangle::sampleUniform() ),
                tester la visibilité entre p et q,
                évaluer l'énergie émise par q réfléchie par la matière du point p vers l'observateur,
                évaluer la pdf de q ( cf. gk::Triangle::sampleUniform() et gk::Triangle::pdfUniform() ),
                accumuler le résultat,
                recommencer N fois...

        calculer la moyenne


Ecrire une fonction : gk::Color eclairage_direct( const gk::Point& p, const gk::Normal& n, const MeshMaterial& matiere, const std::vector<Source>& sources );

    pour chaque source :
        si la source peut a priori éclairer p,
            générer un point q aléatoirement à la surface de la source
            tester la visibilité entre p et q
            ...


exercice 2 : toutes les sources

On souhaite maintenant calculer rapidement l'éclairage direct du à un "grand" nombre de sources de lumière. Il est immédiat de faire N calculs pour chaque source, mais on peut faire plus économique.

Au lieu de calculer l'éclairage direct du à chaque source, on considère l'union des sources comme une seule source de lumière. Il ne reste plus qu'à choisir aléatoirement, un point à la surface de cette "source" (très) étendue, comment faire ?

Testez et vérifiez votre méthode de sélection avec une scène construite pour tester ce cas précis (202 sources de lumière) : emission

indications : chaque source de lumière n'émet pas la même quantité d'énergie (cf. emission et aire de la source), de même, selon sa position et son orientation chaque source peut émettre une quantité d'énergie différente vers un point p.

éclairage direct avec 202 sources

indications : vous aurez besoin de construire une fonction de répartition permettant de choisir une source de lumière en fonction de son aire, ou de l'énergie émise, etc.
une classe Cdf et un programme d'exemple est disponible : tp2_cdf.cpp

Construisez une fonction de répartition des sources de lumières (sur quel critère ?) avant de commencer le calcul de l'image et utilisez-la ensuite pour estimer l'éclairage direct de chaque point.

Modifiez votre fonction eclairage_direct :
    gk::Color eclairage_direct( const gk::Point& p, const gk::Normal& n, const MeshMaterial& matiere, const Cdf& repartition, const std::vector<Source>& sources );


Serait-il plus interessant de construire une fonction de répartition pour chaque point avant de calculer son éclairage direct ? quels sont les avantages et les inconvénients par rapport à la solution précédente (une seule fonction de répartition pour l'ensemble des points) ?

exercice bonus : matières réfléchissantes

Modifiez les matières de certains objets afin d'utiliser une brdf (très) réfléchissante (cf. modèle de Blinn-Phong). Que se passe-t-il ? pourquoi ? proposez une amélioration.

un petit peu de lecture pour corriger le problème :


Partie 4 : éclairage indirect


    éclairage direct + indirect, 100 échantillons    éclairage direct + indirect, 500 échantillons


exercice bonus : longueur de chemin fixée à R rebonds

Dans un premier temps, on choisira un seul rayon pour estimer l'éclairage direct et un autre pour estimer l'éclairage indirect.
L'éclairage direct ne sera estimé qu'avec un seul point à la surface de l'ensemble des sources de lumière (cf. exercice 2).
L'éclairage indirect ne sera estimé qu'avec une seule direction choisie uniformément et en construisant un chemin comportant R rebonds. R sera fixé à 3, par exemple.

Ecrivez une fonction :
     gk::Energy eclairage_indirect( const gk::Point& p, const gk::Normal& n, const MeshMaterial& matiere, const int rebonds );

indications : commencez par R=1, un seul rebond simulé.

Pour obtenir une image de qualité correcte, il faudra cependant générer plusieurs chemins par pixel. Le plus simple est de modifier la construction du rayon pour pouvoir générer plusieurs rayons différents passant par un pixel de l'image.

intuition : au lieu de toujours prendre un coin ou le centre du pixel, il suffit de choisir aléatoirement un point à l'intérieur du pixel.

rappels : gagnez du temps, exprimez toutes les densités de probabilité sur les aires et évitez de "mélanger" des points choisis sur les aires et des directions choisies sur l'hémisphère.

exercice bonus : longueur variable, roulette russe

Modifiez la construction du chemin pour terminer le chemin lorsque sa longeur est supérieure à R, en utilisant une roulette russe.
Proposez d'autres stratégies de construction de chemins plus adaptées aux matières des différents rebonds du chemin, ou à la quantité d'énergie pouvant "circuler" sur le chemin.


Testez vos stratégies sur une scène construite dans cet objectif : secondary

exercice bonus : tirage préférentiel des directions

Pour améliorer la qualité de l'estimation, il est relativement simple de modifier la manière de choisir les directions dans la construction du chemin.
Proposez un choix proportionnel à cos \theta, ou à la matière de l'objet, cf partie 1, dernière intégrale.