Effizientes Zeichnen einer großen Anzahl von Kacheln in Monogame (XNA)

8

Ich stelle diese Frage, weil ich keine endgültige Antwort darauf gefunden habe.

Lassen Sie mich zunächst einige Dinge über das Spiel und das, was ich bereits getan habe, darlegen. Das Spiel wird ein RTS-Set in einer prozedural generierten Welt sein, in der Simplex-Rauschen verwendet wird. Die Welt besteht aus Blöcken mit einer Größe von 16 x 16, die aus Sprites mit einer Größe von 64 x 64 bestehen. Ich habe es geschafft, Blöcke dynamisch zu laden und zu entladen, was einwandfrei funktioniert. Die Welt wird ein bisschen wie Rimworld aussehen, also von oben nach unten mit verschiedenen Schichten von Sprites (Terrain zuerst, Übergangs-Sprites, Bäume, Abziehbilder usw.). Neu erzeugte Welten können Entitäten enthalten, die die Umwelt beeinflussen können (z. B. ein Dorf, das zu einer Stadt geworden ist) und damit den Brocken. Ich bin sicher, dass dies mit einer Funktion berechnet werden kann, aber es ist etwas zu beachten.

Das Hauptproblem, das ich habe, ist, dass beim Verkleinern immer mehr Kacheln gezeichnet werden, was die Leistung stark beeinträchtigt. Bei ungefähr 30000 Sprites dauert der Ziehabschnitt 8 ms, was der Hälfte dessen entspricht, was für den Betrieb mit 60 FPS erforderlich ist. Und das ist nur das Gelände. Ich verwende Texturatlanten, um die Anzahl der Ziehungen zu begrenzen (30000 Sprites, die in 6 Zählungen gezeichnet wurden).

Das Ziel ist es, von der Ebene von Stadt / Dorf / Stadt bis zum Sehen eines ganzen Landes herauszoomen zu können. Dies muss dynamisch erfolgen (z. B. nicht durch Klicken auf ein Minikartensymbol, sondern einfach wie in Supreme Commander zurückblättern).

Ich habe viele Artikel zu diesem Thema gelesen, aber ich habe kein klares Beispiel dafür gefunden oder gesehen, wo es funktionieren würde. Hier ist eine Liste der Techniken, die ich gefunden habe und die funktionieren sollen:

  • Schmutzige Rechtecke, wie hier beschrieben , in denen Sie nur neues Material zeichnen und den Rest im Backbuffer aufbewahren. Das macht sehr viel Sinn, aber ich weiß nicht, wie ich das in Monogame implementieren soll.
  • Meine bevorzugte Wahl: Verwenden Sie RenderTargets, wie hier beschrieben , in großem Umfang, um auf ein RenderTarget zu zeichnen und es dann als Textur zu speichern. In meinem Fall würde ein 16 x 16-Block, der aus 64 x 64 besteht, eine 1024 x 1024-Textur erzeugen. Ich bezweifle wirklich, dass dies in Bezug auf die Leistung funktionieren wird, aber das Ergebnis würde aus sehr detaillierten Texturen bestehen und ist auch großartig zu verwenden, wenn man bedenkt, dass das meiste davon statisch ist (Gelände / Bäume usw.) und sich nicht so stark ändert. Dies bedeutet aber auch, dass jedes Mal, wenn eine Änderung an einem Block vorgenommen wird, Texture2D mithilfe von SetData geändert werden muss, was meiner Erfahrung nach ziemlich CPU-intensiv ist. Wenn die Texturen jedoch 16 x 16 wären, könnte es tatsächlich funktionieren und es würde auch den eigenen Speicher und die Festplattennutzung reduzieren.
  • Bis Texturen durch Angabe des SourceRectangle in SpriteBatch. Für riesige Graslandschaften / Ozeane ist dies ein Plus, da nur ein Sprite gezeichnet wird. Für detailliertes Gelände mit unterschiedlichen Farben und unterschiedlichen Sprites (Biomes und Biomübergänge) befürchte ich jedoch, dass dies keinen großen Unterschied macht.
  • Meine eigene Lösung, die Details beeinträchtigt, bestand darin, eine weiße Standardfliese (64 x 64) zu verwenden, ihr die Farbe der umgebenden 4 Kacheln zu geben und sie dann so zu skalieren, dass sie die 4 vorherigen Kacheln abdeckt. Der Unterschied besteht hier (abgesehen von der einfarbigen Fliese) darin, dass sich die Landschaft sichtbar verändert hat. Ich sollte auch erwähnen, dass Wälder immer noch einzeln gezeichnet werden müssen, da sie nicht perfekt quadratisch sind.

Wenn jemand eine Idee hat, wie er dieses Problem angehen kann, auch wenn es bedeutet, bestimmte Dinge zu kompromittieren (z. B. Details oder die Verwendung von 16 x 16 Sprites), würde ich es gerne hören.

