Java3D Tutoriel:

Chapitre 4: Texturation des géométries

Pré-requis : Chapitre 1: théorie et primitives et Chapitre 2: création de geométries élementaires
Nous contacter

Nous discuterons dans ce chapitre d'une partie très importante dans la création d'une scène Java3D: le placage des textures. Vous apprendrez à texturer aussi bien les formes primitives (section 1), que les formes élementaires comme celles élaborées au chapitre 2 (section 2). Grâce aux textures et sans trop de difficultés, vos formes deviendront beaucoup plus attrayantes car plaquer des textures transformera vos objets, les rendra plus réalistes et plus complexes.


Exemple 1, texturation de formes primitives

Télécharger le code complet

Extraits de code

Extrait 1, les primitives :


//Box texturée
Appearance boxApp=mkAppWithTexture("stripe.gif");
Box box=new Box(0.3f,0.4f,0.4f,Box.GENERATE_TEXTURE_COORDS,boxApp);
TriangleStripArray tri=(TriangleStripArray)(box.getShape(Box.FRONT).getGeometry());
tri.setTextureCoordinate(0,new Point2f(3f,0f));
tri.setTextureCoordinate(1,new Point2f(3f,3f));
tri.setTextureCoordinate(2,new Point2f(0f,0f));
tri.setTextureCoordinate(3,new Point2f(0f,3f));

//Sphere texturée
Appearance sphereApp=mkAppWithTexture("rock.gif");
Sphere sphere=new Sphere(0.4f,Sphere.GENERATE_TEXTURE_COORDS,sphereApp);

Extrait 2, la création de l'Appareance :


private Appearance mkAppWithTexture(String textureName)
{

Appearance app=new Appearance();
TextureLoader loader=new TextureLoader(textureName,this);
ImageComponent2D image=loader.getImage();

Texture2D texture=new Texture2D(Texture.BASE_LEVEL,Texture.RGBA,image.getWidth(),image.getHeight());
texture.setImage(0, image);
texture.setEnable(true);
texture.setMagFilter(Texture.BASE_LEVEL_LINEAR);
texture.setMinFilter(Texture.BASE_LEVEL_LINEAR);

app.setTexture(texture);
app.setTextureAttributes(new TextureAttributes());
return app;

}


Extrait 3, petite technique utile :


//méthode de création d'un TransformGroup pour les translations
private TransformGroup mkTranslation(Vector3f vect)
{

Transform3D t3d=new Transform3D();
t3d.setTranslation(vect);
return new TransformGroup(t3d);

}
//méthode de création d'un TransformGroup pour les rotations
private TransformGroup mkRotation(double angle)
{

Transform3D t3d=new Transform3D();
t3d.rotY(angle);
return new TransformGroup(t3d);

}


Commentaire du code

Extrait 1, les paramètres de texturation :

Il s'agit dans cette partie de souligner comment passer les paramètres aux constructeurs des objets primitifs du Java3D pour obtenir finalement une texturation correcte de celui-ci.

Rappelons le constructeur général des primitives : "public Box(float xdim, float ydim, float zdim, int primflags, Appearance ap)".

On distingue pour ce type d'objet trois types de paramètres possibles aux maximum pour les constructeurs:

Il nous faut maintenant expliquer le principe général de l'application d'une texture sur un objet 3D. En fait le principe est tout le temps le même (en OpenGl comme en Java3D ...), il faut définir sur chaque face à texturer un repère par rapport auquel seront collées les textures. Ensuite, chaque texture à appliquer l'est par carré de 1*1. Pour mieux comprendre, observons un petit exemple qui expliquera pourquoi, dans le carré de notre exemple, sur une des faces, la texture est appliquée 3*3=9 fois alors que sur une autre face elle ne l'est qu'une fois. En réalité, comme l'explique les schémas suivants, cela est fonction du repère fixé sur ces deux faces :

Quand la texture est plaquée 9 fois sur une face, c'est que le répère est défini comme celui de la figure de gauche; quand la texture est plaquée une seule fois, le repère est identique à celui de la figure de droite. Il faut savoir que lorsque dans le constructeur de l'objet primitif on spécifie GENERATE_TEXTURE_COORDS, pour une Box par exemple, c'est le repère de droite qui est automatiquement généré par Java3D. Ce repère par défaut tend donc à étaler la texture en entier une seule et unique fois et sur toute la face. Cette opération automatique peut certes être très pratique, mais souvent elle ne conviendra pas du tout. Par exemple, si vous voulez appliquer une texture brique sur un mûr, vous voudrez la plaquer une fois puis qu'elle se répète pour couvrir toute la face du mur. Dans ce cas, le repère automatique ne conviendra pas du tout, car le résultat serait que le mur aurait une seule énorme brique !! Il faut donc savoir redéfinir ce repère comme nous l'avons fait pour la face texturée en 3*3.

