Faire du multi-texturing avec VTK

Dans ce billet, je vais parler de ce qui a été mon principal sujet de recherche les deux derniers mois, au cours d’un stage à temps partiel.

Il s’agissait de faire de l’application multi-textures sur un polygone, en 3D, à l’aide de la bibliothèque open source VTK. Je parle de sujet de recherche car, à l’heure où j’écris, il semble que personne ou presque n’a déjà essayé d’en faire avec VTK… Alors que pourtant, la fonctionnalité est bien présente, mais non fonctionnelle « out of the box ».

C’est notamment pourquoi j’ai mis autant de temps à trouver comment faire: absence de doc totale sur le Net, et visiblement personne n’ayant réussi à le faire ayant partagé sa science… Mon salut étant venu d’un fichier C++ de test perdu au fin fond du dépôt VTK. J’y reviens plus tard, d’abord, remettons un peu dans le contexte.

 

The Visualization ToolKit

Logo de VTK

Il s’agit en fait d’une bonne grosse bibliothèque proposant une surcouche sympathique à OpenGL, une API de génération d’images 3D (et 2D aussi, d’ailleurs) libre. OpenGL étant une solution multiplateforme à la base, c’est donc tout naturellement que VTK l’est lui aussi, et il est de plus proposé dans de nombreux langages (C++/Python/Java/Tcl-Tk). Étant donné son utilisation par beaucoup d’instituts de recherche voire d’entreprises l’intégrant dans des solutions commerciales, VTK simplifie la vie de beaucoup de gens travaillant dans l’imagerie 3D around the world (qui n’ont pas forcément envie de se frotter à du code OpenGL « pur »).

Daft Punk Around the World

L'imagerie 3D "around the world", un secteur d'activité étrange

En bref, VTK sait faire un tas de trucs : des cubes, des sphères, des triangles, des formes géométriques plus complexes (genre un corps humain) et plus encore. En effet le toolkit ne touche pas seulement à la géométrie pure et permet de disposer de quelques classes très utiles pour l’interfacer plus facilement avec les formats de fichiers standards de l’imagerie 3D (et de l’imagerie tout court). Par exemple dans mon cas, des classes comme le lecteur de fichier VRML, le lecteur de fichier OBJ, ou le lecteur de fichier JPEG m’ont été très utiles. Dans le monde réel de l’imagerie 3D, il est courant de générer des formes à partir de photographies dans un logiciel, et les retravailler ou les visionner dans un autre. D’où l’intérêt d’avoir ce genre de classes à disposition qui permettent de facilement importer/exporter des formes 3D dans des formats standard généralement utilisés par les gros logiciels du milieu !

J’en profite pour préciser que tous les exemples qui suivent ont été réalisés avec VTK 5.8.0.

 

Le plaquage de texture

plaquage de texture

Jean-Michel n'avait pas vraiment compris le sens de "plaquer la texture"

Aujourd’hui, la fonctionnalité de VTK qui nous intéresse particulièrement, c’est le fait de pouvoir plaquer une (ou des) texture(s) sur un objet 3D. On appelle ça plus couramment « texturer » un objet, c’est-à-dire « habiller » l’objet 3D grâce aux pixels contenus dans une image 2D, en fournissant un moyen de conversion au programme lui permettant de comprendre que « tel pixel de tel image doit être associé à tel point dans l’espace ». Il n’est pas très intéressant que je réexplique toute la théorie ici étant donné que la littérature sur le sujet est abondante.

Ce qu’il faut retenir, c’est que nous utilisons la technique du UV Mapping, qui consiste à dire que toute image de texture, quelle que soit sa taille, est en fait une image dans laquelle nous pouvons nous déplacer au moyen de coordonnées allant du point tout en bas à gauche possédant les coordonnées (0,0) au point tout en haut à droite, qui lui aura alors les coordonnées (1, 1). (Si vous avez bien compris, le point au centre de l’image, par exemple, aura donc toujours les coordonnées : (0.5, 0.5) )

