Jak poruszać duszka w przyrostach subpikselowych?

11

Piksele są włączone lub wyłączone. Minimalna ilość, jaką możesz przenieść duszka, to pojedynczy piksel. Jak sprawić, by duszek poruszał się wolniej niż 1 piksel na klatkę?

W ten sposób dodałem prędkość do zmiennej i sprawdziłem, czy osiągnęła 1 (lub -1). Gdyby tak było, przesunąłbym duszka i zresetowałbym zmienną do 0, tak jak poniżej:

update(dt):
    temp_dx += speed * dt
    temp_dy += speed * dt

    if (temp_dx > 1)
        move sprite
        reset temp_dx to 0
    if (tempy_dy > 1)
        move sprite
        reset temp_dy to 0

Nie podobało mi się to podejście, ponieważ wydaje się głupie, a ruch duszka wygląda bardzo gwałtownie. Więc w jaki sposób wdrożyłbyś ruch subpikseli?

bzzr
źródło
Jedyne, co możesz zrobić, to filtrowanie dwuliniowe.
AturSams,
Jeśli przesuwasz się mniej niż 1 piksel na klatkę, jak może wyglądać na nieciekawy?

Odpowiedzi:

17

Istnieje wiele opcji:

  1. Rób jak ty. Powiedziałeś już, że nie wygląda gładko. W obecnej metodzie są jednak pewne wady. Dla x, można użyć następujących:

    tempx += speed * dt
    
    while (tempx > 0.5)
      move sprite to x+1
      tempx -= 1
    while (tempx < -0.5)
      move sprite to x-1
      tempx += 1

    powinno być lepiej. Zmieniłem statystyki if, aby używały 0,5, ponieważ gdy przekroczysz 0,5, jesteś bliżej następnej wartości niż poprzedniej. Użyłem pętli while, aby umożliwić ruch o więcej niż 1 piksel na krok czasu (nie jest to najlepszy sposób na zrobienie tego, ale zapewnia zwarty i czytelny kod). W przypadku wolno poruszających się obiektów nadal będzie jednak nerwowy, ponieważ nie zajmowaliśmy się podstawową kwestią wyrównania pikseli.

  2. Posiadaj wiele grafik dla swojego duszka i użyj innej w zależności od przesunięcia subpiksela. Aby wykonać wygładzanie subpikseli tylko na przykład w x, możesz utworzyć grafikę dla swojego duszka w x+0.5, a jeśli pozycja jest pomiędzy x+0.25i x+0.75, użyj tego duszka zamiast oryginału. Jeśli chcesz dokładniejszego pozycjonowania, po prostu utwórz więcej subpikseli. Jeśli to zrobisz, xa yliczba renderingów może szybko się powiększać, ponieważ liczba skaluje się z kwadratem liczby interwałów: odstępy 0.5wymagałyby 4 renderingów, 0.25wymagałyby 16.

  3. Supersample. Jest to leniwy (i potencjalnie bardzo drogi) sposób tworzenia obrazu o rozdzielczości subpikseli. Zasadniczo podwaj (lub więcej) rozdzielczość, przy której renderujesz scenę, a następnie zmniejsz ją w czasie wykonywania. Polecam tutaj opiekę, ponieważ wydajność może szybko spaść. Mniej agresywnym sposobem na zrobienie tego byłoby po prostu próbkowanie twojego duszka i zmniejszenie jego skali w czasie wykonywania.

  4. Jak sugeruje Zehelvion , być może platforma, z której korzystasz, już to obsługuje. Jeśli możesz określić niecałkowite współrzędne x i y, mogą istnieć opcje zmiany filtrowania tekstur. Najbliższy sąsiad jest często domyślny, co spowoduje ruch „gwałtowny”. Inne filtrowanie (liniowe / sześcienne) skutkowałoby znacznie płynniejszym efektem.

Wybór między 2. 3. a 4. zależy od tego, w jaki sposób twoja grafika jest implementowana. Styl grafiki bitmapowej znacznie lepiej nadaje się do renderowania wstępnego, podczas gdy styl wektorowy może pasować do supersamplingu sprite. Jeśli twój system to obsługuje, 4. droga może być dobrym rozwiązaniem.

Jez
źródło
Po co zastępować próg wartością 0,5 w opcji 1? Nie rozumiem również, dlaczego przeniosłeś instrukcje do pętli while. Funkcje aktualizacji są wywoływane raz na tyknięcie.
bzzr
1
Dobre pytanie - wyjaśniłem odpowiedź na podstawie twojego komentarza.
Jez
Supersampling może być „leniwy”, ale w wielu przypadkach koszt wydajności 2x lub nawet 4x nadpróbkowania nie stanowiłby szczególnego problemu, szczególnie jeśli pozwoliłby na uproszczenie innych aspektów renderowania.
supercat
W grach o dużym budżecie zazwyczaj widzę 3 jako opcję wymienioną w ustawieniach grafiki jako wygładzanie.
Kevin
1
@Kevin: Prawdopodobnie nie. Większość gier używa multisampling , co jest nieco inne. Powszechnie stosowane są również inne warianty, np. O różnym zasięgu i próbkach głębokości. Supersampling prawie nigdy nie jest wykonywany ze względu na koszt wykonania modułu cieniującego fragmenty.
imallett,
14

Jednym ze sposobów rozwiązania (lub ukrywania) wielu gier old-skool był problem z animacją duszka.

Oznacza to, że jeśli twój duszek miałby poruszać się mniej niż jeden piksel na klatkę (lub, szczególnie, jeśli stosunek pikseli / klatek miałby być dziwny jak 2 piksele w 3 klatkach), możesz ukryć szarpnięcie, robiąc n pętla animacji klatek, która w ciągu tych n klatek spowodowała przesunięcie duszka o kilka k < n pikseli.

