Image de couverture du post

2019-05-18 21:31 - Technique

J’ai testé ShaderToy

Si vous baignez un peu dans le milieu de l’imagerie numérique, vous avez certainement entendu parler de ShaderToy. Cet outil est le bac à sable des programmeurs graphiques, une gigantesque base ouverte de shaders, c’est-à-dire de programmes destinés à générer une image.

Je me suis enfin décidé à tester l’outil; retour d’expérience sur ma modeste création.

Principe de ShaderToy

ShaderToy est un site où chacun peut ajouter ses shaders, regarder ceux des autres créateurs et échanger avec ces derniers.

Le site est basé sur WebGL, un standard de programmation graphique pour le web, supporté par tous les principaux navigateurs. Les shaders sont écrits en GLSL, le langage des shaders d’OpenGL qui est un standard de programmation massivement adopté par l’industrie (que ce soit pour les jeux-vidéos, le cinéma, la conception assistée par ordinateur, etc).

Le site a cependant pas mal de restrictions par rapport à un programme OpenGL ou WebGL classique; en effet un programme graphique classique est composé d’une succession d’étapes qui transforment de la géométrie est des textures en une image finale. Mais, dans ShaderToy, le programmeur ne peut implémenter qu’une seule étape: le pixel shader, c’est-à-dire un programme qui est exécuté en parallèle pour chaque pixel. Toutes les opérations, y compris la création de la géométrie, sont donc exécutées pour chaque pixel, ce qui est très coûteux en termes de performances. De plus, il est impossible d’utiliser des textures à part celles mises à disposition par les créateurs du site, il faut donc rivaliser d’ingéniosité.

Explication de ma création

Cette partie est plus technique, si vous n’êtes pas intéressé par les détails techniques vous pouvez sauter directement à la suite.

Pour implémenter l’attaque tonnerre de pikachu, j’avais besoin de plusieurs choses: créer le tonnerre lui-même, et créer une texture de pikachu.

La texture de pikachu

La difficulté est d’encoder une image en minimisant l’occupation de mémoire et le temps passé à récupérer la valeur d’un pixel. L’image mesurant 128 par 120 pixels, la stocker dans un tableau de valeurs RGBA stockées dans des nombres flottants à précision simple coûte 128*120*4*4=245760 octets.

J’ai donc opté pour l’utilisation de couleurs indexées, c’est-à-dire 7 couleurs numérotées (une 8ème pour indiquer un pixel transparent). Chaque pixel occupe donc 3 bits. En stockant l’image dans un tableau d’entiers de 32 bits, on peut stocker 10 pixels par entier. 2 bits sur 32 sont inutilisés (dont le bit de signe donc pas besoin de se soucier du signe ou d’utiliser un type non signé). La texture n’occupe donc maintenant que 128*120/10*4=6144 octets. On a divisé par 40 la taille de la texture, au seul coût d’avoir une palette assez limitée.

J’ai donc créé l’image en pixel art, en partant d’une image de pikachu trouvée sur Internet, l’ai traitée pour qu’elle se retrouve à la bonne dimension et avec la bonne palette, puis ai peaufiné les détails du pixel art à la main dans Krita. J’ai ensuite écrit un script Python pour transformer cette image en une fonction GLSL.

Le concept de la fonction est de lire les indices des couleurs dans un tableau d’entiers, avec des opérations bit-à-bit pour lire seulement les 3 bits qui nous intéressent:

const vec3[7] colors = (...);
int idx = idx_y * width + idx_x;
const int[...] indexv = int[...] (...);
int index = (indexv[uint(idx / 10)] >> (3u * (idx % 10u))) & 7;

Le code réel est un peu plus complexe puisque, pour des questions de performances, il est plus efficace de découper le tableau en morceaux de taille 128, et utiliser des if et modulos pour utiliser la bonne section.

Les éclairs

Les éclairs sont la combinaison de plusieurs effets, mais je ne vais en donner que les grandes lignes. L’idée générale est d’encoder manuellement la structure des éclairs en forme d’abre (chaque point a exactement un parent à part l’origine), puis appliquer les transformations suivantes:

  • déplacer les points selon le temps: les points calculent un déplacement en fonction du déplacement de leur parent et du temps, avec des composantes transversales et longitudinales d’amplitude proportionnelle à la distance au parent, basées sur des sinusoïdes.
  • diviser chaque ligne en sous-lignes, au nombre fixé à 3, et qui ondulent grâce à des sinusoïdes en fonction du temps.
  • déformer la position du pixel pour un éclair plus “organique”: d’une part étirer horizontalement en fonction de l’ordonnée du pixel pour arrondir un peu les arcs, d’autre part déplacer le pixel selon du bruit “simplex” avec deux niveaux: niveau “macro” pour déformer les arcs, niveau “micro” pour un effet foudre
  • enfin, calculer la couleur des pixels affectés par l’éclair en calculant la distance d’un pixel aux lignes qui composent l’éclair. Je génère une ligne très claire au centre des éclairs et un dégradé jaune autour

Bon et du coup le résultat ?

Ça vient, ça vient. Si votre ordinateur est équipé d’une carte graphique assez récente, vous pouvez aller contempler le résultat à l’adresse suivante: https://www.shadertoy.com/view/3tfGWl

Sinon, vous devrez vous contenter de l’image de couverture de cet article. Cliquer sur le lien avec un vieil ordinateur ou sur mobile est à vos risques et périls (bon, normalement dans le pire des cas ça fera seulement planter votre navigateur).

Et en conclusion

Cet outil est intéressant pour s’entraîner et apprendre des autres, mais l’idée de hardcoder mon image est un peu débile, l’outil est plus intéressant pour faire de la génération procdéurale ou tester des effets, mais pour des expérimentations graphiques qui impliquent des textures ou de la géométrie, il vaut mieux faire un bon vieux programme OpenGL (ou utiliser un moteur qui supporte l’écriture de shaders en GLSL).

Log in to post a comment.