Effizientes Zeichnen von Umrissen um Sprites

21

Ich programmiere ein Spiel mit XNA und experimentiere mit verschiedenen Methoden, um einen "ausgewählten" Effekt auf meine Sprites zu erzielen. Das Problem ist, dass jedes im Spritebatch gezeichnete anklickbare Element mit mehr als einem Sprite gezeichnet wird (jedes Objekt kann aus bis zu 6 Sprites bestehen).

Ich würde mich freuen, wenn mir jemand einen Rat geben könnte, wie ich meinen Sprites mit X Pixeln einen Umriss hinzufügen kann (die Umrissbreite kann also unterschiedlich viele ganze Pixel betragen).

Danke im Voraus,

  • Greg.
Greg
quelle

Antworten:

20

Bei weitem der einfachste Weg, dies zu tun (der wahrscheinlich beste Weg, es sei denn, Sie sind wirklich auf Leistung bedacht), besteht darin, zwei Kopien Ihrer Sprites zu haben.

  • Die reguläre Version
  • Eine "fette", ungefärbte Version - im Grunde genommen eine weiße Version Ihres Sprites. X-viele Pixel "dicker" als das Original.

Zeichne dein gesamtes Objekt mit der "fetten" Version, dann zeichne die reguläre Version darüber.

Indem Sie die "fette" Version weiß machen, können Sie die in SpriteBatch integrierte Farbtönung verwenden, um die Auswahlfarbe dynamisch zu ändern.

Um Ihre "fette" Version zu erstellen, empfehle ich, eine Inhalts-Pipeline-Erweiterung zu schreiben , die automatisch Ihre ursprünglichen Sprites aufnimmt , deren Alphakanal liest und einen neuen Alphakanal erstellt, indem der maximale Alphakanal im Originalbild x-viele Pixel um jeden Pixel abgetastet wird. und Einstellen von RGB = (1,1,1).

Sie müssen sicherstellen, dass alle Sprites einen ausreichenden transparenten Rand haben, um die Kontur hinzuzufügen (Sie können dies im Inhaltsprozessor überprüfen - und bei Bedarf sogar Platz schaffen).

Wenn Sie nur ein paar Sprites haben, können Sie einfach einen guten Bildeditor (GIMP, Photoshop) verwenden und dies von Hand tun: Alphakanal zur Auswahl, Auswahl erweitern, Auswahl zur Alpha, Farbkanäle weiß füllen.

Andrew Russell
quelle
4

Ich nehme an, Sie müssen zuerst alle Teile, die jedes Objekt haben, auf ein einzelnes Sprite zeichnen. Dann müsste man wohl einen Shader schreiben, um die Kanten des Sprites zu erkennen und ein Pixel dort zu zeichnen, wo es eine Kante findet. Ich gehe davon aus, dass es dafür bereits einige Shader geben muss, die Sie entweder verwenden oder portieren können.

Iain
quelle
11
Ich habe gehört, dass es überraschend nervig ist, einen solchen Shader zu schreiben (wir haben ihn in einem unserer 3D-Spiele verwendet und sind immer wieder auf unschöne Fälle gestoßen). Möglicherweise möchten Sie die Kontur mit einer bestimmten Farbe wie (0, 255, 255, 0) direkt in die Sprite-Textur codieren und diese Farbe nur vom Shader transformieren lassen, wenn das Sprite ausgewählt wird. Dann ist der Shader ein trivialer Farbwechsel, und Sie haben ein hohes Maß an künstlerischer Kontrolle über die Kontur und die gewünschten Details.
4

Abhängig von den Anforderungen kann es auch sinnvoll sein, für das Sprite nach Bedarf eine Übersicht zu erstellen. Ich gehe davon aus, dass Ihre Sprites Transparenz haben und unregelmäßig geformt sind, anstatt nur Rechtecke zu sein (obwohl dies gut funktionieren würde, sollten Konturrechtecke Trivial sein).

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

Beachten Sie, dass Sie dies nicht bei jedem Zeichnen tun müssen (ich nehme an, Sie könnten es auch tun), sondern nur das neue Gliederungs-Sprite erstellen, wenn Sie das Sprite wechseln.

