Nahtloses Tilemap-Rendering (randlose benachbarte Bilder)

18

Ich habe eine 2D-Spiel-Engine, die Karten zeichnet, indem sie Kacheln aus einem Kachelsatzbild zeichnet. Da OpenGL standardmäßig nur die gesamte Textur ( GL_REPEAT) und nicht nur einen Teil davon umschließen kann, wird jede Kachel in eine separate Textur aufgeteilt. Dann werden Bereiche derselben Kachel nebeneinander gerendert. So sieht es aus, wenn es wie vorgesehen funktioniert:

Tilemap ohne Nähte

Sobald Sie die fraktionierte Skalierung einführen, werden jedoch folgende Nähte angezeigt:

Tilemap mit Nähten

Warum passiert das? Ich dachte, dass es an der linearen Filterung liegt, die die Grenzen der Quads überblendet, aber es passiert immer noch mit der Punktfilterung. Die einzige Lösung, die ich bisher gefunden habe, besteht darin, sicherzustellen, dass die gesamte Positionierung und Skalierung nur bei ganzzahligen Werten erfolgt, und Punktfilterung zu verwenden. Dies kann die visuelle Qualität des Spiels beeinträchtigen (insbesondere, wenn die Subpixel-Positionierung nicht mehr funktioniert und die Bewegung nicht mehr so ​​flüssig ist).

Dinge, die ich versucht / in Betracht gezogen habe:

  • Antialiasing reduziert die Nähte, beseitigt sie jedoch nicht vollständig
  • das ausschalten von mipmapping hat keine wirkung
  • Rendern Sie jede Kachel einzeln und extrudieren Sie die Kanten um 1px - dies ist jedoch eine Deoptimierung, da nicht mehr Bereiche von Kacheln auf einmal gerendert werden können und andere Artefakte entlang der Kanten von Transparenzbereichen erstellt werden
  • Fügen Sie einen 1-Pixel-Rand um die Quellbilder hinzu und wiederholen Sie die letzten Pixel. In diesem Fall handelt es sich jedoch nicht mehr um Zweierpotenzen, was zu Kompatibilitätsproblemen mit Systemen ohne NPOT-Unterstützung führt
  • Schreiben eines benutzerdefinierten Shaders für gekachelte Bilder - aber was würden Sie dann anders machen? GL_REPEATsollte das Pixel von der gegenüberliegenden Seite des Bildes an den Rändern greifen und nicht Transparenz wählen.
  • Die Geometrie ist genau benachbart, es gibt keine Gleitkomma-Rundungsfehler.
  • Wenn der Fragment-Shader hartcodiert ist, um dieselbe Farbe zurückzugeben, verschwinden die Nähte .
  • Wenn die Texturen auf GL_CLAMPanstatt gesetzt sind GL_REPEAT, verschwinden die Nähte (obwohl das Rendering falsch ist).
  • Wenn die Texturen auf gesetzt sind GL_MIRRORED_REPEAT, verschwinden die Nähte (obwohl das Rendering wieder falsch ist).
  • Wenn ich den Hintergrund rot mache, sind die Nähte immer noch weiß. Dies deutet darauf hin, dass das opake Weiß nicht von der Transparenz, sondern von einer anderen Stelle stammt.

Die Nähte erscheinen also nur, wenn GL_REPEATeingestellt ist. Aus irgendeinem Grund tritt nur in diesem Modus an den Rändern der Geometrie ein Anschnitt / Leck / Transparenz auf. Wie kann das sein? Die gesamte Textur ist undurchsichtig.