Chodzi o to, że dopóki duszek zawsze porusza się w jakiś sposób na każdej klatce, nigdy nie będzie żadnej pojedynczej klatki, w której cały duszek nagle „szarpnie” do przodu.


Nie mogłem znaleźć prawdziwego duszka ze starej gry wideo, aby to zilustrować (chociaż myślę, że np. Niektóre animacje kopania z Lemmings były takie), ale okazuje się, że wzór „szybowca” z Gry Życia Conwaya tworzy bardzo ładna ilustracja:

wprowadź opis zdjęcia tutaj
Animacja przez kijowski / Wikimedia Commons , używane pod CC-BY-SA 3.0 licencji.

Tutaj małe bloki czarnych pikseli pełzające w dół i w prawo to szybowce. Jeśli przyjrzysz się uważnie, zauważysz, że biorą cztery klatki animacji, aby czołgać się jeden piksel po przekątnej, ale ponieważ poruszają się w jakiś sposób na każdej z tych klatek, ruch nie wygląda tak gwałtownie (cóż, przynajmniej nie więcej szarpane niż cokolwiek wygląda przy tej liczbie klatek).

Ilmari Karonen
źródło
2
Jest to doskonałe podejście, jeśli prędkość jest ustalona lub jest mniejsza niż 1/2 piksela na klatkę, lub jeśli animacja jest „wystarczająco duża” i wystarczająco szybka, aby ukryć zmiany w ruchu.
supercat
Jest to dobre podejście, chociaż jeśli kamera musi podążać za duchem, nadal będziesz mieć problemy, ponieważ nie będzie poruszał się dość płynnie.
Elden Abob,
6

Pozycja duszka powinna być zachowana jako liczba zmiennoprzecinkowa i zaokrąglona do liczby całkowitej tylko przed wyświetleniem.

Możesz również utrzymywać duszka w super rozdzielczości i próbkować duszka przed wyświetleniem. Jeśli utrzymałeś duszkę na 3-krotnej rozdzielczości wyświetlania, miałbyś 9 różnych rzeczywistych duszków, w zależności od ich subpikseli.

ddyer
źródło
3

Jedynym realnym rozwiązaniem jest tutaj zastosowanie filtrowania dwuliniowego. Chodzi o to, aby procesor graficzny obliczał wartość każdego piksela na podstawie czterech nakładających się na siebie pikseli sprite. To popularna i skuteczna technika. Musisz po prostu umieścić duszka na zwykłej powierzchni 2d (billboard) jako teksturę; następnie użyj GPU do renderowania tych równin. Działa to dobrze, ale spodziewaj się, że uzyskasz nieco rozmyte wyniki i stracisz 8-bitowy lub 16-bitowy wygląd, jeśli celujesz.

plusy:

Już wdrożone, bardzo szybkie, sprzętowe rozwiązanie.

Cons:

Utrata 8-bitowej / 16-bitowej wierności.

AturSams
źródło
1

Punkt zmiennoprzecinkowy jest normalnym sposobem na zrobienie tego, szczególnie jeśli ostatecznie renderujesz do celu GL, gdzie sprzęt jest całkiem zadowolony z zacienionego wielokąta ze współrzędnymi zmiennoprzecinkowymi.

Istnieje jednak inny sposób robienia tego, co obecnie robisz, ale nieco mniej szarpiąco: ustalony punkt. 32-bitowa wartość pozycji może reprezentować wartości od 0 do 4 294 967 295, nawet jeśli ekran prawie na pewno ma mniej niż 4k pikseli wzdłuż każdej osi. Tak więc umieść stały „punkt binarny” (analogicznie do punktu dziesiętnego) na środku i możesz reprezentować pozycje pikseli od 0-65536 z kolejnymi 16 bitami rozdzielczości subpikseli. Następnie możesz dodawać liczby w normalny sposób, musisz tylko pamiętać, aby przekonwertować, przesuwając 16 bitów w prawo za każdym razem, gdy zamieniasz przestrzeń ekranu lub gdy pomnożysz dwie liczby razem.

(Napisałem silnik 3D w 16-bitowym punkcie stałym w czasach, gdy miałem Intel 386 bez jednostki zmiennoprzecinkowej. Osiągnął oślepiającą prędkość 200 wielokątów cieniowanych Phong na ramkę.)

pjc50
źródło
1

Oprócz innych odpowiedzi tutaj możesz w pewnym stopniu użyć ditheringu. Dithering polega na tym, że krawędź pikseli obiektu jest jaśniejsza / ciemniejsza, aby pasowała do tła, dzięki czemu krawędź jest bardziej miękka. Załóżmy na przykład, że masz kwadrat 4 piksele, a ja przybliżę go za pomocą:

OO
OO

Gdybyś przesunął ten 1/2 piksela w prawo, tak naprawdę nic by się nie poruszyło. Ale z pewnym ditheringiem możesz trochę złudzić ruch. Uważaj O za czarne, a o za szare, więc możesz zrobić:

oOo
oOo

W jednej ramce i

 OO
 OO

W następnym. Rzeczywisty „kwadrat” z szarymi krawędziami zajmowałby w rzeczywistości 3 piksele, ale ponieważ są jaśniejsze niż szary, wydają się mniejsze. Może to być trudne do zakodowania i nie jestem tak naprawdę świadomy niczego, co teraz to robi. Mniej więcej od czasu, kiedy zaczęli używać ditheringu do rzeczy, rozdzielczości szybko stały się lepsze i nie było takiej potrzeby, z wyjątkiem takich rzeczy jak manipulacja obrazem.

Serpardum
źródło