Guguhl Pluhs
quelle
Lassen Sie den Benutzer nicht so stark
Bálint
Ich habe auch Leistungsprobleme mit meinem prozedural generierten MonoGame-Projekt festgestellt. Meine Lösung bestand darin, das Rendern einzuschränken (den Verkleinerungsbereich zu begrenzen und nur das zu zeichnen, was auf dem Bildschirm angezeigt wird). Es scheint, als ob MonoGame mit dem Mantra "Alles in jedem Frame zeichnen" entworfen wurde, aber ich hoffe, dass mich jemand, der mehr Erfahrung mit MonoGame hat, korrigieren wird.
Logischer Irrtum
Wie rendern Sie die Kacheln? Genauer gesagt - verwenden Sie SpriteBatch und wie verwenden Sie es?
Loodakrawa

Antworten:

3

Grafikkarten sind so optimiert, dass sie ein paar Dinge oft zeichnen und nicht umgekehrt.

In Ihrem Fall haben Sie viele Dinge (verschiedene Texturen), die Sie oft zeichnen möchten (Anzahl der Kacheln).

IMO, die einfachsten Optimierungen, die Sie vornehmen können, um signifikante Leistungssteigerungen zu erzielen, sind:

  1. Kombinieren Sie Ihre Textur zu Atlanten. Dies sollte die Arbeit erheblich beschleunigen, da Ihre Grafikkarte jetzt nur noch eine (oder mehrere) Texturen anstelle vieler kleiner Texturen zeichnet.
  2. Stapeln Sie Ihre Kacheln mit SpriteBatch im SpriteSortMode.Texture-Modus. Dadurch werden Ihre Sprites nach Textur sortiert und die Anzahl der Texturwechsel auf die tatsächliche Anzahl verschiedener Texturen minimiert. Der Nachteil dieses Ansatzes ist, dass Ihre Sprites nicht nach Tiefe sortiert werden. Das ist vermutlich in Ordnung, da Ihre Fliesen alle die gleiche Tiefe haben. Ich gehe davon aus, dass Sie auch andere Dinge haben, daher ist es sinnvoll, einen SpriteBatch für Kacheln und einen anderen für alles andere zu trennen (auf SpriteSortMode.BackToFront gesetzt).

Ich würde empfehlen, beide Dinge zu tun. Viel Glück.

Loodakrawa
quelle
2

Ich habe herausgefunden, dass SpriteBatch nicht zum Zeichnen von Kacheln geeignet ist. Ein Grund dafür ist, dass Sprites für dynamische Objekte gedacht sind und für mehrere Zwecke verwendet werden. Ein weiterer Grund ist, dass es unglaublich CPU-gebunden ist , z. Wie in der Beschreibung erläutert, kostet das Zeichnen von 30.000 Objekten 8 ms (während mein Chipsatz maximal 30% betrug). Außerdem können bis zu 3000 Sprites gezeichnet werden, bevor ein neuer Draw-Aufruf an die GPU gesendet werden muss (Batched and All).

Die Lösung bestand darin, meine eigenen Kacheln mit strukturierten Quads zu zeichnen . In Kombination mit einem VertexBuffer konnte ich in einem Zeichenaufruf bis zu 500.000 strukturierte Kacheln zeichnen, wobei die Zeichenmethode ungefähr 1/20 Millisekunde in Anspruch nahm . Natürlich muss ich wahrscheinlich nicht so viele zeichnen, aber so oder so habe ich meine GPU endlich dazu gebracht, eine Arbeitslast von 75% bei konstanten 60 fps zu haben.

Guguhl Pluhs
quelle
Klingt sehr gut. Ich würde gerne mehr darüber erfahren - haben Sie Tutorials befolgt oder haben Sie Links mit einer detaillierteren Beschreibung dieses Prozesses?
Loodakrawa
2
Ich habe das meiste davon überall dort gelernt, wo XNA / Monogame und strukturierte Quads erwähnt wurden. Riemers.net hat mir jedoch sehr geholfen, da es sich an Leute richtet, die nur schnell etwas einrichten möchten, das funktioniert. Sie sollten sein 3D-Terrain-Tutorial genau befolgen, da es auch VertexBuffers und IndexBuffers behandelt. Es sollte kein Problem sein, es in ein Fliesentyp zu verwandeln.
Guguhl Pluhs
Wenn es das ist, was Ihre Frage am meisten beantwortet, können Sie sie gerne als akzeptiert markieren :)
Vaillancourt
@GuguhlPluhs Ich weiß, dass dies spät ist, aber haben Sie noch weitere Informationen zu diesem Thema? Ich stehe vor dem gleichen Problem.
Jelle