Aller le contenu

De l’art génératif par champs de forces extraits d’une image : une analyse technique

Cette année, l’institut SimplX vous déroule le tapis rouge à l’entrée de son restaurant. Au menu, une nouvelle animation à base de la technologie préférée de nos équipes : le JavaScript. Ce langage qui nous sert à développer vos projets d’applications web les plus folles est aussi le moyen d’expression visuelle privilégié pour nos pages web.

Voici pour vous un article qui dévoile les mécaniques en coulisses de l’animation de bonne année SimplX 2018.

Vous pouvez trouver la version en ligne sur http://website.simplx.fr/happy-2018

1 – Mise en place du code

Pour rester dans la métaphore culinaire, l’application est un millefeuilles aux multiples saveurs. C’est par l’utilisation de couches de dessin à fond transparent superposées les unes sur les autres que sont générés les effets graphiques successifs et simultanés de l’animation. Chaque couche est en réalité un Canvas HTML5, une balise qui nous donne accès à des méthodes puissantes pour dessiner, effacer, transformer. Contrairement au SVG (Scalable Vector Graphics), une autre technologie appréciée chez SimplX qui base la construction de ses objets sur les coordonnées et la géométrie des éléments, le Canvas est une surface fixe où l’on vient appliquer de la coloration par pixels comme avec un pinceau. Le nom est donc plutôt bien choisi…

Il s’agit donc d’intégrer les différents Canvas à sa structure HTML :

index.html

La technique développée pour l’occasion consiste à mettre en place 3 Canvas avec des rôles distincts, à savoir respectivement :

  • Persister tout ce qui a été dessiné
  • Faire disparaitre progressivement ce qui a été dessiné (effets de traces)
  • Effacer immédiatement ce qui vient d’être dessiné pour ne laisser que l’empreinte actuelle

Ce sont les comportements des Canvas painting, trace et flash.
Voici le code qui est exécuté à chaque passage dans la méthode de dessin (fonction draw() principale) :

main.js

A noter que les variables fCtx et tCtx permettent d’accéder aux contextes des Canvas flash et trace. Rien n’est à effacer sur le painting Canvas, d’où l’absence d’instruction.

2 – Lecture d’image

Vous avez probablement remarqué un quatrième Canvas sur l’extrait HTML précédent. Feraient-ils mal leur vaisselle en cuisine à nous laisser des résidus en assiette ? Moi, je sors…

En réalité le quatrième Canvas source-image a bien un rôle, qui est particulier à la discipline d’art numérique mise en œuvre qu’est le Field Art – la création d’art génératif basé sur les champs de force. Il sert de tampon d’impression pour la lecture de l’information contenue dans une image.

Une fois une image chargée en JavaScript, il est en effet très intéressant de pouvoir en récupérer les données de couleur (RGBA) pixel par pixel. La technique consiste à imprimer l’image sur un Canvas, « découper » la zone du canvas imprimée pour ensuite la lire. Voyez plutôt :

ECRITURE

 

LECTURE

Vous pouvez remarquer ici que l’on calcule la moyenne des canaux RGB d’un pixel. On obtient ainsi son niveau de gris inflatable party tents.

L’intérêt de récupérer un information scalaire pour chaque pixel analysé (ici on a choisi le niveau de gris) est que l’on obtient une sorte de height map, un cartographie de la clarté de l’image. C’est en somme un champ de potentiel 2D qui pourrait servir de base à… un mouvement de particules ?

3 – Création du champ de force

Bien sûr ! Un terrain de jeu pour un ensemble de particules qui seraient soumises à un champ de force, généré à partir de l’image.
Attend un peu, ça lui fait une belle jambe à la particule de connaître son « altitude » sur un position donnée, qu’est-ce qui lui indique dans quelle direction se déplacer ?