Dash-Tom-Bang
quelle
Ja, das wollte ich auch vorschlagen. Rendern Sie das maskierte Sprite 1 Pixel links, rechts, oben und unten vom ursprünglichen Sprite unter dem vorhandenen Sprite. In vielen Spielen wurde diese Methode verwendet, um gerenderten Text zu skizzieren oder um mit nur zwei der vier Positionen einen Schlagschatten zu erstellen.
Kaj
1

Der einfachste Brute-Force-Ansatz besteht darin, zwei Kopien jedes Sprites zu erstellen, eine normale und eine hervorgehobene. Tauschen Sie sie dann einfach aus, wenn sie markiert sind.

Wenn Sie Speicherplatz haben, müssen Sie nicht komplizierter werden. Außerdem haben Künstler die vollständige Kontrolle über das Aussehen, wenn sie hervorgehoben werden, so dass Sie einen Umriss oder alles andere machen können, was Sie möchten.

wkerslake
quelle
Ich denke, ein Teil des Problems ist, dass das OP besagt, dass jedes Objekt aus mehreren Sprites bestehen kann. Ich denke also, dass der Umriss der Umriss des kombinierten Objekts sein muss, anstatt einen separaten Umriss um jedes Komponenten-Sprite zu haben.
Chris Howe
1
Chris, es muss nicht der Umriss des kombinierten Objekts sein, und es funktioniert gut für ein Objekt, das aus mehreren Sprites besteht. Machen Sie genau das, was wkerslake gesagt hat, aber stellen Sie sicher, dass Sie die hervorgehobenen Sprites hinter allen normalen Sprites für dieses Objekt zeichnen. Auf diese Weise wird unabhängig von der Anzahl der Sprites, aus denen ein Objekt besteht, für die Hervorhebung nur etwas mehr als die doppelte Zeichenzeit für dieses Objekt benötigt. Dies sollte weitaus weniger sein, als die Umrisse bei der Wiedergabe mit den kombinierten Sprites zu generieren.
AttackingHobo
1

Wie wäre es mit einem anderen Sprite für jedes Sprite, das einen Umriss des Basissprites darstellt? Zeichnen Sie beim Zeichnen eines umrandeten Objekts die Basis-Sprites, erstellen Sie eine Maske für das kombinierte Rendering und zeichnen Sie dann die Umrandungs-Sprites ohne die Maske.

grau
quelle
2
Warum zeichnen Sie nicht einfach zuerst die Gliederungssprites und dann die regulären Sprites darüber?
Chris Howe
Ein Grund, dies nicht zu tun (oder Chris 'Vorschlag), besteht darin, dass doppelt so viel Texturspeicher verwendet wird. Ein weiterer Grund ist, dass der Artist-Workflow Mist ist, weil Sie jedes Mal, wenn Sie ein Sprite ändern, zwei Dateien aktualisieren müssen.
2
Nun ja, du hättest idealerweise nicht zwei tatsächliche Sprite-Assets. Wenn Sie Alpha-Tests für Ihre Sprites verwenden, können Sie den Umriss in das Sprite einfügen und ihm ein Alpha geben, das etwas höher ist als Ihre transparenten Bereiche. Dann können Sie durch Steuern des Alpha-Test-Referenzwerts steuern, ob der Umriss sichtbar ist oder nicht. Ich habe nur gesagt, dass Sie, wenn Sie 2 Versionen der Sprites haben (unabhängig davon, ob es sich um 2 Assets oder 2 Status des gleichen Assets handelt), zuerst die Gliederungsversion und dann die normale Version zeichnen sollten. Auf diese Weise benötigen Sie keine ausgefallenen Shader, Masken oder was auch immer.
Chris Howe
1
Die Idee von Chris hat Verdienst; Außerdem kann vermieden werden, dass der Künstler 2 Dateien (oder sogar dasselbe Asset) aktualisiert, wenn Sie ein Tool zum Generieren des Alphas für die Gliederungs-Sprites erstellen, die als Teil Ihrer Asset- / Inhalts-Pipeline ausgeführt werden. Der Texturspeicher kann ein Problem sein oder auch nicht, separate Gliederungs-Sprites sollten jedoch in hohem Maße DXT-komprimierbar sein. Möglicherweise 1/6 der Größe der Originaltextur im Videospeicher und ohne Wiedergabetreueverlust.
Jpaver
1

Einige unterschiedliche Lösungen mit unterschiedlichen Kompromissen.

Am einfachsten: Rendern Sie das Objekt mit einer flachen Farbe mehrmals und ruckeln Sie an der Position (nach links, oben, unten, rechts usw.). Dadurch wird eine Outlines-Version von allem erstellt, was Sie darüber rendern erlauben Sie fette Ränder ohne viele Extrarender. Ein Rand von ein oder zwei Pixeln kann bei 4x in Ordnung sein.

Am schnellsten: Verarbeiten Sie die Textur vor und erstellen Sie eine Kopie, die bereits umrandet ist oder nur umrandet ist, oder es handelt sich um eine flache 8-Bit-Graustufenmaske, die Sie in einem Shader einfärben können. Dies geht wahrscheinlich schnell zu Lasten des Speichers.

Am besten: Meiner Meinung nach ist es wahrscheinlich die beste Lösung, eine SDF-Darstellung (Signed Distance Field) Ihres Objekts zu erstellen. Diese Texturen können viel kleiner als die Quelltextur sein und dennoch nützliche Daten erfassen. Im Wesentlichen codiert jedes Pixel, wie weit es von dem Objekt entfernt ist, mit dem es generiert wurde. Mit diesen Daten können Sie alle Arten von Effekten von Glühen bis zu Konturen schreiben. Der Rand kann sich in Größe und Farbe usw. ändern und ist immer noch ein relativ billiger Shader und nur ein zusätzlicher Draw. Nachteil ist das Werkzeug und die Vorverarbeitung.

Aku
quelle
0

Ich bin mir nicht sicher, ob es effizient ist, aber der einfachste Weg, den ich sehen kann, wäre, eine größere Version des Sprites in der Farbe zu zeichnen, die Sie zuerst auswählen möchten. Zeichne das Sprite darüber. Sie sehen nur den Rand des ersten Sprites und erhalten den Auswahleffekt.

EDIT: Wie Sie den Kommentaren entnehmen können, ist dies jedoch keine gute Idee.

Die kommunistische Ente
quelle
1
Nur ein Sprite
Iain
2
Ich glaube nicht, dass es so wäre, aber es wäre einfach.
Die kommunistische Ente
2
Viele Dinge sind einfach, aber lösen das Problem nicht. Für Sprites mit jeder Art von interessanter Silhouette führt eine Vergrößerung zu absolutem Müll.
Die Vergrößerung macht nur bei quadratischen oder kreisförmigen Objekten eine gute Figur. Wenn die Form wirklich breit oder hoch ist oder Lücken aufweist, sieht sie wirklich schlecht aus.
AttackingHobo
3
Eine Vergrößerung führt zu besseren Ergebnissen als nichts und wäre auch ein nützlicher Schritt auf dem Weg zur "Perfektion". Die grafischen Ergebnisse sind zwar nicht perfekt, aber für ein internes Tool ist es möglicherweise gut genug.
Dash-Tom-Bang
0

Ich bin damit einverstanden, das Sprite zu vergrößern. Bei weitem die einfachste Route, und Sie können sie auf die Auswahl eines beliebigen Sprites anwenden, ohne zusätzliche Sprites speziell für diesen Zweck erstellen zu müssen.

  • spriteOutline.Scale = spriteOriginal.Scale * 0.1f; spriteOutline.Color = neue Farbe (255, 0, 0, 255);

quelle
0

Ersetzen Sie die Farbe des Original-Sprites durch eine Konturfarbe (oder tönen Sie sie sogar, wenn Sie möchten). Rendern Sie dieses flach schattierte oder getönte Sprite viermal mit einem Versatz von 1 Pixel: Bei x ist y = (- 1, -1), dann (+ 1, -1), dann (-1, + 1) und dann (+1) , +1). Wiederholen Sie diesen Vorgang für alle Sprites, aus denen das Objekt besteht.

Danach rendern Sie die Original-Sprites in der richtigen Reihenfolge bei (0,0).

Ilya Bossov
quelle