Come evitare il sanguinamento delle trame in un atlante delle trame?


46

Nel mio gioco c'è un terreno simile a Minecraft fatto di cubi. Genero un buffer di vertici dai dati voxel e utilizzo un atlante di trama per look di blocchi diversi:

atlante texture per terreni a base di voxel

Il problema è che la trama di cubi distanti si interpola con tessere adiacenti nell'atlante della trama. Ciò si traduce in linee di colori errati tra i cubi (potrebbe essere necessario visualizzare lo screenshot di seguito a dimensioni intere per vedere i difetti grafici):

screenshot del terreno con strisce di colore sbagliato

Per ora utilizzo queste impostazioni di interpolazione ma ho provato ogni combinazione e anche GL_NEARESTsenza il mipmapping non fornisce risultati migliori.

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

Ho anche provato ad aggiungere un offset nelle coordinate della trama per selezionare un'area leggermente più piccola della piastrella, ma poiché l'effetto indesiderato dipende dalla distanza dalla fotocamera, questo non può risolvere completamente il problema. A grandi distanze le strisce si verificano comunque.

Come posso risolvere questo sanguinamento della trama? Poiché l'utilizzo di un atlante di texture è una tecnica popolare, potrebbe esserci un approccio comune. Purtroppo, per alcuni motivi spiegati nei commenti, non posso passare a trame o matrici di trama diverse.


4
Il problema sta nel mipmapping: le coordinate della trama possono funzionare bene per il livello di mip più grande, ma man mano che le cose si allontanano, le piastrelle confinanti si fondono insieme. Puoi combatterlo non usando le mipmap (probabilmente non è una buona idea), facendo crescere le zone di guardia (area tra le tessere), facendo crescere dinamicamente le zone di guardia in uno shader basato sul livello di mip (difficile), facendo qualcosa di strano durante la generazione di mipmap (può non penso a nulla però) .. Non ho mai affrontato questo problema da solo, ma c'è roba da cui iniziare .. =)
Jari Komppa

Come ho detto tristemente, disattivare il mipmapping non risolve il problema. Senza le mipmap interpolerà lineare, il GL_LINEARche darà un risultato simile o selezionerà il pixel più vicino GL_NEARESTche comunque comporterà anche strisce tra i blocchi. L'ultima opzione menzionata diminuisce ma non elimina il difetto dei pixel.
danijar,

7
Una soluzione consiste nell'utilizzare un formato che ha generato mipmap, quindi è possibile creare te stesso mipmap e correggere l'errore. Un'altra soluzione è forzare un livello mipmap specifico nel fragmentshader durante il campionamento della trama. Un'altra soluzione è riempire le mipmap con la prima mipmap. In altre parole, quando crei la tua trama, puoi semplicemente aggiungere immagini secondarie a quella particolare immagine, contenente gli stessi dati.
Tordin

1
Ho avuto questo problema in precedenza ed è stato risolto aggiungendo un'imbottitura di 1-2 pixel nel tuo atlante tra ogni diversa trama.
Chuck D

1
@Kylotan. Il problema riguarda il ridimensionamento delle trame indipendentemente dal fatto che venga eseguito da mipmap, interpolazione lineare o selezione dei pixel più vicina.
Daniel

Risposte:


34

Ok, penso che tu abbia due problemi qui.

Il primo problema riguarda il mipmapping. In generale, non vuoi mescolare ingenuamente l'atlante con il mipmapping, perché a meno che tutte le sottotesture abbiano dimensioni esattamente di 1 x 1 pixel, si verificherà un sanguinamento della trama. L'aggiunta di un'imbottitura sposta semplicemente il problema a un livello mip inferiore.

Come regola generale, per il 2D, che è dove di solito si fa l'atlante, non si esegue il mipmap; mentre per il 3D, che è dove mipmap, non atlante (in realtà, la pelle in modo tale che il sanguinamento non sarà un problema)

Tuttavia, ciò che penso stia succedendo con il tuo programma in questo momento è che probabilmente non stai usando le coordinate di trama corrette, e per questo motivo le tue trame stanno sanguinando.

Supponiamo che una trama 8x8. Probabilmente stai ottenendo il quarto in alto a sinistra usando le coordinate uv da (0,0) a (0,5, 0,5):

Mappatura errata

E il problema è che alla coordinata u 0,5, il campionatore interpolerà il frammento di destinazione usando metà del texel sinistro e metà del texel destro. Lo stesso accadrà con la coordinata u 0 e lo stesso per le coordinate v. Questo perché stai affrontando i confini tra texel e non i centri.