Si VTK gère très bien les cas simples, avec une seule image de texture, nous verrons que texturer un polygone à l’aide de plusieurs images peut relever de la prouesse… Mais le but de cet article est justement de démontrer que c’est possible.

 

« Back to Basics » : Monotexture

Je ne vais pas trop m’attarder sur la question étant donné que c’est ce que VTK gère de base. Juste histoire de montrer un example de code qui fonctionne. Si vous voulez essayer à la maison, je me base sur les fichiers Bugs Bunny dispo sur le site d’Artec 3d . Je n’explique pas non plus comment installer VTK ou comment la bête marche, de ce côté, il y a pas mal de documentation.

Je mets le code pour générer un vtkPolyData (la structure de données générique lorsqu’on commence à gérer des polygones compliqués dans VTK) à la fois à partir d’un fichier VRML (avec le vtkVRMLImporter) et d’un fichier OBJ (avec le vtkOBJReader). Si vous voulez essayer, n’oubliez pas de commenter la méthode que vous n’utilisez pas. Il faut faire attention car il serait faux de dire que les deux méthodes sont équivalentes: un fichier OBJ ne sert qu’à décrire un objet dans l’espace. Le VRML, qui est un peu un langage de programmation à part entière, a, à la base, un objectif beaucoup plus large et peut décrire des scènes 3D entières, avec animations etc… Mais je m’égare 😉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
void TestSimpleTexturing()

{

std::string iname = "Bugs_Bunny.obj";

std::string imagename = "Bugs_Bunny_0.jpg";

// Read the image which will be the texture

std::cout << "Reading image " << imagename << "..." << std::endl;

vtkSmartPointer<vtkJPEGReader> jPEGReader = vtkSmartPointer<vtkJPEGReader>::New();

jPEGReader->SetFileName ( imagename.c_str() );

jPEGReader->Update();

std::cout << "Done" << std::endl;

// Creating the texture

std::cout << "Making a texture out of the image... " << std::endl;

vtkSmartPointer<vtkTexture> texture = vtkSmartPointer<vtkTexture>::New();

texture->SetInputConnection(jPEGReader->GetOutputPort());

std::cout << "Done" << std::endl;

// Import geometry from a VRML file

// DANGER !! Crashes if lines in the files are too long

vtkVRMLImporter *importer=vtkVRMLImporter::New();

std::cout << "Importing VRML file..." << std::endl;

importer->SetFileName(iname.c_str());

importer->Read();

importer->Update();

vtkDataSet *pDataset;

vtkActorCollection *actors = importer->GetRenderer()->GetActors();

actors->InitTraversal();

pDataset = actors->GetNextActor()->GetMapper()->GetInput();

vtkPolyData *polyData = vtkPolyData::SafeDownCast(pDataset);

polyData->Update();

std::cout << "Done" << std::endl;

// Import geometry from an OBJ file

std::cout << "Reading OBJ file " << iname << "..." << std::endl;

vtkOBJReader* reader = vtkOBJReader::New();

reader->SetFileName(iname.c_str());

reader->Update();

vtkPolyData *polyData2 = reader->GetOutput();

std::cout << "Obj reader = " << polyData2->GetNumberOfPoints() << std::endl;

std::cout << "Obj point data = " << polyData2->GetPointData()->GetNumberOfArrays() << std::endl;

std::cout << "Obj point data tuples = " << polyData2->GetPointData()->GetArray(0)->GetNumberOfTuples() << std::endl;

std::cout << "Obj point data compon = " << polyData2->GetPointData()->GetArray(0)->GetNumberOfComponents() << std::endl;

// Renderer

vtkSmartPointer mapper = vtkSmartPointer::New();

mapper->SetInput(polyData2);

vtkSmartPointer<vtkActor> texturedQuad = vtkSmartPointer<vtkActor>::New();

texturedQuad->SetMapper(mapper);

texturedQuad->SetTexture(texture);

// Visualize the textured plane

vtkSmartPointer renderer = vtkSmartPointer::New();

renderer->AddActor(texturedQuad);

renderer->SetBackground(0.2,0.5,0.6); // Background color white

renderer->ResetCamera();

vtkSmartPointerrenderWindow =

vtkSmartPointer::New();

renderWindow->AddRenderer(renderer);

vtkSmartPointerrenderWindowInteractor =

vtkSmartPointer::New();

renderWindowInteractor->SetRenderWindow(renderWindow);

renderWindow->Render();

renderWindowInteractor->Start();

}

Quelques trucs amusants:

Au début on essayait avec une carte graphique vraiment vieille, et la texture ne s’affichait pas très bien. À la place de l’image s’affichait une espèce de mélange de damiers totalement aléatoires. Je n’ai pas gardé de capture d’écran mais c’était assez symptomatique, si quelqu’un a déjà rencontré ce problème…

De plus, nous nous sommes rapidement tournés vers les versions OBJ des objets scannés par Artec car, en essayant d’ouvrir un fichier VRML de Artec, on s’est en fait rendu compte que Artec place toutes les infos d’un polygone sur une seule et même très longue ligne (pouvant aller jusqu’à quelques centaines de milliers de colonnes). Ce qui fait lamentablement planter le VRMLImporter de VTK, qui utilise une solution à base de yacc/lex (et qui pour le coup plante de manière totalement silencieuse, ne laissant apparaitre qu’une fenêtre vide, sans maillage). Ceci dit, même après avoir séparé ces très longues lignes en plusieurs de taille raisonnable, le maillage ne s’affichait toujours pas. Je penche, pour ma part, pour le fait que le VRMLImporter de VTK ne gère pas d’aussi grands maillages (il n’est pas fait pour ça).

Assez amusant de voir  qu’à partir de cette image de texture moche :

Texture Bugs Bunny

Pour des raisons d'économie de place, en 3D, une texture ressemble souvent à ça

on puisse obtenir ça :

Représentation 3D texturée de Bugs Bunny

Cliquez pour le voir en entier

Plaquer plusieurs textures, ou « multitexturing », ou le début des choses sérieuses

Le multi-textures

"Non non mais là c'est n'importe quoi Jean-Michel"

Seulement voilà, texturer un objet avec une image, c’est bien, mais il arrive que dans le cas de maillages très grands, on ait besoin de lire les informations de texture à partir de plusieurs images différentes. Et c’est là que commencent les ennuis: je vais prendre l’exemple d’un fichier OBJ, car c’est un format très simple. Dans un fichier OBJ classique, on trouve :

  • d’abord les points dans l’espace, préfixés par un « v » (pour « vertex » en anglais) suivi de trois coordonnées dans l’espace (x y z).
  • des points de coordonnées de texture, préfixés par un « vt » (pour « vertex texture »), suivi de deux coordonnées qui, si vous suivez toujours, sont forcément comprises entre 0 et 1
  • les faces, préfixées par un « f » (pour, et bien… « face »), suivi de l’index des points de l’espace (les « v » précédemment définis) formant la face. L’index c’est tout simplement leur numéro, décidé par leur ordre d’apparition dans le fichier. Là c’est variable, étant donné que ça dépend du nombre de points on veut que nos faces relient. Mais vu qu’en général, il s’agit de triangles (en géométrie 3D, TOUT est triangle), on se retrouve la plupart du temps avec des lignes type « f 1/1 2/2 3/3 ». La notation « x/n » servent à dire au programme qui lit qu’il faudra lier le point spatial numéro x au point de texture (« vt ») numéro n (même principe de notation).
  • d’autres trucs plus ou moins utiles. Il faut lire la spécification du .obj, mais c’est tout ce qu’on a besoin de savoir pour le moment.

Que fait VTK lorsqu’il doit lire un tel fichier ?

Simple : il lit les points de l’espace (normal), il construit les différentes faces du polygone (normal)… Et qu’arrive-t-il aux coordonnées de texture, me direz-vous ? Et bien, l’OBJReader de VTK les met tout simplement dans une liste de points à l’intérieur du vtkPolyData qui sera produit en sortie du Reader.

Avec une seule texture, ça ne pose pas de problème. Au moment d’afficher le polygone, VTK va juste regarder dans cette liste et appliquer un point de texture à chaque point de l’espace du polygone (c’est notamment pour ça qu’il est important qu’il y ait autant de coordonnées de textures spécifiées que de points dans l’espace).

Les problèmes de cette méthode commencent dès qu’on a plusieurs images à appliquer… En effet, il y a une subtilité dans le format OBJ. Il permet d’utiliser un fichier externe de « matériaux » (un .mtl) permettant de spécifier différentes zones ou modifications… et, notamment, le nom de l’image à laquelle se réfèrent les prochaines coordonnées de texture (les « vt », remember ?). Le format OBJ utilise pour cela la directive usemtl.

Problème: VTK ne sait pas lire cette instruction. Il ne peut donc pas lire le fait que certaines textures de coordonnées font référence à une image et les suivantes à une autre image. Il place toutes les coordonnées dans une seule liste, et ne peut pas différencier les coordonnées texture d’une image des autres.

Avant d’aller plus loin, je précise que j’utilise les exemples suivants j’utilise les fichiers dispos à cette adresse.

Du coup, lorsqu’on essaye d’appliquer la méthode « classique » que j’ai mis ci-dessus, on obtient ce résultat un peu chaotique:

Mauvais plaquage de texture

Non non, ce n'est pas normal que ce mec ait une tête bleue et blanche

Bizarre, vous avez dit bizarre ?

Le résultat est étrange. La moitié du corps a l’air correctement texturée (le côté gauche, droit sur l’image), mais pas le reste. Pour le reste, c’est comme si VTK avait utilisé la même image, mais pas au bon endroit. Et pour cause: c’est exactement ce qui se passe ici. VTK ayant juste une liste générale de points de texture, mais ne sachant pas à quelles coordonnées correspondent à quelle texture, même en mettant plusieurs textures sur l’acteur, il agit par défaut, c’est-à-dire qu’il texture l’intégralité du polygone avec la première texture qu’on lui envoie. Cet exemple utilise trois textures, chacune servant à texturer une zone du polygone. Si nous changeons la première texture qui lui est passée, c’est une autre zone du polygone qui sera correctement texturée.

Alors comment faire ?

C’est la question que je me suis longtemps posé… Et la réponse m’est venue d’un fichier de test VTK, venu du fond de leur dépôt Git. La marche à suivre est légèrement plus compliquée.

Le but est simplement de créer plusieurs « unités texture » et de dire à VTK que chaque unité utilise une image différente. À côté de ça, il faut aussi lui préciser un liste de coordonnées à utiliser pour chaque texture. Reprenons, point par point, les étapes importantes dans leur test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
vtkFloatArray *TCoords = vtkFloatArray::New();

TCoords->SetNumberOfComponents(2);

TCoords->Allocate(8);

TCoords->InsertNextTuple2(0.0, 0.0);

TCoords->InsertNextTuple2(0.0, 1.0);

TCoords->InsertNextTuple2(1.0, 0.0);

TCoords->InsertNextTuple2(1.0, 1.0);

TCoords->SetName("MultTCoords");

polyData->GetPointData()->AddArray(TCoords);

TCoords->Delete();

Ils créent une liste de coordonnées de texture et l’ajoutent à l’intérieur du PolyData. Notez qu’ils lui donnent un nom spécifique (« MultTCoords »). Cette liste est en réalité notre principal problème, mais j’y reviens plus tard.

1
2
3
4
5
6
7
8
9
10
11
12
vtkTexture * textureRed = vtkTexture::New();
vtkTexture * textureBlue = vtkTexture::New();
vtkTexture * textureGreen = vtkTexture::New();
textureRed->SetInputConnection(imageReaderRed->GetOutputPort());
textureBlue->SetInputConnection(imageReaderBlue->GetOutputPort());
textureGreen->SetInputConnection(imageReaderGreen->GetOutputPort());

// replace the fargments color and then accumulate the textures
// RGBA values.
textureRed->SetBlendingMode(vtkTexture::VTK_TEXTURE_BLENDING_MODE_REPLACE);
textureBlue->SetBlendingMode(vtkTexture::VTK_TEXTURE_BLENDING_MODE_ADD);
textureGreen->SetBlendingMode(vtkTexture::VTK_TEXTURE_BLENDING_MODE_ADD);

Après avoir lu les images au moyen d’un JPEGReader et avoir converti les données en texture, ils leur donnent un blending mode (ou modes de déformation) spécifiques.  C’est une étape très importante, absente dans le cas d’une seule texture, car il faut ici préciser à VTK ce qu’il doit faire lorsque deux textures se superposent (dans notre cas, chaque image de texture recouvrant une partie différente du maillage, ça n’a théoriquement pas beaucoup d’importance). À noter que l’ordre aussi est important: la première texture à plaquer doit forcément être en mode REPLACE, et toutes les autres, en mode ADD, sinon les textures ne s’afficheront pas comme il faut. Il existe pas mal d’infos sur ce que signifient ces modes, comme ici.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
vtkActor * actor = vtkActor::New();

vtkOpenGLHardwareSupport * hardware =
vtkOpenGLRenderWindow::SafeDownCast(renWin)->GetHardwareSupport();

bool supported=hardware->GetSupportsMultiTexturing();
int tu=0;
if(supported)
{
tu=hardware->GetNumberOfFixedTextureUnits();
}

if(supported && tu > 2)
{
mapper->MapDataArrayToMultiTextureAttribute(
vtkProperty::VTK_TEXTURE_UNIT_0, "MultTCoords",
vtkDataObject::FIELD_ASSOCIATION_POINTS);
mapper->MapDataArrayToMultiTextureAttribute(
vtkProperty::VTK_TEXTURE_UNIT_1, "MultTCoords",
vtkDataObject::FIELD_ASSOCIATION_POINTS);
mapper->MapDataArrayToMultiTextureAttribute(
vtkProperty::VTK_TEXTURE_UNIT_2, "MultTCoords",
vtkDataObject::FIELD_ASSOCIATION_POINTS);

actor->GetProperty()->SetTexture(vtkProperty::VTK_TEXTURE_UNIT_0,
textureRed);
actor->GetProperty()->SetTexture(vtkProperty::VTK_TEXTURE_UNIT_1,
textureBlue);
actor->GetProperty()->SetTexture(vtkProperty::VTK_TEXTURE_UNIT_2,
textureGreen);
}
else
{
// no multitexturing just show the green texture.
if(supported)
{
textureGreen->SetBlendingMode(
vtkTexture::VTK_TEXTURE_BLENDING_MODE_REPLACE);
}
actor->SetTexture(textureGreen);
}

actor->SetMapper(mapper);

La partie décisive.

Ils commencent par invoquer une classe liée au hardware en passant par la classe qui sert à définir la fenêtre du rendu 3D, afin de vérifier que la carte graphique supporte le multi-texture, et si oui, combien de textures à la fois peut-elle gérer. La partie suivante est importante: c’est vraiment là qu’ils disent à VTK que « l’unité de texture N devra utiliser la liste de coordonnées de texture s’appelant « MultTCoords »  » et que « l’unité de texture N correspond à telle image ». Si ils trouvent que le matériel ne supporte pas le multi-texturing, ils n’affichent que la première texture et basta.

Et c’est à peu près tout. Il suffit ensuite d’ajouter l’acteur à la scène de rendu et de lancer le bouzin.

1
2
3
4
5
6
7
8
9
10
11
12
13
renWin->SetSize(300, 300);
renWin->AddRenderer(renderer);
renderer->SetBackground(1.0, 0.5, 1.0);

renderer->AddActor(actor);
renWin->Render();

int retVal = vtkRegressionTestImage( renWin );

if ( retVal == vtkRegressionTester::DO_INTERACTOR)
{
iren->Start();
}

