Charger des fichiers .OBJ

Formation

En Ligne

Prix sur demande

Appeler le centre

Avez-vous besoin d'un coach de formation?

Il vous aidera à comparer différents cours et à trouver la solution la plus abordable.

Description

  • Typologie

    Formation

  • Méthodologie

    En ligne

Grâce à cette formation vous pourrez acquérir les connaissances nécessaires qui vous permettrons d’ajouter des compétences à votre profil et obtenir de solides aptitude qui vous offriront de nombreuses opportunités professionnelles.

Questions / Réponses

Ajoutez votre question

Nos conseillers et autres utilisateurs pourront vous répondre

À qui souhaitez-vous addresser votre question?

Saisissez vos coordonnées pour recevoir une réponse

Nous ne publierons que votre nom et votre question

Les Avis

Le programme

Introduction du cours

Vous utilisez OpenGL et vous trouvez fastidieux le système de définition de modèles point par point ? Et bien j'ai conçu une petite lib sans prétention qui permet de charger facilement des fichiers .OBJ que vous pouvez obtenir en exportant vos modèles dans ce format, à partir de votre logiciel de modélisation. J'utilise Blender, par conséquent si vous utilisez un autre logiciel vous devrez vous y adapter ;) . A l'heure actuelle, elle gère les modèles statiques comme animés, plusieurs matériaux et une seule texture en tout.
Une connaissance des tutoriels Apprenez à programmer en C++ de M@teo21 et Créez des programmes en 3D avec OpenGL de Kayl est requise pour bien comprendre le tutoriel.
Voilà, j'espère que ce tutoriel vous plaira :) .

Les formats OBJ et MTL

Bien qu'il ne soit pas toujours encodé de la même manière, le format OBJ a une syntaxe particulière.
Il se divise en deux fichiers : un fichier .OBJ qui donne toutes les informations sur les sommets et les faces, et un fichier .mtl (comme Material Template Library) qui contient les données sur les matériaux.

On peut décomposer un fichier .OBJ de cette manière :

  • Indication du fichier .MTL

  • Définition des sommets

  • Attribution des faces.

L'indication du fichier .MTL se fait comme ceci :

mtllib mon_fichier.mtl

C'est donc cette ligne qui permet de déterminer où se trouve le .mtl à charger.

Ensuite vient la définition des sommets en position, coordonnées de texture et en normales.
Chaque ligne de définition de sommets commence par un "v" comme "vertex", "sommet" en Anglais.
La position se note comme ceci :

v X Y Z

Où X, Y et Z sont respectivement les coordonnées X, Y et Z du sommet.
Par exemple :

v 0.532 1.265 0.273

Cette ligne permet d'indiquer que l'on crée un sommet de coordonnées (0.532 ; 1.265 ; 0.273).
Pour les coordonnées de texture, la ligne sera similaire sauf qu'on mettra un "t" (comme "texture") après le "v" et que nous n'avons que deux axes :

vt X Y

Même principe pour les normales, mais avec "vn" ("n" comme "normal") et trois axes :

vn X Y Z

Maintenant que nous avons une liste de sommets, il s'agit de les ordonner pour former des faces. Votre exportateur est intelligent : il regroupe toutes les faces d'un même matériau ensemble.

Mais comment on sait quel matériau appliquer ?

Grâce à leur nom :

usemtl nom_du_materiau