AshleysBrain
quelle
Sie könnten versuchen, Ihren Textur-Sampler so einzustellen, dass er klemmt, oder die Randfarbe anpassen, da ich vermute, dass dies damit zusammenhängt. Außerdem umhüllt GL_Repeat einfach die UV-Koordinaten, sodass ich persönlich etwas verwirrt bin, wenn Sie sagen, dass nur die gesamte Textur und nicht ein Teil davon wiederholt werden kann.
Evan
1
Ist es möglich, dass Sie auf irgendeine Weise Risse in Ihrem Netz verursachen? Sie können dies testen, indem Sie den Shader so einstellen, dass er nur eine Volltonfarbe zurückgibt, anstatt eine Textur abzutasten. Wenn Sie an diesem Punkt noch Risse sehen, sind diese in der Geometrie. Die Tatsache, dass Antialiasing die Nähte reduziert, lässt darauf schließen, dass dies das Problem sein könnte, da Antialiasing die Texturabtastung nicht beeinflussen sollte.
Nathan Reed
Sie können das Wiederholen einzelner Kacheln in einem Texturatlas implementieren. Sie müssen allerdings einige zusätzliche Texturkoordinaten berechnen und Randtexte um jede Kachel einfügen. Dieser Artikel erklärt eine Menge davon (obwohl er sich hauptsächlich auf die nervige Texturkoordinatenkonvention von D3D9 konzentriert). Wenn Ihre Implementierung neu genug ist, können Sie alternativ Array-Texturen für Ihre Kachelzuordnung verwenden. Angenommen, jede Kachel hat die gleichen Abmessungen. Dies funktioniert hervorragend und erfordert keine zusätzliche Koordinatenberechnung.
Andon M. Coleman
3D-Texturen mit GL_NEARESTAbtastung in RKoordinatenrichtung funktionieren für die meisten Dinge in diesem Szenario genauso gut wie Array-Texturen . Mipmapping wird nicht funktionieren, aber Ihrer Anwendung nach benötigen Sie wahrscheinlich sowieso keine Mipmaps.
Andon M. Coleman
1
Inwiefern ist das Rendering "falsch", wenn CLAMP eingestellt ist?
Martijn Courteaux

Antworten:

1

Die Nähte sind korrekt für die Probenahme mit GL_REPEAT. Betrachten Sie die folgende Kachel:

Randfliese

Bei der Abtastung an den Rändern der Kachel mit gebrochenen Werten werden Farben von der Kante und der gegenüberliegenden Kante gemischt, was zu falschen Farben führt: Die linke Kante sollte grün sein, ist jedoch eine Mischung aus Grün und Beige. Der rechte Rand sollte beige sein, ist aber auch eine Mischfarbe. Vor allem die beigen Linien auf dem grünen Hintergrund sind gut sichtbar, aber wenn Sie genau hinsehen, können Sie das Grün in den Rand bluten sehen:

skalierte Fliese

Antialiasing reduziert die Nähte, beseitigt sie jedoch nicht vollständig

MSAA arbeitet, indem mehr Proben um Polygonkanten genommen werden. Abtastwerte, die links oder rechts vom Rand genommen werden, sind "grün" (wieder unter Berücksichtigung des linken Randes des ersten Bildes), und nur ein Abtastwert befindet sich "am" Rand, wobei teilweise aus dem "beige" Bereich abgetastet wird. Wenn die Proben gemischt werden, wird der Effekt verringert.

Mögliche Lösungen:

In jedem Fall sollten Sie zu wechseln, GL_CLAMPum ein Ausbluten des Pixels auf der gegenüberliegenden Seite der Textur zu verhindern. Dann haben Sie drei Möglichkeiten:

  1. Aktivieren Sie das Anti-Aliasing, um den Übergang zwischen den Kacheln etwas zu glätten.

  2. Stellen Sie die Texturfilterung auf GL_NEAREST. Dies gibt allen Pixeln harte Kanten, so dass Polygon- / Sprite-Kanten nicht mehr zu unterscheiden sind, aber es ändert offensichtlich den Stil des Spiels ziemlich stark.

  3. Fügen Sie den bereits besprochenen 1px-Rand hinzu und achten Sie darauf, dass der Rand die Farbe der angrenzenden Kachel hat (und nicht die Farbe der gegenüberliegenden Kante).

    • Dies könnte auch ein guter Zeitpunkt sein, um zu einem Texturatlas zu wechseln (vergrößern Sie ihn einfach, wenn Sie Bedenken hinsichtlich der NPOT-Unterstützung haben).
    • Dies ist die einzige "perfekte" Lösung, die eine GL_LINEARähnliche Filterung über die Kanten von Polygonen / Sprites ermöglicht.
Jens Nolte
quelle
Oh, danke - das Umwickeln der Textur erklärt es. Ich werde mich um diese Lösungen kümmern!
AshleysBrain
6

