Charger des fichiers .OBJ
Formation
En Ligne
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.
Les Avis
Le programme
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 :) .
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.mtlC'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 :
Où X, Y et Z sont respectivement les coordonnées X, Y et Z du sommet.
Par exemple :
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 :
Même principe pour les normales, mais avec "vn" ("n" comme "normal") et trois axes :
vn X Y ZMaintenant 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_materiauMa 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é) :
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 :
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 :
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//6Les 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" :
Ensuite la couleur globale de l'objet sera la couleur diffuse "Kd" :
Kd R G BOù R, G et B sont des flottants allant de 0 à 1.
Par exemple :
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 :
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 MTLMaintenant 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 :
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 :
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 :
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; }Charger des fichiers .OBJ
