Creazione di un effetto di scambio di palette in stile retrò in OpenGL


37

Sto lavorando a un gioco simile a Megaman in cui ho bisogno di cambiare il colore di alcuni pixel in fase di esecuzione. Per riferimento : in Megaman quando cambi l'arma selezionata, la tavolozza del personaggio principale cambia per riflettere l'arma selezionata. Non tutti i colori dello sprite cambiano, solo alcuni lo fanno .

Questo tipo di effetto era comune e abbastanza facile da eseguire sul NES poiché il programmatore aveva accesso alla tavolozza e alla mappatura logica tra pixel e indici della tavolozza. Sull'hardware moderno, tuttavia, questo è un po 'più impegnativo perché il concetto di palette non è lo stesso.

Tutte le mie trame sono a 32 bit e non usano le tavolozze.

Ci sono due modi che conosco per ottenere l'effetto che desidero, ma sono curioso di sapere se ci sono modi migliori per ottenere facilmente questo effetto. Le due opzioni che conosco sono:

  1. Usa uno shader e scrivi un po 'di GLSL per eseguire il comportamento di "scambio di palette".
  2. Se gli shader non sono disponibili (ad esempio, perché la scheda grafica non li supporta), è possibile clonare le trame "originali" e generare versioni diverse con i cambiamenti di colore pre-applicati.

Idealmente, vorrei usare uno shader poiché sembra semplice e richiede poco lavoro aggiuntivo rispetto al metodo a texture duplicata. Temo che duplicare le trame solo per cambiarne un colore stia sprecando VRAM - non dovrei preoccuparmene?

Modifica : ho finito per usare la tecnica della risposta accettata ed ecco il mio shader per riferimento.

uniform sampler2D texture;
uniform sampler2D colorTable;
uniform float paletteIndex;

void main()
{
        vec2 pos = gl_TexCoord[0].xy;
        vec4 color = texture2D(texture, pos);
        vec2 index = vec2(color.r + paletteIndex, 0);
        vec4 indexedColor = texture2D(colorTable, index);
        gl_FragColor = indexedColor;      
}

Entrambe le trame sono a 32 bit, una trama viene utilizzata come tabella di ricerca contenente diverse tavolozze che hanno tutte le stesse dimensioni (nel mio caso 6 colori). Uso il canale rosso del pixel sorgente come indice della tabella dei colori. Questo ha funzionato come un incantesimo per ottenere lo scambio di palette simile a Megaman!

Risposte:


41

Non mi preoccuperei di sprecare VRAM per alcune trame dei personaggi.

Per me usare la tua opzione 2. (con diverse trame o diversi offset UV se ciò si adatta) è la strada da percorrere: più flessibile, basata sui dati, meno impatto sul codice, meno bug, meno preoccupazioni.


A parte questo, se inizi ad accumulare tonnellate di personaggi con tonnellate di animazioni sprite in memoria, forse potresti iniziare a usare ciò che è raccomandato da OpenGL , tavolozze fai-da-te:

Trame palette

Il supporto per l' estensione EXT_paletted_texture è stato abbandonato dai principali fornitori GL. Se hai davvero bisogno di trame palette sul nuovo hardware, puoi usare gli shader per ottenere questo effetto.

Esempio di shader:

//Fragment shader
#version 110
uniform sampler2D ColorTable;     //256 x 1 pixels
uniform sampler2D MyIndexTexture;
varying vec2 TexCoord0;

void main()
{
  //What color do we want to index?
  vec4 myindex = texture2D(MyIndexTexture, TexCoord0);
  //Do a dependency texture read
  vec4 texel = texture2D(ColorTable, myindex.xy);
  gl_FragColor = texel;   //Output the color
}

Questo è semplicemente il campionamento in ColorTable(una tavolozza in RGBA8), usando MyIndexTexture(una trama quadrata di 8 bit in colori indicizzati). Riproduce semplicemente il modo in cui funzionano le tavolozze in stile retrò.


L'esempio sopra citato ne usa due sampler2D, dove potrebbe effettivamente usare uno sampler1D+ uno sampler2D. Ho il sospetto che ciò sia dovuto a motivi di compatibilità ( nessuna texture monodimensionale in OpenGL ES ) ... Ma non importa, per OpenGL desktop questo può essere semplificato per:

uniform sampler1D Palette;             // A palette of 256 colors
uniform sampler2D IndexedColorTexture; // A texture using indexed color
varying vec2 TexCoord0;                // UVs

void main()
{
    // Pick up a color index
    vec4 index = texture2D(IndexedColorTexture, TexCoord0);
    // Retrieve the actual color from the palette
    vec4 texel = texture1D(Palette, index.x);
    gl_FragColor = texel;   //Output the color
}

Paletteè una trama monodimensionale di colori "reali" (ad esempio GL_RGBA8) ed IndexedColorTextureè una trama bidimensionale di colori indicizzati (in genere GL_R8, che ti dà 256 indici). Per crearli, ci sono diversi strumenti di authoring e formati di file di immagini che supportano le immagini con palette, dovrebbe essere fattibile trovare quello che si adatta alle tue esigenze.


2
Esattamente la risposta che avrei dato, solo più dettagliata. Farei +2 se potessi.
Ilmari Karonen,

Sto riscontrando dei problemi nel far sì che questo funzioni per me, soprattutto perché non capisco come viene determinato l'indice nel colore: potresti forse espanderci un po 'di più?
Zack The Human,

Probabilmente dovresti leggere sui colori indicizzati . La trama diventa una matrice 2D di indici, che indica gli indici corrispondenti ai colori in una tavolozza (in genere una trama unidimensionale). Modificherò la mia risposta per semplificare l'esempio fornito dal sito Web OpenGL.
Laurent Couvidou,

Mi dispiace, non ero chiaro nel mio commento originale. Ho familiarità con i colori indicizzati e come funzionano, ma non sono chiaro su come funzionano nel contesto di uno shader o di una trama a 32 bit (dal momento che quelli non sono indicizzati - giusto?).
Zack The Human,

Bene, il fatto è che qui hai una tavolozza a 32 bit contenente 256 colori e una trama di indici a 8 bit (che vanno da 0 a 255). Vedi la mia modifica;)
Laurent Couvidou l'

10

Ci sono 2 modi in cui posso pensare di farlo.

Modo n. 1: GL_MODULATE

Puoi creare lo sprite nei toni del grigio e GL_MODULATEla trama quando viene disegnato con un unico colore a tinta unita.

Per esempio,

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine

Ciò non ti consente tuttavia molta flessibilità, in pratica puoi solo diventare più chiaro e più scuro della stessa tonalità.

Modo n. 2: ifdichiarazione (male per le prestazioni)

Un'altra cosa che potresti fare è colorare le tue trame con alcuni valori chiave che coinvolgono R, G e B. SO per esempio, il primo colore è (1,0,0), il secondo colore è (0,1,0), il terzo il colore è (0,0,1).

Dichiarate un paio di uniformi simili

uniform float4 color1 ;
uniform float4 color2 ;
uniform float4 color3 ;

Quindi nello shader, il colore finale del pixel è qualcosa di simile

float4 pixelColor = texel.r*color1 + texel.g*color2 + texel.b*color3 ;

color1, color2, color3, Sono divise in modo che possano essere impostate prima dell'esecuzione dello shader (a seconda di ciò megaman "vestito" è in), allora lo shader fa semplicemente il resto.

Potresti anche usare le ifistruzioni se avessi bisogno di più colori, ma dovresti fare in modo che i controlli di uguaglianza funzionino correttamente (le trame sono generalmente R8G8B8comprese tra 0 e 255 ciascuna nel file di trama, ma nello shader hai rgba float)

Modo n. 3: memorizza in modo ridondante le diverse immagini colorate nella mappa sprite

inserisci qui la descrizione dell'immagine

Questo potrebbe essere il modo più semplice, se non stai cercando follemente di conservare la memoria


2
Il n. 1 può essere pulito, ma il n. 2 e il n. 3 sono consigli terribilmente cattivi. La GPU è in grado di indicizzare dalle mappe (di cui sostanzialmente una tavolozza) e lo fa meglio di entrambi i suggerimenti.
Skrylar,

6

Userei gli shader. Se non si desidera utilizzarli (o non è possibile) sceglierei una trama (o parte di una trama) per colore e utilizzare solo il bianco pulito. Ciò ti consentirà di colorare la trama (ad es. Attraverso glColor()) senza doversi preoccupare di creare trame aggiuntive per i colori. Puoi anche scambiare i colori in fuga senza ulteriori lavori di trama.


Questa è una buona idea In realtà ci avevo pensato un po 'di tempo fa, ma non mi è venuto in mente quando ho pubblicato questa domanda. Vi è una certa complessità in più poiché ogni sprite che usa questo tipo di trama composita avrà bisogno di una logica di disegno più complessa. Grazie per questo fantastico suggerimento!
Zack The Human,

Sì, avrai anche bisogno di x passaggi per sprite, il che può aggiungere una certa complessità. Considerando che l'hardware di oggi di solito supporta almeno pixel shader di base (anche GPU per netbook), preferirei invece questi. Può essere pulito se vuoi solo colorare cose specifiche, ad esempio barre della salute o icone.
Mario,

3

"Non tutti i colori dello sprite cambiano, solo alcuni lo fanno."

I vecchi giochi facevano trucchi con la tavolozza perché era tutto ciò che avevano. In questi giorni, puoi usare più trame e combinarle in vari modi.

È possibile creare una trama maschera di gradazione di grigio separata che viene utilizzata per decidere quali pixel cambieranno colore. Le aree bianche nella trama ricevono la modifica completa del colore e le aree nere rimangono invariate.

