Rendering seamless tilemap (immagini adiacenti senza bordi)


18

Ho un motore di gioco 2D che disegna tessere disegnando tessere da un'immagine di tessera. Perché per impostazione predefinita OpenGL può solo avvolgere l'intera trama ( GL_REPEAT) e non solo una parte di essa, ogni riquadro è suddiviso in una trama separata. Quindi le regioni della stessa tessera vengono rese adiacenti l'una all'altra. Ecco come appare quando funziona come previsto:

Tilemap senza cuciture

Tuttavia, non appena si introduce il ridimensionamento frazionario, appaiono le cuciture:

Tilemap con cuciture

Perché succede? Ho pensato che fosse dovuto al filtro lineare che fonde i bordi dei quad, ma succede ancora con il filtro punti. L'unica soluzione che ho trovato finora è garantire che tutto il posizionamento e il ridimensionamento avvengano solo a valori interi e utilizzare il filtro punti. Ciò può degradare la qualità visiva del gioco (in particolare il posizionamento dei pixel secondari non funziona più, quindi il movimento non è così fluido).

Cose che ho provato / considerato:

  • l'antialiasing riduce, ma non elimina del tutto, le cuciture
  • la disattivazione del mipmapping non ha alcun effetto
  • renderizzare ciascuna piastrella singolarmente ed estrudere i bordi di 1px - ma questa è una de-ottimizzazione, poiché non può più eseguire il rendering di regioni di tessere in una volta sola e crea altri artefatti lungo i bordi di aree di trasparenza
  • aggiungi un bordo 1px attorno alle immagini sorgente e ripeti gli ultimi pixel, ma non sono più power-of-two, causando problemi di compatibilità con i sistemi senza supporto NPOT
  • scrivere uno shader personalizzato per gestire le immagini piastrellate, ma cosa faresti in modo diverso? GL_REPEATdovrebbe afferrare il pixel dal lato opposto dell'immagine ai bordi e non scegliere la trasparenza.
  • la geometria è esattamente adiacente, non ci sono errori di arrotondamento in virgola mobile.
  • se lo shader di frammenti è codificato per restituire lo stesso colore, le cuciture scompaiono .
  • se le trame sono impostate su GL_CLAMPinvece di GL_REPEAT, le cuciture scompaiono (sebbene il rendering sia errato).
  • se le trame sono impostate su GL_MIRRORED_REPEAT, le cuciture scompaiono (anche se il rendering è di nuovo sbagliato).
  • se rendo rosso lo sfondo, le cuciture sono ancora bianche. Ciò suggerisce che sta campionando il bianco opaco da qualche parte piuttosto che la trasparenza.

Quindi le cuciture appaiono solo quando GL_REPEATè impostato. Per qualche motivo solo in questa modalità, ai bordi della geometria c'è un po 'di spurgo / perdita / trasparenza. Come può essere? L'intera trama è opaca.


Potresti provare a impostare il campionatore di trame per bloccare o regolare il colore del bordo, poiché sospetto che possa essere correlato a quello. Inoltre, GL_Repeat avvolge semplicemente le coordinate UV, quindi sono personalmente un po 'confuso quando dici che può solo ripetere l'intera trama, piuttosto che una parte di essa.
Evan,

1
È possibile che tu stia introducendo in qualche modo delle crepe nella tua maglia? Puoi provarlo impostando lo shader per restituire solo un colore solido anziché campionare una trama. Se vedi ancora delle crepe in quel punto, sono nella geometria. Il fatto che l'antialiasing riduca le cuciture suggerisce che questo potrebbe essere il problema, poiché l'antialiasing non dovrebbe influire sul campionamento delle texture.
Nathan Reed,

È possibile implementare la ripetizione di singole tessere in un atlante di trama. Tuttavia, è necessario eseguire alcuni calcoli di coordinazione delle trame extra e inserire i margini dei bordi attorno a ogni riquadro. Questo articolo spiega molto di questo (anche se principalmente con un focus sulla fastidiosa convenzione delle coordinate delle texture di D3D9). In alternativa, se l'implementazione è abbastanza nuova, puoi usare le trame dell'array per la tua mappa delle piastrelle; supponendo che ogni tessera abbia le stesse dimensioni, funzionerà alla grande e non richiederà alcun calcolo aggiuntivo delle coordinate.
Andon M. Coleman,