A partir de là, deux approches sont possibles. La première est de détecter à chaque arrivée sur un nouveau pixel l’altitude des voisins, et de se déplacer sur le plus élevé ou le plus bas selon le comportement choisi. Il est possible d’obtenir des résultats intéressants avec cette technique (http://icosacid.com/benoitstanek/ ?). Pour cette application, la technique va plus loin : nous allons dériver un champ vectoriel de R² dans R² à partir du champ scalaire en calculant le gradient ! Par nature, ce champ sera donc conservatif, et les particules soumises directement à ce champ pour chaque position n’auront pas à se soucier de leurs voisins car leur environnement est décrit dans son intégralité depuis le début.

Une fois que l’on a construit notre champ scalaire de niveaux de gris à partir de l’image, il s’agit donc d’écrire la fonction de création du champ vectoriel :

On termine ainsi avant la génération des particules le procédé qui leur permettra d’être contrôlées par l’image d’entrée. On parle dans l’univers de création 3D de baking, cette phase où l’on lance tous les calculs qui n’ont pas besoin d’êtres effectués pendant la performance de rendu. On vous prépare un bonne brioche les gars.

Note : la complexité de cette phase peut durer un certain temps selon le nombre de calculs à effectuer. Il est souvent judicieux d’en analyser la complexité au sens algorithmique. Ici l’élément critique (O(n²)) est le nombre de divisions pour la fragmentation. En effet on ne calcule pas le champ vectoriel sur chaque point mais sur un carré, sur lequel le champ sera uniforme. Cette même notion de division en field art 3D a une complexité en O(n3) – naturellement – et il devient parfois plus coûteux d’augmenter la précision du champ vectoriel de 50% que d’ajouter 500% de particules.

4 – Construire l’image source

Si l’on choisit les parties les plus sombres d’une image comme puits de potentiel, les particules vont avoir tendance à s’y rendre. En laissant systématiquement une trace derrière elles, elles vont donc dessiner davantage sur les zones sombres. En choisissant une couleur foncée à faible opacité, on obtiendra progressivement (et avec beaucoup de particules) l’image initiale dans un style tracé.

Le champ initial étant conservatif (tout chemin fermé emprunté par une particule la ramène à la même énergie/vitesse), une particule qui entre dans un puits de potentiel gardera assez d’énergie pour en ressortir. Si l’on veut l’y coincer, il faut casser ce caractère conservatif du champ. Pour cela un moyen simple est de faire intervenir de la viscosité dans l’équation. Cette dernière peut être mise en œuvre de plusieurs manières, dans tous les cas elle viendra amputer le vecteur vitesse d’une partie de sa norme.

Une viscosité trop grande entraînera une agglutination des particules sur les zones à bas potentiel. Il faut ajuster sa valeur par rapport à l’intensité du champ de forces, par rapport aux vitesses initiales, et surtout par rapport à l’effet graphique que l’on veut obtenir. Ce procédé d’essai/erreur peut durer des heures (un peu comme le gigot de 7 heures).

Une autre variable dans le procédé, et non des moindres, est bien entendu l’image source elle-même ! C’est bien elle qui génère le champ qui régit le mouvement.

Vous avez probablement remarqué sur l’animation que les particules formaient peu à peu le logo de l’institut.

sx

En utilisant directement le logo officiel, on ne se rend pas bien compte de la différence entre notre perception des limites visuelles et du champ de niveau de gris créé. Les résultats sur le mouvement des particules sont inattendus, et surtout ne dessinent pas le logo…

L’approche qui a permis de mieux contrôler le mouvement des particules a été de ne s’intéresser qu’aux contours de l’image :

simplx-out-smaller

Cette fois-ci, les changements de vitesse des particules avaient lieu de manière évidente sur les pourtours du logo. Par contre, la vitesse devenait soudain très/trop élevée, et la particule partait trop loin avant d’avoir pu être ralentie par l’effet de viscosité.

L’étape finale a été d’appliquer un flou gaussien à cette image des contours, pour créer des puits de potentiels légèrement plus étendus et aux bords plus doux. Le floutage optimal est de 5px.

simplx-out-5g

5 – Tout est dans le timing

Après s’être intéressés à la génération du champ de forces, regardons un peu du côté des différentes phases de l’animation.

L’entrée : saumonade de feux d’artifices artificiels sur son lit gravitationnel stoïque et lourd
Le plat principal : tartare de particules sculptées dans du simplx cuit
L’entremets : excitade de confettis sautés à l’huile de colza primeur
Le dessert : croquant aux étoiles et vœux de bonheur

5.1 Phase feux d’artifices

La genèse du système de particules a lieu de début de cette phase. Une fonction de naissance et de décès sont mises en place, la création de particule consiste à ajouter un objet JavaScript à un tableau, avec les caractéristiques nécessaires au mouvement et au dessin :

Le nommage unique permet l’extermination de la particule, par exemple. On utilise Underscore/Lodash (qui avait anticipé depuis longtemps les besoins en programmation fonctionnelle) pour nettoyer le tableau de particules global avec peu d’efforts :

Cette fonction doit être appelée systématiquement si une particule dépasse les frontières du Canvas (x < 0 || x > this.w || y < 0 || y > this.h), sans quoi notre navigateur entrerait vite en sudation. En l’occurrence, cela permet d’en finir avec les particules du feu d’artifice de lancement une fois qu’elles atteignent le sol.

20 feux d’artifices aux couleurs et vitesses différentes sont tirés. Il s’agit de ne pas tirer trop fort sinon la particule meurt au plafond, mais suffisamment pour combattre la gravité. Une fois le point le plus élevé atteint (on détecte le passage à 0 de la vitesse orientée négativement), la particule donne naissance d’un coup à une quinzaine de particules sous différents angles et avec différentes vitesses. Les sages femmes sont débordées, l’hôpital ferme.

Les traces sont dessinées de la couleur de la particule sur le trace Canvas et le point courant en blanc sur le flash Canvas. Un mapping unique régit la relation entre vitesse de la particule et rayon dessiné :

On dérive ainsi systématiquement les paramètres graphiques pour chaque particule à partir de données physiques spécifiques à la particule.

5.2 Phase de soumission au champ

Au bout d’un certain temps – temps qui est défini dans notre application simplement par un compteur d’itérations, pas de séparation graphique/calcul – les particules nées dans le fond se trouvent soumises à deux forces : le gradient de potentiel généré par l’image source, et une légère action radiale de type ressort centrée sur le centre du logo SimplX. Ainsi, les particules vont progressivement se diriger vers la zone d’intérêt, et une fois à proximité suffisante, vont tomber dans les puits de potentiel du champ généré. Le logo se dessine alors petit à petit…

 

5.3 Phase d’explosion

Doucement mais sûrement, un paramètre croît de manière insoupçonnée. C’est la raideur de la force de ressort central. A chaque itération, elle est multipliée par un facteur de 105%. Immanquablement, elle finit par devenir telle que toutes les particules se ruent vers le centre du logo, jusqu’à atteindre une accélération suffisante pour dépasser les effets visqueux. Le système est donc en phase explosive.

 

5.4 Phase retour au calme

Finalement, la multiplication de la raideur par 105% s’arrête. La viscosité reprend le dessus et les particules se calment. Il ne nous reste qu’à contempler le dessin unique car aléatoire qui vient se se créer sous un ciel étoilé. Une légère pulsation anime les particules d’un mouvement léger.

Le dîner est terminé ; bonne année !

 

Si cet article vous a plu, vous pouvez trouver nos précédentes aventures à base de particules sur le blog SimplX :

L’aventure fluide : des systèmes de particules à Navier-Stokes
Animation de particules sur un réseau hexagonal

Note : cette application a été développée en local et non sur le CodePen officiel de SimplX pour des raisons d’accès aux ressources, ici les images.
Note2 : Il a été choisi d’écrire cet article avec la version ES5 de JavaScript, la version initiale ayant été développée en ES6/2015.

(@SimplX les gars j’ai cherché un générateur de noms de plats pompeux  —> ça n’existe pas ! à creuser)

Soyez le premier à commenter

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *