Moyen efficace de dessiner des contours autour des sprites

21

J'utilise XNA pour programmer un jeu et j'ai expérimenté différentes façons d'obtenir un effet «sélectionné» sur mes sprites. Le problème que j'ai est que chaque cliquable qui est dessiné dans le spritebatch est dessiné en utilisant plus d'un sprite unique (chaque objet peut être composé de jusqu'à 6 sprites).

J'apprécierais que quelqu'un me conseille sur la manière de réaliser l'ajout d'un contour à mes sprites de X pixels (la largeur du contour peut donc être de plusieurs nombres de pixels entiers).

Merci d'avance,

  • Greg.
Greg
la source

Réponses:

20

De loin, le moyen le plus simple de le faire (donc probablement le meilleur moyen, sauf si vous êtes vraiment à court de performances) est d'avoir deux copies de vos sprites.

  • La version régulière
  • Une version "grasse" et non colorée - essentiellement une version blanche de vos pixels sprite X-many "plus gros" que l'original.

Dessinez tout votre objet en utilisant la version "grasse", puis dessinez la version régulière par-dessus.

En rendant la version "grasse" blanche, vous pouvez utiliser la teinte de couleur intégrée de SpriteBatch pour changer dynamiquement la couleur de sélection.

Pour générer votre version "fat", je recommande d'écrire une extension de pipeline de contenu qui peut automatiquement prendre vos sprites d'origine, lire leur canal alpha, créer un nouveau canal alpha en échantillonnant le canal alpha maximum dans l'image d'origine X-plusieurs pixels autour de chaque pixel, et régler RVB = (1,1,1).

Vous devrez vous assurer que vos sprites ont tous une bordure transparente suffisante pour ajouter le contour (vous pouvez le vérifier dans le processeur de contenu - et même faire de la place si nécessaire).

Si vous ne disposez que de quelques sprites, vous pouvez simplement utiliser un bon éditeur d'images (GIMP, Photoshop) et le faire à la main: canal alpha à sélection, développer la sélection, sélection à alpha, remplir les canaux de couleur en blanc.

Andrew Russell
la source
4

Je suppose que vous devez d'abord dessiner toutes les pièces qui chaque objet à un sprite unique. Ensuite, je pense qu'il faudrait écrire un shader pour détecter les bords du sprite et dessiner un pixel partout où il trouve un bord. Je m'attends à ce qu'il y ait déjà des shaders pour le faire, que vous pouvez utiliser ou porter.

Iain
la source
11
Ce genre de shader est étonnamment ennuyeux à écrire, j'ai entendu (nous l'avons utilisé dans l'un de nos jeux 3D et nous avons continué à rencontrer des cas de bord disgracieux). Une chose que vous voudrez peut-être considérer est d'encoder le contour directement dans la texture de l'image-objet avec une couleur spécifique comme (0, 255, 255, 0), et que le shader transforme cette couleur lorsque l'image-objet est sélectionnée. Ensuite, le shader est un changement de couleur trivial, et vous avez un haut niveau de contrôle de l'artiste sur le contour et tous les détails que vous souhaitez.
4

En fonction des besoins, ce qui pourrait également être efficace consiste simplement à créer un aperçu à la demande du sprite. Je suppose que vos sprites ont une transparence et sont de forme irrégulière plutôt que d'être simplement des rectangles (alors que cela fonctionnerait bien pour cela, les rectangles de contour devraient être triviaux).

when selected:
   outline = new sprite canvas of appropriate size
   for sprite in object:
      # use larger numbers for thicker outlines
      for x in (-1, 0, 1) and y in (-1, 0, 1):
         render sprite mask into canvas at x,y with desired color

Notez que vous n'avez pas besoin de faire cela à chaque tirage (bien que je suppose que vous le pourriez), mais créez simplement le nouveau sprite de contour lorsque vous changez de sprite.

dash-tom-bang
la source
Oui, c'est aussi ce que je voulais suggérer. Rendez l'image-objet masquée de 1 pixel à gauche, à droite, en haut et en bas de l'image-objet d'origine, sous l'image-objet existante. De nombreux jeux ont utilisé cette méthode pour délimiter le texte rendu, ou avec seulement 2 des 4 positions pour créer une ombre portée.
Kaj
1

L'approche la plus simple de la force brute consiste à créer deux copies de chaque image-objet, une normale et une en surbrillance. Il suffit ensuite de les échanger lorsqu'ils sont mis en surbrillance.

Si vous avez de la mémoire à épargner, il n'est pas nécessaire de devenir plus compliqué. De plus, les artistes ont un contrôle total sur l'apparence lorsqu'ils sont mis en évidence afin que vous puissiez faire un contour ou tout autre chose que vous souhaitez.

wkerslake
la source
Je pense qu'une partie du problème est que l'OP dit que chaque objet peut être composé de plusieurs sprites. Je suppose donc que le contour doit être le contour de l'objet combiné, au lieu d'avoir un contour séparé autour de chaque sprite composant.
Chris Howe
1
Chris, il n'est pas nécessaire que ce soit le contour de l'objet combiné, et cela fonctionnera bien pour un objet composé de plusieurs sprites. Faites exactement ce que wkerslake a dit, mais assurez-vous de dessiner les sprites en surbrillance derrière tous les sprites normaux pour cet objet. De cette façon, quel que soit le nombre de sprites dont un objet est fait, la surbrillance n'utilisera que légèrement plus du double du temps de dessin pour cet objet, ce qui devrait être beaucoup moins que de générer les contours au moment du rendu avec les sprites combinés.
AttackingHobo
1

