Tworzenie efektu zamiany palet w stylu retro w OpenGL

37

Pracuję nad grą podobną do Megamana , w której muszę zmienić kolor niektórych pikseli w czasie wykonywania. Dla porównania : w Megaman, kiedy zmieniasz wybraną broń, wówczas zmienia się paleta głównego bohatera, aby odzwierciedlić wybraną broń. Nie wszystkie kolory duszka zmieniają się, tylko niektóre z nich się zmieniają .

Ten rodzaj efektu był powszechny i ​​dość łatwy do zrobienia w NES, ponieważ programista miał dostęp do palety i logicznego mapowania między pikselami i indeksami palety. Jednak na nowoczesnym sprzęcie jest to nieco trudniejsze, ponieważ koncepcja palet nie jest taka sama.

Wszystkie moje tekstury są 32-bitowe i nie używam palet.

Znam dwa sposoby osiągnięcia pożądanego efektu, ale jestem ciekawy, czy istnieją lepsze sposoby na osiągnięcie tego efektu z łatwością. Dwie znane mi opcje to:

  1. Użyj modułu cieniującego i napisz GLSL, aby wykonać zachowanie „zamiany palet”.
  2. Jeśli moduły cieniujące nie są dostępne (powiedzmy, ponieważ karta graficzna ich nie obsługuje), możliwe jest sklonowanie „oryginalnych” tekstur i wygenerowanie różnych wersji ze wstępnie zastosowanymi zmianami kolorów.

Idealnie chciałbym użyć modułu cieniującego, ponieważ wydaje się prosty i wymaga niewiele dodatkowej pracy w przeciwieństwie do metody powielonej tekstury. Martwię się, że powielanie tekstur tylko w celu zmiany koloru marnuje pamięć VRAM - czy nie powinienem się tym przejmować?

Edycja : Skończyłem przy użyciu techniki przyjętej odpowiedzi i tutaj jest mój moduł cieniujący w celach informacyjnych.

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

Obie tekstury są 32-bitowe, jedna jest używana jako tabela przeglądowa zawierająca kilka palet, które są tego samego rozmiaru (w moim przypadku 6 kolorów). Używam czerwonego kanału piksela źródłowego jako indeksu do tabeli kolorów. To zadziałało jak urok dla zamiany palet podobnych do Megamana!

Zack The Human
źródło

Odpowiedzi:

41

Nie martwiłbym się marnowaniem pamięci VRAM na kilka tekstur postaci.

Dla mnie skorzystanie z opcji 2. (z różnymi teksturami lub różnymi przesunięciami UV, jeśli to pasuje) to droga: bardziej elastyczny, oparty na danych, mniejszy wpływ na kod, mniej błędów, mniej zmartwień.


Odkładając to na bok, jeśli zaczniesz gromadzić mnóstwo postaci z mnóstwem animacji sprite w pamięci, być może możesz zacząć korzystać z tego, co zaleca OpenGL , zrób to sam:

Paletowane tekstury

Obsługa rozszerzenia EXT_paletted_texture została odrzucona przez głównych dostawców GL. Jeśli naprawdę potrzebujesz paletowanych tekstur na nowym sprzęcie, możesz użyć shaderów, aby osiągnąć ten efekt.

Przykład modułu cieniującego:

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

Jest to po prostu próbkowanie ColorTable(paleta w RGBA8), przy użyciu MyIndexTexture(8-bitowa kwadratowa tekstura w indeksowanych kolorach). Po prostu odtwarza sposób działania palet w stylu retro.


W przytoczonym przykładzie użyto dwóch sampler2D, w których można użyć jednego sampler1D+ jednego sampler2D. Podejrzewam, że dzieje się tak ze względu na kompatybilność ( brak jednowymiarowych tekstur w OpenGL ES ) ... Ale nieważne, w przypadku OpenGL na komputery można to uprościć:

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
}

Palettejest jednowymiarową teksturą „prawdziwych” kolorów (np. GL_RGBA8) i IndexedColorTexturejest dwuwymiarową teksturą kolorów indeksowanych (zwykle GL_R8daje 256 wskaźników). Aby je utworzyć, istnieje kilka narzędzi do tworzenia i formatów plików graficznych , które obsługują paletowane obrazy, powinno być możliwe znalezienie tego, który odpowiada Twoim potrzebom.