In shader languague:

vec3 baseColor = texture (BaseSampler, att_TexCoord) .rgb;
float amount = texture (MaskSampler, att_TexCoord) .r;
vec3 color = mix (baseColor, baseColor * ModulationColor, quantità);

Oppure puoi usare la multi-texturing a funzione fissa con varie modalità di combinazione delle trame.

Ci sono ovviamente molti modi in cui puoi farlo; è possibile inserire maschere per colori diversi in ciascuno dei canali RGB o modulare i colori spostando la tinta in uno spazio colore HSV.

Un'altra opzione, forse più semplice, è quella di disegnare semplicemente lo sprite usando una trama separata per ciascun componente. Una trama per i capelli, una per l'armatura, una per gli stivali, ecc. Ogni colore è stampabile separatamente.


2

In primo luogo viene generalmente indicato come ciclismo (pallet pallet), quindi ciò potrebbe aiutare il tuo Google-fu.

Questo dipenderà esattamente da quali effetti vuoi produrre e se hai a che fare con contenuti 2D statici o cose 3D dinamiche (cioè vuoi solo occuparti di dispetti / trame o rendere un'intera scena 3D, quindi scambia pallet). Anche se ti stai limitando a un numero di colori o usi il colore pieno.

Un approccio più completo, utilizzando gli shader, sarebbe quello di produrre effettivamente immagini indicizzate a colori con una trama pallet. Crea una trama ma invece di avere colori, usa un colore che rappresenti un indice da cercare, quindi un'altra trama di colori che è il piatto. Ad esempio, rosso potrebbe essere la coordinata t delle trame e verde potrebbe essere la coordinata s. Usa uno shader per fare la ricerca. E anima / modifica i colori di quella trama pallet. Questo sarebbe abbastanza vicino all'effetto retrò ma funzionerebbe solo per contenuti 2D statici come sprite o trame. Se stai realizzando una grafica retrò autentica, potresti essere in grado di produrre sprite di colore indicizzate reali e scrivere qualcosa per importare loro e i pallet.

Ti consigliamo di allenarti anche se stai per utilizzare un pallet "globale". Come il vecchio hardware funzionerebbe, meno memoria per i pallet in quanto ne hai solo bisogno ma più difficile da produrre per la tua opera d'arte in quanto devi sincronizzare il pallet su tutte le immagini) o dare ad ogni immagine il proprio pallet. Ciò ti darebbe maggiore flessibilità ma utilizzerebbe più memoria. Ovviamente potresti fare entrambe le cose, inoltre puoi usare solo i pallet per le cose che stai ciclando a colori. Scopri anche fino a che punto non vuoi limitare i tuoi colori, ad esempio i vecchi giochi userebbero 256 colori. Se stai usando una trama, otterrai il numero di pixel, quindi un 256 * 256 ti darà

Se desideri solo un effetto falso economico, come l'acqua che scorre, potresti creare una seconda trama che contiene una maschera in scala di grigi l'area che desideri modificare, quindi utilizzare le trame alberate per modificare la tonalità / saturazione / luminosità in base alla quantità nella trama maschera in scala di grigi. Potresti essere anche più pigro e cambiare la tonalità / luminosità / saturazione di qualsiasi cosa in una gamma di colori.

Se ne avessi bisogno in 3D, potresti provare a generare l'immagine indicizzata al volo ma sarebbe lenta poiché dovresti cercare il pallet, probabilmente anche la gamma di colori sarebbe limitata.

Altrimenti potresti semplicemente falsificarlo nello shader, se color == scambia colore, scambialo. Sarebbe lento e funzionerebbe solo con un numero limitato di colori di scambio, ma non dovrebbe stressare nessuno degli hardware di oggi, in particolare se hai a che fare con la grafica 2D e puoi sempre contrassegnare quali sprite hanno lo scambio di colori e utilizzare uno shader diverso.

Altrimenti falli davvero, crea un sacco di trame animate per ogni stato.

Ecco una grande demo del ciclo di pallet fatto in HTML5 .


0

Credo che se sono abbastanza veloci da dividere in due lo spazio colore [0 ... 1]:

if (color.r > 0.5) {
   color.r = (color.r-0.5) * uniform_r + uniform_base_r;
} else {
  color.r *=2.0;
} // this could be vectorised for all colors

In questo modo i valori di colore compresi tra 0 e 0,5 sono riservati ai valori di colore normali; e 0,5 - 1,0 sono riservati per valori "modulati", dove 0,5..1,0 viene mappato su qualsiasi intervallo selezionato dall'utente.

Solo la risoluzione del colore viene troncata da 256 valori per pixel a 128 valori.

Probabilmente si possono usare funzioni integrate come max (color-0.5f, 0.0f) per rimuovere completamente gli if (scambiandoli con moltiplicazioni per zero o uno ...).

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.