Projection 3D

Transformation

Remplissage des faces

Eclairage

Textures

Z-Buffering



Programmation 3D



Chapitre IV
GESTION FACE CACHEE



Ce chapitre traite du tri puis du remplissage des faces par une couleur unie et tenant compte de l'éclairage.

La position de la caméra peut être rapportée comme une translation puis une rotation à l'intérieur même du monde, or ces deux manipulations ont étés vues dans le chapitre 2. Désormais on peut considérer que la caméra est fixe sur (0,0,1), il est donc facile de vérifier si une face est visible ou non. Il suffit de tester le signe de la composante Z de la normale à la face, il faut effectuer le produit vectoriel, qui retourne alors le vecteur perpendiculaire à la face. Le problème c'est que nous avons des triangles et non pas des vecteurs, mais qu'à cela ne tienne, il nous suffit de rapporter deux côtés du triangle pour obtenir nos vecteurs.
Le produit vectoriel :
Xn
=(y1-y0)(z0-z2)-(z1-z0)(y0-y2)
Yn =(z1-y0)(x0-x2)-(x1-x0)(z0-z2)
Zn =(x1-x0)(y0-y2)-(y1-y0)(x0-x2)

Comme je l'ai dit ci-dessus, seul les z nous intéressent, en effet si la normale nous retourne une valeur négative, cela signifie que le polygone est face à nous et donc qu'il doit être traité. De plus, la normale à la face peut nous permettre de calculer l'éclairage de la face. En effet si la face est perpendiculaire à la source de lumière, elle est éclairée au maximum, puis progressivement elle vas être de moins en moins éclairée jusqu'à ce quelle soit dans l'alignement du vecteur lumière ce qui correspond plus concrètement à calculer l'angle entre la normale à la face et le vecteur lumière.

Le modèle d'illumination de Lambert
Lumière réfléchie par la face = Lumière ambiante + Lumière diffuse * angle de réflexion.

Il ne manque que l'angle à déterminer, et comme vu dans le Chapitre 2, le produit scalaire peut nous aider dans ce cas là.
A.B = Ax*BX + Ay*By + Az*Bz
Le résultat de cette opération, si nous supposons que nous avons normalisé le vecteur, est le cosinus de l'angle.
A.B = A B cos angle avec A B égale à 1.
Nous n'avons pas besoin de calculer l'angle en tant que tel. Le cosinus étant compris entre -1 et 1, arccos(1) valant 0 deg, arccos(0) valant 90 deg et arccos(-1) valant 180 deg, il nous suffit de traiter les valeurs positives, les valeurs négatives étant fixées à la lumière ambiante, sinon on utilise directement le cos(angle) comme coefficient.

Une foi déterminé les faces visibles ainsi que leurs réflexion, il faut déterminer le recouvrement des faces, car en effet la normal n'est pas suffisante s'il y a un objet concave ou bien tout simplement s'il y a plusieurs objet a afficher.

L'ALGORITHME DU PEINTRE
Simple de compréhension, je dessine les faces les plus éloignées pour les recouvrir par les faces les plus proches.
Pour cela on peut utiliser le z moyenne des faces. Cette technique très populaire est assez rapide mais n'est pas sans faille. C'est pour cela qu'on a inventé le Z-Buffering, que j'espère pouvoir vous expliquer dans un futur proche.

Récapitulatif des nouveaux types définis

/*
Matrice homogène de transformation
*/
typedef float MAT4x4[4][4];

/*
Structure pour représenter un point dans l'espace 2D
*/
typedef struct str_2d
{
        int x,y;
} _2D;

/*
Structure pour représenter un point dans l'espace 3D
*/
typedef struct str_3d
{
        float x,y,z;
} _3D

/*
La structure qui définie un sommet dans ses différents repères
*/
typedef struct str_sommet
{
        _2D ecran;
        _3D local;
        _3D monde;
}SOMMET;

/*
Structure pour les faces des polygones (un triangle)
*/
typedef struct str_face
{
        int a,b,c;
        unsigned char éclairage;
        float z_moyenne;
        _3D normale;
}FACE;

/*
Structure pour contenir un objet
*/
typedef struct str_objet
{
        int nbsommet;
        int nbface;
        SOMMET *sommet;
        FACE *face;
}OBJET;

Récapitulatif des fonctions 3D

/*
Produit scalaire (retourne l'angle entre v1 et v2)
*/
double scalaire(_3D v1, _3D v2)
{
        return (v1.x * v2.x) + (v1.y * v2.y) + (v1.z * v2.z);
}