Da OpenGL standardmäßig nur die gesamte Textur (GL_REPEAT) und nicht nur einen Teil davon umbrechen kann, wird jede Kachel in eine separate Textur aufgeteilt. Dann werden Bereiche derselben Kachel nebeneinander gerendert.

Betrachten Sie die Anzeige eines einzelnen, gewöhnlichen texturierten Quad in OpenGL. Gibt es Nähte in jeder Größenordnung? Nein niemals. Ihr Ziel ist es, alle Szenenkacheln dicht auf eine einzige Textur zu packen und diese dann an den Bildschirm zu senden. EDIT Um dies zu verdeutlichen weiter: Wenn Sie einzelne Begrenzungsscheitelpunkte auf jeder Kachel Quad haben, Sie werden haben scheinbar unberechtigt Nähte in den meisten Fällen. So funktioniert GPU-Hardware ... Gleitkommafehler erzeugen diese Lücken basierend auf der aktuellen Perspektive ... Fehler, die auf einem einheitlichen Verteiler vermieden werden, da wenn zwei Flächen innerhalb desselben Verteilers nebeneinander liegen(submesh), die Hardware wird sie garantiert ohne Nähte rendern. Sie haben dies in unzähligen Spielen und Apps gesehen. Sie müssen eine dicht gepackte Textur auf einem einzelnen Subnetz ohne doppelte Scheitelpunkte haben, um dies ein für alle Mal zu vermeiden. Dieses Problem tritt auf dieser Website und anderswo immer wieder auf: Wenn Sie die Eckpunkte Ihrer Kacheln nicht zusammenführen (alle 4 Kacheln teilen sich einen Eckpunkt), müssen Sie mit Nähten rechnen.

(Bedenken Sie, dass Sie möglicherweise nicht einmal Scheitelpunkte benötigen, außer an den vier Ecken der gesamten Karte. Dies hängt von Ihrer Herangehensweise an die Schattierung ab.)

Lösung: Rendere (mit 1: 1) alle deine Kacheltexturen in einen FBO / RBO ohne Lücken und sende diesen FBO an den Standard-Framebuffer (den Bildschirm). Da es sich beim FBO im Grunde genommen um eine einzelne Textur handelt, kann es nicht zu Lücken bei der Skalierung kommen. Alle Texelgrenzen, die nicht auf eine Bildschirmpixelgrenze fallen, werden überblendet, wenn Sie GL_LINEAR verwenden ... genau das, was Sie möchten. Dies ist der Standardansatz.

Dies eröffnet auch verschiedene Möglichkeiten zur Skalierung:

  • Skalieren der Größe des Quadrates, auf das der FBO gerendert werden soll
  • Ändern Sie die UVs auf diesem Quad
  • herumspielen mit kameraeinstellungen
Ingenieur
quelle
Ich denke nicht, dass das bei uns gut funktionieren wird. Es hört sich so an, als würden Sie vorschlagen, dass wir bei 10% Zoom einen 10-fach größeren Bereich rendern. Dies ist kein gutes Zeichen für Geräte mit begrenzter Füllrate. Ich bin auch immer noch verblüfft, warum speziell GL_REPEAT nur Nähte verursacht und kein anderer Modus. Wie kann das sein?
AshleysBrain
@AshleysBrain Nicht sequitur. Ihre Aussage hat mich verwirrt . Bitte erläutern Sie, wie eine Vergrößerung um 10% die Füllrate um das 10-fache erhöht. Oder wie viel bringt es, den Zoom um das 10-fache zu erhöhen - da durch das Beschneiden von Ansichtsfenstern bei einem einzelnen Durchgang eine Füllrate von mehr als 100% verhindert würde? Ich schlage Sie genau zeigen , was Sie bereits sind die Absicht hat - nicht mehr und nicht weniger - in dem, was wahrscheinlich eine mehr effiziente und nahtlose Art und Weise, indem Sie Ihre endgültige Ausgabe mit RTT Vereinheitlichung, eine gemeinsame Technik. Die Hardware übernimmt das Zuschneiden, Skalieren, Mischen und Interpolieren von Ansichtsfenstern praktisch kostenlos, da dies nur eine 2D-Engine ist.
Ingenieur
@AshleysBrain Ich habe meine Frage bearbeitet, um die Probleme mit Ihrem aktuellen Ansatz kristallklar zu machen.
Ingenieur
@ArcaneEngineer Hallo, ich habe deinen Ansatz ausprobiert, der das Problem der Nähte perfekt löst. Jetzt habe ich jedoch keine Nähte mehr, sondern Pixelfehler. Sie können eine Vorstellung von der Frage habe ich mich beziehe hierher . Haben Sie eine Ahnung, woran das liegen könnte? Beachten Sie, dass ich alles in WebGL mache.
Nemikolh
1
@ArcaneEngineer Ich stelle eine neue Frage, hier zu erklären ist zu umständlich. Es tut mir leid für den Ärger.
Nemikolh
1

