Erstellen eines Palettentauscheffekts im Retro-Stil in OpenGL

37

Ich arbeite an einem Megaman- ähnlichen Spiel, bei dem ich zur Laufzeit die Farbe bestimmter Pixel ändern muss. Als Referenz : Wenn Sie in Megaman Ihre ausgewählte Waffe ändern, ändert sich die Palette des Hauptcharakters entsprechend der ausgewählten Waffe. Nicht alle Farben des Sprites ändern sich, nur bestimmte .

Diese Art von Effekt war weit verbreitet und auf dem NES recht einfach zu erzielen, da der Programmierer Zugriff auf die Palette und die logische Zuordnung zwischen Pixeln und Palettenindizes hatte. Bei moderner Hardware ist dies jedoch etwas schwieriger, da das Konzept der Paletten nicht dasselbe ist.

Alle meine Texturen sind 32-Bit und verwenden keine Paletten.

Ich kenne zwei Möglichkeiten, um den gewünschten Effekt zu erzielen. Ich bin jedoch gespannt, ob es bessere Möglichkeiten gibt, diesen Effekt auf einfache Weise zu erzielen. Die beiden mir bekannten Optionen sind:

  1. Verwenden Sie einen Shader und schreiben Sie etwas GLSL, um das Verhalten "Palettentausch" auszuführen.
  2. Wenn Shader nicht verfügbar sind (z. B. weil sie von der Grafikkarte nicht unterstützt werden), können Sie die "ursprünglichen" Texturen klonen und mit den vorab angewendeten Farbänderungen verschiedene Versionen erstellen.

Im Idealfall würde ich gerne einen Shader verwenden, da dieser unkompliziert zu sein scheint und im Gegensatz zur Methode der duplizierten Textur nur wenig zusätzliche Arbeit erfordert. Ich mache mir Sorgen, dass das Duplizieren von Texturen, nur um eine Farbe in ihnen zu ändern, VRAM verschwendet - sollte ich mir darüber keine Sorgen machen?

Bearbeiten : Ich habe die Technik der akzeptierten Antwort verwendet und hier ist mein Shader als Referenz.

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;      
}

Beide Texturen sind 32-Bit, eine Textur wird als Nachschlagetabelle verwendet, die mehrere Paletten enthält, die alle die gleiche Größe haben (in meinem Fall 6 Farben). Ich verwende den roten Kanal des Quellpixels als Index für die Farbtabelle. Dies hat wie ein Zauber gewirkt, um einen Megaman-ähnlichen Palettentausch zu erreichen!

Zack der Mensch
quelle

Antworten:

41

Ich würde mir keine Sorgen machen, VRAM für ein paar Charaktertexturen zu verschwenden.

Für mich ist die Verwendung Ihrer Option 2 (mit unterschiedlichen Texturen oder unterschiedlichen UV-Offsets, falls dies passt) der richtige Weg: flexibler, datengesteuert, weniger Auswirkungen auf den Code, weniger Fehler, weniger Sorgen.


Wenn Sie anfangen, Tonnen von Zeichen mit Tonnen von Sprite-Animationen im Speicher anzusammeln, könnten Sie vielleicht anfangen, die von OpenGL empfohlenen " Do-it-yourself" -Paletten zu verwenden:

Palettierte Texturen

Die Unterstützung für die EXT_paletted_texture- Erweiterung wurde von den wichtigsten GL-Anbietern eingestellt. Wenn Sie auf neuer Hardware wirklich palettierte Texturen benötigen, können Sie Shader verwenden, um diesen Effekt zu erzielen.

Shader-Beispiel:

//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
}

Dies wird einfach in ColorTable(einer Palette in RGBA8) unter Verwendung von MyIndexTexture(einer 8-Bit-Quadrat-Textur in indizierten Farben) abgetastet . Reproduziert einfach die Funktionsweise von Retro-Paletten.