/*
Produit vectoriel (retourne l'orthogonale de v1 et v2)
*/
void vectoriel(_3D *v, _3D v1, _3D v2)
{
        v->x = (v1.y * v2.z) - (v2.y * v1.z;
        v->y = (v1.z * v2.x) - (v2.z * v1.x;
        v->z = (v1.x * v2.y) - (v2.x * v1.y;
}

/*
Normalise un vecteur (longueur de 1)
*/
void normalise(_3D *n)
{
        double longeur;
        longeur = sqrt(n->x*n->x + n->y*n->y + n->z*n->z);
        if(longueur==0) return;
        n->x /= longueur;
        n->y /= longueur;
        n->z /= longueur;
}

/*
Copie une matrice source vers une matrice de destination
*/
void copie_matrice(MAT4x4 source, MAT4x4 dest)
{
        memcpy(dest,source,sizeof(MAT4x4));
}

/*
Multiplie 2 matrices et met le résultat dans dest
*/
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];
}

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

/*
Matrice de changement d'échelle
*/
void echelle(MAT4x4 m,float ex,float ey,float ez)
{
        MAT4x4 emat,mat1;
        ident_matrice(emat);
        emat[0][0]=ex;
        emat[1][1]=ey;
        emat[2][2]=ez;
        mult_matrice(m,emat,mat1);
        copie_matrice(mat1,m);
}

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

/*
Matrice de rotations
Elle utilise les tableaux de sinus et cosinus expliqués plus bas
dans le récapitulatif des autres fonctions
*/
void rotation(MAT4x4 m,int ax,int ay,int az)
{
        MAT4x4 xmat, ymat, zmat, mat1, mat2;
        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,mat1);
        mult_matrice(mat1,xmat,mat2);
        mult_matrice(mat2,zmat,m);
}

/*
Transformation 3D -< 2D
ResX_2 = résolution X divisée par 2
ResY_2 = résolution Y divisée par 2
*/
void projection(SOMMET *sommet)
{
        sommet->ecran.x = sommet->monde.x*DISTANCE/sommet->monde.z+ResX_2;
        sommet->ecran.y = sommet->monde.y*DISTANCE/sommet->monde.z+ResY_2;
}

/*
Multiplication de chaque sommets par la matrice
*/
void transformation(OBJET *Objet,MAT4x4 m);
{
        SOMMET *sommet;
        int i;
        for(i=0;i<Objet->nbsommet;i++)
        {
                sommet = Objet->sommet + i;
                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);
        }
}

