2 points par GN⁺ 2025-01-05 | 1 commentaires | Partager sur WhatsApp
  • Dans Unity, les principales méthodes pour dessiner un outline autour d’un objet sont l’effet de rim, l’expansion de sommets, le blur buffer, l’algorithme Jump Flood et la détection d’arêtes, avec des compromis différents en qualité, performances et coût de mise en place
  • L’effet de rim approxime le Fresnel via le produit scalaire entre la normale et la direction de vue pour accentuer l’intérieur des bords, mais le résultat varie fortement entre les modèles arrondis et les modèles anguleux
  • L’expansion de sommets crée un contour externe en agrandissant une copie du mesh à l’arrière, et un traitement en clip space permet d’obtenir une largeur plus constante à l’écran
  • Le blur buffer et l’algorithme Jump Flood suivent une approche consistant à dessiner la silhouette dans un buffer puis à l’étendre, avec des points forts respectifs pour les lignes douces ou les outlines très larges
  • La détection d’arêtes crée des lignes en recherchant à l’échelle de tout l’écran les discontinuités dans les buffers de profondeur, de normales et de couleur, mais demande un réglage fin des seuils et de la modulation pour réduire les lignes indésirables

Pourquoi utiliser un rendu d’outline

  • Les outlines servent dans les jeux à créer un style visuel ou comme aide de gameplay pour mettre en évidence ou sélectionner des objets
    • Sable utilise des outlines pour obtenir un style proche de la bande dessinée
    • The Last of Us utilise des outlines pour mettre en évidence les ennemis quand le joueur entre en mode furtif
  • Les cinq techniques impliquent des compromis différents entre performances, qualité visuelle et quantité de réglages manuels

Effet de rim : accentuer les bords internes avec une approximation du Fresnel

  • L’effet de rim approxime l’effet Fresnel pour rendre une ligne sur le rebord / bord de l’objet
  • Le calcul repose sur le produit scalaire entre le vecteur normalisé des normales N et la direction de vue normalisée V
    • On élève la valeur 1.0 - saturate(dot(N, V)) à une puissance P pour produire l’intensité de l’effet
    • Il s’agit davantage d’une approximation pour outline que d’un Fresnel physiquement correct
  • Dans l’implémentation Unity, un shader personnalisé contrôle la largeur, la puissance, la douceur et la couleur de l’outline
    • L’exemple d’implémentation utilise smoothstep, lerp, _OutlineWidth, _OutlineSoftness, _OutlinePower, _OutlineColor
  • Cette méthode apparaît toujours comme une ligne intérieure et n’est pas visible sur le contour externe de l’objet
  • Elle fonctionne bien sur les objets ronds et lisses, mais sur des cubes ou des modèles complexes, l’épaisseur peut devenir irrégulière ou ne pas ressembler à un outline