Das oben zitierte Beispiel verwendet zwei sampler2D, wobei es tatsächlich eins sampler1D+ eins verwenden könnte sampler2D. Ich vermute, dies ist aus Kompatibilitätsgründen ( keine eindimensionalen Texturen in OpenGL ES ) ... Aber egal, für Desktop-OpenGL kann dies vereinfacht werden:

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
}

Paletteist eine eindimensionale Textur aus "echten" Farben (z. B. GL_RGBA8) und IndexedColorTextureeine zweidimensionale Textur aus indizierten Farben (in der Regel GL_R8256 Indizes). Um diese zu erstellen, gibt es verschiedene Authoring- Tools und Bilddateiformate , die palettierte Bilder unterstützen. Es sollte möglich sein, das zu finden, das Ihren Anforderungen entspricht.

Laurent Couvidou
quelle
2
Genau die Antwort hätte ich gegeben, nur ausführlicher. Würde +2 wenn ich könnte.
Ilmari Karonen
Ich habe einige Probleme damit, dies für mich zum Laufen zu bringen, hauptsächlich, weil ich nicht verstehe, wie der Index in der Farbe bestimmt wird. Könnten Sie das möglicherweise ein bisschen weiter ausbauen?
Zack The Human
Sie sollten sich wahrscheinlich über indizierte Farben informieren . Ihre Textur wird zu einem 2D-Array von Indizes, wobei die Indizes den Farben in einer Palette entsprechen (normalerweise eine eindimensionale Textur). Ich bearbeite meine Antwort, um das Beispiel auf der OpenGL-Website zu vereinfachen.
Laurent Couvidou
Es tut mir leid, ich war in meinem ursprünglichen Kommentar nicht klar. Ich bin mit indizierten Farben vertraut und weiß nicht, wie sie im Kontext eines Shaders oder einer 32-Bit-Textur funktionieren (da diese nicht indiziert sind - oder?).
Zack The Human
Nun, die Sache ist, dass Sie hier eine 32-Bit- Palette mit 256 Farben und eine Textur von 8-Bit- Indizes (von 0 bis 255) haben. Siehe meine Bearbeitung;)
Laurent Couvidou
10

Ich kann mir das auf zwei Arten vorstellen.

Weg # 1: GL_MODULATE

Sie können das Sprite in Graustufen erstellen und GL_MODULATEdie Textur, wenn es mit einer einzelnen Volltonfarbe gezeichnet wird.

Beispielsweise,

Bildbeschreibung hier eingeben

Bildbeschreibung hier eingeben

Dies lässt Ihnen jedoch nicht viel Flexibilität zu. Grundsätzlich können Sie nur heller und dunkler werden, wenn Sie denselben Farbton haben.

Weg # 2: ifAussage (schlecht für die Leistung)

Sie können Ihre Texturen auch mit einigen Schlüsselwerten für R, G und B einfärben. So ist beispielsweise die erste Farbe (1,0,0), die zweite Farbe (0,1,0) und die dritte Farbe Farbe ist (0,0,1).

Sie erklären ein paar Uniformen wie

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

Im Shader ist die endgültige Pixelfarbe ungefähr so

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

color1, color2, color3, Sind Uniformen , so dass sie vor den Shader - Läufe eingestellt werden (je nachdem , was „Anzug“ Megaman ist in), dann wird der Shader einfach den Rest.

Sie könnten auch ifAnweisungen verwenden, wenn Sie mehr Farben benötigen, aber die Gleichheitsprüfungen müssten korrekt funktionieren (Texturen sind normalerweise R8G8B8zwischen 0 und 255 in der Texturdatei, aber im Shader haben Sie rgba floats).

Methode 3: Speichern Sie die Bilder in verschiedenen Farben redundant in der Sprite-Map

Bildbeschreibung hier eingeben

Dies ist möglicherweise der einfachste Weg, wenn Sie nicht wahnsinnig versuchen, Speicherplatz zu sparen

Bobobobo
quelle
2
# 1 kann ordentlich sein, aber # 2 und # 3 sind schrecklich schlechte Ratschläge. Die GPU ist in der Lage, anhand von Karten (von denen eine Palette im Grunde genommen ist) zu indizieren, und dies ist besser als bei beiden dieser Vorschläge.
Skrylar
6

Ich würde Shader verwenden. Wenn Sie sie nicht verwenden möchten (oder nicht können), würde ich eine Textur (oder einen Teil einer Textur) pro Farbe verwenden und nur sauberes Weiß verwenden. Auf diese Weise können Sie die Textur (z. B. durchgehend glColor()) tönen, ohne zusätzliche Texturen für Farben erstellen zu müssen. Sie können sogar während des Laufs die Farben tauschen, ohne dass zusätzliche Texturarbeiten anfallen.

Mario
quelle
Das ist eine ziemlich gute Idee. Ich hatte tatsächlich vor einiger Zeit darüber nachgedacht, aber es kam mir nicht in den Sinn, als ich diese Frage stellte. Dies ist etwas komplizierter, da jedes Sprite, das diese Art von Komposit-Textur verwendet, eine komplexere Zeichenlogik benötigt. Vielen Dank für diesen tollen Vorschlag!
Zack The Human
Ja, Sie benötigen auch x Pässe pro Sprite, was zu einer erheblichen Komplexität führen kann. In Anbetracht der heutigen Hardware, die normalerweise mindestens grundlegende Pixel-Shader (sogar Netbook-GPUs) unterstützt, würde ich stattdessen diese verwenden. Es kann ordentlich sein, wenn Sie nur bestimmte Dinge einfärben möchten, z. B. Statusbalken oder Symbole.
Mario
3

"Nicht alle Farben des Sprites ändern sich, nur bestimmte."

Alte Spiele machten Palettentricks, weil das alles war, was sie hatten. Heutzutage können Sie mehrere Texturen verwenden und auf verschiedene Arten kombinieren.

Sie können eine separate Graustufenmasken-Textur erstellen, mit der Sie entscheiden, welche Pixel die Farbe ändern. Weiße Bereiche in der Textur erhalten die volle Farbmodifikation und schwarze Bereiche bleiben unverändert.

In Shader-Sprache:

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

Oder Sie können die Mehrfachtexturierung mit festen Funktionen mit verschiedenen Texturkombinierermodi verwenden.

Es gibt offensichtlich viele verschiedene Wege, wie Sie dies erreichen können. Sie können Masken für verschiedene Farben in jeden der RGB-Kanäle einfügen oder die Farben durch Farbtonverschiebung in einem HSV-Farbraum modulieren.

Eine andere, vielleicht einfachere Möglichkeit besteht darin, das Sprite einfach mit einer separaten Textur für jede Komponente zu zeichnen. Eine Textur für die Haare, eine für die Rüstung, eine für die Stiefel usw. Jede Farbe ist separat tönbar.

ccxvii
quelle
2

Erstens wird es allgemein als Radfahren (Palettenradfahren) bezeichnet, was Ihrem Google-fu möglicherweise hilft.

Dies hängt genau davon ab, welche Effekte Sie erzeugen möchten und ob Sie sich mit statischen 2D-Inhalten oder dynamischen 3D-Inhalten beschäftigen (dh, Sie möchten nur mit Spites / Texturen arbeiten oder eine ganze 3D-Szene rendern und diese dann auf Paletten tauschen). Auch wenn Sie sich auf eine Reihe von Farben beschränken oder Vollfarben verwenden.