Wenn die Bilder als eine Oberfläche angezeigt werden sollen, rendern Sie diese als eine Oberflächentextur (Maßstab 1x) auf einem 3D-Netz / einer 3D-Ebene. Wenn Sie jedes als separates 3D-Objekt rendern, hat es aufgrund von Rundungsfehlern immer Nähte.

Die Antwort von @ Nick-Wiggill ist richtig, ich denke du hast es falsch verstanden.

Barry Staes
quelle
0

2 Möglichkeiten, die einen Versuch wert sein könnten:

  1. Verwenden Sie GL_NEARESTanstelle von GL_LINEARfür die Texturfilterung. (Wie von Andon M. Coleman hervorgehoben)

    GL_LINEARVerwischt das Bild (im Endeffekt) geringfügig (interpoliert zwischen den nächsten Pixeln), wodurch ein sanfter Farbübergang von einem Pixel zum nächsten möglich wird. Dies kann in vielen Fällen dazu beitragen, dass Texturen besser aussehen, da Ihre Texturen nicht mit diesen blockartigen Pixeln überzogen sind. Dies bewirkt auch, dass ein bisschen Braun von einem Pixel einer Kachel auf das angrenzende grüne Pixel der angrenzenden Kachel übergeht.

    GL_NEARESTfindet nur das nächste Pixel und verwendet diese Farbe. Keine Interpolation. Infolgedessen geht es eigentlich etwas schneller zu.

  2. Verkleinern Sie die Texturkoordinaten für jede Kachel ein wenig.

    So ähnlich wie das Hinzufügen dieses zusätzlichen Pixels um jede Kachel als Puffer, außer dass Sie die Kachel in der Software verkleinern, anstatt sie auf dem Bild zu vergrößern. 14x14px Kacheln beim Rendern anstelle von 16x16px Kacheln.

    Sie könnten sogar mit 14,5x14,5 Kacheln davonkommen, ohne dass zu viel Braun eingemischt wird.

--bearbeiten--

Visualisierung der Erklärung in Option # 2

Wie im obigen Bild gezeigt, können Sie immer noch leicht eine Zweierpotenz-Textur verwenden. Sie können also Hardware unterstützen, die keine NPOT-Texturen unterstützt. Was sich ändert, sind Ihre Texturkoordinaten, anstatt von (0.0f, 0.0f)nach (1.0f, 1.0f)Sie würden von (0.03125f, 0.03125f)nach gehen (0.96875f, 0.96875f).

Dadurch werden Ihre Tex-Koordinaten leicht in die Kachel eingefügt, wodurch die effektive Auflösung im Spiel verringert wird (allerdings nicht auf Hardware, sodass Sie immer noch Texturen mit Zweierpotenzen haben). Sie sollten jedoch den gleichen Rendereffekt haben wie das Erweitern der Textur.

Wolfgang Skyler
quelle
Ich habe bereits erwähnt, dass ich GL_NEAREST (auch als Punktfilterung bezeichnet) ausprobiert habe, und es behebt es nicht. Ich kann Nr. 2 nicht ausführen, da ich NPOT-Geräte unterstützen muss.
AshleysBrain
Führt eine Bearbeitung durch, die möglicherweise einige Dinge aufklärt. (Ich gehe davon aus, dass "NPOT [(non-power-of-two)] Geräte unterstützen müssen" etwas in der Art bedeuten sollte, wie Sie es brauchen, um die Texturen auf einer Potenz von 2 zu halten?)
Wolfgang Skyler
0

