M2 - Images

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



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 : 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 dans le repere de la scene

        // trouver le point visible pour le rayon
        pour chaque triangle de chaque objet :
            transformer le rayon dans le repere local de l'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.


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(hit.t);


remarque : pour choisir le point de vue, le plus simple est d'utiliser la classe gk::FirstPersonCamera et sa fonction read(). Cela permet d'utiliser mesh_viewer pour naviguer dans la scene et d'enregistrer les parametres de la camera (appuyez sur C pour copier, et V pour coller...).

exercice 2 : structures accélératrices

Construisez un arbre permettant de grouper les objets / les primitives de la scène en utilisant le premier algorithme décrit en cours.

Comparer les temps d'execution avec et sans la structure accélératrice. Est-elle rentable pour toutes les scènes ?


indications : utilisez std::partition de la stl pour organiser les objets.

il faut fournir un prédicat à std::partition, la manière la plus souple consiste à créer un foncteur :

struct compareTriangle
{
    int axe;
    float coupe;
   
    compareTriangle( const int _axe, const float _coupe )
        :
        axe(_axe),
        coupe(_coupe)
    {}
   
    bool operator( )( const gk::RTTriangle& triangle ) const
    {
        return (triangle.getBBoxCenter(axe) < coupe);
    }
};

on peut utiliser ce predicat avec std::partition :

gk::RTTriangle *pivot= std::partition( &triangles[triangle_start], &triangles[triangle_end], compareTriangle(axe, milieu) );


utilisation de la structure accélératrice :

Ecrivez la fonction intersect( const gk::Ray& ray, gk::Hit& hit ) qui teste les intersections entre le rayon et les triangles stockés dans l'arbre.

Utilisez les champs de la structure gk::Hit pour stocker le point d'intersection, sa normale, sa matiere, etc.

indication : pour retrouver facilement la matière d'un triangle lors du calcul d'éclairage, le plus simple est de la récuperer dans le gk::Mesh, mais il faut stocker l'indice du triangle... cf gk::RTTriangle::id

	// charger un objet
	gk::Mesh *mesh= gk::MeshIO::read("bigguy.obj");
	if(mesh == NULL)
		return "erreur";
	
	// recupere les triangles 
	int n= mesh->triangleCount();
	for(int i= 0; i < n; i++)
{
gk::RTTriangle triangle= mesh->getTriangle(i);
triangle.id= i; // conserver l'indice du triangle dans l'objet

triangles.push_back( triangle );
}

Il faut également penser à remplir correctement la structure gk::Hit dans votre fonction d'intersection :

for(unsigned int i= debut; i < fin; i++) { float t, u, v; if(triangles[i].Intersect(ray, hit.t, t, u, v)) { hit.t= t; hit.u= u; hit.v= v; hit.p= ray(t); hit.object_id= triangles[i].id; // permet de retrouver toutes les infos associees au triangle } }

Ce qui permet de retrouver la normale et la matière du triangle directement :
gk::MeshMaterial& material= mesh->getTriangleMaterial(hit.object_id);
gk::Normal normal= mesh->getTriangleNormal(hit.object_id);

// ou la normale interpolée à l'interieur du triangle, au lieu de la normale géométrique
gk::Normal normal= mesh->getUVNormal(hit.object_id, hit.u, hit.v);



Partie 2 : éclairage direct et méthode de Monte Carlo

Reprenez l'exemple du cours et écrivez la fonction direct( ) qui estime l'énergie directe réfléchie par un point p. :


    é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);

    for(int i= 0; i <
mesh->triangleCount(); i++)
    {
        // recuperer la matiere associe a chaque triangle de l'objet
        const gk::MeshMaterial& material= mesh->triangleMaterial(i);
        if(isBlack(material.emission) == false)
        {
            // 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 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



Partie 3 : éclairage indirect et méthode de Monte Carlo

Reprenez l'exemple du cours et calculez l'éclairage indirect en choisissant un point à la surface des objets :


rappel : pensez à exprimer toutes les probabilités sur le même domaine / mesure...


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



Annexe : squelette d'application pour démarrer.


#include "Geometry.h"
#include "RTTriangle.h"
#include "Transform.h"
#include "MeshIO.h"
#include "ImageIO.h"

// objet
gk::Mesh *mesh= NULL;

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

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

// ensemble de triangles

std::vector<gk::RTTriangle> triangles;

// calcule l'intersection d'un rayon et de tous les triangles
bool intersect( const gk::Ray& ray, gk::Hit& hit )

{
for(unsigned int i= 0; i < triangles.size(); i++) { float t, u, v; if(triangles[i].Intersect(ray, hit.t, t, u, v)) { hit.t= t; hit.u= u; hit.v= v; hit.p= ray(t); hit.object_id= triangles[i].id; // permet de retrouver toutes les infos associees au triangle } }

return (hit.object_id != -1);
}

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

    // identifier les sources de lumiere
    for(int i= 0; i <
mesh->triangleCount(); i++)
    {
        // recuperer la matiere associe a chaque triangle de l'objet
        const gk::MeshMaterial& material= mesh->triangleMaterial(i);
       
        if(
material.emission.isBlack() == false)
        {
            // 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);
        }
    }


    // recupere les triangles
    for(int i= 0; i < mesh->triangleCount(); i++)
    {

        gk::RTTriangle triangle= mesh->getTriangle(i);

              triangle.id= i; // conserver l'indice du triangle dans l'objet
        triangles.push_back( triangle );

    }

   
    // creer une image resultat
    gk::HDRImage *image= new gk::HDRImage(512, 512);
   
    // definir les transformations
    gk::Transform model;
   
    gk::Transform view= gk::Translate( gk::Vector(0.f, 0.f, -50.f) );   // recule la camera pour observer tout l'objet
    gk::Transform projection= gk::Perspective(50.f, 1.f, 1.f, 1000.f);  // projection perspective
    gk::Transform viewport= gk::Viewport(image->width(), image->height());      // transformation adaptee a la resolution de l'image resultat

    // compose les transformations utiles
    gk::Transform vpv= viewport * projection * view;
   
    // parcours tous les pixels de l'image
    for(int y= 0; y < image->height(); y++)
    {
        for(int x= 0; x < image->width(); x++)
        {
            // generer le rayon pour le pixel (x,y) dans le repere de l'image
            gk::Point origine(x +.5f, y + .5f, 0.f);    // sur le plan near
            gk::Point extremite(x +.5f, y + .5f, 1.f);    // sur le plan far
           
            // transformer le rayon dans le repere de la scene
            gk::Point o= {...}; // transformation de origine, par exemple vpv(origine) ou vpv.inverse(origine)
            gk::Point e= {...}; // transformation de extremite
               
            gk::Ray(o, e);
            gk::Hit hit(ray);
            gk::Color color;
           
            // calculer l'intersection du rayon avec les triangles du mesh
            intersect(ray, hit);
           
            // si une intersection existe...
            if(hit.object_id != -1)
            {
                // calculer l'energie reflechie par le point vers la camera
                gk::Point p= ray(hit.t);
                gk::Normal= mesh->getTriangleNormal(hit.object_id);
                const gk::MeshMaterial& material= mesh->getTriangleMaterial(hit.object_id);

                // eclairage direct
                color= direct(p, n, material);
               
                // eclairage indirect
                color= color + indirect(p, n, material);
            }
           
            // ecrire une couleur dans le pixel de l'image
            image->setPixel(x, y, color);
        }
    }
   
    // enregistrer l'image
    gk::HDRImageIO::write(image, "render.hdr");
    delete image;
    return 0;
}