Laurent Couvidou
źródło
2
Dokładnie odpowiedź, której udzieliłbym, tylko bardziej szczegółowa. Dałbym +2 gdybym mógł.
Ilmari Karonen,
Mam pewne problemy z tym, żeby mi to działało, głównie dlatego, że nie rozumiem, w jaki sposób określa się indeks w kolorze - czy mógłbyś rozwinąć to nieco bardziej?
Zack The Human
Prawdopodobnie powinieneś przeczytać o indeksowanych kolorach . Twoja tekstura staje się tablicą 2D wskaźników, które odpowiadają kolorom w palecie (zazwyczaj jednowymiarowej teksturze). Zmienię swoją odpowiedź, aby uprościć przykład podany na stronie OpenGL.
Laurent Couvidou
Przepraszam, nie było jasne w moim oryginalnym komentarzu. Znam indeksowane kolory i sposób ich działania, ale nie jestem pewien, jak działają w kontekście modułu cieniującego lub 32-bitowej tekstury (ponieważ nie są one indeksowane - prawda?).
Zack The Human
Chodzi o to, że tutaj masz jedną 32-bitową paletę zawierającą 256 kolorów i jedną teksturę 8-bitowych wskaźników (od 0 do 255). Zobacz moją edycję;)
Laurent Couvidou
10

Są dwa sposoby, aby to zrobić.

Sposób nr 1: GL_MODULATE

Możesz zrobić duszka w odcieniach szarości i GL_MODULATEtekstury, gdy jest rysowany jednym jednolitym kolorem.

Na przykład,

wprowadź opis zdjęcia tutaj

wprowadź opis zdjęcia tutaj

Nie zapewnia to jednak dużej elastyczności, w zasadzie można tylko jaśniej i ciemniej o tym samym odcieniu.

Sposób 2: ifStwierdzenie (źle wpływa na wydajność)

Inną rzeczą, którą możesz zrobić, jest pokolorowanie tekstur za pomocą kilku kluczowych wartości obejmujących R, G i B. Na przykład SO pierwszy kolor to (1,0,0), drugi kolor to (0,1,0), trzeci kolor to (0,0,1).

Deklarujesz kilka takich mundurków

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

Następnie w module cieniującym ostateczny kolor piksela jest podobny

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

color1, color2, color3, Są mundury, więc mogą być ustawione przed tras cieniujących (w zależności od tego, co „garnitur” Megaman jest), wówczas shader prostu zrobi resztę.

Możesz także użyć ifinstrukcji, jeśli potrzebujesz więcej kolorów, ale musiałbyś mieć pewność, że kontrole równości działają poprawnie (tekstury zwykle mają wartość R8G8B8od 0 do 255 każda w pliku tekstur, ale w module cieniującym masz zmiennoprzecinkowe rgba)

Sposób nr 3: Po prostu nadmiarowo przechowuj różne kolorowe obrazy na mapie duszka

wprowadź opis zdjęcia tutaj

Może to być najłatwiejszy sposób, jeśli nie próbujesz szaleńczo oszczędzać pamięci

Bobobobo
źródło
2
# 1 może być schludne, ale # 2 i # 3 to przerażająco złe rady. GPU jest w stanie indeksować na podstawie map (których zasadniczo jest paleta) i robi to lepiej niż obie te sugestie.
Skrylar,
6

Używałbym shaderów. Jeśli nie chcesz ich używać (lub nie możesz), wybrałbym jedną teksturę (lub część tekstury) na kolor i używam tylko czystej bieli. Umożliwi to zabarwienie tekstury (np. Przez glColor()) bez martwienia się o tworzenie dodatkowych tekstur dla kolorów. Możesz nawet zamieniać kolory w biegu bez dodatkowej pracy z teksturami.

Mario
źródło
To całkiem niezły pomysł. Właściwie to kiedyś o tym myślałem, ale nie przyszło mi do głowy, kiedy zadałem to pytanie. Jest w tym dodatkowa złożoność, ponieważ każdy duszek, który korzysta z tego rodzaju tekstury złożonej, będzie potrzebował bardziej złożonej logiki rysowania. Dzięki za tę niesamowitą sugestię!
Zack The Human
Tak, będziesz potrzebować również x przejść na duszka, co może nieco zwiększyć złożoność. Biorąc pod uwagę dzisiejszy sprzęt zwykle obsługujący przynajmniej podstawowe shadery pikseli (nawet GPU netbooków), wybrałbym je zamiast tego. Może być fajnie, jeśli chcesz tylko odcieńować określone rzeczy, np. Paski zdrowia lub ikony.
Mario,
3

„Nie wszystkie kolory duszka się zmieniają, tylko niektóre z nich się zmieniają”.

Stare gry wykonywały sztuczki z paletą, ponieważ to wszystko, co mieli. Obecnie możesz używać wielu tekstur i łączyć je na różne sposoby.

Możesz utworzyć osobną teksturę maski w skali szarości, za pomocą której zdecydujesz, które piksele zmienią kolor. Białe obszary tekstury otrzymują pełną modyfikację kolorów, a czarne obszary pozostają niezmienione.

W shader languague:

vec3 baseColor = texture (BaseSampler, att_TexCoord) .rgb;
liczba zmiennoprzecinkowa = tekstura (MaskSampler, att_TexCoord) .r;
vec3 color = mix (baseColor, baseColor * ModulationColor, ilość);

Lub możesz użyć funkcji teksturowania o stałej funkcji z różnymi trybami łączenia tekstur.