Expansion de sommets : agrandir une copie du mesh pour créer un contour externe

  • L’expansion de sommets consiste à rendre à nouveau une copie de l’objet ou du mesh, à étendre ses sommets derrière l’original pour créer une forme plus grande, puis à la rendre en couleur unie
  • Choix de la direction d’expansion

    • La qualité de l’outline dépend fortement de la direction dans laquelle on déplace les sommets
    • Un déplacement dans la direction de la position du sommet donne un effet de mesh gonflé
    • En espace local, la position du sommet peut servir de vecteur allant du centre de l’objet jusqu’au sommet
    • Sans normalisation, les sommets plus éloignés du centre se déplacent davantage
    • L’utilisation de normalize(positionOS) * width permet un déplacement plus uniforme en object space
    • Un déplacement dans la direction du vecteur normal donne de bons résultats sur les objets aux arêtes lisses, comme les sphères ou les capsules
    • Sur des objets à arêtes vives comme les cubes, des trous peuvent apparaître dans l’outline
    • On peut aussi utiliser les vertex colors comme direction d’expansion
    • Cette méthode consiste à générer des normales personnalisées et à les stocker dans le canal de vertex colors du mesh
    • En cuisant des normales lissées de type sphérique dans les vertex colors d’un cube, on peut obtenir un meilleur outline
    • L’inconvénient est le travail manuel nécessaire pour créer ces normales personnalisées pour chaque mesh, même si cela peut être automatisé par script
  • Choix de l’espace d’expansion

    • Dans l’étape vertex du shader, les coordonnées des sommets commencent en object space puis sont transformées en clip space via la matrice MVP
    • Le flux est : object/model/local space → world space → camera/view space → clip space → screen space → viewport/window space
    • Une expansion en object space peut déformer l’outline car la transformation MVP n’a pas encore été appliquée
    • L’application de la matrice de modèle peut introduire des problèmes de mise à l’échelle
    • Lors du passage du clip space au screen space, la division en perspective provoque une réduction liée à la perspective
    • Une partie des déplacements en espace 3D se fait dans la direction de la caméra et ne contribue donc pas à la largeur visible de l’outline à l’écran
    • Une expansion en clip space permet d’obtenir un outline propre avec une largeur presque constante à l’écran
    • On transforme la position du sommet et la normale en clip space, puis on ne modifie que les coordonnées x et y
    • On divise par la largeur et la hauteur de l’écran pour corriger le rapport d’aspect
    • On multiplie par le w du clip space afin que la division en perspective qui suit n’ait pas d’effet net sur l’épaisseur de l’outline
    • On multiplie par width * 2 pour que l’unité de largeur 1 corresponde à 1 pixel à l’écran
    • L’approche en clip space dépend elle aussi des normales du mesh
    • Sans normales personnalisées, des trous peuvent apparaître sur les arêtes vives
    • Si les normales sont mal définies et pointent dans la mauvaise direction, les sommets de l’outline se déplacent eux aussi dans la mauvaise direction, créant des trous
    • Voir aussi creating an outline in clip space
  • Masquage

    • Sur la copie du mesh, seule la partie saillante de l’outline doit être visible
    • L’approche la plus courante consiste à faire du culling sur la géométrie front-facing de la copie et à créer l’outline avec les backfaces
    • Le test de profondeur utilise less than or equal to pour que les backfaces ne soient visibles qu’à l’emplacement de l’outline
    • Une autre méthode consiste à utiliser un masque stencil pour empêcher la copie du mesh d’apparaître devant l’original
    • Dans ce cas, aucun culling n’est nécessaire
    • Aucune ligne interne n’est produite
    • Quand deux objets se chevauchent, l’outline n’apparaît qu’autour de chacun des deux objets

Blur buffer : étendre une silhouette en la floutant

  • L’approche par blur buffer consiste à rendre la silhouette de l’objet dans un buffer, puis à l’étendre avec un flou avant d’utiliser le résultat pour rendre l’outline
  • Buffer de silhouette

    • La première étape consiste à créer un buffer de silhouette
    • Chaque objet est rendu dans une texture à l’aide d’un shader à sortie couleur unie
    • Si toutes les silhouettes sont rendues en blanc, on peut ensuite multiplier par la couleur d’outline souhaitée pour obtenir une couleur unique
    • Si chaque objet doit avoir une couleur d’outline différente, on peut rendre chaque silhouette dans une couleur spécifique
  • Passe de flou

    • La passe de flou sert à étendre le buffer de silhouette
    • On utilise généralement un box blur ou un gaussian blur
    • Pour améliorer les performances, on peut réduire la résolution du buffer de silhouette avant le flou
    • La passe de flou est coûteuse car elle calcule la moyenne ou la moyenne pondérée de plusieurs pixels autour de chaque pixel
    • Il vaut mieux effectuer le flou en 2 passes
    • Avec des filtres séparables comme le box blur et le gaussian blur, on peut réduire la complexité de l’algorithme de O(N²) à O(2N)
    • On floute d’abord verticalement, puis on floute horizontalement le résultat pour produire l’image finale
    • La largeur de l’outline est contrôlée par le paramètre _KernelSize du shader de flou
  • Passe d’outline et masquage

    • Après la passe de flou, on compose la silhouette floutée avec la scène d’origine pour créer l’outline
    • Le blur buffer convient bien aux outlines doux ou lumineux
    • En appliquant un traitement step au résultat du flou, on peut aussi rendre un outline net
    • Comme avec l’approche par expansion de sommets, on peut utiliser un masque stencil pour que l’outline ne soit rendu que derrière la géométrie
    • L’impact sur les performances peut être plus élevé qu’avec les autres méthodes

