Come si sposta uno sprite in incrementi di sottopixel?


11

I pixel sono attivati ​​o disattivati. La quantità minima che puoi spostare di uno sprite è un singolo pixel. Quindi, come si fa a spostare lo sprite più lentamente di 1 pixel per frame?

Il modo in cui l'ho fatto è stato quello di aggiungere la velocità a una variabile e testare se avesse raggiunto 1 (o -1). In tal caso, sposterei lo sprite e reimposterei la variabile su 0, in questo modo:

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

Non mi è piaciuto questo approccio perché sembra sciocco e il movimento dello sprite sembra molto a scatti. Quindi, in che modo implementeresti il ​​movimento sub-pixel?


L'unica cosa che potresti fare è il filtro bilineare.
AturSams,

Se stai spostando meno di 1 px per fotogramma, come può sembrare a scatti?

Risposte:


17

C'è una serie di opzioni:

  1. Fai come te. Hai già detto che non sembra liscio. Tuttavia, ci sono alcuni difetti con il tuo metodo attuale. Per x, è possibile utilizzare quanto segue:

    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

    questo dovrebbe essere migliore. Ho cambiato le istruzioni if ​​per usare 0,5, poiché quando hai superato 0,5 sei più vicino al valore successivo rispetto al precedente. Ho usato i cicli while per consentire lo spostamento di più di 1 pixel per passo temporale (non è il modo migliore per farlo, ma rende il codice compatto e leggibile). Per gli oggetti che si muovono lentamente, tuttavia, sarà comunque nervoso, poiché non abbiamo affrontato il problema fondamentale dell'allineamento dei pixel.

  2. Avere più grafiche per il tuo sprite e usarne una diversa a seconda dell'offset subpixel. Per eseguire il livellamento dei subpixel solo in x, ad esempio, è possibile creare un'immagine per lo sprite in x+0.5e, se la posizione è compresa tra x+0.25e x+0.75, utilizzare questo sprite anziché l'originale. Se desideri un posizionamento più preciso, basta creare più grafica subpixel. Se lo fai in xe il ytuo numero di rendering può diventare rapidamente un palloncino, poiché il numero si ridimensiona con il quadrato del numero di intervalli: una spaziatura 0.5richiederebbe 4 rendering, 0.25richiederebbe 16.

  3. Sovracampiona. Questo è un modo pigro (e potenzialmente molto costoso) di creare un'immagine con risoluzione subpixel. Essenzialmente, raddoppia (o più) la risoluzione con cui esegui il rendering della scena, quindi ridimensionala in fase di esecuzione. Vorrei raccomandare attenzione qui, poiché le prestazioni possono diminuire rapidamente. Un modo meno aggressivo per farlo sarebbe solo quello di sostituire il tuo sprite e ridimensionarlo in fase di esecuzione.

  4. Come suggerito da Zehelvion , è possibile che la piattaforma in uso lo supporti già. Se ti è permesso specificare coordinate x e y non intere, potrebbero esserci delle opzioni per cambiare il filtro delle texture. Il vicino più vicino è spesso il valore predefinito e questo causerà movimenti "a scatti". Un altro filtraggio (lineare / cubico) si tradurrebbe in un effetto molto più uniforme.

La scelta tra 2. 3. e 4. dipende da come vengono implementate le grafiche. Uno stile di grafica bitmap si adatta molto meglio al pre-rendering, mentre uno stile vettoriale può adattarsi al sovracampionamento degli sprite. Se il sistema lo supporta, 4. potrebbe essere la strada da percorrere.


Perché sostituire la soglia con 0,5 in opt 1? Inoltre non capisco perché hai spostato le dichiarazioni in un ciclo while. Le funzioni di aggiornamento vengono chiamate una volta per tick.
bzzr,

1
Buona domanda: ho chiarito la risposta in base al tuo commento.
Jez,

Il sovracampionamento può essere "pigro", ma in molti casi il costo prestazionale del sovracampionamento 2x o addirittura 4x non sarebbe un problema particolare, soprattutto se permettesse di semplificare altri aspetti del rendering.
supercat

Nei giochi a budget elevato, di solito vedo 3 come un'opzione elencata nelle impostazioni grafiche come anti-aliasing.
Kevin,

1
@Kevin: Probabilmente no. La maggior parte dei giochi usa il multicampionamento , che è leggermente diverso. Altre varianti sono anche comunemente utilizzate, ad esempio con una copertura variabile e campioni di profondità. Il supercampionamento non viene quasi mai eseguito a causa del costo dell'esecuzione dello shader di frammenti.
imallett,

14

Un modo in cui molti giochi old-skool hanno risolto (o nascosto) questo problema è stato quello di animare lo sprite.

Cioè, se il tuo sprite avrebbe spostato meno di un pixel per fotogramma (o, soprattutto, se il rapporto pixel / fotogramma sarebbe stato qualcosa di strano come 2 pixel in 3 fotogrammi), potresti nascondere il jerkiness facendo un n loop di animazione frame che, su quegli n frame, ha finito per spostare lo sprite di alcuni k < n pixel.