Anche le trame 3D con GL_NEARESTcampionamento nella Rdirezione delle coordinate funzionano allo stesso modo delle trame array per la maggior parte delle cose in questo scenario. Il mipmapping non funzionerà, ma a giudicare dalla tua applicazione probabilmente non avrai bisogno di mipmap.
Andon M. Coleman,

1
In che modo il rendering è "sbagliato", quando impostato su CLAMP?
Martijn Courteaux,

Risposte:


1

Le giunture hanno un comportamento corretto per il campionamento con GL_REPEAT. Considera la seguente tessera:

piastrella di bordo

Il campionamento sui bordi della piastrella usando valori frazionari mescola i colori del bordo e del bordo opposto, dando luogo a colori sbagliati: il bordo sinistro dovrebbe essere verde, ma è un mix tra verde e beige. Il bordo destro dovrebbe essere beige, ma è anche un colore misto. Soprattutto le linee beige sullo sfondo verde sono molto visibili, ma se guardi da vicino puoi vedere il sanguinamento verde sul bordo:

piastrella in scala

l'antialiasing riduce, ma non elimina del tutto, le cuciture

MSAA funziona prendendo più campioni attorno ai bordi del poligono. I campioni prelevati a sinistra oa destra del bordo saranno "verdi" (di nuovo, considerando il bordo sinistro della prima immagine), e solo un campione sarà "sul" bordo, campionando parzialmente dall'area "beige". Quando i campioni vengono miscelati, l'effetto verrà ridotto.

Possibili soluzioni:

In ogni caso dovresti passare a GL_CLAMPper evitare il sanguinamento dal pixel sul lato opposto della trama. Quindi hai tre opzioni:

  1. Abilita l'antialiasing per facilitare un po 'la transizione tra i riquadri.

  2. Impostare il filtro texture su GL_NEAREST. Questo dà a tutti i pixel bordi duri, quindi i bordi poligono / sprite diventano indistinguibili, ma ovviamente cambia lo stile del gioco un po '.

  3. Aggiungi il bordo 1px già discusso, assicurati solo che il bordo abbia il colore della tessera adiacente (e non il colore del bordo opposto).

    • Questo potrebbe anche essere un buon momento per passare a un atlante delle trame (basta ingrandirlo se si è preoccupati per il supporto NPOT).
    • Questa è l'unica soluzione "perfetta", che consente un GL_LINEARfiltro simile ai poligoni / sprite.

Oh, grazie - l'avvolgimento attorno alla trama lo spiega. Esaminerò quelle soluzioni!
AshleysBrain

6

Perché per impostazione predefinita OpenGL può solo avvolgere l'intera trama (GL_REPEAT) e non solo una parte di essa, ogni riquadro è suddiviso in una trama separata. Quindi le regioni della stessa tessera vengono rese adiacenti l'una all'altra.

Considera la visualizzazione di un singolo quadrante strutturato normale in OpenGL. Ci sono cuciture, a qualsiasi scala? No, mai. Il tuo obiettivo è quello di mettere tutte le tessere della scena imballate strettamente su una singola trama, quindi inviarle allo schermo. EDIT Per chiarire ulteriormente questo: se si dispone di delimitazione vertici discreti su ogni quad piastrelle, si avrà avere cuciture apparentemente ingiustificati In molte circostanze. Funziona così come l'hardware della GPU ... Gli errori in virgola mobile creano questi vuoti in base alla prospettiva corrente ... Gli errori che vengono evitati su una varietà unificata, poiché se due facce sono adiacenti all'interno della stessa varietà(sottomissione), l'hardware li renderà senza cuciture, garantito. Lo hai visto in innumerevoli giochi e app. È necessario disporre di una trama fitta su una singola sottomissione senza vertici raddoppiati per evitarlo una volta per tutte. È un problema che si presenta più volte su questo sito e altrove: se non unisci i vertici angolari delle tue tessere (ogni 4 tessere condividono un vertice d'angolo), aspettati delle cuciture.

(Considera che potresti anche non aver bisogno di vertici se non ai quattro angoli dell'intera mappa ... dipende dal tuo approccio all'ombreggiatura.)

Per risolvere: esegui il rendering (a 1: 1) di tutte le trame delle tue tessere in un FBO / RBO senza spazi vuoti, quindi invia quell'FBO al framebuffer predefinito (lo schermo). Poiché l'FBO stesso è fondamentalmente una singola trama, non è possibile colmare lacune nel ridimensionamento. Tutti i confini del texel che non rientrano in un limite dei pixel dello schermo verranno miscelati se si utilizza GL_LINEAR .... che è esattamente ciò che si desidera. Questo è l'approccio standard.

Questo apre anche una serie di percorsi diversi per il ridimensionamento:

  • ridimensionando la dimensione del quad su cui renderizzerete l'FBO
  • cambiando gli UV su quel quad
  • armeggiare con le impostazioni della fotocamera

Non penso che funzionerà bene per noi. Sembra che tu stia proponendo che con uno zoom del 10% rendiamo un'area 10 volte più grande. Questo non è di buon auspicio per i dispositivi con una velocità di riempimento limitata. Inoltre sono ancora sconcertato perché nello specifico GL_REPEAT causa solo cuciture, e nessun'altra modalità lo fa. Come potrebbe essere?
AshleysBrain,

@AshleysBrain Non sequitur. Sono sconcertato dalla tua affermazione. Spiega come aumentare lo zoom del 10% aumenta il tasso di riempimento di 10x? O quanto aumenta lo zoom di 10 volte - dal momento che il ritaglio della finestra impedirebbe una percentuale di riempimento superiore al 100% su un singolo passaggio? Ti sto suggerendo di mostrare esattamente ciò che stai già pensando - niente di più, niente di meno - in quella che è probabilmente una maniera più efficiente E senza soluzione di continuità, unificando il tuo output finale usando RTT, una tecnica comune. L'hardware gestirà il ritaglio, il ridimensionamento, la fusione e l'interpolazione della vista praticamente a costo zero, dato che si tratta solo di un motore 2D.
Ingegnere

@AshleysBrain Ho modificato la mia domanda per chiarire in modo chiaro i problemi del tuo attuale approccio.
Ingegnere

@ArcaneEngineer Ciao, ho provato il tuo approccio che risolve perfettamente il problema delle cuciture. Tuttavia, ora, invece di avere cuciture, ho errori di pixel. Puoi avere un'idea del problema a cui mi riferisco qui . Hai idea di quale potrebbe essere la causa? Nota che sto facendo tutto in WebGL.
Nemikolh,

1
@ArcaneEngineer Ti farò una nuova domanda, spiegando qui è troppo ingombrante. Mi dispiace per il fastidio.
Nemikolh,

1

Se le immagini devono apparire come una superficie, renderle come una trama di superficie (scala 1x) su una mesh / piano 3D. Se si esegue il rendering di ciascuno come un oggetto 3D separato, avrà sempre cuciture a causa di errori di arrotondamento.

La risposta di @ Nick-Wiggill è corretta, penso che tu l'abbia frainteso.


0

2 possibilità che potrebbe valere la pena provare:

  1. Usa GL_NEARESTinvece di GL_LINEARper il tuo filtro trama. (Come sottolineato da Andon M. Coleman)

    GL_LINEAR(in effetti) sfocerà leggermente l'immagine (interpolando tra i pixel più vicini), il che consente una transizione graduale di colore da un pixel al successivo. Questo può aiutare a rendere le trame un aspetto migliore in molti casi, poiché impedisce alle trame di avere quei pixel bloccati su di essa. Inoltre, consente di sfocare un po 'di marrone da un pixel di una tessera al pixel verde adiacente della tessera adiacente.

    GL_NEARESTtrova solo il pixel più vicino e usa quel colore. Nessuna interpolazione. Di conseguenza, in realtà è un po 'più veloce.

  2. Riduci leggermente le coordinate della trama per ogni riquadro.

    In un certo senso è come aggiungere quel pixel in più attorno a ogni riquadro come buffer, tranne che invece di espandere il riquadro sull'immagine, si riduce il riquadro nel software. Piastrelle 14x14px durante il rendering anziché 16x16px.

    Potresti anche riuscire a cavartela con tessere 14,5x14,5 senza mescolare troppo marrone.

--modificare--

Visualizzazione della spiegazione nell'opzione n. 2

Come mostrato nell'immagine sopra, puoi ancora facilmente usare una potenza di due texture. Quindi puoi supportare hardware che non supporta trame NPOT. Ciò che cambia è il tuo coordinate di texture, invece di andare da (0.0f, 0.0f)a (1.0f, 1.0f)Faresti andare da (0.03125f, 0.03125f)a (0.96875f, 0.96875f).

Ciò mette i tuoi tex-coords leggermente all'interno del riquadro, riducendo la risoluzione effettiva nel gioco (anche se non sull'hardware in modo da avere ancora trame di potenza di due), tuttavia dovrebbe avere lo stesso effetto di rendering dell'espansione della trama.


Ho già detto che ho provato GL_NEAREST (aka filtro punti) e non lo risolve. Non posso fare il n. 2 perché devo supportare i dispositivi NPOT.
AshleysBrain

Hai apportato una modifica che potrebbe chiarire alcune cose. (Suppongo che "la necessità di supportare i dispositivi NPOT [(non-power-of-two)]" avrebbe significato qualcosa sulla falsariga di te per mantenere le trame a una potenza di 2?)
Wolfgang Skyler

0

Ecco cosa penso che potrebbe accadere: dove due piastrelle si scontrano, la componente x dei loro bordi dovrebbe essere uguale. Se ciò non è vero, potrebbero essere arrotondati nella direzione opposta. Quindi assicurati che abbiano esattamente lo stesso valore x. Come assicurate che ciò accada? Potresti provare, diciamo, a moltiplicare per 10, arrotondare e poi dividere per 10 (fallo solo sulla CPU, prima che i tuoi vertici entrino nello shader di vertici). Ciò dovrebbe dare risultati corretti. Inoltre, non disegnare una tessera per tessera utilizzando le matrici di trasformazione, ma inserirle in un VBO batch per assicurarsi che i risultati errati non provengano dal sistema in perdita IEEE in virgola mobile in combinazione con le moltiplicazioni di matrice.

Perché lo consiglio? Perché dalla mia esperienza, se i vertici hanno le stesse coordinate esatte quando escono dallo shader di vertice, riempire i triangoli corrispondenti creerà risultati senza soluzione di continuità. Tieni presente che qualcosa che è matematicamente corretto, potrebbe essere un po 'fuori causa a causa dell'IEEE. Più calcoli esegui sui tuoi numeri, meno accurato sarà il risultato. E sì, le moltiplicazioni di matrici richiedono alcune operazioni, che potrebbero essere fatte solo in una moltiplicazione e un'aggiunta durante la creazione del VBO, il che darà risultati più accurati.

Ciò che potrebbe anche essere il problema è che stai usando un foglio di calcolo (chiamato anche atlante) e che quando si campiona la trama, vengono selezionati i pixel della trama della tessera adiacente. Garantire che ciò non accada è creare un piccolo bordo nelle mappature UV. Quindi, se hai una tessera 64x64, la tua mappatura UV dovrebbe coprire un po 'meno. Quanto? Penso di aver usato nei miei giochi 1 quarto di pixel su ciascun lato del rettangolo. Quindi offset i tuoi UV di 1 / (4 * larghezza di TheAtlas) per i componenti x e 1 / (4 * altezza di TheAtlas) per i componenti y.


Grazie, ma come ho già notato la geometria è decisamente esattamente adiacente - in effetti tutte le coordinate risultano in interi esatti, quindi è facile da dire. Qui non vengono utilizzati atlanti.
AshleysBrain,

0

Per risolvere questo problema nello spazio 3D, ho mescolato array di trame con il suggerimento di Wolfgang Skyler. Ho messo un bordo di 8 pixel attorno alla mia trama reale per ogni riquadro (120x120, 128x128 in totale) che, per ogni lato, che potevo riempire con un'immagine "avvolta" o il lato che veniva appena esteso. Il campionatore legge quest'area quando sta interpolando l'immagine.

Ora con filtro e mipmapping, il campionatore può ancora leggere facilmente oltre il bordo di 8 pixel. Per cogliere quel piccolo problema (dico piccolo perché succede solo su alcuni pixel quando la geometria è davvero inclinata o molto lontana), ho diviso le tessere in un array di trame, quindi ogni tessera ha il suo spazio di trama e può usare il serraggio sul bordi.

Nel tuo caso (2D / piatto), andrei sicuramente con il rendering di una scena pixel perfetta, e poi ridimensionando la porzione desiderata del risultato nella finestra, come suggerito da Nick Wiggil.

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.