Ma lib fonctionne avec les faces triangulaires ou les quadrilatères, autrement dit elle accepte les face à trois ou quatre sommets.
Pour définir une face, on va assembler les numéros des sommets concernés comme ceci (dans le cas d'un carré) :

f V1/T1/N1 V2/T2/N2 V3/T3/N3 V4/T4/N4

Les V1, V2, V3, V4 sont les numéros des positions ; les T1, T2, T3, T4 sont les numéros des coordonnées de texture ; et les N1, N2, N3, N4 sont les numéros des normales.
Par exemple :

f 1/2/3 4/5/6 7/8/9

On définit une face triangulaire dont le premier point est défini par la position n°1, les coordonnées de texture n°2 et les normales n°3 ; et ainsi de suite pour les autres sommets.
S'il n'y a pas de textures, vous pourrez avoir une ligne de la sorte :

f 1//2 4//6 7//9

Rien de tel qu'un exemple récapitulatif : le cube !

# Blender3D v249 OBJ File:  # www.blender3d.org mtllib cube.mtl v 1.000000 1.000000 -1.000000 v 1.000000 -1.000000 -1.000000 v -1.000000 -1.000000 -1.000000 v -1.000000 1.000000 -1.000000 v 1.000000 0.999999 1.000000 v 0.999999 -1.000001 1.000000 v -1.000000 -1.000000 1.000000 v -1.000000 1.000000 1.000000 vn 0.000000 0.000000 -1.000000 vn 0.000000 0.000000 1.000000 vn 1.000000 -0.000000 0.000000 vn -0.000000 -1.000000 -0.000000 vn -1.000000 0.000000 -0.000000 vn 0.000000 1.000000 0.000000 usemtl Material s off f 1//1 2//1 3//1 4//1 f 5//2 8//2 7//2 6//2 f 1//3 5//3 6//3 2//3 f 2//4 6//4 7//4 3//4 f 3//5 7//5 8//5 4//5 f 5//6 1//6 4//6 8//6

Les deux premières lignes sont des commentaires de mon exportateur, et la ligne "s off" signifie "smooth off" mais on ne la prend pas en compte car on peut jouer dessus directement dans notre code avec glEnable(GL_SMOOTH) et glDisable(GL_SMOOTH) .
On peut voir que certains points ne sont pas parfaits (0.999999 à la place de 1.000000) mais globalement le contenu du fichier sera fidèle à ce que vous avez modélisé ^^ .

A présent, regardons la syntaxe du format MTL vu que nous nous sommes familiarisés avec le format OBJ.
Nous n'utiliserons pas toutes les données, mais le strict nécessaire, ce qui est déjà suffisant.
En premier lieu, le nom du matériau, ce qui permet de les identifier lorsque l'on fait appel à "usemtl" :

newmtl nom_du_materiau

Ensuite la couleur globale de l'objet sera la couleur diffuse "Kd" :

Kd R G B

Où R, G et B sont des flottants allant de 0 à 1.
Par exemple :

Kd 1.000000 0.500000 0.000000

Vous aurez une couleur orangée. Vérifiez ces données car il se peut qu'elles ne correspondent pas exactement à celles entrées dans votre logiciel de modélisation.
Dernier paramètre : la transparence "d" allant de 0 (totalement transparent) à 1 (totalement opaque).
Ce sont les seules données prises en compte dans ma lib, néanmoins ce sont les principales.
Voici le MTL de notre cube :

# Blender3D MTL File:  # Material Count: 1 newmtl Material Ns 96.078431 Ka 0.000000 0.000000 0.000000 Kd 0.640000 0.640000 0.640000 Ks 0.500000 0.500000 0.500000 Ni 1.000000 d 1.000000 illum 2

Le résultat sera donc un carré gris et opaque :) !

Nous allons nous attaquer au plus intéressant : coder la lib !

Parser les formats OBJ et MTL

Maintenant que nous avons vu dans le détail les deux formats, nous allons aborder l'implémentation de notre loader.

Certains passages ne seront pas détaillés, étant donné que le but de ce tutoriel est de comprendre comment charger les formats OBJ et MTL.