/*
Calcule les normales de face pour chaque polygones
On peut les calculer à chaque séquences mais une solution
plus économique consiste à les calculer au début et de faire
subir aux normales les mêmes transformations avec les matrices homogènes.
*/
void calcule_normale(OBJET *Objet)
{
        _3D v1,v2;
        int face;
        for(face=0;face<Objet->nbface;face++)
        {
                v1.x = Objet->sommet[Objet->face[face].a].monde.x -
                       Objet->sommet[Objet->face[face].b].monde.x;
                v1.y = Objet->sommet[Objet->face[face].a].monde.y -
                       Objet->sommet[Objet->face[face].b].monde.y;
                v1.z = Objet->sommet[Objet->face[face].c].monde.z -
                       Objet->sommet[Objet->face[face].b].monde.z;
                v2.x = Objet->sommet[Objet->face[face].c].monde.x -
                       Objet->sommet[Objet->face[face].b].monde.x;
                v2.y = Objet->sommet[Objet->face[face].c].monde.y -
                       Objet->sommet[Objet->face[face].b].monde.y;
                v2.z = Objet->sommet[Objet->face[face].c].monde.z -
                       Objet->sommet[Objet->face[face].b].monde.z;
                vectoriel(&(Objet->face[face].normale,v2,v1);
                normalise(&(Objet->face[face].normale);
        }
}

/*
Tri des faces a afficher ordre des z moyennes
*/
void trier_faces(int nb,int *ordre,OBJET *Objet)
{
        int p,i,m;
        float zmem;
        for(i=1;i<nb;i++)
        {
                p=i-1;
                m=ordre[i];
                zmem=Objet->face[ordre[i]].z_moyenne;
                while(p>=0 && zmem>Objet->face[ordre[p]].z_moyenne)
                {
                        ordre[p+1]=ordre[p];
                        p--;
                }
                ordre[p+1]=m;
        }
}

/*
Dessine les sommets de l'objet
*/
void dessine_objet(OBJET *Objet)
{
        int face,nb=0;
        double angle;
        _2D face2D[3];
        int *ordre;
        ordre = (int *)malloc( Objet->nbface * sizeof(int) );
        for(face=0;face<Objet->nbface;face++)
        {
                if(Objet->face[face].normale.z < 0)
                {
                        Objet->face[face].z =
                                Objet->sommet[Objet->face[face].a].monde.z +
                                Objet->sommet[Objet->face[face].b].monde.z +
                                Objet->sommet[Objet->face[face].c].monde.z;
                        ordre[nb++] = face;
                        angle = scalaire(Objet->face[face].normale.lumiere);
                        if(angle<0)
                          Objet->face[face].eclairage =
                            AMBIANT;
                        else
                          Objet->face[face].eclairage =
                            AMBIANT + DIFFUSE * angle;
                }
        }
        trier_faces(nb,ordre,Objet);
        for(face=0;face<nb;face++)
        {
                face2D[0] = Objet->sommet[Objet->face[ordre[face]].a].ecran;
                face2D[1] = Objet->sommet[Objet->face[ordre[face]].b].ecran;
                face2D[2] = Objet->sommet[Objet->face[ordre[face]].c].ecran;
                dessine_poly(face2D,Objet->face[ordre[face]].eclairage);
        }
}

Récapitulatif des autres déclarations et autres fonctions

/*
On a vu comment déterminer les faces à afficher voici l'affichage
des faces ( triangle )
Pour mieux comprendre je vous conseille d'aller voir le chapitre sur
la 2D qui traite des polygones.
*/
#define MIN(a,b) ( a < b ? a : b )
#define MAX(a,b) ( a > b ? a : b )
int minY;
int maxY;

typedef struct str_scanline
{
        int droite,gauche;
}SCANLINE;
SCANLINE scanline[ResY];

void swap(int I1, int I2)
{
        int Itemp;
        Itemp=I1;
        I1=I2;
        I2=Itemp;
}

void scan(int x1, int y1, int x2, int y2)
{
        int Y;
        double Xinc;
        double X;
        minY=MAX(0,MIN(minY,MIN(y1,y2)));
        maxY=MIN(ResY - 1,MAX(minY,MAX(y1,y2)));
        if(y1==y2 && y1>=0 && y1<ResY)
        {
                scanline[y1].gauche =
                        MAX(0,MIN(MIN(scanline[y1].gauche,x1),x2));
                scanline[y1].droite =
                        MIN(ResX - 1,MAX(MAX(scanline[y1].droite,x1),x2));
        }
        if(y2<y1) {swap(y1,y2); swap(x1,x2);}
        Xinc = (x2-x1) / (y2-y1);
        X=x1;
        for(Y=y1;Y<=y2;Y++)
        {
                scanline[Y].gauche =
                        MAX(0,MIN(scanline[Y].gauche,X));
                scanline[Y].droite =
                        MIN(ResX -1 ,MAX(scanline[Y].droite,X));
                X+=Xinc;
        }
}

void dessine_poly(_2D *listesommet, unsigned char couleur)
{
        _2D *ptr1,*ptr2;
        int i;
        for(i=0;i<ResY;i++)
        {
                scanline[i].gauche = ResX;
                scanline[i].droite = -1;
        }
        minY=ResY;
        maxY=-1;
        ptr1=listesommet+1;
        ptr2=listesommet+2;
        scan(listesommet->x,listesommet->y,ptr1->x,ptr1->y);
        scan(listesommet->x,listesommet->y,ptr2->x,ptr2->y);
        scan(ptr1->x,ptr1->y,ptr2->x,ptr2->y);
        for(i=minY;i<=maxY;i++)
        {
                hline(scanline[i].gauche,scanline[i].droite,i,couleur);
        }
}

/*
On remarquera l'utilisation de hline qui dessine une ligne horizontale,
mais qui n'a pas été définie. C'est tout simplement parce qu'elle dépend
du mode d'affichage choisit.
Maintenant il est plus facile d'avoir une table des cosinus et des sinus
déjà pré-calculée sous forme de tableau, ainsi on allège le temps de calcul.
*/
float SIN[360],COS[360];
void Precal(void)
{
        int angle;
        for(angle=0;angle<360;angle++)
        {
                SIN[angle]=sin(angle*M_PI/180.0);
                COS[angle]=cos(angle*M_PI/180.0);
        }
}