Mam silnik do gry 2D, który rysuje mapy warstwowe, rysując kafelki z obrazu zestawu klocków. Ponieważ domyślnie OpenGL może owinąć tylko całą teksturę ( GL_REPEAT
), a nie tylko jej część, każda płytka jest podzielona na osobną teksturę. Następnie regiony tego samego kafelka są renderowane obok siebie. Oto jak to wygląda, gdy działa zgodnie z przeznaczeniem:
Jednak po wprowadzeniu skalowania ułamkowego pojawiają się szwy:
Dlaczego to się dzieje? Myślałem, że było to spowodowane liniowym filtrowaniem mieszającym granice quadów, ale nadal dzieje się tak z filtrowaniem punktowym. Jedynym rozwiązaniem, jakie do tej pory znalazłem, jest zapewnienie, że całe pozycjonowanie i skalowanie odbywa się tylko przy wartościach całkowitych, i stosowanie filtrowania punktów. Może to pogorszyć jakość wizualną gry (szczególnie, że pozycjonowanie subpikseli nie działa, więc ruch nie jest tak płynny).
Rzeczy, które próbowałem / rozważałem:
- antyaliasing zmniejsza szwy, ale nie całkowicie je eliminuje
- wyłączenie mipmapowania nie ma żadnego efektu
- renderuj każdy kafelek osobno i wyciągnij krawędzie o 1px - ale jest to de-optymalizacja, ponieważ nie może już renderować obszarów kafelków za jednym razem i tworzy inne artefakty wzdłuż krawędzi obszarów przezroczystości
- dodaj obramowanie 1px wokół obrazów źródłowych i powtórz ostatnie piksele - ale wtedy nie są one już mocą dwóch, powodując problemy z kompatybilnością z systemami bez obsługi NPOT
- napisanie niestandardowego modułu cieniującego do obsługi obrazów sąsiadujących - ale co byś zrobił inaczej?
GL_REPEAT
powinien chwytać piksel z przeciwnej strony obrazu na granicach, a nie wybierać przezroczystości. - geometria jest dokładnie przylegająca, nie ma błędów zaokrąglania zmiennoprzecinkowego.
- jeśli moduł cieniujący fragmenty jest mocno zakodowany, aby zwrócić ten sam kolor, szwy znikają .
- jeśli
GL_CLAMP
zamiast tekstury ustawionoGL_REPEAT
, szwy znikają (chociaż renderowanie jest nieprawidłowe). - jeśli tekstury są ustawione na
GL_MIRRORED_REPEAT
, szwy znikają (chociaż renderowanie jest znowu nieprawidłowe). - jeśli sprawię, że tło będzie czerwone, szwy nadal będą białe. Sugeruje to, że próbuje skądś nieprzezroczystą biel, a nie przezroczystość.
Więc szwy pojawiają się tylko po GL_REPEAT
ustawieniu. Tylko z jakiegoś powodu w tym trybie na krawędziach geometrii występuje upust / wyciek / przezroczystość. Jak to możliwe? Cała tekstura jest nieprzezroczysta.
GL_NEAREST
próbkowaniem wR
kierunku współrzędnych również działają tak samo dobrze, jak tekstury tablic dla większości rzeczy w tym scenariuszu. Mipmapping nie zadziała, ale sądząc po twojej aplikacji prawdopodobnie i tak nie potrzebujesz.Odpowiedzi:
Szwy zachowują się poprawnie podczas próbkowania za pomocą GL_REPEAT. Rozważ następujący kafelek:
Próbkowanie na krawędziach płytki przy użyciu wartości ułamkowych miesza kolory z krawędzi i przeciwległej krawędzi, co powoduje nieprawidłowe kolory: lewa krawędź powinna być zielona, ale jest mieszanką zieleni i beżu. Prawa krawędź powinna być beżowa, ale ma również mieszany kolor. Szczególnie beżowe linie na zielonym tle są bardzo widoczne, ale jeśli przyjrzysz się uważnie, zobaczysz zielone krwawiące do krawędzi:
MSAA działa poprzez pobieranie większej liczby próbek wokół krawędzi wielokąta. Próbki pobrane z lewej lub prawej krawędzi będą „zielone” (ponownie, biorąc pod uwagę lewą krawędź pierwszego zdjęcia), a tylko jedna próbka będzie „na” krawędzi, częściowo próbkując z obszaru „beżowego”. Po zmieszaniu próbek efekt zostanie zmniejszony.
Możliwe rozwiązania:
W każdym razie należy przełączyć na,
GL_CLAMP
aby zapobiec krwawieniu z piksela po przeciwnej stronie tekstury. Masz trzy opcje:Włącz wygładzanie, aby nieco wygładzić przejście między płytkami.
Ustaw filtrowanie tekstur na
GL_NEAREST
. Daje to wszystkim pikselom ostre krawędzie, więc krawędzie wielokąta / duszka stają się nierozróżnialne, ale oczywiście zmienia nieco styl gry.Dodaj już omawianą ramkę 1px, po prostu upewnij się, że ramka ma kolor sąsiedniej płytki (a nie kolor przeciwległej krawędzi).
GL_LINEAR
podobne filtrowanie przez krawędzie wielokątów / duszków.źródło
Rozważ wyświetlanie pojedynczego, zwykłego quada z teksturą w OpenGL. Czy są jakieś szwy, w dowolnej skali? Nie, nigdy. Twoim celem jest ścisłe upakowanie wszystkich kafelków sceny na jednej teksturze, a następnie przesłanie ich na ekran. EDYCJA Aby wyjaśnić to dalej: Jeśli masz dyskretne wierzchołki ograniczające na każdym kwadracie płytek, w większości przypadków będziesz mieć pozornie nieuzasadnione szwy. Tak działa sprzęt GPU ... błędy zmiennoprzecinkowe tworzą luki w oparciu o aktualną perspektywę ... błędy, których można uniknąć w ujednoliconym kolektorze, ponieważ jeśli dwie ściany przylegają do tego samego kolektora(submesh), sprzęt renderuje je bez szwów, gwarantowane. Widziałeś to w niezliczonych grach i aplikacjach. Aby uniknąć tego raz na zawsze, musisz mieć jedną ciasno upakowaną teksturę na jednym półpełniku bez podwójnych wierzchołków. Jest to problem, który pojawia się wielokrotnie na tej stronie i gdzie indziej: jeśli nie scalisz wierzchołków narożników swoich płytek (co 4 płytki dzielą wierzchołek narożnika), oczekuj szwów.
(Weź pod uwagę, że możesz nie potrzebować nawet wierzchołków, z wyjątkiem czterech rogów całej mapy ... zależy od twojego podejścia do cieniowania).
Aby rozwiązać: renderuj (w proporcji 1: 1) wszystkie tekstury kafelków do FBO / RBO bez przerw, a następnie wyślij to FBO do domyślnego bufora ramki (ekranu). Ponieważ samo FBO jest w zasadzie pojedynczą teksturą, nie możesz skończyć z lukami w skalowaniu. Wszystkie granice tekstu, które nie spadają na granicę pikseli ekranu, zostaną wymieszane, jeśli używasz GL_LINEAR .... dokładnie to, czego chcesz. To jest standardowe podejście.
Otwiera to również wiele różnych dróg do skalowania:
źródło
Jeśli obrazy powinny wyglądać jak jedna powierzchnia, renderuj je jako jedną teksturę powierzchni (skala 1x) na jednej siatce / płaszczyźnie 3D. Jeśli renderujesz każdy jako osobny obiekt 3D, zawsze będzie miał szwy z powodu błędów zaokrąglania.
Odpowiedź @ Nick-Wiggill jest poprawna, myślę, że źle ją zrozumiałeś.
źródło
2 możliwości, które warto wypróbować:
Użyj
GL_NEAREST
zamiastGL_LINEAR
do filtrowania tekstur. (Jak zauważył Andon M. Coleman)GL_LINEAR
(w efekcie) rozmyje obraz bardzo nieznacznie (interpolując między najbliższymi pikselami), co pozwala na płynne przejście kolorów z jednego piksela na drugi. Może to pomóc w lepszym wyglądzie tekstur w wielu przypadkach, ponieważ zapobiega powstawaniu blokujących pikseli w teksturach. Powoduje to również, że odrobina brązu z jednego piksela jednego kafelka może zostać zamazana na sąsiedni zielony piksel sąsiedniej płytki.GL_NEAREST
po prostu znajduje najbliższy piksel i używa tego koloru. Bez interpolacji. W rezultacie jest to trochę szybsze.Zmniejsz nieco współrzędne tekstury dla każdej płytki.
Coś w rodzaju dodania dodatkowego piksela dookoła każdego kafelka jako bufora, ale zamiast rozszerzać kafelek na obrazie, kafelek zmniejsza się w oprogramowaniu. Płytki 14 x 14 pikseli podczas renderowania zamiast płytek 16 x 16 pikseli.
Być może uda ci się uciec z płytkami 14,5 x 14,5 bez mieszania zbyt dużej ilości brązu.
--edytować--
Jak pokazano na powyższym obrazku, nadal możesz łatwo użyć mocy dwóch tekstur. Możesz więc obsługiwać sprzęt, który nie obsługuje tekstur NPOT. Jakie zmiany mają współrzędne tekstury, zamiast przechodzić od
(0.0f, 0.0f)
do(1.0f, 1.0f)
Przechodziłbyś od(0.03125f, 0.03125f)
do(0.96875f, 0.96875f)
.Powoduje to nieznaczne umieszczenie współrzędnych tekstowych w kafelku, co zmniejsza efektywną rozdzielczość w grze (choć nie na sprzęcie, dzięki czemu nadal masz potęgę dwóch tekstur), jednak powinien mieć taki sam efekt renderowania, jak rozszerzanie tekstury.
źródło
Oto, co moim zdaniem może się zdarzyć: tam, gdzie zderzają się dwa kafelki, składnik x ich krawędzi powinien być równy. Jeśli to nie jest prawda, mogą zostać zaokrąglone w przeciwnym kierunku. Upewnij się więc, że mają dokładnie taką samą wartość x. Jak zapewnić, że tak się stanie? Możesz spróbować, powiedzmy, pomnożyć przez 10, zaokrąglić, a następnie podzielić przez 10 (zrób to tylko na CPU, zanim twoje wierzchołki wejdą w moduł cieniujący wierzchołki). To powinno dać poprawne wyniki. Nie należy również rysować kafelek po kafelku za pomocą macierzy transformacji, ale należy umieścić je w partii VBO, aby upewnić się, że nieprawidłowe wyniki nie pochodzą z systemu zmiennoprzecinkowego IEEE w połączeniu z mnożeniem macierzy.
Dlaczego to sugeruję? Ponieważ z mojego doświadczenia wynika, że jeśli wierzchołki mają dokładnie takie same współrzędne, gdy wychodzą z modułu cieniującego wierzchołki, wypełnienie odpowiednich trójkątów da płynne rezultaty. Należy pamiętać, że coś, co jest matematycznie poprawne, może być nieco nieaktualne z powodu IEEE. Im więcej obliczeń wykonasz na liczbach, tym mniej dokładny będzie wynik. I tak, mnożenie macierzy wymaga dość pewnych operacji, które można wykonać tylko poprzez pomnożenie i dodanie podczas tworzenia VBO, co da dokładniejsze wyniki.
Problemem może być również to, że używasz arkusza sprite (zwanego także atlasem) i że podczas próbkowania tekstury piksele sąsiedniej tekstury kafelków są wybierane. Aby upewnić się, że tak się nie stanie, należy utworzyć niewielką ramkę w mapowaniach UV. Tak więc, jeśli masz kafelek 64x64, twoje mapowanie UV powinno obejmować nieco mniej. Ile? Myślę, że użyłem w swoich grach 1 ćwierć piksela po każdej stronie prostokąta. Więc przesunąć UV o 1 / (4 * widthOfTheAtlas) dla x komponentów i 1 / (4 * heightOfTheAtlas) dla y komponentów.
źródło
Aby rozwiązać ten problem w przestrzeni 3D, wymieszałem tablice tekstur z sugestią Wolfganga Skylera. Umieszczam 8-pikselową ramkę wokół mojej faktycznej tekstury dla każdego kafelka (120 x 120, 128 x 128 łącznie), który dla każdej strony, który mógłbym wypełnić „owiniętym” obrazem lub stroną, która właśnie się wysuwa. Próbnik odczytuje ten obszar podczas interpolacji obrazu.
Teraz, dzięki filtrowaniu i mipmapowaniu, próbnik nadal może łatwo odczytać poza całą granicę 8 pikseli. Aby złapać ten mały problem (mówię mały, ponieważ zdarza się to tylko na kilku pikselach, gdy geometria jest naprawdę pochylona lub bardzo daleko), podzielę płytki na tablicę tekstur, aby każda płytka miała własną przestrzeń tekstury i mogła użyć zaciskania na krawędzie
W twoim przypadku (2D / płaski) z pewnością wybrałbym renderowanie pikselowej sceny idealnej, a następnie skalowanie pożądanej części wyniku do rzutni, jak zasugerował Nick Wiggil.
źródło