M2 - Images


TP1 - transformations et pipeline



 

Partie 1 : affichage d'une primitive.


L'opération fondamentale réalisée par un pipeline de rendu est le dessin d'une primitive : déterminer quels pixels permettent de remplir une forme "simple" dans l'image résultat.

Cette opération est découpée en plusieurs étapes :


exercice 1 : transformations

    Les objets et leurs sommets sont décrits dans un repère local, puis ces objets sont placés et orientés dans le repère de la scène. Un observateur / camera est également placé et orienté dans le repère de la scène. Une transformation de projection est aussi associée à l'observateur. Ces 3 transformations sont classiquement représentées par des matrices homogenes 4x4 :
    Model (transformation du repère local au repère de la scène),
    View (transformation du repère de la scène au repère camera)
    Projection (transformation du repère camera au repère projectif homogène de la camera).

Plus une autre qui représente les dimensions de l'image résultat :
    Viewport (transformation du repère projectif homogène vers le repère de l'image).

Il est possible de composer ces matrices afin de construire une seule matrice de transformation permettant de passer directement du repère local de l'objet au repère de l'image. Ecrivez cette relation.

Par construction de la transformation de projection, les points visibles par l'observateur se retrouvent dans le repere projectif (après transformation) à l'interieur du cube unitaire [-1 1] sur les 3 axes.

Si l'on choisit une matrice identité comme projection, ou peut on placer des points qui seront visibles / associés à un pixel de l'image ?



prise en main de gKit :

installez gKit, les informations sont sur la page précédente.

Vous pouvez générer la documentation avec doxygen. Elle sera consultable dans html/index.html (ou en ligne). Les classes de bases sont documentées dans la partie module de la documentation générée.

archive actuelle, si vous n'avez pas accès à la forge lyon 1 : gkit2_07_08.zip


gKit utilise la classe Transform pour représenter et manipuler les transformations, (cf mat.h). Les classes Point et Vector (cf vec.h) permettent de représenter un point et un vecteur. Les fonctions de construction des transformations standards sont aussi disponibles : Identity(), Translation(), Rotate(), Perspective().

La transformation d'un point s'écrit directement :
    #include "mat.h"
    #include "vec.h"

    Transform T;                          // identité

    Transform T= Identity();              // identité aussi
    Transform T= RotateX(30);             // rotation de 30° autour de l'axe X
    Transform T= Translate( 0, 0, 50 );   // translation sur l'axe Z

    Point p;
    Point q= T(p);                        // renvoie le point reel transforme

La composition de transformations est aussi disponible :
    Transform A, B;
    Transform C= A * B;

La transformation inverse est également calculée :
    Transform M= C.inverse();
    Transform M= Inverse(C);

Pour obtenir le point homogène après la transformation d'un point p :
    Point p= Point(1, 0, 1);
    vec4 h= p;

    vec4 ph= M(h);                        // renvoie le point homogene apres la transformation


gKit utilise la classe Image pour représenter un ensemble de pixels et fournit également des fonctions permettant d'enregistrer l'image dans un fichier.
    #include "image.h"
    #include "image_io.h"

    Image image(largeur, hauteur);

    write_image(image, "resultat.bmp");


Les operateurs () de Image permettent de lire et de modifier la couleur du pixel de coordonnées x, y.
    Image image(1024, 512);
    image(x, y)= Color(1, 0, 0);
    Color pixel= image(x, y);

La classe de base Color représente une couleur par comme un vecteur à 4 composantes : rouge, vert, bleu, transparence (alpha).

exemple :

    #include "vec.h"         // vecteur, point
    #include "color.h"       // couleur
    #include "image.h"       // image
    #include "image_io.h"    // entrees / sorties sur les images

    int main( )
    {
        Image image(512, 512);    // cree une image de 512x512 pixels
   
        // parcourir tous les pixels de l'image
        for(int y= 0; y < image.height(); y++)        // chaque ligne
            for(int x= 0; x < image.width(); x++)     // chaque colonne
                image(x, y)= Color(1, 0, 0, 1);       // colorie chaque pixel en rouge opaque
   
        // enregistre le resultat
        write_image(image, "out.bmp");
        return 0;
    }

exercice 2 : version Reyes, subdivision.

Une solution relativement souple applique le principe algorithmique "diviser pour règner" au problème. Il est immédiat de dessiner un objet plus petit qu'un pixel, dans les autres cas, il faut le découper. C'est la base de la technique qui a permis a Pixar de produire de nombreux films d'animation depuis le milieu des années 80, avant d'utiliser le lancer de rayons et l'intégration numérique.

Proposez une solution utilisant cette idée pour dessiner un triangle.
indication : pour subdiviser un triangle en 4, une solution consiste à calculer le point milieu de chaque arête et à construire les 4 sous triangles.

Cette solution peut-elle fonctionner lorsque certains sommets sont en dehors de la zone visible ? Modifiez votre programme pour inclure cette fonctionnalité.
indication : il serait judicieux d'arreter la subdivision lorsque un sous triangle est entierement non visible / à l'extérieur de la zone visible.

Ecrivez une fonction permettant de savoir si un triangle est visible pour la camera. Un triangle ne peut pas être visible par la camera s'il existe un plan séparant le triangle et une face de la zone visible de la camera.
indication : ou se trouvent les 8 sommets qui définissent le volume visible par la camera ? dans quel repère ont-ils des coordonnées "simples" ? comment connaitre les coordonnées des sommets dans le repère du monde ? de la camera ? etc.

indication : si les 3 sommets du triangle se trouvent du mauvais cote d'une seule face du volume visible, le triangle ne peut pas etre visible...

Comment arreter la subdivision ?
indication : un rectangle englobant aligné sur les axes peut etre une approximation correcte d'un (petit) triangle, dans certains cas...

question bonus : et dans l'autre sens ? comment choisir des points à la surface d'un triangle pour dessiner tous les pixels qu'il couvre, sans laisser de trous dans l'image, et minimiser le nombre de points utilisé ? cf "Forward Rasterization", V. Popescu, P. Rosen, 2006


pour les curieux : conception de Reyes : "The Reyes rendering architecture", R.L. Cook, L. Carpenter, E. Catmull, 1987
la page 100 qui discute de comment éviter de découper les objets partiellement derrière la camera devrait vous interresser...


question bonus : et avec une sphère ? on fait comment (sans la trianguler...) ?


Partie 2 : plusieurs primitives.

Lorsque la scène est composée de plusieurs objets ou primitives, il est (très) fréquent que plusieurs primitives recouvrent le même pixel. En général, on souhaite donner au pixel la couleur de l'objet (opaque) le plus proche de la camera.

Comment déterminer la distance associée à un fragment issu d'un triangle ? Comment conserver le plus proche de la camera ?
rappel : la somme des aires des sous-triangles pp0p1, pp1p2, pp2p0 correspond à l'aire du triangle p0p1p2.

Comment réaliser ce test dans la version Reyes ?

question bonus : en plus de reconstruire la distance à la camera, on peut appliquer la même démarche à d'autres valeurs associées aux sommets des triangles, aux normales, par exemple. cette perturbation est couramment utilisée, elle permet de donner un aspect lisse aux objets, sinon l'utilisation des normales géométriques, constantes à l'intérieur des triangles donnent un aspect facette à l'objet.



Partie bonus :

complétez le tuto sur la fragmentation / rasterization et répondez aux questions posées dans la solution directe.