Projection 3D

Transformation

Remplissage des faces

Eclairage

Textures

Z-Buffering



Programmation 3D



Chapitre II
Echelles et transformations




Ce chapitre traite des différentes transformations possibles sur un objet 3D, plus précisément les translations, les changements d’échelles et les rotations.
Il est important de bien saisir les notions de trigonométrie de base.

Algèbre linéaire
En géométrie, le vecteur peut être imaginé comme une flèche qui pointe dans une certaine direction avec une certaine longueur.
En 3D le vecteur est défini par ses trois composantes x, y et z qui donnent sa longueur et sa direction.

Il existe plusieurs opérations réalisables sur les vecteurs.
La somme où l'on additionne leurs composantes respectives, la soustraction qui fonctionne de la même manière, mais il existe cependant deux formes de multiplication :

Le produit scalaire et le produit vectoriel.
Soit A et B des vecteurs, thêta l'angle entre les vecteurs et A.B le produit scalaire de A et B.

A . B = A B cos thêta
A . B = Ax*Bx + Ay*By + Az*Bz


En isolant cos dans l'équation du produit scalaire, on peut obtenir l'angle entre deux vecteurs. En fait, on obtient le cosinus de cet angle. Soulignons que très souvent, c'est précisément ce que l'on recherche. Si on désire l’angle, on utilisera alors arccos.

Le produit vectoriel quand à lui associe à deux vecteurs un troisième qui est perpendiculaire (orthogonale) aux deux premiers. En voici la définition exacte:

A ^ B = (Ay*Bz - Az*By)x + (Az*Bx - Ax*Bz)y + (Ax*By - Ay*Bx)z


Les opérations sur les vecteurs sont utiles quand il s'agit de déterminer si une face est cachée et les calculs d'ombrages.

Matrices de transformations
Si on veut animer nos objets 3D sur l'écran, il va falloir leur faire subir des translations, des rotations et des changements d'échelle.
Les transformations géométriques peuvent être représentées très élégamment dans des matrices(une matrice est tout simplement un tableau à 2 dimensions de nombres réels).
Nous utilisons ces matrices pour transformer nos coordonnées locales en coordonnées du monde 3D. Chacune de ces opérations transforme un vecteur par l’entremise d’une multiplication vecteur-matrice. Ces matrices sont généralement représentées sous la forme 3x3. Cependant, il est possible de combiner plusieurs matrices ensemble afin de pouvoir travailler avec une seule matrice. On appelle cette dernière la matrice de transformation homogène. Nous verrons comment construire cette matrice un peu plus loin.

Matrice de changements d'échelle
Le changement d'échelle permet à un objet de changer de taille, donc de l'agrandir ou de le réduire. Une telle transformation s'exprime par la matrice suivante:
Pour changer l'échelle d'un objet par un facteur de 3, nous devons inscrire dans ex, ey et ez la valeur 3 et multiplier les vecteurs formant cet objet par la matrice en question. Ceci revient au même de multiplier chaque composante d’un vecteur par 3. Bien que cette dernière méthode semble plus rapide, n’oublions pas que nous cherchons par la suite à construire une matrice homogène. Nous avons donc besoin de la représentation matricielle.

Matrices de rotation
Les rotations 3D ne sont en fait qu'une série de rotations 2D. Pour passer en 3D, il suffit d'ajouter une dimension à notre matrice.
Le problème, c'est qu'il ne faut pas que nos transformations sur un axe modifie les composantes des coordonnées locales de l'objet. Chaque axe requiert donc une matrice unique. Il faut s'assurer que la rotation autour de l'axe des X ne modifiera pas les composantes Y et Z des coordonnées locales de l'objet, ainsi de suite pour les autres axes. Cela nous donne donc 3 Matrices :

Matrice des X avec l'influence des X sur X après rotation, l'influence des Y sur X après rotation et l'influence des Z sur X après rotation.
Matrice des Y avec l'influence des X sur Y après rotation, l'influence des Y sur Y après rotation et l'influence des Z sur Y après rotation.
Matrice des Z avec l'influence des X sur Z après rotation, l'influence des Y sur Z après rotation et l'influence des Z sur Z après rotation.

Finalement il nous faut construire une matrice de translation. Cependant, nous ne pouvons pas effectuer une translation sur un vecteur en utilisant des combinaisons linéaires. Une solution populaire est d'utiliser le système de coordonnées homogènes. Dans ce nouveau système, chaque vecteur a 4 composantes, x, y, z et la coordonnée homogène w, initialement à 1. Nous pouvons à présent effectuer une translation en utilisant des combinaisons linéaires :
Cette nouvelle matrice vient cependant brouiller les cartes. En effet, nos anciennes matrices ne fonctionnent plus, car le produit d’un matrice de 4x4 par une matrice de 3x3 est indéterminé. Fort heureusement, nos anciennes matrices se transforment tout aussi facilement en matrice de 4x4.
Matrice homogène de changement d’échelle
Matrice homogène de rotation X
Matrice homogène de rotation Y
Matrice homogène de rotation Z

Programmation graphique 3D


Après cette attaque massive de mathématiques, nous allons maintenant regarder l'aspect plus pratique.

/*
        La matrice 4x4 pour les transformations
*/
typedef float MAT4x4[4][4];
/*
Les structures de coordonnées bi-dimensionnelles et tri-dimensionnelles
*/
typedef struct str_2d
{
        int x,y;
} _2D;
typedef struct str_3d
{
        int x,y,z;
} _3D;
/*
 Comme vu dans le chapitre 1, les sommets nécessitent une structure
 les exprimant sur 3 repères différents.
 Ensuite les objets sont des suites de sommets.
*/
typedef struct str_sommet
{
        _3D local;  /* coordonnées locales       */
        _3D monde;  /* coordonnées dans le monde */
        _2D ecran;  /* coordonnées a l'écran     */
} SOMMET;

typedef struct str_objet
{
        int nbsommet;    /* nombre de sommets                 */
        SOMMET *sommet;  /* pointeur sur la suite des sommets */
} OBJET;

Nous pouvons donc à présent définir un objet, avec un ensemble de sommets le représentant. Ce sont ces sommets que nous allons modifier afin de transformer l'objet.
Pour chaque transformation nous allons avoir besoin d'une matrice homogène d'identité.

void ident_matrice(MAT4x4 m)
{
        memset(m,NULL,sizeof(MAT4x4)); /* initialise la matrice */
        m[0][0]=1.0; /* 1 0 0 0 */
        m[1][1]=1.0; /* 0 1 0 0 */
        m[2][2]=1.0; /* 0 0 1 0 */
        m[3][3]=1.0; /* 0 0 0 1 */
}

Nous allons maintenant manipuler ces matrices, mais avant tout il serait confortable de pouvoir les copier et les multiplier.

void copie_matrice(MAT4x4 source, MAT4x4 dest)
{
        memcpy(dest,source,sizeof(MAT4x4));
}

void mult_matrice(MAT4x4 m1, MAT4x4 m2, MAT4x4 dest)
{
        short i,j;
        for(i=0;i<4;i++)
                for(j=0;j<4;j++)
                        dest[i][j] = m1[i][0]*m2[0][j]+
                                     m1[i][1]*m2[1][j]+
                                     m1[i][2]*m2[2][j]+
                                     m1[i][3]*m2[3][j];
}

Désormais nous pouvons appliquer nos algorithmes de transformation.

void echelle(MAT4x4 m,float ex,float ey, float ez)
{
        MAT4x4 emat,m1;
        ident_matrice(emat);
        emat[0][0]=ex; /* ex  .  .  . */
        emat[1][1]=ey; /*  . ey  .  . */
        emat[2][2]=ez; /*  .  . ez  . */
                       /*  .  .  .  . */
        mult_matrice(m,emat,m1);
        copie_matrice(m1,m);
}

void translation(MAT4x4 m,float tx,float ty,float tz)
{
        MAT4x4 tmat,m1;
        ident_matrice(tmat);
        tmat[3][0]=tx; /*  .  .  . tx */
        tmat[3][1]=ty; /*  .  .  . ty */
        tmat[3][2]=tz; /*  .  .  . tz */
                       /*  .  .  .  . */
        mult_matrice(m,tmat,m1);
        copie_matrice(m1,m);
}

void rotation(MAT4x4 m,int ax,int ay,int az)
{
        MAT4x4 xmat,ymat,zmat,m1,m2;
        ident_matrice(xmat);
        ident_matrice(ymat);
        ident_matrice(zmat);

        xmat[1][1] = COS(ax);
        xmat[1][2] = SIN(ax);
        xmat[2][1] =-SIN(ax);
        xmat[2][2] = COS(ax);

        ymat[0][0] = COS(ay);
        ymat[0][2] =-SIN(ay);
        ymat[2][0] = SIN(ay);
        ymat[2][2] = COS(ay);

        zmat[0][0] = COS(az);
        zmat[0][1] = SIN(az);
        zmat[1][0] =-SIN(az);
        zmat[1][1] = COS(az);

        mult-matrice(m,ymat,m1);
        mult-matrice(m1,xmat,m2);
        mult-matrice(m2,zmat,m);
}

L'ordre dans lequel on effectue nos transformations importe peu. Vous aurez sans doute remarquer que la matrice de base est unitaire et que les objets étant solides et indéformables nous pouvons donc par la suite utiliser cette matrice comme un coefficient de transformation. Il faut ensuite projeter les coordonnées sur l'écran.

void projection(SOMMET *sommet)
{
        sommet->ecran.x = sommet->monde.x * DISTANCE / sommet->monde.z + MX;
        sommet->ecran.y = sommet->monde.y * DISTANCE / sommet->monde.z + MX;
}

void transformation(OBJET *Objet,MAT4x4 m)
{
        int v;
        SOMMET *Sommet;
        for(v=0;v<Objet->nbsommet;v++)
        {
                sommet = &Objet->sommet[v];
                sommet->monde.x = sommet->local.x*m[0][0] +
                                  sommet->local.y*m[1][0] +
                                  sommet->local.z*m[2][0] +
                                  m[3][0];
                sommet->monde.y = sommet->local.x*m[0][1] +
                                  sommet->local.y*m[1][1] +
                                  sommet->local.z*m[2][1] +
                                  m[3][1];
                sommet->monde.z = sommet->local.x*m[0][2] +
                                  sommet->local.y*m[1][2] +
                                  sommet->local.z*m[2][2] +
                                  m[3][2];
                projection(sommet);
                &Objet->sommet[v] = sommet;
        }
}


Désormais, vous pouvez projeter et animer n'importe quel objet. Dans le chapitre suivant nous allons voir comment remplir ces objets avec une couleur unie tout en tenant compte de la luminosité. Pour cela je vous conseille de regarder l'affichage de polygones dans les chapitres sur la 2D.