Algorithme Jump Flood : gérer des outlines très larges

  • La quatrième méthode consiste à rendre l’outline avec l’algorithme Jump Flood
  • Son avantage est de permettre le rendu d’outlines très larges avec un coût en performances raisonnable
  • Pour plus de détails, voir Ben Golus et The Quest for Very Wide Outlines

Détection d’arêtes : trouver les discontinuités sur toute l’image

  • La méthode de détection d’arêtes dessine des lignes en recherchant les discontinuités de la scène dans une passe plein écran
  • Les discontinuités peuvent être détectées à partir des valeurs du buffer de profondeur, des vecteurs normaux, des couleurs d’albédo ou d’autres données disponibles pendant le rendu
  • Roberts cross

    • Le Roberts cross operator est un opérateur différentiel qui calcule la somme des carrés des différences entre pixels diagonaux
    • Dans une implémentation concrète, on détecte les arêtes en convoluant l’image source avec le noyau
    • On utilise 2 noyaux, l’un dans la direction x, l’autre dans la direction y
    • La taille du noyau est 2 x 2
    • Seuls 4 échantillons autour d’un pixel sont nécessaires
    • C’est un opérateur simple, mais il peut donner de bons résultats
  • Sobel operator

    • Le Sobel operator utilise lui aussi 2 noyaux, l’un en direction x, l’autre en direction y
    • Les noyaux Sobel sont de taille 3 x 3 et utilisent 9 échantillons autour d’un pixel
    • Voir aussi blog post on Sobel filters pour plus de détails sur le fonctionnement du filtre Sobel
  • Sources de discontinuités

    • L’approche la plus courante consiste à rechercher les discontinuités dans les textures de profondeur, de normales et de couleur générées par le pipeline de rendu pour la scène
    • La passe de détection d’arêtes échantillonne ces textures et détecte les discontinuités avec les opérateurs mentionnés plus haut
    • Les arêtes ainsi produites peuvent provenir d’une discontinuité détectée dans l’un quelconque des trois buffers
    • Cette méthode applique l’outline à tous les objets qui écrivent dans ces buffers, ce qui offre peu de contrôle objet par objet
    • Autoriser plusieurs sources de discontinuités permet de construire un système d’outline plus robuste
    • Certaines arêtes sont détectées par les trois sources à la fois
    • Beaucoup d’arêtes ne sont détectées que grâce à la contribution d’une seule source spécifique
    • Il est possible d’attribuer des poids et des seuils différents à chaque source afin de contrôler le rendu visuel de l’outline
  • Modulation de la détection d’arêtes

    • Appliquer uniquement un opérateur de détection d’arêtes sur les buffers de discontinuités ne suffit pas toujours pour obtenir un résultat sans artefacts
    • Le buffer de profondeur est implémenté de manière non linéaire dans beaucoup de pipelines de rendu
    • Quand deux objets proches de la caméra sont séparés de 1 mètre, leur différence de profondeur est plus grande que pour deux objets éloignés séparés de la même distance
    • Pour compenser cela, on peut moduler le seuil de détection des discontinuités de profondeur à l’aide du buffer de profondeur lui-même
    • Une géométrie proche doit présenter une discontinuité de profondeur plus importante avant d’être détectée comme une arête
    • Des arêtes indésirables peuvent aussi apparaître à de faibles grazing angles
    • On peut les moduler avec un masque Fresnel construit à partir du produit scalaire entre le vecteur normal N et la direction de vue V
    • Ce masque est du même type que celui utilisé dans la méthode par effet de rim
    • D’autres techniques de modulation sont possibles, mais le choix dépend de l’effet visuel recherché
  • Sources de discontinuités personnalisées

    • On peut aussi fournir au shader d’outline des sources de discontinuités personnalisées
    • Il s’agit de render textures créées directement pendant le rendu et contenant des données personnalisées destinées à la génération d’outline
    • Comme on contrôle précisément quels objets écrivent dans ce buffer personnalisé, on contrôle aussi quels objets recevront un outline
    • Par exemple, on peut rendre les vertex colors d’un mesh dans une texture pour créer une source de discontinuités
    • Une autre méthode consiste à colorer les faces selon leur position dans le monde, ou à construire un buffer personnalisé combinant les informations du buffer de profondeur et du buffer de normales
    • Plus d’informations dans Linework section map