Tout d'abord vous l'avez bien vu, on a souvent besoin de coordonnées de points, de couleurs, etc. donc on va créer une classe contenant 4 flottants (x, y, z et a ; XYZ pour les coordonnées et A pour l'opacité avec RGB=XYZ) :

class FloatVector { /* Classe FloatVector : simple vecteur XYZ ou XYZA (dans le cas de couleurs). */ public: FloatVector(float px=0,float py=0,float pz=0,float pa=0); /* FloatVector(float px=0,float py=0,float pz=0,float pa=0); Constructeur, prend en paramètres des flottants correspondant respectivement à x, y, z et a. */ ~FloatVector(); /* ~FloatVector(); Destructeur, totalement inutile. */ FloatVector operator=(const FloatVector &fv); /* FloatVector operator=(const FloatVector &fv); Affecte au vecteur courant le contenu du vecteur passé en argument. Retourne le vecteur courant ainsi modifié. */ float x,y,z,a; }; FloatVector::FloatVector(float px,float py,float pz,float pa):x(px),y(py),z(pz),a(pa) { } FloatVector::~FloatVector() { } FloatVector FloatVector::operator=(const FloatVector &fv) { x=fv.x; y=fv.y; z=fv.z; a=fv.a; return *this; }

Attaquons-nous aux matériaux, on se limitera à sa couleur et à son nom. Pour la couleur nous allons donc prendre un FloatVector et pour le nom un std::string :

class Material { /* Classe Material : définition d'un matériau, composé d'une couleur et d'un nom spécifique. */ public: Material(float r,float g,float b,std::string n); /* Material(float r,float g,float b,std::string n); Constructeur, les trois premiers arguments représentent la couleur RGB du matériau et n est son nom. */ Material(Material *mat); /* Material(Material *mat); Constructeur alternatif, affecte au matériau courant le contenu du matériau passé en argument. */ ~Material(); /* ~Material(); Destructeur, totalement inutile. */ FloatVector coul; std::string name; }; Material::Material(float r,float g,float b,string n):name(n) { coul.x=r; coul.y=g; coul.z=b; } Material::Material(Material *mat) { coul=mat->coul; name=mat->name; }

Il reste maintenant le plus intéressant, commençons par une classe représentant un modèle statique.
Tout d'abord réfléchissons au mode d'affichage, dans la lib nous utiliserons les Vertex Arrays (tutoriel de Yno).
Notre classe MeshObj contiendra alors un GLuint pour la texture, un entier pour le nombre de quads à dessiner, des tableaux dynamiques pour les coordonnées de sommets, de texture, de normales ainsi que les couleurs par sommet. Enfin, elle contiendra un std::vector de Material :

class MeshObj { /* Classe MeshObj : définition d'un modèle statique. */ public: MeshObj(std::string,MeshObj *first=NULL); /* MeshObj(std::string,MeshObj *first=NULL); Constructeur, prend en arguments le nom du modèle à charger et le pointeur de la première frame si le modèle appartient à une animation (sinon laissez-le à NULL). */ ~MeshObj(); /* ~MeshObj(); Destructeur, libère toute la mémoire qui lui a été allouée. */ void charger_obj(std::string,MeshObj *first=NULL); /* void charger_obj(std::string,MeshObj *first=NULL); Charge un fichier OBJ et son MTL, prend en arguments le nom du modèle à charger et le pointeur de la première frame si le modèle appartient à une animation (sinon laissez-le à NULL). Cette fonction est appelée par le constructeur. Aucune valeur de retour. */ void charger_mtl(std::string); /* void charger_mtl(std::string); Charge un fichier MTL, prend en argument le nom du fichier à charger. Cette fonction est appelée par charger_obj. Aucune valeur de retour. */ void draw_model(bool nor=true,bool tex=false); /* void draw_model(bool nor=true,bool tex=false); Dessine le modèle, prend en arguments deux booléens représentant respectivement les normales et la texture. Si nor vaut true alors on prend en compte les normales, et si tex vaut true alors on applique la texture. Aucune valeur de retour. */ void setMaterialsAndTex(std::vector<Material*> mats,GLuint tex); /* void setMaterialsAndTex(std::vector<Material*> mats,GLuint tex); Définit directement les matériaux et la texture du modèle, prend en arguments un vector<Material*> et la texture. Cette fonction est appelée par giveMaterialsAndTex. Aucune valeur de retour. */ void giveMaterialsAndTex(MeshObj *target); /* void giveMaterialsAndTex(MeshObj *target); Modifie les matériaux et la texture de target en les remplaçant par ses propres matériaux et sa texture. Cette fonction est appelée par charger_obj uniquement lorsque first!=NULL. Aucune valeur de retour. */ private: GLuint texture; int n_data; float *vertice,*normals,*textures,*colours; std::vector<Material*> materiaux; };

Ne faites pas attention aux deux dernières méthodes de cette classe, elle n'ont pas de rapport avec le parsage des formats OBJ et MTL.
Occupons-nous du constructeur et du destructeur :

MeshObj::MeshObj(string s,MeshObj *first) { charger_obj(s,first); } MeshObj::~MeshObj() { free(vertice); free(normals); free(textures); free(colours); for(unsigned int i=0;i<materiaux.size();i++) delete materiaux[i]; materiaux.clear(); } Format OBJ

Ca y est, nous arrivons enfin à la méthode MeshObj::charger_obj :) ! Nous savons que dans le format OBJ on définit d'abord chaque point, puis ensuite on les assemble pour former des faces. Nous allons donc créer un std::vector de FloatVector pour les coordonnées de sommets, de normales, de textures et pour les couleurs ; ainsi qu'un std::vector d'entiers non signés représentant les indices des points à assembler. Au premier abord, ça peut paraître dur mais en réalité ce sera assez simple à mettre en place. Déjà, regardons le code que nous obtenons :

vector<FloatVector> ver,nor,tex,col; vector<unsigned int> iv,it,in;

Maintenant on ouvre le fichier passé en argument :

ifstream fichier(nom.c_str(),ios::in);

Nous allons le lire ligne après ligne, donc nous allons créer un std::string et par la même occasion un autre std::string qui correspond au nom du matériau en cours :

string ligne,curname="";

On peut enfin lire le fichier, à condition que celui-ci existe ! C'est pourquoi il faudra faire un test au préalable.

Ensuite il faut différencier plusieurs cas :

  • les lignes commençant par 'v'

  • les lignes commençant par 'f'

  • les lignes commençant par "mtllib"

  • les lignes commençant par "usemtl".

Occupons-nous des premières. Elles se divisent en trois catégories : "v " qui définissent les coordonnées des points, "vt" pour les textures et "vn" pour les normales :

if(ligne[0]=='v') //Coordonnées de points (vertex, texture et normale) { if(ligne[1]==' ') //Vertex { char x[255],y[255],z[255]; sscanf(ligne.c_str(),"v %s %s %s",x,y,z); ver.push_back(FloatVector(strtod(x,NULL),strtod(y,NULL),strtod(z,NULL))); } else if(ligne[1]=='t') //Texture { char x[255],y[255]; sscanf(ligne.c_str(),"vt %s %s",x,y); tex.push_back(FloatVector(strtod(x,NULL),strtod(y,NULL))); } else if(ligne[1]=='n') //Normale { char x[255],y[255],z[255]; sscanf(ligne.c_str(),"vn %s %s %s",x,y,z); nor.push_back(FloatVector(strtod(x,NULL),strtod(y,NULL),strtod(z,NULL))); } }

Ce code est assez clair (juste les [255] qui sont un peu bourrins ^^ ), au final on se retrouve avec les vector de coordonnées de points, de texture et de normales.

Maintenant regardons du côté des définitions de faces.
Dans certains modèles il n'y a pas de texture, donc on se retrouvera avec des "//" (car on omet les numéros de textures, ce qui est logique ^^ ), on va les remplacer par "/1/".
C'est ma fonction doubleSlash :

string doubleSlash(string s) { //Remplace "//" par "/1/". string s1=""; for(unsigned int i=0;i<s.size();i++) { if(i<s.size()-1&&s[i]=='/'&&s[i+1]=='/') { s1+="/1/"; i++; } else s1+=s[i]; } return s1; }

Puis on remplace les slashes par des espaces, c'est ma fonction remplacerSlash :

string remplacerSlash(string s) { //Remplace les '/' par des espaces. string ret=""; for(unsigned int i=0;i<s.size();i++) { if(s[i]=='/') ret+=' '; else ret+=s[i]; } return ret; }

Appeler le centre

Avez-vous besoin d'un coach de formation?

Il vous aidera à comparer différents cours et à trouver la solution la plus abordable.

Charger des fichiers .OBJ

Prix sur demande