INTRODUCTION

Original Author: Damiano Vitulli

Translation by: Click here

Premièrement, il est nécessaire d’expliquer le principe d’un moteur 3D.

La réponse est relativement simple:

un moteur 3D est un ensemble de structures, fonctions et algorithmes utilisés pour visualiser, après de nombreux calculs et transformations, des objets 3D sur un écran en 2D.

Les 3 parties principales d’un moteur 3D sont:

  1. L’aquisition des données des objets dans des structures
  2. Les transformations de position des objets dans le monde
  3. Le rendu de la scène sur l’écran 2D

C’est pourquoi nous existons… allez maintenant au taf !

Dans cette leçon, nous verrons comment définir les structures principales pour tracer un objet 3D.

LES VERTICES (Sommets)

Supposons que l’on ai un objet et que l’on veuille le montrer sur un écran en 2D. Afin de le réaliser, il est nécessaire d’obtenir des informations a propos de sa structure. Comment faire? Premièrement, on définit des points clés : les vertices de l’objet. Chaque vertex est composé de 3 coordonnées x, y et z. Chaque coordonnée doit être exprimée travers une variable FLOAT ou DOUBLE, car nous avons toujours besoin de la meilleure résolution pour rendre la scène. Cette solution est utilisée dans tout moteur 3D mais n’est pas sans défauts, en effet on perd de lz précision pour des valeurs numériques trop élevés. Cela limite l’espace dans lequel la simulation évolue. Dans un simulateur spatial on peut éventuellement faire face a ce problème. En bref pour définir un vertex en C, on utilise une structure composée de 3 variables x,y,z.

typedef struct {
   float x,y,z;
} vertex_type;

Il est très important de garder à l’esprit que les calculs traitant du positionnement et de la rotation de l’objet sont appliqués aux vertex, puisque ce sont les unités de la structure de base. La deuxième structure, en terme d’importance est le polygone.

LES POLYGONES

Les polygones sont les faces de l’objet et sont composées de N vertex. Dans la plupart des moteurs 3D, les polygones sont composés de 3 vertex, en accord avec cette règle. Utilisant notre structure vertex_type , on peut définir une structure de polygone qui contient 3 vertices:

typedef struct {
   vertex_type a,b,c;
} polygon_type;

Nous déclarons aussi une variable tableau polygon_type qui sera remplie par tous les polygones qui constitue l’objet

#define MAX_POLYGONS 2000
polygon_type polygon[MAX_POLYGONS]; 

Mais… attention ! Notre définition assigne 3 vertices à chaque polygone et ces vertex ne sont pas partagés avec les autres polygones. En effet, si l’on réfléchit un peu nous allons voir que chaque polygone d’un objet partage en fait ses cotés, et aussi ses vertices avec les autres polygones. Donc nous avons fait une erreur ! Bon, c’est pas vraiment une erreur, mais nous avons augmenté considérablement le nombre réel de vertex de la scène bien plus que de nécessaire. On a déjà dit que le moteur utiliserait les vertex pour faire ses calculs donc on devrait vraiment trouver une autre méthode pour définir nos polygones. On pourrait créer une liste de vertices qui contiendrait l’ensemble des vertices de l’objet. Afin de définir les polygones, on utilisera une sorte de schéma de références qui pointe sur les vertices de la liste. On déclare maintenant un tableau de type vertex_type qui contiendra MAX_VERTICES vertices.

#define MAX_VERTICES 2000
vertex_type vertex[MAX_VERTICES];

La structure polygone ne contiendra plus les vertices mais seulement 3 nombres qui pointent sur 3 éléments de la liste des vertices. Dans ce sens, plus de polygones peuvent pointés sur le même vertex. Cela optimise grandement le design du moteur.

typedef struct {
   int a,b,c;
} polygon_type;

L’OBJET

On effectue ici un peu de nettoyage, et on organise les définitions précédentes dans une structure que l’on appelle obj_type

typedef struct {
   vertex_type vertex[MAX_VERTICES];
   polygon_type polygon[MAX_POLYGONS];
} obj_type,*obj_type_ptr;

C’est la seule définition de base. Plus tard, on ajoutera plus de champs qui identifierons la position, rotation et état de l’objet.

A ce point nous pouvons déclarer la variable objet et remplir la liste des vertices.

obj_type obj;

obj.vertex[0].x=0;  obj.vertex[0].y=0;  obj.vertex[0].z=0; //vertex v0
obj.vertex[1].x=1;  obj.vertex[1].y=0;  obj.vertex[1].z=0; //vertex v1
obj.vertex[2].x=1;  obj.vertex[2].y=0;  obj.vertex[2].z=1; //vertex v2
obj.vertex[3].x=0;  obj.vertex[3].y=0;  obj.vertex[3].z=1; //vertex v3
obj.vertex[4].x=0;  obj.vertex[4].y=1;  obj.vertex[4].z=0; //vertex v4
obj.vertex[5].x=1;  obj.vertex[5].y=1;  obj.vertex[5].z=0; //vertex v5
obj.vertex[6].x=1;  obj.vertex[6].y=1;  obj.vertex[6].z=1; //vertex v6
obj.vertex[7].x=0;  obj.vertex[7].y=1;  obj.vertex[7].z=1; //vertex v7

Maintenant le problème et de subdiviser notre cube en triangles. La réponse est simple : chaque face du cube est un carré composé par deux triangles adjacents. Donc notre cube sera composé de 12 polygones (triangles) et 8 vertices.

Tut 3dengine cube.png

La liste des polygones doit être remplie comme suit:

obj.polygon[0].a=0;  obj.polygon[0].b=1;  obj.polygon[0].c=4;  //polygon v0,v1,v4
obj.polygon[1].a=1;  obj.polygon[1].b=5;  obj.polygon[1].c=4;  //polygon v1,v5,v4
obj.polygon[2].a=1;  obj.polygon[2].b=2;  obj.polygon[2].c=5;  //polygon v1,v2,v5
obj.polygon[3].a=2;  obj.polygon[3].b=6;  obj.polygon[3].c=5;  //polygon v2,v6,v5
obj.polygon[4].a=2;  obj.polygon[4].b=3;  obj.polygon[4].c=6;  //polygon v2,v3,v6
obj.polygon[5].a=3;  obj.polygon[5].b=7;  obj.polygon[5].c=6;  //polygon v3,v7,v6
obj.polygon[6].a=3;  obj.polygon[6].b=0;  obj.polygon[6].c=7;  //polygon v3,v0,v7
obj.polygon[7].a=0;  obj.polygon[7].b=4;  obj.polygon[7].c=7;  //polygon v0,v4,v7
obj.polygon[8].a=4;  obj.polygon[8].b=5;  obj.polygon[8].c=7;  //polygon v4,v5,v7
obj.polygon[9].a=5;  obj.polygon[9].b=6;  obj.polygon[9].c=7;  //polygon v5,v6,v7
obj.polygon[10].a=3; obj.polygon[10].b=2; obj.polygon[10].c=0; //polygon v3,v2,v0
obj.polygon[11].a=2; obj.polygon[11].b=1; obj.polygon[11].c=0; //polygon v2,v1,v0

On doit garder à l’esprit que pour définir les polygones proprement, il est nécessaire de toujours utiliser la même direction sens des aiguille d’une montre ou sens inverse pour tous les polygones de la scène. On verra dans le prochain tutoriel, comment la direction est utilisée pour déterminer si le polygone est visible ou pas. Donc faites très attention à la façon dont vous définissez les polygones ou nombres d’entre eux ne seront pas visibles. On utilisera ici la méthode sens inverse des aiguilles (par exemple le premier polygone est définit par v0,v1,v4 ou v1,v4,v0 ou v4,v0,v1 et non pas par v1,v0,v4 ou v0,v4,v1 ou v4,v1,v0).


UNE AUTRE METHODE PLUS ELEGANTE POUR DEFINIR NOTRE OBJET

En C/C++ on peut remplir la structure obj_type de cette façon plus élégante:

obj_type cube = 
{
    {
        -10,-10, 10, //vertex v0
         10,-10, 10, //vertex v1
         10,-10,-10, //vertex v2
        -10,-10,-10, //vertex v3
        -10, 10, 10, //vertex v4
         10, 10, 10, //vertex v5
         10, 10,-10, //vertex v6 
        -10, 10,-10  //vertex v7
    },  
    {
        0, 1, 4, //polygon v0,v1,v4
        1, 5, 4, //polygon v1,v5,v4
        1, 2, 5, //polygon v1,v2,v5
        2, 6, 5, //polygon v2,v6,v5
        2, 3, 6, //polygon v2,v3,v6
        3, 7, 6, //polygon v3,v7,v6
        3, 0, 7, //polygon v3,v0,v7
        0, 4, 7, //polygon v0,v4,v7
        4, 5, 7, //polygon v4,v5,v7
        5, 6, 7, //polygon v5,v6,v7
        3, 2, 0, //polygon v3,v2,v0
        2, 1, 0, //polygon v2,v1,v0
    }
};

On peut également noter que les coordonnées de vertices de 0,1 à 10, -10. Cela est intentionnel, en effet, on a besoin d’avoir le centre de l’objet à 0,0 (on comprendra pourquoi dans le prochain tutoriel). Notre objet est également 20 fois plus gros (pour faciliter la manipulation des coordonnées).

CONCLUSIONS

C’est tout pour cette leçon. Dans la suivante, on commencera par utiliser OpenGL pour afficher notre cube à l’écran.

SOURCE CODE

The Source Code of this lesson can be downloaded from the Tutorials Main Page