1 commentaires

 
GN⁺ 2025-01-05
Avis de Hacker News
  • L’article lié sur le Jump Flood Algorithm était vraiment excellent : https://bgolus.medium.com/the-quest-for-very-wide-outlines-b...
    Il est intéressant de réfléchir aux différentes approches utilisables au niveau des pixels/texels, et ici aussi les champs de distance signés (SDF) sont employés de façon astucieuse pour faire une grande partie du travail à la place
    Le fait d’obtenir des contours de la largeur souhaitée en temps linéaire est impressionnant par rapport à une méthode brute pour les grandes largeurs
    Je recommande vivement les SDF, qu’ils soient vectoriels, basés sur des fonctions comme dans les travaux d’Inigo Quilez, ou encore rasterisés à base de texels/voxels comme dans l’article
    Houdini prend aussi très bien en charge les SDF raster et dispose d’un ensemble d’outils SDF mature, donc cela vaut le coup d’y jeter un œil, même avec la version gratuite

  • Un jour, j’aimerais creuser en profondeur les graphismes 3D stylisés comme projet de R&D.
    Il y a eu pas mal de progrès récemment, mais j’ai l’impression qu’il reste encore beaucoup de fruits faciles à cueillir.
    Je me demande comment réduire les détails des modèles 3D en rendu toon quand la caméra s’éloigne, et comment effectuer une transition naturelle entre une apparence plus stylisée et une autre moins stylisée.
    La question est aussi de savoir s’il est possible de rendre de façon convaincante, dans une scène 3D, les décors à l’aquarelle d’une animation 2D dessinée à la main, et comment animer en douceur, dans l’espace écran, les traces de pinceau et la texture du papier.
    La manière de représenter des éléments comme la fumée, le feu, les arbres, l’herbe, la boue, la pluie, la fourrure ou l’eau dans un jeu 3D stylisé reste également ouverte.
    Je me demande aussi s’il serait possible, dans un jeu à caméra libre, d’automatiser les déformations subtiles du modèle pour qu’il rende mieux sous l’angle actuel de la caméra, comme dans l’animation dessinée à la main.
    Autre grande question : à quoi ressembleraient l’éditeur de maillages et l’éditeur de décors idéaux pour un renderer stylisé, et faut-il nécessairement des surfaces 3D physiquement exactes et des rigs, ou peut-on les définir de façon plus abstraite ?
    Il serait aussi intéressant de voir si l’on peut créer des jeux 2D procéduraux en rendant du pixel art rétro à partir de modèles 3D simples ; et on pourrait sans doute utiliser la stylisation pour rendre moins visibles les intersections accidentelles entre deux maillages.
    Rien qu’avec ces questions, on pourrait remplir les carrières de dix personnes, mais je trouve que c’est plutôt une bonne chose.

    • Pour les décors à l’aquarelle, il existe plusieurs techniques, et celle qui me frappe le plus personnellement est le travail côté Blender avec les Geometry Nodes de Blender : https://www.youtube.com/watch?v=ljjUoup2uTw
      Le filtre de Kuwahara est aussi suffisamment convaincant pour la plupart des gens.
      Pour les outils d’édition destinés à un renderer stylisé, Blender + Rigify + clés de forme + un peu de magie avec les drivers ont suffi à mes besoins.
      Le texturing dans Blender est pénible, mais supportable à l’échelle hobby ; si l’on a besoin de plus de contrôle pour le rendu non photoréaliste, le fork de DillonGoo Studio peut être préférable : https://www.dillongoostudios.com/gooengine
      Pour créer du pixel art à partir de modèles 3D, j’ai essayé de rendre les animations/modèles en basse résolution ; le résultat était correct, mais il a fallu tâtonner.
      Je me souviens aussi de cas où le post-traitement était plus sophistiqué pour supprimer des problèmes comme le scintillement des pixels.
    • Ce n’est pas de la 3D, mais Stamen, un studio de visualisation de données et de cartographie, a créé un magnifique renderer de cartes à l’aquarelle : https://maps.stamen.com/watercolor/#14/52.3718/4.8958
    • Ce n’est pas exactement du pixel art rétro au sens strict, mais à partir de Commandos 2, les personnages, véhicules, etc. étaient créés sous forme de modèles 3D, puis rendus à l’exécution en sprites 2D et mélangés à des sprites 2D et des décors prérendus.
    • En développant un jeu en pixel art 3D, il a fallu changer la taille des pixels quand la caméra dézoomait.
      Basse résolution : https://x.com/Navy_Green/status/1525564342975995904
      Stabilisation : https://x.com/Navy_Green/status/1693820282245431540
    • Pour un exemple de pixel art créé à partir de modèles 3D, voir Dead Cells : https://www.gamedeveloper.com/production/art-design-deep-div...
  • J’ai commencé ma carrière avec des apps VR, puis je suis rapidement passé au développement web parce que le marché était meilleur.
    Quand je lis ce genre d’article, ce domaine me manque.
    Il y avait quelque chose de magique dans le travail sur les graphismes 3D, les collisions et les shaders, quelque chose qu’on retrouve difficilement ailleurs.
    En pratique, on crée des mondes et on reproduit la physique, et les maths y sont aussi beaucoup plus concrètes et présentes que dans d’autres domaines de la programmation.

    • Je suis passé dans l’autre sens, du développement web au jeu vidéo, et c’est vraiment aussi amusant et intéressant que ça en a l’air.
      La satisfaction au travail est bien plus élevée, et la profondeur comme le plafond du sujet sont aussi plus grands.
      Cela fait quatre ans que je fais ça, et je n’ai jamais heurté le mur de l’ennui comme à l’époque du développement web.
      Il n’y a rien de tel que d’arriver au travail le lundi, d’ouvrir l’éditeur du moteur, de voir se rendre le petit monde sur lequel on travaille et de réfléchir à la prochaine fonctionnalité géniale à y ajouter.
    • Dans le développement web, si les gens aspirent tout le code pour nourrir des réseaux neuronaux, les parties ennuyeuses et pénibles peuvent diminuer et laisser davantage de travail intéressant.
      À l’inverse, dans les domaines de l’art technique, si l’on aspire les productions pour nourrir des réseaux neuronaux, les parties intéressantes et gratifiantes disparaissent, et il ne reste pour le travail professionnel que les tâches ennuyeuses existantes, voire encore davantage.
      La valeur de tout l’ensemble de compétences baisse aussi, et les gens de la tech se comportent comme s’ils connaissaient ce travail mieux que les principaux concernés, tout en accusant ces derniers de ne pas se réjouir que des entreprises de la Silicon Valley, sans rémunérer les personnes qui ont créé les données d’origine, sortent des résultats C- à prix F et détruisent le marché.
      En pesant le pour et le contre, je pense que le choix de passer au développement web était le bon.
  • Dans mon jeu Astral Divide, j’ai mis au point une technique qui n’est pas dans l’article
    Elle ressemble à Blurred Buffer, mais sans passe de flou, et exploite les contours créés par l’antialiasing
    On dessine l’objet en blanc opaque sur un fond noir transparent, puis, dans le fragment shader, on filtre avec un seuil codé en dur uniquement les pixels dont le canal alpha n’est ni totalement opaque ni totalement transparent
    Le résultat est suffisamment bon, peu coûteux en performances et très simple à implémenter

    • Ces astuces sont vraiment satisfaisantes
      Il y a quelque temps, en implémentant un outil de recadrage dans un logiciel de montage vidéo, il fallait flouter temporairement la zone extérieure au recadrage pendant l’édition
      Comme je ne voulais pas ajouter encore une passe de flou coûteuse, j’ai simplement modifié le biais de mipmap pour rendre une texture en plus basse résolution, et le filtrage de texture a fait tout le travail gratuitement
      En comparant avec l’effet de flou de recadrage similaire de PowerPoint, c’était presque identique ; PowerPoint avait même un peu de banding de couleur, absent de mon implémentation
      De même, dans un contexte sans antialiasing pleine résolution, la majeure partie du canvas étant constituée de rectangles 2D pouvant être pivotés, les bords présentaient de l’aliasing en escalier
      Au lieu d’activer l’antialiasing plein écran, j’ai légèrement agrandi tous les rectangles et réduit proportionnellement les coordonnées UV, de sorte que les bords visibles se retrouvent à l’intérieur du vrai rectangle 3D ; là encore, le filtrage de texture a réglé le problème gratuitement
    • Si on règle ces pixels sur une couleur unie, ça ne crée pas de crénelage ?
      Ou bien l’idée est-elle de conserver l’alpha et de ne modifier que la couleur, pour que le contour soit léger mais sans effet d’escalier ?
  • Les notes sont excellentes
    En cherchant récemment des approches de détection de contours, je suis tombé sur une bonne méthode du développeur de Mars First Logistics : https://www.reddit.com/r/Unity3D/comments/taq2ou/improving_e...

  • L’article est excellent et le résultat est très agréable visuellement : https://ameye.dev/notes/rendering-outlines/edge-detection/co...
    On dirait une scène de la bande dessinée néerlandaise Franka

    • C’est une reproduction d’une case de Tintin, L’Île Noire
  • Article vraiment remarquable, et l’expérience de lecture est excellente elle aussi
    Les concepts difficiles sont expliqués avec des mots que tout le monde peut comprendre, les schémas et les exemples sont bons, et l’espacement comme la typographie rendent la lisibilité de tout premier ordre
    Je serais curieux de savoir ce qui a motivé la création du thème actuel, et si l’auteur a déjà envisagé de créer une plateforme de publication centrée sur les ingénieurs

  • Le technical art reste de loin mon premier amour dans le logiciel
    J’aimerais que Godot rende plus simple le pipeline de compute shaders pour les effets de post-traitement
    La configuration actuelle des plugins de compositing comporte pas mal de boilerplate
    Ce dépôt est un bon exemple de post-traitement dans Godot : https://github.com/sphynx-owner/JFA_driven_motion_blur_demo

  • Je crois que la première fois que j’ai vu cet effet, c’était dans Wacky Races sur Dreamcast
    À l’époque, il y avait beaucoup de communication disant que c’était le premier jeu à introduire cet effet et que les développeurs l’avaient en quelque sorte inventé
    Je ne sais pas si c’était vrai ou si c’était une exagération marketing, mais en tant que joueur, c’est clairement là que je l’ai découvert pour la première fois

    • Je pense que beaucoup de gens ont probablement inventé cet effet chacun de leur côté à la même période
      La Dreamcast était, honnêtement, le premier matériel capable de gérer cet effet en temps réel avec une qualité élevée
      J’ai développé l’effet de cel shading du jeu Dreamcast Looney Tunes: Space Race, et je l’ai réalisé dès la première semaine où nous avons reçu le kit de développement Dreamcast
      Infogrames Sheffield, qui a créé Wacky Racers, a vu une première version de notre implémentation et a ajouté un effet similaire à son propre jeu
      Le rendu était bon, mais comme c’est arrivé tard dans la production, il n’était pas optimisé autour de cet effet comme notre jeu l’était
      L’équipe de Jet Grind Radio a également créé le même effet indépendamment, et leur jeu est sorti avant le nôtre
      L’algorithme était exactement le même, mais l’utilisation en était différente : eux assumaient pleinement les contours irréguliers, larges et dentelés, tandis que Sheffield et nous luttions contre cette caractéristique pour obtenir un style artistique plus uniforme et traditionnel
      Environ un an plus tard, sur Xbox, quelqu’un semble avoir trouvé comment faire tourner en temps réel du cel shading par détection de contours dans Dragons Lair 3D
      Nous avions essayé d’implémenter cette approche sur Dreamcast, mais les performances étaient loin d’être suffisantes pour l’appliquer simultanément à plusieurs personnages tout en faisant tourner le jeu
      Je ne sais pas si c’est parce que la Xbox était plus puissante ou parce que l’algorithme était plus malin, mais le résultat est indéniable
      Si l’on veut créer un jeu qui ressemble vraiment à un dessin animé dessiné à la main, je pense personnellement que cette méthode reste celle qui offre la meilleure qualité
      J’aimerais trouver un jour une excuse pour la réimplémenter, et les performances ne devraient plus être un problème maintenant
    • Il semble être sorti en même temps que Jet Set Radio, qui était lui aussi sur Dreamcast et avait un effet similaire
      Sacrée coïncidence
  • Il faut vraiment que je me mette à la programmation de shaders et au rendu 3D
    Ce genre d’article est excellent, et j’aimerais vraiment savoir utiliser les shaders en pratique

    • J’ai appris avec Shadron il y a environ 8 ans