Le principe est de récupérer avec la méthode getShape() existant pour les primitives la face sur laquelle vous désirez plaquer votre repère personnalisé, c'est le rôle de la ligne TriangleStripArray tri=(TriangleStripArray)(box.getShape(Box.FRONT).getGeometry()) qui va stocker dans un objet TriangleStripArray la géometrie de la face frontale (Box.FRONT). Ainsi, nous aurons accès à chacun des points de la face indexée de 0 à 3 pour une face de Box. Nous devons alors noter une petite curiosité quant à l'indexage des points sur une face de Box. Cet indexage est précisé en rouge sur le schéma ci-dessus. Il m'a fallu beaucoup chercher avant de comprendre que c'est cet indexage étrange qui m'empêchait de plaquer correctement mes textures. Ainsi pour générer un bon repère il faut associer au bon index de point la bonne coordonnée comme cela :


tri.setTextureCoordinate(0,new Point2f(3f,0f));
tri.setTextureCoordinate(1,new Point2f(3f,3f));
tri.setTextureCoordinate(2,new Point2f(0f,0f));
tri.setTextureCoordinate(3,new Point2f(0f,3f));

Vous pouvez maintenant texturer facilement une face de Box, mais que faire quant il s'agit de texturer une Sphere ou un Cylinder ? Le problème est beaucoup plus compliqué et il nous est actuellement impossible de répondre à cette question correctement car nous ignorons comment Sun a élaboré ses primitives et donc comment placer notre repère dessus. Réussir à le faire nous demanderait beaucoup de temps et de tatonnement pour peu de chose. En fait, il serait plus facile de recréer ces primitives comme nous avons créé la forme du chapitre 2. Cependant, dans ce cas nous pouvons toujours nous contenter du repère par défaut associé à chacune de ces primitives, et c'est précisément ce que nous avons fait pour texturer la Sphere de droite. Le résultat est correct, seul problème : la texture est appliquée une seule fois sur toute la sphère mais nous nous en accomoderons.

Voilà, pour cet extrait qui contient en fait la théorie principale à connaître pour la texturation: la notion de repère de texture.

Extrait 2, les paramètres de l'objet Appearance :

Nous avions déjà souligné l'importance de l'objet Appearance dans le rendu final d'un Shape3D. C'est de nouveau cet objet qui va permettre le placage des textures grâce à la méthode "setTexture(Texture texture)" qui ne pose pas de problème particulier. Seul point délicat: comment obtenir, à partir d'un fichier .jpeg ou .gif cet objet Texture (qui en fait sera un objet Texture2D)? C'est ce que vous montre l'extrait 2. Examinons le constructeur de Texture2D: "public Texture2D(int mipmapMode, int format, int width, int height)".

Celui-ci précise :

Ensuite il faut avoir un objet ImageComponent2D que l'on crée toujours de la même façon avec ces deux lignes :

TextureLoader loader=new TextureLoader(textureName,this);
ImageComponent2D image=loader.getImage();

Finalement, on effecute quelques petits réglages sur notre Texture2D :

texture.setImage(0, image);
texture.setEnable(true);
texture.setMagFilter(Texture.BASE_LEVEL_LINEAR);
texture.setMinFilter(Texture.BASE_LEVEL_LINEAR);

La première ligne associe l'objet ImageComponent2D à l'objet Texture2D, elle est donc cruciale ! La deuxième ligne active la texure, et enfin on règle les MagFilter et MinFilter, chacun d'entre eux pouvant être positionné sur FATEST (rendu rapide), NICEST (rendu de qualité), BASE_LEVEL_POINT ou BASE_LEVEL_LINEAR. Nous utilisons ce dernier car il utilise un procédé d'interpolation par rapport au quatres points les plus proches et offre un bon rendu.

Ainsi, grâce à cette fonction, on crée un Appearance près à être appliquée et qui peut s'utiliser aussi bien pour les objets primitives que pour les objets élementaires, vous serez donc amené à retrouver cette fonction dans de nombreux exemples du tutoriel.

Extrait 3, programmation organisée :

Juste une petite remarque pour vous signaler l'importance de la lisibilité du code. Je vous conseille donc de prendre un minimum de temps pour écrire des méthodes telles que celles-la car vous serez souvent amener à les reutiliser. A long terme, ce type de programmation vous sera d'un grand secours.


Exemple 2, texturation de formes élementaires

Télécharger le code complet

Cet exemple complètera le chapitre 2 en ajoutant à la création à partir de triangles élementaires d'une géometrie sa texturation. Il ne manquera plus à cet objet que la gestion des lumières pour enfin être un objet 3D sans complexes.

Extraits de code

Extrait 1, création de la géométrie:


//creation d'un element de barriere
Geometry createBarriere()
{

int tab[]=new int[4];
tab[0]=5;
tab[1]=5;
tab[2]=5;
tab[3]=5;

TriangleStripArray objGeom=new TriangleStripArray(20,
TriangleStripArray.COORDINATES|
TriangleStripArray.COLOR_3|
TriangleStripArray.TEXTURE_COORDINATE_2,
tab);

objGeom.setCoordinate(0,new Point3f(0f,0.1f,0f));
objGeom.setCoordinate(1,new Point3f(0.05f,0f,0.05f));
objGeom.setCoordinate(2,new Point3f(-0.05f,0f,0.05f));
objGeom.setCoordinate(3,new Point3f(0.05f,-0.8f,0.05f));
objGeom.setCoordinate(4,new Point3f(-0.05f,-0.8f,0.05f));

objGeom.setCoordinate(5,new Point3f(0f,0.1f,0f));
objGeom.setCoordinate(6,new Point3f(0.05f,0f,-0.05f));
objGeom.setCoordinate(7,new Point3f(-0.05f,0f,-0.05f));
objGeom.setCoordinate(8,new Point3f(0.05f,-0.8f,-0.05f));
objGeom.setCoordinate(9,new Point3f(-0.05f,-0.8f,-0.05f));

objGeom.setCoordinate(10,new Point3f(0f,0.1f,0f));
objGeom.setCoordinate(11,new Point3f(-0.05f,0f,0.05f));
objGeom.setCoordinate(12,new Point3f(-0.05f,0f,-0.05f));
objGeom.setCoordinate(13,new Point3f(-0.05f,-0.8f,0.05f));
objGeom.setCoordinate(14,new Point3f(-0.05f,-0.8f,-0.05f));

objGeom.setCoordinate(15,new Point3f(0f,0.1f,0f));
objGeom.setCoordinate(16,new Point3f(0.05f,0f,0.05f));
objGeom.setCoordinate(17,new Point3f(0.05f,0f,-0.05f));
objGeom.setCoordinate(18,new Point3f(0.05f,-0.8f,0.05f));
objGeom.setCoordinate(19,new Point3f(0.05f,-0.8f,-0.05f));

objGeom.setTextureCoordinate(0,new Point2f(0.5f,1.05f));
objGeom.setTextureCoordinate(1,new Point2f(1f,1f));
objGeom.setTextureCoordinate(2,new Point2f(0f,1f));
objGeom.setTextureCoordinate(3,new Point2f(1f,0f));
objGeom.setTextureCoordinate(4,new Point2f(0f,0f));

objGeom.setTextureCoordinate(5,new Point2f(0.5f,1.05f));
objGeom.setTextureCoordinate(6,new Point2f(1f,1f));
objGeom.setTextureCoordinate(7,new Point2f(0f,1f));
objGeom.setTextureCoordinate(8,new Point2f(1f,0f));
objGeom.setTextureCoordinate(9,new Point2f(0f,0f));

objGeom.setTextureCoordinate(10,new Point2f(0.5f,1.05f));
objGeom.setTextureCoordinate(11,new Point2f(1f,1f));
objGeom.setTextureCoordinate(12,new Point2f(0f,1f));
objGeom.setTextureCoordinate(13,new Point2f(1f,0f));
objGeom.setTextureCoordinate(14,new Point2f(0f,0f));

objGeom.setTextureCoordinate(15,new Point2f(0.5f,1.05f));
objGeom.setTextureCoordinate(16,new Point2f(1f,1f));
objGeom.setTextureCoordinate(17,new Point2f(0f,1f));
objGeom.setTextureCoordinate(18,new Point2f(1f,0f));
objGeom.setTextureCoordinate(19,new Point2f(0f,0f));

for (int i=0;i<20;i++) objGeom.setColor(i,new Color3f(1f,0f,0f));

return objGeom;

}


Extrait 2, création d'un Background:


Background mkBackground(String textureName)
{

TextureLoader loader=new TextureLoader(textureName,this);
ImageComponent2D image=loader.getImage();
Background background=new Background(image);
background.setApplicationBounds(new BoundingSphere(new Point3d(0,0,0),150d));
return background;

}


Commentaire du code

Extrait 1, complément du chapitre 2:

Nous avons de nouveau crée un objet à partir de TriangleStripArray, mais cette fois nous l'avons doté d'une texture et immédiatement l'objet gagne en qualité à moindre côut. Chacune des quatre faces de l'objet est constituée à part puis assemblée. Si vous ne comprenez pas cette opération c'est sans qu'un renvoie au chapitre 2 s'impose.

En fait la seule nouveauté est l'adjonction d'un repère de texture sur chacune des quatres faces de notre objet. Mais cela ne devrait pas vous choquer outre mesure car la technique est rigouresement celle abordée pour le premier exemple de ce même chapitre.

Extrait 2, mise en place d'un background:

Cette petite méthode est reutilisable à volonté. Elle vous montre juste comment utiliser l'objet Background en Java3D. Il faut savoir que l'application d'une texture en background semble réellement excessivement lourd pour Java3D. Pour le constater, essayez cet exemple (qui est animé) avec et sans texture: normalement la différence de vitesse est notable. C'est pour cela que dans la mesure du possible, il me semble plus adapté d'utiliser une couleur de fond plutôt qu'une texture. Il existe pour cela un constructeur por l'objet Background : "public Background(Color3f color)" qui applique une couleur unie sur le fond. De même, il existe un constructeur qui place une Geometry en fond, je le laisse à votre curiosité.

Maintenant vous pouvez aborder les chapitres suivants:

-> Chapitre 5: Les lumières