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:
Sobald Sie die fraktionierte Skalierung einführen, werden jedoch folgende Nähte angezeigt:
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_REPEAT
sollte 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_CLAMP
anstatt gesetzt sindGL_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_REPEAT
eingestellt 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.
GL_NEAREST
Abtastung inR
Koordinatenrichtung 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.Antworten:
Die Nähte sind korrekt für die Probenahme mit GL_REPEAT. Betrachten Sie die folgende Kachel:
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:
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_CLAMP
um ein Ausbluten des Pixels auf der gegenüberliegenden Seite der Textur zu verhindern. Dann haben Sie drei Möglichkeiten:Aktivieren Sie das Anti-Aliasing, um den Übergang zwischen den Kacheln etwas zu glätten.
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.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).
GL_LINEAR
ähnliche Filterung über die Kanten von Polygonen / Sprites ermöglicht.quelle
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:
quelle
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.
quelle
2 Möglichkeiten, die einen Versuch wert sein könnten:
Verwenden Sie
GL_NEAREST
anstelle vonGL_LINEAR
für die Texturfilterung. (Wie von Andon M. Coleman hervorgehoben)GL_LINEAR
Verwischt 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_NEAREST
findet nur das nächste Pixel und verwendet diese Farbe. Keine Interpolation. Infolgedessen geht es eigentlich etwas schneller zu.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--
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.
quelle
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).
quelle
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.
quelle