Quello che dovresti fare invece è rivolgersi al centro di ogni texel. In questo esempio, queste sono le coordinate che vuoi usare:

Mappatura corretta

In questo caso, campionerai sempre il centro di ogni texel e non dovresti avere alcun sanguinamento ... A meno che tu non usi il mipmapping, nel qual caso otterrai sempre sanguinamento a partire dal livello di mip in cui la sottotitolazione in scala non si adatta in modo ordinato adattarsi alla griglia dei pixel .

Per generalizzare, se si desidera accedere a un texel specifico, le coordinate vengono calcolate come:

function get_texel_coords(x, y, tex_width, tex_height)
    u = (x + 0.5) / tex_width
    v = (y + 0.5) / tex_height
    return u, v
end

Questo si chiama "correzione di mezzo pixel". C'è una spiegazione più dettagliata qui se sei interessato.


La tua spiegazione sembra molto promettente. Purtroppo dal momento che devo scheda video al momento, non posso ancora implementarlo. Lo farò se arriva la carta riparata. E calcolerò da solo le mipmap dell'atlante senza interpolare i confini delle piastrelle.
danijar,

@sharethis Se devi assolutamente atlante, assicurati che le tue sottotesture abbiano dimensioni in potenze di 2, quindi puoi limitare l'emorragia ai livelli mip più bassi. Se lo fai, non hai davvero bisogno di creare a mano le tue mipmap.
Panda Pajama,

Anche le trame di dimensioni PoT possono avere artefatti sanguinanti, poiché il filtro bilineare può campionare oltre quei confini. (Almeno nelle implementazioni che ho visto.) Per quanto riguarda la correzione a mezzo pixel, dovrebbe valere in questo caso? Se volessi mappare la sua struttura rocciosa, ad esempio, sicuramente sarà sempre da 0,0 a 0,25 lungo le coordinate U e V?
Kylotan,

1
@Kylotan Se la dimensione della trama è pari e tutte le sottotesture hanno dimensioni pari e si trovano a coordinate pari, il filtro bilineare quando si dimezza la dimensione della trama non si mescolerà tra le sottotesture. Generalizza per poteri di due (se vedi artefatti, probabilmente stai usando un filtro bicubico, non bilineare). Per quanto riguarda la correzione di mezzo pixel, è necessario, poiché 0,25 non è il texel più a destra, ma il punto centrale tra le due sottotesture. Per metterlo in prospettiva, immagina di essere il rasterizzatore: di che colore è la trama (0,00, 0,25)?
Panda Pajama,

1
@sharethQuesta altra risposta che ho scritto potrebbe essere di aiuto a gamedev.stackexchange.com/questions/50023/…
Panda Pajama

19

Un'opzione che sarà molto più semplice che giocherellare con le mipmap e aggiungere i fattori fuzz delle coordinate delle trame è usare un array di trame. Le matrici di trama sono simili alle trame 3d, ma senza mipmapping nella terza dimensione, quindi sono ideali per atlanti di trama in cui le "sottotesture" hanno tutte le stesse dimensioni.

http://www.opengl.org/wiki/Array_Texture


Se sono disponibili, sono una soluzione molto migliore: ottieni anche altre funzionalità, come il wrapping delle trame, che ti manca con gli atlanti.
Jari Komppa,

@JariKomppa. Non voglio usare array di trame perché in questo modo avrei degli shader extra per il terreno. Dato che il motore che scrivo dovrebbe essere il più generale possibile, non voglio fare questa eccezione. Esiste un modo per creare uno shader in grado di gestire sia array di trame che singole trame?
Daniel

8
@sharethis Respingere inequivocabilmente soluzioni tecniche da superiour perché vuoi essere "generale" è una ricetta per fallire. Se scrivi una partita, non scrivere un motore.
Sam Hocevar,

@SamHocevar. Sto scrivendo un motore e il mio progetto riguarda un'architettura specifica in cui i componenti sono completamente disaccoppiati. Quindi il componente del modulo non dovrebbe occuparsi del componente del terreno che dovrebbe fornire solo buffer standard e una trama standard.
danijar,

1
@sharethis OK. Sono stato in qualche modo fuorviato dalla parte "Nel mio gioco" della domanda.
Sam Hocevar,

6

Dopo aver lottato molto con questo problema, ho finalmente trovato una soluzione.

