Ersetzen von Paletten im Spiel mithilfe von Shadern

In diesem Blog zeige ich Ihnen meine Lieblingstechnik, die ich in meinem Vagabond-Spiel aktiv verwende: das Ersetzen von Paletten.

Palettentausch ist eine Änderung der Texturpalette. In dem Artikel implementieren wir es mit Shadern. Früher war dies eine nützliche Technik, mit der Sie Ressourcen variabel gestalten konnten, ohne unnötig Speicherplatz zu verschwenden. Heute wird es bei der Erstellung von Prozeduren verwendet, um neue Ressourcen zu erstellen.



Bildvorbereitung


Der erste Schritt besteht darin, die Bilder für das Ersetzen der Paletten vorzubereiten. In einer Bitmap enthält jedes Pixel eine Farbe. Stattdessen muss der Farbindex in der Palette enthalten sein. Aus diesem Grund werden wir die Bildstruktur (Bereiche der gleichen Farbe) von den echten Farben trennen.

Tatsächlich unterstützen einige Bildformate diese Speichermethode. Das PNG-Format bietet beispielsweise die Möglichkeit, indizierte Farben zu speichern. Leider erzeugen viele Bildbibliotheken eine Reihe von Farben, auch wenn das Bild im indizierten Modus gespeichert wurde. Dies gilt auch für die von mir verwendete SFML-Bibliothek. Im Inneren wird stb_image verwendet , wodurch die Palette der Bilder automatisch "entfernt" wird, d. H. Ersetzt Indizes durch die entsprechende Palettenfarbe.

Um dieses Problem zu vermeiden, müssen Sie das Bild und die Palette daher separat speichern. Das Bild wird in Graustufen aufgezeichnet, und die Graustufe jedes Pixels entspricht seinem Farbindex in der Palette.

Hier ist ein Beispiel dafür, was wir erwarten:


Um dies zu erreichen, verwende ich eine kleine Python-Funktion, die die Pillow- Bibliothek verwendet:

import io import numpy as np from PIL import Image def convert_to_indexed_image(image, palette_size): # Convert to an indexed image indexed_image = image.convert('RGBA').convert(mode='P', dither='NONE', colors=palette_size) # Be careful it can remove colors # Save and load the image to update the info (transparency field in particular) f = io.BytesIO() indexed_image.save(f, 'png') indexed_image = Image.open(f) # Reinterpret the indexed image as a grayscale image grayscale_image = Image.fromarray(np.asarray(indexed_image), 'L') # Create the palette palette = indexed_image.getpalette() transparency = list(indexed_image.info['transparency']) palette_colors = np.asarray([[palette[3*i:3*i+3] + [transparency[i]] \ for i in range(palette_size)]]).astype('uint8') palette_image = Image.fromarray(palette_colors, mode='RGBA') return grayscale_image, palette_image 

Zunächst konvertiert die Funktion das Bild in den Palettenmodus. Sie interpretiert es dann als Graustufenbild neu. Ruft dann die Palette ab. Nichts kompliziertes, die Hauptarbeit wird von der Pillow-Bibliothek erledigt.

Shader


Nachdem wir die Bilder vorbereitet haben, können wir einen Shader schreiben, der die Paletten ersetzt. Es gibt zwei Strategien zum Übertragen einer Palette in einen Shader: Sie können eine Textur oder ein homogenes Array verwenden. Ich fand heraus, dass es einfacher ist, ein homogenes Array zu verwenden, also habe ich es verwendet.

Hier ist mein Shader, den ich in GLSL geschrieben habe, aber ich denke, dass er leicht in eine andere Sprache übertragen werden kann, um Shader zu erstellen:

 #version 330 core in vec2 TexCoords; uniform sampler2D Texture; uniform vec4 Palette[32]; out vec4 Color; void main() { Color = Palette[int(texture(Texture, TexCoords).r * 255)]; } 

Wir verwenden nur eine Textur, um den roten Kanal des aktuellen Pixels zu lesen. Der rote Kanal ist ein Gleitkommawert im Bereich von 0 bis 1, daher multiplizieren wir ihn mit 255 und konvertieren ihn in int , um den ursprünglichen Graupegel von 0 bis 255 zu erhalten, der im Bild gespeichert ist. Als nächstes verwenden wir es, um die Farbe aus der Palette zu erhalten.

Die Animation am Anfang des Artikels stammt aus Screenshots im Spiel, in denen ich die folgenden Paletten verwende, um die Farbe des Körpers des Charakters zu ändern:

Source: https://habr.com/ru/post/de480836/


All Articles