Ça peut paraître facile comme ça, mais c’est parce que leur exemple est simpliste. En effet, ils peuvent se permettre de déclarer la liste de coordonnées de texture en dur dans le code, et d’utiliser la même pour toutes leurs textures. Or, lorsque par exemple on importe un PolyData d’un fichier OBJ, ça marche pas du tout pareil ! Car, en effet, comme je l’ai dit précédemment VTK va mettre sans distinction toutes les coordonnées de texture qu’il trouve dans une seule et même liste. Et leur exemple est conçu de telle manière que les textures puissent utiliser toutes les trois les mêmes coordonnées, or, en situation réelle, ce n’est jamais vraiment le cas. Ainsi, là où nous aurions, en fait,  besoin d’autant de listes que de textures à utiliser, nous avons une grosse liste qui n’est aucunement segmentée et où il est impossible de savoir a priori « qui correspond à qui » (de où à où vont les coordonnées de texture se référant à telle image).

Le début d’une solution

C’est donc là que je n’ai plus vu qu’une solution: relire derrière VTK le fichier comportant l’objet 3D (le fichier .obj, par exemple), et compter à chaque fois quelle taille font séparément chaque liste de coordonnées de texture. Cela permet de « délimiter » la grande liste, et de savoir que, par exemple, les 500 premières coordonnées sont à associer avec la première image …

C’est ainsi que j’ai résolu mon problème: en relisant le fichier OBJ, j’ai créé autant de listes qu’il y a de textures, et rempli ces listes correctement. Relire le fichier OBJ permet de savoir à quelle image correspond quelles coordonnées. Il ne faut par contre pas oublier de remplir le reste de la liste avec des couples de -1.0, car comme expliqué plus haut, chaque liste de coordonnées de texture n’est pas à appliquer à l’ensemble du maillage, mais juste à une portion spécifique à cette texture.

Avec par exemple un maillage de 5000 points, une première liste de 500 coordonnées de textures devra donc avoir une taille de 5000, avec bien sûr les 500 coordonnées, suivies de 4500 (-1.0, -1.0).

En revanche, si il y a une seconde liste de 500 coordonnées (correspondant à une autre image), il faudra que la nouvelle liste soit d’abord composée de, si vous avez bien suivi :

  1. 500 paires (-1.0, -1.0) (car les 500 premiers points sont déjà texturés par la première image !)
  2. les 500 coordonnées de la nouvelle liste, celles qui correspondent à l’image de la deuxième texture
  3. et enfin, 4000 paires (-1.0, 1.0) à nouveau, car cette image ne couvre pas le reste du maillage.

En suivant cette méthode, on obtient bien les résultats attendu :

Good Guy Texture

Cette fois, c'est bon !

Zoom sur la figure

La texture est plutôt bien appliquée au triangle près

Vue fil de fer de la figure

Je vous l'avais bien dit qu'en 3D, tout est un triangle

La solution,  une classe maison: le vtkTexturingHelper

OK, maintenant qu’on a la théorie, et les exemples faits « en dur » dans le code pour la vérifier, force est de constater que VTK ne gère effectivement pas correctement le multi-texture, à l’importation d’un fichier OBJ en tout cas.

Le problème est que, même dans le cas d’un fichier OBJ (à la structure et syntaxe très simple), l’opération est tout de même assez complexe.