Per usare entrambi, un atlante di texture e un mipmapping, ho bisogno di eseguire il downsampling me stesso, perché OpenGL altrimenti interpolerebbe oltre i confini delle piastrelle nell'atlante. Inoltre, avevo bisogno di impostare i giusti parametri di trama, perché l'interpolazione tra le mipmap avrebbe anche causato sanguinamenti di trama.

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

Con questi parametri, non si verificano sanguinamenti.

C'è una cosa che devi notare quando fornisci mipmap personalizzate. Poiché non ha senso ridurre l'atlante anche se ogni riquadro è già 1x1, il livello massimo di mipmap deve essere impostato in base a ciò che si fornisce.

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 7); // pick mipmap level 7 or lower

Grazie per tutte le altre risposte e commenti molto utili! A proposito, non so ancora come usare il ridimensionamento lineare, ma suppongo che non ci sia modo.


bello sapere che l'hai risolto. Dovresti contrassegnare questa risposta come quella corretta anziché la mia.
Panda Pajama,

il mipmapping è una tecnica esclusivamente per il downsampling, che non ha senso per l'upsampling. L'idea è che se hai intenzione di disegnare un piccolo triangolo, ci vorrebbe molto tempo per campionare una trama di grandi dimensioni solo per estrarre alcuni pixel da esso, quindi pre-downsampionarlo e usare il livello di mip appropriato quando disegnando piccoli triangoli. Per i triangoli più grandi, usare qualcosa di diverso dal livello di mip più grande non ha senso, quindi è un errore fare qualcosa di simileglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_FILTER, GL_NEAREST_MIPMAP_LINEAR);
Panda Pajama

@PandaPajama. Hai ragione, ho corretto la mia risposta. Impostare GL_TEXTURE_MAX_FILTERsu GL_NEAREST_MIPMAP_LINEARcause emorragie non per il motivo del mipmapping, ma per il motivo dell'interpolazione. Quindi in questo caso è equivalente GL_LINEARpoiché viene comunque utilizzato il livello mipmap più basso.
danijar,

@danijar - Puoi spiegare più in dettaglio come esegui tu stesso il downsampling e come dici a OpenGL dove (e quando) usare l'atlante downsampling?
Tim Kane,

1
@TimKane Atlante diviso nelle piastrelle. Per ogni livello di mipmap, sottocampiona i riquadri in modo bilaterale, combinali e caricalo nella GPU specificando il livello di mipmap come parametro in glTexImage2D(). La GPU sceglie automaticamente il livello corretto in base alla dimensione degli oggetti sullo schermo.
danijar,

4

Sembra che il problema potrebbe essere causato da MSAA. Vedi questa risposta e l' articolo collegato :

"quando attivi MSAA, diventa possibile che lo shader venga eseguito per campioni che si trovano all'interno dell'area pixel, ma al di fuori dell'area triangolare"

La soluzione è utilizzare il campionamento del centroide. Se stai calcolando il wrapping delle coordinate della trama in uno shader di vertici, spostare la logica nello shader di frammenti potrebbe anche risolvere il problema.


Come ho già scritto in un altro commento, al momento non ho una scheda video funzionante, quindi non posso verificare se questo è il problema reale. Lo farò appena posso.
danijar,

Ho disabilitato l'antialiasing hardware ma l'emorragia rimane ancora. Faccio già la trama guardando nello shader di frammenti.
danijar

1

Questo è un vecchio argomento e ho avuto un problema simile all'argomento.

Le coordinate della trama erano perfette, ma modificando la posizione della videocamera (che ha cambiato la posizione del mio elemento) in questo modo:

public static void tween(Camera cam, Direction dir, Vec2 buffer, Vec2 start, Vec2 end) {
    if(step > steps) {
        tweening = false;
        return;
    }

    final float alpha = step/steps;

    switch(dir) {
    case UP:
    case DOWN:
        buffer = new Vec2(start.getX(), Util.lerp(start.getY(), (float)end.getY(), alpha));
        cam.getPosition().set(buffer);
        break;
    case LEFT:
    case RIGHT:
        buffer = new Vec2(Util.lerp(start.getX(), end.getX(), alpha), start.getY());
        cam.getPosition().set(buffer);
        break;
    }

    step += 1;
}

L'intero problema era che Util.lerp () restituiva un float o un double. Questo è stato negativo perché la fotocamera stava traducendo off-grid e causando alcuni strani problemi con i frammenti di trama in cui> 0 in X e> 0 in Y.

TLDR: non interpolare o altro usando i float

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.