Istnieje oczywiście wiele różnych sposobów, w jaki można tego dokonać; możesz umieścić maski dla różnych kolorów w każdym z kanałów RGB lub modulować kolory poprzez przesunięcie odcienia w przestrzeni kolorów HSV.

Inną, być może prostszą opcją jest po prostu narysowanie duszka przy użyciu osobnej tekstury dla każdego elementu. Jedna tekstura na włosy, jedna na zbroję, jedna na buty itp. Każdy kolor można barwić osobno.

ccxvii
źródło
2

Po pierwsze, jest ogólnie określany jako jazda na rowerze (jazda na paletach), więc może to pomóc Twojemu Google-fu.

Będzie to zależeć od dokładnie tego, jakie efekty chcesz uzyskać, a jeśli masz do czynienia ze statyczną zawartością 2D lub dynamicznymi rzeczami 3D (tj. Czy chcesz po prostu poradzić sobie ze spite / teksturami, czy też renderować całą scenę 3D, a następnie zamień paletę). Także jeśli ograniczasz się do wielu kolorów lub używasz pełnego koloru.

Bardziej kompletnym podejściem, wykorzystującym shadery, byłoby rzeczywiście tworzenie obrazów z indeksami kolorów z teksturą palety. Stwórz teksturę, ale zamiast kolorów, miej kolor reprezentujący indeks do wyszukania, a następnie inną teksturę kolorów, którą jest płyta. Na przykład czerwony może być współrzędną tekstury, a zielony może być współrzędną. Wykonaj wyszukiwanie za pomocą modułu cieniującego. I animuj / modyfikuj kolory tekstury palety. Byłoby to dość zbliżone do efektu retro, ale działałoby tylko w przypadku statycznych treści 2D, takich jak duszki lub tekstury. Jeśli robisz prawdziwą grafikę retro, możesz być w stanie wygenerować sprite'y z indeksowanymi kolorami i napisać coś, co zaimportuje je i palety.

Będziesz także chciał poćwiczyć, jeśli zamierzasz używać palety „globalnej”. Podobnie jak stary sprzęt będzie działał, mniej pamięci na paletach, ponieważ potrzebujesz tylko jednej, ale trudniej jest stworzyć swoją grafikę, ponieważ musisz zsynchronizować paletę na wszystkich obrazach) lub nadać każdemu obrazowi własną paletę. Dałoby to większą elastyczność, ale zużywa więcej pamięci. Oczywiście możesz zrobić obie rzeczy, a także możesz używać palet tylko do rzeczy, które jeździsz kolorem. Sprawdź także, do jakiego stopnia nie chcesz ograniczać kolorów, na przykład stare gry używałyby 256 kolorów. Jeśli używasz tekstury, otrzymasz liczbę pikseli, więc 256 * 256 da ci

Jeśli chcesz po prostu taniego fałszywego efektu, takiego jak płynąca woda, możesz utworzyć drugą teksturę, która zawiera maskę w skali szarości w obszarze, który chcesz zmodyfikować, a następnie użyj tekstur z masterem, aby zmienić odcień / nasycenie / jasność o wartość w tekstura maski w skali szarości. Możesz być jeszcze bardziej leniwy i po prostu zmienić odcień / jasność / nasycenie czegokolwiek w zakresie kolorów.

Jeśli potrzebujesz go w pełnym 3D, możesz spróbować wygenerować indeksowany obraz w locie, ale byłby on wolny, ponieważ musiałbyś spojrzeć na paletę, prawdopodobnie byłbyś również ograniczony w zakresie kolorów.

W przeciwnym razie możesz po prostu sfałszować go w module cieniującym, jeśli color == zamień kolor, zamień go. Byłoby to powolne i działałoby tylko przy ograniczonej liczbie zamienianych kolorów, ale nie powinno obciążać żadnego dzisiejszego sprzętu, szczególnie jeśli masz do czynienia z grafiką 2D i zawsze możesz zaznaczyć, które duszki mają zamianę kolorów i użyć innego modułu cieniującego.

W przeciwnym razie naprawdę sfałszuj je, utwórz kilka animowanych tekstur dla każdego stanu.

Oto świetna prezentacja cyklu jazdy na paletach wykonana w HTML5 .

David C. Bishop
źródło
0

Uważam, że jeśli są wystarczająco szybkie, aby przestrzeń kolorów [0 ... 1] można było podzielić na dwie części:

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

W ten sposób wartości kolorów od 0 do 0,5 są zarezerwowane dla normalnych wartości kolorów; i 0,5 - 1,0 są zarezerwowane dla wartości „modulowanych”, gdzie 0,5..1,0 mapuje na dowolny zakres wybrany przez użytkownika.

Tylko rozdzielczość kolorów jest obcięta z 256 wartości na piksel do 128 wartości.

Prawdopodobnie można użyć wbudowanych funkcji, takich jak max (kolor-0,5f, 0,0f), aby całkowicie usunąć ifs (zamieniając je na mnożenie przez zero lub jeden ...).

Aki Suihkonen
źródło