Tekstury arkuszy sprite zbierają krawędzie sąsiadujących tekstur

10

Mam niestandardową procedurę sprite (openGL 2.0), która wykorzystuje prosty arkusz sprite (moje tekstury są ułożone poziomo obok siebie).

Oto przykładowy arkusz testowy z 2 prostymi teksturami:

wprowadź opis zdjęcia tutaj

Teraz, gdy robię mój obiekt sprite openGL, określam całkowitą liczbę ramek w jego atlasie, a podczas rysowania określ, którą ramkę chcę narysować.

Następnie sprawdza, skąd pobrać teksturę:

Dzielenie wymaganego numeru klatki przez całkowitą liczbę klatek (aby uzyskać lewą współrzędną)

A następnie nurkowanie 1 o całkowitą liczbę klatek i dodawanie wyniku do obliczonej powyżej współrzędnej lewej ręki.

To wydaje się działać, ale czasami mam problemy. Powiedzmy na przykład, że chcę narysować X poniżej i otrzymam ...........

wprowadź opis zdjęcia tutaj

Słyszałem o umieszczeniu „wypełnienia” 1 piksela między każdą teksturą, ale czy ktoś mógłby dokładnie wyjaśnić, jak to działa? Mam na myśli, że jeśli to zrobię, z pewnością odrzuci obliczenia dotyczące uzyskania tekstury.

Jeśli po prostu dołączę wypełnienie do wybranej tekstury (więc duszek zostanie narysowany z pustą ramką), to na pewno spowoduje to problem z wykrywaniem kolizji? (tzn. duszki mogą wydawać się kolidować podczas używania obwiedni, gdy kolidują przezroczyste części).

Byłbym wdzięczny, gdyby ktoś mógł wyjaśnić.

BungleBonce
źródło
Używasz GL_NEARESTlub GL_LINEARdo renderowania tekstury?
MichaelHouse
Używam GL_Linear @ Byte56
BungleBonce,

Odpowiedzi:

15

Problem z użyciem atlasów tekstur i przeciekania sąsiadujących tekstur ma związek ze sposobem liniowego filtrowania tekstur.

W przypadku dowolnego punktu tekstury, który nie jest próbkowany dokładnie w środku teksla, próbkowanie liniowe spróbuje pobrać 4 sąsiadujące tekseli i obliczyć wartość w zadanym miejscu jako średnią ważoną (na podstawie odległości od punktu próbkowania) średnią wszystkich 4 próbki.

Oto ładna wizualizacja problemu:

  

Ponieważ nie możesz użyć czegoś takiego GL_CLAMP_TO_EDGE w atlasie tekstur, należy utworzyć tekstury graniczne wokół krawędzi każdej tekstury. Te tekstury graniczne zapobiegną zmianie sąsiednich próbek z zupełnie różnych tekstur w atlasie poprzez ważoną interpolację wyjaśnioną powyżej.

Pamiętaj, że podczas korzystania z filtrowania anizotropowego może być konieczne zwiększenie szerokości ramki. Wynika to z faktu, że filtrowanie anizotropowe zwiększy wielkość sąsiedztwa próbki pod ekstremalnymi kątami.


Aby zilustrować, co mam na myśli, używając obramowania wokół krawędzi każdej tekstury, rozważ różne tryby zawijania dostępne w OpenGL. Zwróć szczególną uwagę na CLAMP TO EDGE.

  http://lucera-project.com/blog/wp-content/uploads/2010/06/wrap.png

Mimo że istnieje tryb o nazwie „Clamp to Border”, tak naprawdę nie jesteśmy tym zainteresowani. Ten tryb pozwala zdefiniować pojedynczy kolor, który będzie używany jako ramka wokół tekstury dla dowolnych współrzędnych tekstury, które nie mieszczą się w znormalizowanym [0.0 -1.0] zakres.

Chcemy odtworzyć zachowanie CLAMP_TO_EDGE, w którym dowolna współrzędna tekstury poza odpowiednim zakresem dla (pod-) tekstury otrzymuje wartość ostatniego środka texel w kierunku, w którym była poza granicami. Ponieważ masz prawie całkowitą kontrolę nad współrzędne tekstury w systemie atlasu, jedynym scenariuszem, w którym (skuteczne) współrzędne tekstury mogą odnosić się do lokalizacji poza teksturą, jest średni ważony etap filtrowania tekstury.

Wiemy to GL_LINEAR będą próbkować 4 najbliższych sąsiadów, jak pokazano na powyższym schemacie, więc potrzebujemy tylko granicy 1-texel. Jeśli korzystasz z filtrowania anizotropowego, możesz potrzebować szerszej ramki tekstowej, ponieważ zwiększa ona wielkość sąsiedztwa próbki w określonych warunkach.

Oto przykład tekstury, która lepiej ilustruje ramkę, chociaż dla twoich celów możesz zwiększyć szerokość 1 teksela lub 2 tekseli.

  

(UWAGA: obramowanie, o którym mówię, nie jest czarne wokół wszystkich czterech krawędzi obrazu, ale obszar, w którym wzór szachownicy przestaje się regularnie powtarzać)

Jeśli się zastanawiasz, oto dlaczego ciągle wspominam o filtrowaniu anizotropowym. Zmienia kształt sąsiedztwa próbki na podstawie kąta i może powodować użycie więcej niż 4 tekstur do filtrowania:

  http://www.arcsynthesis.org/gltut/Texturing/ParallelogramDiag.svg

Im większy stopień anizotropii użyjesz, tym większe prawdopodobieństwo, że będziesz mieć do czynienia z okolicami próbnymi zawierającymi więcej niż 4 teksle. Obramowanie 2 tekstów powinno być odpowiednie w większości sytuacji filtrowania anizotropowego.


Last but not least, oto jak zbudowany jest spakowany atlas tekstur, który replikuje GL_CLAMP_TO_EDGEzachowanie w obecności GL_LINEARfiltra tekstury:

( Odejmij 1 od X i Y we czarnych współrzędnych, nie poprawiłem odczytu obrazu przed wysłaniem. )   

Ze względu na przechowywanie granic, przechowywanie 4 256 x 256 tekstur w tym atlasie wymaga tekstury o wymiarach 516 x 516. Ramki są oznaczone kolorami w oparciu o sposób wypełnienia ich danymi tekstowymi podczas tworzenia atlasu:

  • Czerwony = Zamień na tekst bezpośrednio poniżej
  • Żółty = Zamień na texel bezpośrednio powyżej
  • Zielony = Zastąp tekstem bezpośrednio po lewej stronie
  • Niebieski = Zastąp tekstem bezpośrednio po prawej stronie

Skutecznie w tym upakowanym przykładzie każda tekstura w atlasie wykorzystuje region atlasu o wymiarach 258 x 258, ale wygenerowane zostaną współrzędne tekstury odwzorowane na widoczny region 256 x 256. Tekstury graniczne są zawsze używane tylko wtedy, gdy filtrowanie tekstur odbywa się na krawędziach tekstur w atlasie, a sposób, w jaki zostały zaprojektowane, naśladuje GL_CLAMP_TO_EDGEzachowanie.

Jeśli zastanawiasz się, możesz zaimplementować inne typy trybów zawijania, stosując podobne podejście - GL_REPEATmożna je zrealizować, wymieniając tekstury lewej / prawej i górnej / dolnej granicy w atlasie tekstur oraz odrobinę sprytnej matematyki współrzędnych tekstury w moduł cieniujący. To jest trochę bardziej skomplikowane, więc na razie się nie martw. Ponieważ masz do czynienia tylko z arkuszami sprite, ogranicz się do GL_CLAMP_TO_EDGE:)

Andon M. Coleman
źródło
Dzięki @AndonMColeman, czy mógłbyś pokazać ramkę na diagramie, ponieważ nadal nie jestem pewien, czy rozumiem, jak to działa - ponownie, czy to będzie oznaczać, że po nałożeniu tekstury na mój obiekt będzie zawierać ramkę? Chcę, aby faktyczna tekstura poszła prosto na krawędzie mojego quada - przepraszam, jeśli nie rozumiem tego poprawnie - doceniłbym więcej szczegółów - dzięki
BungleBonce
@ user22241 Z pewnością sposób, w jaki działa ta ramka, polega na powielaniu tekstu na krawędzi tekstury.
Andon M. Coleman,
@ user22241: Nie, ta granica nie będzie widoczna w normalnych okolicznościach. Zapakowane tekstury będą traktowane jako posiadające takie same wymiary do obliczania współrzędnych tekstury, po prostu zastosujesz do nich przesunięcie, aby pominąć granicę. Cały punkt obramowania ma na celu zapobieganie nadmiernemu sięganiu i próbkowaniu tekstur tekstur należących do oddzielnych obrazów w arkuszu sprite. Jeśli użyjesz próbkowania od najbliższego sąsiada, nie będzie to konieczne, ale otrzymasz nieprzyjemne aliasy.
Andon M. Coleman,
Bardzo szczegółowa odpowiedź - dzięki! Jeszcze jedna rzecz, gdybym mógł. Jak opracowałbym „przesunięcie”, aby dodać do moich współrzędnych tekstury? Czy mam rację mówiąc, że będzie to po prostu 1 / widthOfTexture?
BungleBonce,
@ user22241: Tak, początek obrazu tekstury to + 1 / widthOfTexture w kierunku X i + 1 / heightOfTexture w kierunku Y. Będziesz mieć obramowanie wokół każdej tekstury. Zanim chcesz obliczyć współrzędne tekstury dla 3. poziomej tekstury, upakowane miejsce dla tej tekstury to w rzeczywistości +5 tekstur (+2 tekstur dla granicy pierwszej tekstury, +2 dla granicy drugiej tekstury i +1 na początek tej tekstury) oprócz szerokości pozostałych 2 tekstur. Pisanie go wydaje się skomplikowane; W razie potrzeby mogę narysować schemat :)
Andon M. Coleman