A Javascript Proof of concept

Manipulation de textures

La manipulation de textures en temps réel sur un objet 2D et 3D est un concept initialement pensé pour des fabricants d'objets qui souhaitent donner à leur client la possibilité de personnaliser une commande.

Select your colours
Vrai 3D


L'objet se manipule sur les 3 dimensions (Axes X Y Z). C'est donc du vrai 3D et pas simplement une suite d'images en rotation.

Le processus de développement
Création de l'object 3D (le modèle)

Le modèle a été créé à l'aide d'un logiciel de modélisation 3D. Le choix s'est porté sur Blender qui est complet et sous licence libre, ce qui signifie qu'il est gratuit et que nous gardons les droits sur les fichiers générés. Pour un technicien, il n'est pas aussi compréhensible que Rhinocéros mais sa gratuité fait tourner la balance. De plus, Blender est un des logiciels les plus performants sur le marché.

La texture

La texture en 3D représente une image 2D que l'on va appliquer sur un objet 3D. De la même façon qu'un artiste peut appliquer une peinture sur un moulage, cette image est la peau de l'objet.

Comme vous pouvez le voir plus ci-dessous, l'image de la texture est un aplati de l'objet 3D. Le patron est directement fourni par Blender.

Il y a plusieurs méthodes pour pouvoir changer la couleur des différentes parties séparément :

  • On peut découper l'objet 3D en fonction des différentes zones et donc avoir plusieurs objets 3D. Le problème avec cette technique est que pour chaque dessin de t-shirt, il faudra avoir un objet 3D différent alors que la coupe du t-shirt reste la même.
  • On peut créer autant de déclinaisons de texture que de possibilités (nous avons malheureusement vu cette technique en utilisation réelle dans un site commercial). Il faut créer une centaine de fichiers voir plus en fonction du nombre de couleurs.

Ces techniques ne sont pas acceptables ! Avoir un système extrêmement lourd à gérer et qui requiert des compétences que peu de personnes ont n'est pas une option viable pour le vendeur/fabricant.

On sait qu'ils ont des graphistes qui leur font les designs des vêtements à vendre. Leurs compétences se limitent à Illustrator et Photoshop (ou Corel ou Gimp), ce qui est en réalité déjà très suffisant.

La solution que nous avons choisie est de créer une multitexture en différentes couches. Chaque couche représentant une zone de couleur. Le graphiste fera donc simplement son travail d'illustrateurs. Il créera un fichier par couches auxquelles on ajoute une couche fixe qui ne sera pas éditable (comme un logo ou un dessin).


Note: les images des différentes couches doivent être en PNG avec transparence.


Javascript

Depuis l'arrivée du webGL-3D plusieurs librairies Javascript sont apparues. Elles nous simplifient l'interfaçage avec l'api webGL. Nous avons, dans cet exemple, décidé d'utiliser BabylonJS même si en condition réelle, nous opterions plutôt pour ThreeJS.

Pour fusionner les différentes couches, nous avons plusieurs possibilités. L'une d'elles est d'utiliser le moteur 3D en utilisant la propriété "multi-materials", mais nous souhaitons n'utiliser qu'une seule texture. Nous choisissons de fournir à Babylon la texture sous forme d'un Canvas 2D.


Imaginons que nous avons 4 couches. #layer1, #layer2, #layer3 et #layer4. Nous souhaitons les fusionner dans #destination.

<img id="layer1" src="layer01.png" width="200" height="200"/>
<img id="layer2" src="layer02.png" width="200" height="200"/>
<img id="layer3" src="layer03.png" width="200" height="200"/>
<img id="layer4" src="layer04.png" width="200" height="200"/>

<canvas id="destination" width="200" height="200"></canvas>

En javascript les canvas nous permettent de faire cela très facilement :

// On récupère le Canvas de destination
var destination = document.getElementById("destination");

// On informe que nous souhaitons faire une manipulation d'image avec l'api 2D.
// En réponse, on a le context qui représente notre zone dessinable.
var destinationContext = destinationCanvas.getContext("2d");

// Récupère l'image source de la première couche à copier.
var source = document.getElementById("layer1");

// On copie chaque pixel de la source vers la destination.
destinationContext.drawImage(source, 0, 0);

// Copie layer 2
source = document.getElementById("layer2");
destinationContext.drawImage(source, 0, 0);
// Copie layer 3
source = document.getElementById("layer3");
destinationContext.drawImage(source, 0, 0);
// Copie layer 4
source = document.getElementById("layer4");
destinationContext.drawImage(source, 0, 0);

Merger des images est aussi simple que cela. L'astuce réside dans la transparence de nos images. La fonction drawImage va peindre l'image source au-dessus de l'image destination, donc même des pixels en semi-transparent seront pris en compte et mélangés avec les anciens pixels.

La difficulté maintenant est de colorier un layer avec une couleur choisie.

Coloration:

// La couleur de coloration
var couleurChoisis = {R: 255, G: 0, B: 0} ; // Rouge

// Récupère l'image source (#layer1) et son Context 2d
var source = document.getElementById("layer1");
var sourceContext = source.getContext("2d");

// Recupere l'image destination et son context 2d
var destination = document.getElementById("destination");
var destinationContext = destinationCanvas.getContext("2d");

// Pour ne pas modifier l'image source, on crée un Canvas temporaire
var canvasTemporaire = document.createElement('canvas');
canvasTemporaire.width = sourceContext.width;
canvasTemporaire.height = sourceContext.height;

// Le context du Canvas temporaire
var contextTemporaire = canvasTemporaire.getContext('2d');
contextTemporaire.drawImage(sourceContext, 0, 0);

// On extrait les données de l'image (pour travailler sur les pixels au niveau de l'octet).
var imgageData = ctx.getImageData(0, 0, canvasTemporaire.width, canvasTemporaire.height );

// imgData.data est un Array qui contient les informations brutes de l'image.
// Un pixel est stoqué sur 4 octets, on fait donc une boucle avec une incrémentation de 4.
// Octet index 0 = Rouge
// Octet #1 = Vert
// Octet #2 = Bleu
// Octet #3 = transparence
for (var i = 0; i < imgData.data.length; i += 4) {

// Colorie uniquement les pixels non transparents (alpha=0)
// Cela permet de ne pas perdre en performances :
// une image de 1000x1000 pixels représente 1 million de pixels à traiter.
if (imgData.data[i + 3] > 0) {
imgData.data[i] = colorRGB.R;
imgData.data[i + 1] = colorRGB.G;
imgData.data[i + 2] = colorRGB.B;
// On laisse data[i+3], l'octet de transparence, inchangé
// pour garder les effets de l'image source
}

}

// Injecte notre nouvelle table de pixels dans le context temporaire
contextTemporaire.putImageData(imgData,0,0);

// Copie la nouvelle image coloriée dans la destination
destination.drawImg(contextTemporaire,0,0);

// Supprime l'élément temporaire
c.remove();

Comme on peut le voir, l'octet de transparence est préservé. Cela a beaucoup d'implication. Avec un minimum d'imagination, on se rend compte que ce principe permet de gérer des dégradés de couleurs entre deux couches en jouant simplement sur des dégradés de transparence.



Conclusion

Configurer un produit en 3D et en temps réel lors de la commande, directement sur une page web (sans Flash) est parfaitement réalisable. Cependant, l'inconvénient majeur est que ce n'est compatible qu'avec les dernières versions des navigateurs. Chrome a une très bonne compatibilité, mais Internet Explorer intègre le WebGL 3D uniquement à partir de la version 11.
Personnellement, nous ne sommes pas d'avis à faire les applications rétrocompatibles. Il vaut mieux annoncer à l'utilisateur que son navigateur est obsolète. Il n'a pas obligatoirement cette notion, et cela lui permet d'être à jour dans le domaine fonctionnalités et de la sécurité. Cette notion est totalement subjective et n'engage que nous. D'autant plus que nous sommes les premiers à reconnaître que la rétrocomptabilité est obligatoire pour des sites grands publics.


Vous pouvez voir le fichier source complet de la démo en cliquant sur js3d.js

Ce fichier est là uniquement pour vous aider à comprendre le principe à l'aide des commentaires. Ne l'utilisez pas pour une utilisation réelle. Il n'est en rien optimisé et il n'y a aucune solution de remplacement en cas d'erreurs.