Le problème des pixels transparents avec filtrage anisotrope | Blog | Superflu Riteurnz

Le problème des pixels transparents avec filtrage anisotrope | Blog | Superflu Riteurnz Le problème des pixels transparents avec filtrage anisotrope | Blog | Superflu Riteurnz
  • Steam
  • Nintendo Switch
  • Itch.io
  • Google Play
Superflu et son assistante Sophie

Blog

Le problème des pixels transparents avec filtrage anisotrope

2023-04-20

Dans la série des problèmes que je n'aurais pas imaginé devoir gérer quand j'ai commencé à développer ce jeu vidéo, je vous présente : le problème des pixels transparents en PNG.

La base graphique du jeu

Je dessine tous les éléments du jeu sur Inkscape, un chouette logiciel libre de dessin vectoriel. Les images sont ensuite exportées en PNG (puis compressée dans mon format perso, mais ignorons cette étape pour l'instant). Le PNG est le format le plus adapté pour ce type d'usage, notamment car il gère la transparence (le canal alpha de l'image), ce qui permet, lorsqu'on affiche un objet sur une image de fond, de voir l'objet « intégré » à l'image, et non une image avec un fond visible :

Tout cela est très chouette, mais c'est alors qu'un problème inattendu se pose : le filtrage anistrope.

Filtrage anisotrope et pixels transparents

Le filtrage anisotrope, c'est cette technique d'affichage qui permet de redimensionner ou de déplacer des objets de façon lisse, en conservant des bords doux et en évitant un effet de pixellisation : en gros, si vous redimensionnez une image ou/et que vous la déplacez sur des coordonnées non-entières (à x=0,5 et y=100,3 par exemple), le rendu SDL va calculer localement des moyennes pondérées de pixels. Une image valant mieux qu'un long discours, voilà ce que ça donne, à gauche sans filtrage anisotrope, à droite avec :

Pendant très longtemps, cette technique ne m'a posé aucun souci. Jusqu'à ce que pour l'une des pièces du jeu, je me retrouve à afficher des objets quasi-noirs sur un fond quasi-noir (spoiler : c'est une pièce non éclairée où l'énigme consiste à trouver comment allumer la lumière). Et là, j'ai soudainement ce truc bizarre qui apparaît :

Une espèce de contour blanc parasite ! C'est assez moche, car l'illusion d'une scène unique est brisée, on sent très bien qu'il s'agit d'images différentes superposées.

La raison de ces artefacts blancs est très simple : lorsque la SDL calcule son filtrage anisotrope, elle va, pour chaque pixel, choper un petit voisinage pour faire son calcul de moyennage local. Dans 99 % des cas, tout se passe bien. Mais lorsqu'on est sur un pixel du bord, on va parfois choper un pixel qui est 100 % transparent. Et quelles sont les valeurs RGB de ce pixel transparent ? Vous l'avez peut-être deviné : elles correspondent à une couleur blanche. Si on regarde l'image isolée du balai et ses canaux rouge, vert, bleu et alpha (la transparence, blanc étant opaque et noir transparent) :

Évidemment, en général on s'en fiche, car le pixel étant transparent, peu importe sa couleur, on ne la verra jamais. Mais là, cette couleur se retrouve intégrée à un calcul pour un pixel pas totalement transparent, et bim, ça donne ces petits artefacts blancs.

Comment on règle ça

J'ai d'abord cherché à réparer ça directement depuis Inkscape. D'ailleurs, le comportement d'Inkscape a changé récemment : avant, les pixels 100 % transparents étaient exportés avec une couleur noire, ce qui explique que je n'avais jamais remarqué les artefacts (puisque, avec mon style de dessin, la majorité des objets ont un contour noir dur). C'est aussi pour ça que le fond des images est noir sur la première image de cet article (à part la bouche qui a une couleur étrange, mais ça vient d'un autre truc que je ne vais pas détailler ici).

J'ai par exemple essayé de mettre un fond d'une certaine couleur derrière mon objet dans Inkscape, et de mettre cet objet avec une transparence de 100 %. Peine perdue : la phase d'export PNG d'Inkscape s'apparente à un « rendu graphique », donc le fait qu'un objet transparent existe derrière n'est pas pris en compte, Inkscape « voit » un pixel transparent et l'exporte donc avec sa valeur par défaut.

Bon, et puis il faut admettre que cette méthode aurait été relativement fastidieuse à la longue. En plus, pour certains objets, une unique couleur pour les pixels transparents ne marchera pas : par exemple, la tête d'un personnage est séparée de son corps pour être animée différemment, et il faut à la fois que la couleur de sa peau se fonde bien au niveau du cou, mais aussi que les contours restent noirs (sans artefact blanc).

J'ai donc fini par intégrer un petit algorithme de « réparation » au moment où je compresse les données du jeu : pour chaque image avec un canal alpha, on va tout simplement identifier les pixels 100 % transparents qui sont adjacents à des pixels non-100 % transparents, et leur assigner une couleur RGB moyennée sur les pixels non-100 % transparents les plus proches.

En pratique, ça fonctionne très bien, et s'arrêter à un seul pixel plus proche suffit à ne plus perturber l'algorithme de filtrage anisotrope, tout en étant relativement rapide à calculer.

Conclusion

Comme très très souvent quand on développe un jeu de A à Z, le diable se cache dans les détails. C'est typiquement un problème complexe, car il provient d'une accumulation de sources différentes, entre le standard PNG, les paramètres d'Inkscape pour l'exportation, la gestion du filtrage anisotrope par la SDL, etc.

J'imagine que ce serait chouette que ce soit géré par la SDL (qui pourrait, en toute logique, simplement ignorer les pixels 100 % transparents pour le blending, pixels dont il est évident que la couleur n'a pas de sens), mais j'ignore si ce serait réalisable en pratique sans sacrifier l'efficacité du rendu.

i