L3 synthèse d'images
2024

à lire : et les triangles ?

on vient de voir dans le cours comment calculer l'intersection d'un rayon avec un plan ou avec une sphère. ça marche plutôt bien, mais si on souhaite créer un objet avec un logiciel 3d comme Blender, on ne pourra pas l'utiliser pour calculer une image. la plupart des logiciels de modélisation 3d enregistrent uniquement des triangles. mais il n'est pas trop difficile de calculer l'intersection entre un rayon et un triangle...

après tout, un triangle n'est qu'un morceau de plan délimité par 3 sommets. on sait déjà comment calculer le point d'intersection du rayon avec un plan, il ne reste plus qu'à vérifier que le point est aussi à l'intérieur du triangle ! 

une solution consiste à vérifier que le point est du bon coté de chaque arête du triangle. on va utiliser une propriété du produit vectoriel : selon l'ordre des vecteurs, le résultat n'est pas orienté de la même manière !


le schéma permet de vérifier : si on calcule le produit des vecteurs ab et ac, ie de ab vers ac (noté ab x ac), on obtient le vecteur orienté vers le haut. si on calcule le produit dans l'autre sens, de ac vers ab (noté ac x ab), le résultat aussi est orienté dans l'autre sens.

en pratique, ca veut aussi dire que le triangle abc n'est pas orienté dans le même sens que le triangle acb. un triangle abc est orienté comme le produit de ses 2 aretes : ab et ac. le triangle abc est donc orienté comme le produit de ab vers ac et le triangle acb est orienté comme le produit de ac vers ab.

c'est cette propriété que l'on va utiliser pour vérifier que le point est du "bon" coté de chaque arête du triangle. par exemple, pour l'arếte ab, le point p peut se trouver à l'intérieur du triangle ou pas. c'est l'orientation du triangle abp qui permet de décider :


on va donc tester 3 triangles, un par arête. si les 3 triangles abp, bcp, et cap sont orientés comme le triangle abc, le point est du bon coté de chaque arête et il est à l'intérieur du triangle. sinon, il est du mauvais coté d'une arête et donc, à l'extérieur du triangle :


il ne reste plus qu'à comparer les orientations de 3 vecteurs pour écrire le test ! et on sait déja que l'on peut utiliser le produit scalaire de 2 vecteurs pour comparer leur orientation, ie u . v sera positif si les vecteurs u et v sont orientés dans le même sens et négatif sinon.

on peut maintenant écrire le test pour une arête : dot() permet de calculer le produit scalaire et cross() le produit vectoriel.

pour tester p par rapport à l'arête ab :

ce qui s'écrit directement :

Vector abc= cross( Vector(a, b), Vector(a, c) );    // orientation du triangle abc : ab x ac
Vector abp= cross( Vector(a, b), Vector(a, p) );    // orientation du triangle apb : ab x ap

if(dot(abc, abp) < 0)                               // comparaison des orientations
    return false; // le point p est du mauvais coté de l'arete ab !
...

il ne reste plus qu'à recommencer pour les arêtes bc et ca. on peut aussi éviter de calculer 3 fois l'orientation du triangle abc...

le test complet va ressembler à :

bool intersect( /* rayon */ const Point& o, const Vector& d, const float tmax, /* triangle */ const Point& a, const Point& b, const Point& c )
{
    // on commence par calculer la normale du triangle, ie son orientation, ab x ac
    Vector n= cross( Vector(a, b), Vector(a, c) );

    // intersection du plan (a, n) avec le rayon (o, d)
    float t= dot(n, Vector(o, a)) / dot(n, d);
    if(t < 0 || t > tmax)
        return false;    // l'intersection n'est pas valide, soit derriere la camera soit trop loin

    // point d'intersection avec le plan
    Point p= o + t*d;

    // tester le triangle abp / orientation des aretes ab et ap
    Vector abp= cross( Vector(a, b), Vector(a, p) );
    if(dot(n, abp) < 0)
        return false;    // p est à l'extérieur de l'arête ab

    // tester le triangle bcp / orientation des aretes bc et bp
    ...

    // tester le triangle cap / orientation des aretes ca et cp
    ...

   
    // le point est du bon coté des 3 aretes, il est donc à l'intérieur du triangle !
   
return true;

}


question : pourquoi ab x ac est la normale du triangle abc ?
question : pourquoi ab x ac est la normale du plan qui porte le triangle abc ?

question : existe-t-il d'autres manières de faire le calcul ? ie de formuler le problème ? oui ! un peu plus de 384 manières différentes mais équivalentes. c'est expliqué dans l'article "Optimizing Ray-Triangle Intersection via Automated Search", A. Kensler, P. Shirley, 2006.
les auteurs voulaient optimiser leur code de calcul d'intersection et ils ont fini par écrire un programme qui génère automatiquement le code de toutes les solutions pour trouver la plus rapide !


charger un fichier wavefront / .obj et dessiner tous les triangles

maintenant que l'on sait tester l'intersection d'un rayon et d'un triangle, il ne reste plus qu'à charger un fichier... il y a plusieurs fonctions dans gkit3 pour charger les triangles décrits par un fichier wavefront .obj.

la plus simple est read_positions() définie dans mesh_io.h.
    #include "mesh_io.h"

std::vector<Point> positions;
if(!read_positions("data/robot.obj", positions)) return "erreur";

cette fonction renvoie les positions des 3 sommets de chaque triangle dans un std::vector. pour n sommets chargés il y a donc n / 3 triangles. on peut écrire la boucle de parcours sur les sommets  :

for(unsigned i= 0; i +2 < positions.size(); i += 3)
{
    // triangle abc
    Point a= positions[ i ];
    Point b= positions[ i +1 ];
    Point c= positions[ i +2 ];

    ...
}

ou sur les triangles :
// parcours les triangles
for(unsigned i= 0; i < positions.size()/3; i++)
{
    // triangle abc
    Point a= positions[ 3*i ];
    Point b= positions[ 3*i +1 ];
    Point c= positions[ 3*i +2 ];

    ...
}


exporter un fichier wavefront .obj dans Blender

menu File > Export > Wavefront (.obj), puis sélectionner un répertoire et un nom de fichier. les options par défauts sont correctes !