Folgendes könnte passieren: Wenn zwei Kacheln kollidieren, sollte die x-Komponente ihrer Kanten gleich sein. Wenn dies nicht der Fall ist, werden sie möglicherweise in die entgegengesetzte Richtung gerundet. Stellen Sie also sicher, dass sie genau den gleichen x-Wert haben. Wie stellen Sie sicher, dass dies geschieht? Sie können beispielsweise versuchen, mit 10 zu multiplizieren, zu runden und dann durch 10 zu dividieren (dies nur in der CPU, bevor Ihre Scheitelpunkte in den Scheitelpunkt-Shader verschoben werden). Das sollte zu korrekten Ergebnissen führen. Zeichnen Sie auch keine Kacheln für Kacheln mit Transformationsmatrizen, sondern fügen Sie sie in einen Stapel-VBO ein, um sicherzustellen, dass das verlustbehaftete IEEE-Gleitkommasystem in Kombination mit Matrixmultiplikationen keine falschen Ergebnisse liefert.

Warum schlage ich das vor? Denn meiner Erfahrung nach führt das Füllen der entsprechenden Dreiecke zu nahtlosen Ergebnissen, wenn die Scheitelpunkte beim Verlassen des Scheitelpunktshaders exakt dieselben Koordinaten haben. Denken Sie daran, dass etwas, das mathematisch korrekt ist, aufgrund von IEEE möglicherweise ein bisschen abweicht. Je mehr Berechnungen Sie mit Ihren Zahlen durchführen, desto ungenauer wird das Ergebnis. Und ja, Matrixmultiplikationen erfordern einige Operationen, die möglicherweise nur in einer Multiplikation und einer Addition beim Erstellen Ihres VBO durchgeführt werden, wodurch genauere Ergebnisse erzielt werden.

Möglicherweise liegt das Problem auch darin, dass Sie ein Spritesheet (auch als Atlas bezeichnet) verwenden und beim Abtasten der Textur die Pixel der angrenzenden Kacheltextur ausgewählt werden. Um dies zu verhindern, müssen Sie in den UV-Mappings einen winzigen Rand erstellen. Wenn Sie also eine 64x64-Kachel haben, sollte Ihre UV-Zuordnung etwas weniger abdecken. Wie viel? Ich glaube, ich habe in meinen Spielen 1 Viertel Pixel an jeder Seite des Rechtecks ​​verwendet. Versetzen Sie also Ihre UV-Werte für x-Komponenten um 1 / (4 * widthOfTheAtlas) und für y-Komponenten um 1 / (4 * heightOfTheAtlas).

Martijn Courteaux
quelle
Danke, aber wie ich bereits bemerkt habe, ist die Geometrie definitiv genau benachbart - tatsächlich ergeben alle Koordinaten exakte Ganzzahlen, so dass es leicht zu sagen ist. Hier werden keine Atlanten verwendet.
AshleysBrain
0

Um dies im 3D-Raum zu lösen, habe ich Textur-Arrays mit Wolfgang Skylers Vorschlag gemischt. Ich habe für jede Kachel (120x120, 128x128) einen 8-Pixel-Rand um meine echte Textur gelegt, den ich für jede Seite mit einem "eingewickelten" Bild oder der Seite, die gerade erweitert wird, füllen konnte. Der Sampler liest diesen Bereich, wenn er das Bild interpoliert.

Jetzt kann der Sampler mit Filterung und Mipmapping problemlos über den gesamten 8-Pixel-Rand hinweg lesen. Um dieses kleine Problem zu lösen (ich sage klein, weil es nur bei wenigen Pixeln auftritt, wenn die Geometrie stark verzerrt oder weit entfernt ist), teile ich die Kacheln in ein Texturarray auf, sodass jede Kachel ihren eigenen Texturraum hat und eine Klemmung für die Kacheln verwenden kann Kanten.

In Ihrem Fall (2D / Flach) würde ich sicherlich eine pixelgenaue Szene rendern und dann den gewünschten Teil des Ergebnisses in das Ansichtsfenster skalieren, wie von Nick Wiggil vorgeschlagen.

Mukunda
quelle