Que diriez-vous pour chaque sprite, ayez également un autre sprite qui est un contour du sprite de base. Lorsque vous dessinez un objet décrit, dessinez les sprites de base, puis créez un masque du rendu combiné, puis dessinez les sprites de contour à l'exclusion du masque.

gris
la source
2
Pourquoi ne pas simplement dessiner les sprites de contour en premier et dessiner les sprites réguliers dessus?
Chris Howe
Une raison de ne pas le faire (ou la suggestion de Chris) est qu'il utilise deux fois plus de mémoire de texture; une autre raison est que le flux de travail de l'artiste est nul car vous devez mettre à jour deux fichiers à chaque fois que vous modifiez un sprite.
2
Eh bien oui, vous n'auriez pas idéalement deux actifs sprites réels. Si vous utilisez le test alpha pour vos sprites, vous pouvez mettre le contour dans le sprite et lui donner un alpha légèrement supérieur à vos zones transparentes. Ensuite, en contrôlant la valeur de référence du test alpha, vous pouvez contrôler si le contour est visible ou non. Tout ce que je disais, c'est que si vous avez 2 versions des sprites (que ce soit ses 2 actifs ou 2 états du même actif), vous devez d'abord dessiner la version hiérarchique, puis la version normale. De cette façon, vous n'avez pas besoin de shaders, de masques ou autres.
Chris Howe
1
L'idée de Chris a du mérite; de plus, la mise à jour de l'artiste 2 fichiers (ou même le même élément) peut être évitée si vous produisez un outil pour générer l'alpha pour les sprites de contour qui s'exécute dans le cadre de votre pipeline d'éléments / contenu. La mémoire de texture peut être ou ne pas être un problème, mais les sprites de contour séparés doivent être hautement compressibles DXT; peut-être 1/6 de la taille de la texture d'origine dans la mémoire vidéo et sans perte de fidélité.
jpaver
1

Quelques solutions différentes avec différents compromis.

Le plus simple: restituez l'objet à l'aide d'une couleur plate plusieurs fois et installez la position (décalage gauche, haut, bas, droite, etc.), cela créera une version des contours de tout ce que vous rendrez par-dessus, mais a des coûts de performance et ne permettre des bordures grasses sans beaucoup de rendus supplémentaires. Une bordure d'un ou deux pixels peut convenir à 4x.

Plus rapide: prétraitez la texture et obtenez une copie qui est déjà bordée, ou qui est juste la bordure, ou est un masque plat à 8 niveaux de gris que vous pouvez coloriser dans un shader. Ce sera probablement rapide au détriment de la mémoire.

Meilleur: à mon avis, mais générer une représentation du champ de distance signé (SDF) de votre objet serait probablement la meilleure solution. Ces textures peuvent être beaucoup plus petites que la texture source et capturer des données utiles. Essentiellement, chaque pixel code à quelle distance il est de l'objet utilisé pour le générer. Avec ces données en main, vous pouvez écrire toutes sortes d'effets, des lueurs aux contours. La bordure pourrait changer de taille et de couleur, etc., et est toujours un shader relativement bon marché et un seul tirage supplémentaire. L'inconvénient est l'outillage et le prétraitement.

Aku
la source
0

Je ne suis pas sûr de l'efficacité, mais le moyen le plus simple que je peux voir serait de dessiner une version plus grande du sprite dans la couleur que vous souhaitez sélectionner en premier. Dessinez le sprite dessus. Vous ne verrez que le bord du premier sprite, donnant l'effet de sélection.

EDIT: Cependant, comme vous pouvez le voir dans les commentaires, ce n'est pas une bonne idée.

Le canard communiste
la source
1
Le simple agrandissement d'un sprite ne donne pas un vrai aperçu
Iain
2
Je ne pense pas que ce serait le cas, mais ce serait facile.
The Communist Duck du
2
Beaucoup de choses sont faciles mais ne résolvent pas le problème. Pour les sprites avec tout type de silhouette intéressante, une mise à l'échelle produira des déchets absolus.
La mise à l'échelle ne semblera bonne que pour les objets carrés ou circulaires solides. Si la forme est vraiment large ou haute, ou présente des lacunes, elle sera vraiment mauvaise.
AttackingHobo
3
La mise à l'échelle donnera de meilleurs résultats que ne rien faire et serait également une étape utile sur la voie de la «perfection». Bien que les résultats graphiques ne soient pas parfaits, s'il s'agissait d'un outil interne, cela pourrait être suffisant.
dash-tom-bang
0

Je suis d'accord avec l'augmentation du sprite. De loin l'itinéraire le plus simple, et vous pouvez l'appliquer à la sélection de TOUT sprite sans avoir à créer des sprites supplémentaires spécialement à cet effet.

  • ie spriteOutline.Scale = spriteOriginal.Scale * 0.1f; spriteOutline.Color = nouvelle couleur (255, 0, 0, 255);

la source
0

Remplacez la couleur du sprite d'origine par la couleur de contour (ou même la coloriez si vous le souhaitez). Rendez ce sprite plat ou teinté quatre fois avec un décalage de 1 pixel: à x, y = (- 1, -1), puis (+ 1, -1), puis (-1, + 1) puis (+1 , +1). Répétez l'opération pour tous les sprites qui composent l'objet.

Après cela, restituez les sprites d'origine dans le bon ordre en haut à (0,0).

Ilya Bossov
la source