Il punto è che, fintanto che lo sprite si sposta sempre in qualche modo su ciascun frame, non ci sarà mai un singolo frame in cui l'intero sprite improvvisamente si "scatti" in avanti.


Non sono riuscito a trovare uno sprite reale di un vecchio videogioco per illustrarlo (anche se penso che alcune delle animazioni di scavo di Lemmings fossero così), ma si scopre che il modello "aliante" di Conway's Game of Life fa un illustrazione molto bella:

inserisci qui la descrizione dell'immagine
Animazione di Kieff / Wikimedia Commons , utilizzata sotto la licenza CC-By-SA 3.0 .

Qui, i piccoli blocchi di pixel neri che strisciano verso il basso e verso destra sono gli alianti. Se guardi attentamente, noterai che prendono quattro fotogrammi di animazione per strisciare un pixel in diagonale, ma poiché si muovono in qualche modo su ciascuno di quei fotogrammi, il movimento non sembra così a scatti (beh, almeno non più a scatti di qualunque cosa guardi quel frame rate, comunque).


2
Questo è un approccio eccellente se la velocità è fissa, o se è inferiore a 1/2 pixel per fotogramma o se l'animazione è abbastanza "grande" e abbastanza veloce da oscurare le variazioni del movimento.
supercat

Questo è un buon approccio, anche se se una telecamera deve seguire lo sprite, incontrerai comunque dei problemi perché non si muoverà abbastanza bene.
Elden Abob

6

La posizione dello sprite deve essere mantenuta come una quantità in virgola mobile e arrotondata a un numero intero appena prima della visualizzazione.

Puoi anche mantenere lo sprite a super risoluzione e sottocampionare lo sprite prima della visualizzazione. Se mantenessi lo sprite alla risoluzione del display 3x, avresti 9 diversi sprite effettivi a seconda della posizione subpixel dello sprite.


3

L'unica vera soluzione qui è utilizzare il filtro bilineare. L'idea è di consentire alla GPU di calcolare il valore di ciascun pixel in base ai quattro pixel di sprite che si sovrappongono con esso. È una tecnica comune ed efficace. Devi semplicemente posizionare lo sprite su una 2d-plain (un cartellone) come trama; quindi utilizzare la GPU per eseguire il rendering di queste pianure. Funziona bene, ma aspettati di ottenere risultati un po 'sfocati e perdere l'aspetto di 8 o 16 bit se lo stavi puntando.

professionisti:

Soluzione hardware già implementata, molto veloce.

contro:

Perdita di fedeltà a 8 bit / 16 bit.


1

Il virgola mobile è il modo normale di farlo, soprattutto se alla fine si esegue il rendering su un target GL in cui l'hardware è abbastanza soddisfatto di un poligono ombreggiato con coordinate float.

Tuttavia c'è un altro modo di fare quello che stai facendo, ma leggermente meno a scatti: punto fisso. Un valore di posizione a 32 bit può rappresentare i valori da 0 a 4.294.967.295 anche se lo schermo ha quasi sicuramente meno di 4k pixel su entrambi gli assi. Quindi metti un "punto binario" fisso (per analogia con il punto decimale) nel mezzo e puoi rappresentare le posizioni dei pixel da 0 a 65536 con altri 16 bit di risoluzione subpixel. Puoi quindi aggiungere numeri normalmente, devi solo ricordarti di convertire spostando a destra di 16 bit ogni volta che converti nello spazio dello schermo o quando hai moltiplicato due numeri insieme.

(Ho scritto un motore 3D in un punto fisso a 16 bit nel giorno in cui avevo un Intel 386 senza unità a virgola mobile. Ha raggiunto la velocità di accecamento di 200 poligoni Phong-shaded per frame.)


1

Oltre alle altre risposte qui puoi usare il dithering in una certa misura. Il dithering è il punto in cui il bordo dei pixel di un oggetto è più chiaro / più scuro per adattarsi allo sfondo, creando un bordo più morbido. Ad esempio, supponiamo che tu abbia un quadrato di 4 pixel che ti approssimo con:

OO
OO

Se spostassi questo mezzo pixel a destra, nulla si sposterebbe davvero. Ma con un po 'di dithering potresti creare un po' l'illusione del movimento. Considera O come nero e o come grigio, quindi potresti fare:

oOo
oOo

In un frame e

 OO
 OO

Nel prossimo. L'attuale "quadrato" con i bordi grigi occuperebbe effettivamente 3 pixel di diametro, ma poiché sono più chiari del nero rispetto al nero, appaiono più piccoli. Questo può essere difficile da codificare, tuttavia, e non sono davvero a conoscenza di nulla che lo faccia ora. A proposito del tempo in cui hanno iniziato a usare il dithering per le cose, le risoluzioni sono diventate rapidamente migliori e non c'era più bisogno se non in cose come la manipolazione delle immagini.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.