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 ...) :
Utilisez tuto_ray1.cpp comme point de depart.
completez premake4.lua pour generer un projet. cf le wiki de gKit2


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 (cf. la fonction intersect() dans tuto_ray1.cpp) 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

    extraire les sources de lumiere (trouver les objets emettant de la lumiere)
   
    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


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




exercice 2 : matière

Choisissez une couleur arbitraire pour chaque triangle de la scène. Utilisez cette couleur pour construire l'image des objets visibles.

exercice bonus : observateur quelconque

Comment placer et orienter l'observateur directement dans la scène ?

une solution simple consiste à utiliser la classe Orbiter et les fonctions OrbiterIO::readOribter() et gk::OrbiterIO::writeOrbiter() permettant de charger et de stocker les transformations nécessaires.

l'utilitaire mesh_viewer de gKit permet de charger une scène, de se déplacer / changer la position de l'observateur, de relire et d'enregistrer les transformations (cf les touches 'c' et 'v').


exercice 3 : ombres et éclairage direct

Choisissez un point sur chaque source de lumière et vérifiez que chaque point visible par la caméra est également visible par les sources de lumière.
Comment déterminer l'origine et la direction des rayons permettant de faire ce test ?
Les points non éclairés seront noirs.


remarque : gk::Triangle::point(0.3, 0.3) permet de trouver le centre du triangle. pourquoi ?


exercice 4 : sources étendues

Générez plusieurs points à la surface des sources de lumières. cf gk::Triangle::sampleUniform(). Comment déterminer la couleur du pixel ?

pas correct... correct

voila un exemple de ce que l'on peut obtenir. remarque : l'image de gauche est incorrecte. pourquoi ?


exercice 5 : matières

Utilisez la description de matières associée aux triangles pour calculer l'image.

remarque : les matières sont décrites dans un fichier texte, "materials.mtl", il est très simple de le modifier.

Par exemple, la matière par défaut s'appelle "diffuse50SG", elle est déclarée :

newmtl diffuse50SG
Kd 0.7  0.7  0.7

ce qui correspond à une matière diffuse grise qui réflechit 70% (rgb 0.7 0.7 0.7) de la lumière incidente. La couleur décrite par Kd dans le fichier mtl est conservée dans le champ diffuse_color de la structure gk::MeshMaterial.

pour utiliser une texture pour moduler le résultat, il suffit d'ajouter la ligne map_Kd nom_image, par exemple :

newmtl diffuse50SG
Kd 0.7  0.7  0.7
map_Kd simple.png

Voici les correspondances entre le fichier mtl et les valeurs stockées dans un objet gk::MeshMaterial :

Kd r g b
MeshMaterial::diffuse_color= Normalize(r,g,b)
MeshMaterial::kd= Length(r,g,b)
map_Kd image
MeshMaterial::diffuse_texture= image
Ks r g b
MeshMaterial::specular_color= Normalize(r,g,b)
MeshMaterial::ks= Length(r,g,b)
map_Ks image
MeshMaterial::specular_texture= image
Ns MeshMaterial::ns= Ns

Avec les valeurs de l'exemple précédent :

gk::MeshMaterial material;
material.name= "diffuse50SG"
material.kd= 0.7;
material.diffuse= gk::Energy(1.0, 1.0, 1.0);
material.map_Kd= "simple.png"


Il est également assez simple de charger des textures pour changer l'apparence de l'objet.
consultez la mise à jour de tuto_ray1.cpp (sur le depot)



// ensemble de textures, une par triangle
std::vector<gk::Image *> diffuse_textures;

// recupere la texture 'diffuse_texture' de chaque triangle
int build_textures( const gk::Mesh *mesh )
{
    // cree une image blanche
    gk::Image *white= gk::createImage( 2, 2, gk::Vec4(1, 1, 1, 1) );
    // destruction automatique
    gk::ImageManager::manager().insert( white );
   
    for(int i= 0; i < mesh->triangleCount(); i++)
    {
        gk::Image *image= NULL;
       
        const gk::MeshMaterial& material= mesh->triangleMaterial(i);
        if(material.diffuse_texture.empty())
            // pas de texture definie, utiliser une texture blanche (cf multiplication par diffuse_color)
            image= white;
       
        else
        {
            // charger l'image, utilise le cache de lecture de gk::ImageManager
            image= gk::readImage(material.diffuse_texture);
           
            if(image == NULL)
                // utilise une texture par defaut, en cas d'echec de lecture (debug.png)
                image= gk::defaultImage();
        }
       
        diffuse_textures.push_back( image );
    }
   
    printf("%lu textures.\n", diffuse_textures.size());
    return 0;
}

// renvoie une valeur 'filtree' de la texture
gk::Color image_sample( const gk::Image *image, const gk::Point& texcoord )
{
    int u= (image->width -1) * texcoord.x;
    int v= (image->height -1) * texcoord.y;
   
    // mode nearest + repeat, renvoie le pixel le plus proche
    gk::Vec4 color= image->pixel( abs(u % image->width), abs(v % image->height) );
   
    // \todo les autres modes de filtrage... linear, mipmaps, etc.
   
    return gk::Color(color);
}


exercice 6 : toutes les sources

Comment répartir N points à la surface de l'ensemble des sources de lumières (au lieu de N par source, solution a priori utilisee à l'exercice 4) ?

L'idée est de choisir aléatoirement d'abord une source, puis un point sur cette source, et de recommencer N fois.
Proposez au moins 2 manières différentes de choisir une source parmis l'ensemble de sources.

Vous pouvez tester vos idées avec la scène emission.obj, prévue pour ce cas précis.
Quel comportement observez-vous dans chaque cas ?



voila un exemple, avec 8 échantillons par pixel : à gauche une solution a priori correcte, à droite, sélection uniforme de la source.
quel est le problème ? quelle est la répartition des calculs dans les 2 cas (quelles sont les sources sélectionnées dans les 2 cas pour N échantillons ?)



(en orange : les sources de lumière, les 2 panneaux lumineux emettent le même flux)


exercice 7 : éclairage indirect

En plus de calculer la lumière directement incidente, calculez aussi la lumière réfléchie indirectement par les autres objets de la scène.
L'idée est d'estimer la lumière directe avec 1 seul point choisit sur l'ensemble des sources et d'estimer la lumière indirecte en choisissant une autre direction. Il suffit alors de trouver l'objet visible dans cette direction et d'estimer l'energie directe qu'il réfléchit.

Comment choisir cette nouvelle direction pour estimer la lumière indirecte ?

Q1. commencez par écrire la formulation (de la solution) du problème : L(x, o)= L0(x, o) + L1(x, o) + L2(x, o) + ...
    avec L0(x, o)= Le(x, o), la luminance émise par un point x (sur une source de lumière) vers o, la position de l'observateur,

    L1(x,o)=Le(y,x)fr(y,x,o)G(x,y)dyL_1(x, o)= \int L_e(y,x) f_r(y, x, o) G(x, y) dy
        avec Le(y,x)Le(y,x) la luminance emise de y vers x,
        fr(y,x,o)f_r(y, x, o) la reflexion en x vers o de la luminance emise par y,
        G(x,y)=cosθxcosθy||xy||2V(x,y)G(x, y)= \frac{\cos \theta_x \cos \theta_y}{||xy||^2}V(x,y) le terme géométrique associé au changement de variables d'intégration (directions vers points) et V(x, y)= 1 si x et y sont visibles.

    quel est l'estimateur Monte Carlo permettant d'évaluer L1 ?
    L1(x,o)=1NiLe(yi,x)fr(yi,x,o)G(x,yi)1pdf(yi)L_1(x, o)= \frac{1}{N} \sum_i L_e(y_i, x) f_r(y_i, x, o) G(x, y_i) \frac{1}{pdf(y_i)}

    quelle(s) variables aléatoires ?

    quelles densités de probabilités ? y se trouve sur une source lumière, par définition (pourquoi ?)
    pdf(yi)=pdf(si)pdf(pi)pdf(y_i)= pdf(s_i) \, pdf(p_i) avec pdf(pi)=1Aire(si)pdf(p_i)= \frac{1}{Aire(s_i)} et sis_i le triangle / source sélectionné.

    comment choisir si ? (indication: pdf(si)=1NSourcespdf(s_i)= \frac{1}{N_{Sources}} ? ou autre chose de plus adapté ? cf exercice 6)

Q2. quelle est la formulation de L2, L3, etc ? leurs estimateurs Monte Carlo ?
    indication :
    L2(x,o)=Le(z,y)fr(z,y,x)G(y,z)fr(y,x,o)G(x,y)dydzL_2(x, o)= \int \int L_e(z, y) f_r(z, y, x) G(y, z) f_r(y, x, o) G(x, y) dy dz
   
Q3. comment évaluer L2 ?
    indication :
    z se trouve sur une source de lumière (pourquoi ?)
    y doit donc etre visible à la fois de x (cf G(x, y)) et de z (cf G(y, z)). ou se trouvent les points visibles de x ? et ceux visibles de z ?

Q4. existe-t-il plusieurs "stratégies" / manières de choisir y ? citez en 3... et formulez leur densité.

Q5. peut-on construire plusieurs estimateurs Monte Carlo de L2 ? sont-ils équivalents ? quel est le meilleur ? quoi faire dans ce cas ?

Q6. memes questions avec un rebond supplementaire, comment évaluer L3 ?
   

Vous pouvez tester vos idées avec les scènes geometrie.obj et secondary.obj.
Quel comportement observez-vous ? Essayez plusieurs choix de directions pour estimer la lumière indirecte