C’est pour cela que j’ai vite fait écrit une petite classe servant à faciliter ce procédé, au moins pour l’import depuis un fichier OBJ et en utilisant des textures au format JPEG (le cas que j’ai rencontré en stage, en fait). Elle fonctionne principalement grâce à 4 ou 5 fonctions:

  • associateTextureFiles : cette fonction permet de dire à la classe qu’il va falloir utiliser toute une liste d’images dont le nom suit une syntaxe précise. Je l’ai codée avec en tête l’exemple concret de ma propre expérience: sur tous les exemples du site d’Artec3D, les fichiers images sont tous nommés comme le fichier OBJ (exemple: « sasha »), suivi d’un underscore (« _ »), du numéro de l’image (commençant à 0) et de l’extension. Cette fonction permet justement de préciser le « nom racine » de la liste d’images (dans cet exemple: « sasha »), de spécifier leur extension (ici, « .jpg ») et leur nombre (ici, 3). Cela donne l’appel « helper.associateTextureFiles(« sasha », « .jpg », 3); », et il permet de dire à la classe qu’il faudra utiliser « 3 fichiers JPEG dont les noms sont « sasha_0.jpg » , « sasha_1.jpg » , et « sasha_2.jpg » . La classe se chargera de correctement reconstruire le nom du fichier, et d’importer les données image avec la classe VTK qui convient (ici, un vtkJPEGReader).
  •  setGeometryFile : donner un fichier à partir duquel VTK va importer la géométrie du maillage. Pas très utile, elle changera ou fusionnera peut-être avec readGeometryFile
  • readGeometryFile : c’est la fonction qui va appeler le bon Reader de VTK en fonction de l’extension du nom de fichier qu’on lui a précédemment passé avec setGeometryFile. Si il s’agit par exemple d’un nom de fichier en « *.obj », la classe utilisera un vtkOBJReader.
  • applyTextures: la fonction qui va faire le travail d’application des textures sur le polyData obtenu en lisant le fichier de géométrie. Son principal défaut est qu’elle se base entièrement sur l’ordre dans lequel lui ont été passés les noms des fichiers images à utiliser pour créer les textures. Si le fichier OBJ est correctement formaté (c’est-à-dire qu’il utilise d’abord la première image, par exemple sasha_0.jpg, puis la deuxième, sasha_1.jpg, etc.), ça ne pose pas de problème, mais des images importées dans le désordre peuvent l’empêcher de fonctionner correctement. Cela devra tôt ou tard faire l’objet d’une amélioration
  • getActor: en réalité, la classe disposeen interne d’un PolyData pour récupérer l’objet géométrique importé, par exemple, avec un vtkOBJReader, et un vtkActor sur lequel on va plaquer les textures et associer les données des images correspondantes. Ce getter permet simplement d’obtenir l’acteur correspondant déjà tout bien texturé, qu’il n’y a plus qu’à ajouter à un vtkRenderer pour le voir apparaître à l’écran.

Bien qu’en l’état elle soit fonctionnelle, j’y vois déjà plusieurs pistes d’améliorations:

  • une lecture plus fine du fichier OBJ (lecture de l’instruction usemtl, lecture consécutive du fichier .mtl et détection du fichier de texture utilisé en conséquence, ce qui permettrait de ne plus avoir à spécifier les noms des fichiers images) serait souhaitable. Le mieux, ce serait de supplanter totalement l’outil vtkOBJReader de VTK, car dans le cas de gros maillages, faire lire le fichier au Reader de VTK, puis le faire relire par le vtkTexturingHelper, ça commence à demander pas mal de temps
  • ajouter d’autres sources possibles de lecture, tant pour la géométrie (VRML, PLY, STL…) que pour le format des images (PNG…)
  • rendre l’utilisation des extensions par la classe case-insensitive, ou utiliser des enums à la place: plus simple pour tout le monde
  • pour le moment, la classe ne contient en interne qu’un PolyData et un Actor. Cela fonctionne très bien pour de l’importation depuis un fichier OBJ, qui n’est prévu pour importer qu’un seul objet 3D (d’où son nom…). Mais étant donné que certains autres formats permettent d’importer plusieurs figures en un fichier (le VRML le fait), il faudrait réfléchir à passer d’une instance unique de PolyData / Actor à une liste dont on accéderait aux composants via un index.
  • un peu plus de gestion et remontée d’erreurs, peut-être du throw d’exceptions ?

La classe et tous ses fichiers associés sont disponibles sur mon dépôt GitHub. Normalement, sur un système disposant déjà d’une installation classique de VTK, ça devrait marcher direct.

J’encourage fortement quiconque serait intéressé pour reprendre et améliorer ce mini-projet à le faire ! Moi je ne travaille plus vraiment dessus (mon stage est maintenant terminé), et j’ai d’autres projets sur lesquels travailler, celui-là n’avancera peut-être plus beaucoup 😉

À bientôt les grenouilles !

 

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée.