|
Transformation |
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 |
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 langle, 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 lentremise dune 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 dun vecteur par 3. Bien que cette
dernière méthode semble plus rapide, noublions
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 dun 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
|
/*
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. |
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.