Ein vollständigerer Ansatz unter Verwendung von Shadern wäre, tatsächlich farbindizierte Bilder mit einer Palettentextur zu erzeugen. Erstellen Sie eine Textur, aber anstatt Farben zu haben, verwenden Sie eine Farbe, die einen Index zum Nachschlagen darstellt, und verwenden Sie dann eine andere Textur von Farben, nämlich die Platte. Beispielsweise könnte Rot die t-Koordinate der Texturen und Grün die s-Koordinate sein. Verwenden Sie einen Shader, um die Suche durchzuführen. Und Animieren / Ändern Sie die Farben dieser Palettentextur. Dies kommt dem Retro-Effekt sehr nahe, funktioniert jedoch nur für statische 2D-Inhalte wie Sprites oder Texturen. Wenn Sie echte Retro-Grafiken erstellen, können Sie möglicherweise tatsächlich indizierte Farbsprites erstellen und etwas schreiben, um sie und die Paletten zu importieren.

Sie sollten auch herausfinden, ob Sie eine "globale" Palette verwenden. So wie alte Hardware funktionieren würde, weniger Speicher für Paletten, da Sie nur eine benötigen, aber schwerer zu produzieren sind, da Sie die Palette über alle Bilder hinweg synchronisieren müssen) oder jedem Bild eine eigene Palette zuweisen müssen. Das würde Ihnen mehr Flexibilität geben, aber mehr Speicherplatz verbrauchen. Natürlich können Sie beides tun, aber Sie können auch nur Paletten für die Dinge verwenden, die Sie mit dem Farbrad fahren. Überlegen Sie auch, inwieweit Sie Ihre Farben nicht einschränken möchten. In alten Spielen werden beispielsweise 256 Farben verwendet. Wenn Sie eine Textur verwenden, erhalten Sie die Anzahl der Pixel, sodass Sie eine 256 * 256 erhalten

Wenn Sie nur einen billigen Fake-Effekt wie fließendes Wasser wünschen, können Sie eine zweite Textur erstellen, die eine Graustufenmaske für den Bereich enthält, den Sie ändern möchten. Verwenden Sie dann die Mastertexturen, um den Farbton / die Sättigung / die Helligkeit um den Betrag im zu ändern Graustufenmaske Textur. Sie könnten sogar fauler sein und einfach den Farbton / die Helligkeit / die Sättigung von irgendetwas in einem Farbbereich ändern.

Wenn Sie es in 3D benötigen würden, könnten Sie versuchen, das indizierte Bild im laufenden Betrieb zu generieren, aber es wäre langsam, da Sie die Palette nachschlagen müssten, und Sie wären wahrscheinlich auch in der Farbpalette eingeschränkt.

Andernfalls könnten Sie es einfach im Shader vortäuschen. Wenn color == swapping color ist, tauschen Sie es aus. Das wäre langsam und würde nur mit einer begrenzten Anzahl von Farbwechseln funktionieren, sollte aber keine der heutigen Hardware belasten, insbesondere wenn Sie sich nur mit 2D-Grafiken befassen und immer markieren können, welche Sprites Farbwechsel aufweisen und einen anderen Shader verwenden.

Ansonsten fälsche sie wirklich, mache ein paar animierte Texturen für jeden Zustand.

Hier ist eine großartige Demo des Palettenradfahrens in HTML5 .

David C. Bishop
quelle
0

Ich glaube, wenns schnell genug sind, um den Farbraum [0 ... 1] in zwei Teile zu unterteilen:

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

Auf diese Weise werden Farbwerte zwischen 0 und 0,5 für normale Farbwerte reserviert; und 0,5 - 1,0 sind für "modulierte" Werte reserviert, wobei 0,5 - 1,0 jedem vom Benutzer ausgewählten Bereich zugeordnet sind.

Nur die Farbauflösung wird von 256 Werten pro Pixel auf 128 Werte gekürzt.

Wahrscheinlich kann man integrierte Funktionen wie max (color-0.5f, 0.0f) verwenden, um die ifs vollständig zu entfernen (um sie gegen Multiplikationen mit Null oder Eins zu tauschen ...).

Aki Suihkonen
quelle