Jak mogę powielić ograniczenia kolorów NES za pomocą modułu cieniującego piksele HLSL?

13

Ponieważ tryb 256 kolorów jest przestarzały i nie jest już obsługiwany w trybie Direct3D, wpadłem na pomysł, aby zamiast tego użyć modułu cieniującego piksele do symulacji palety NES wszystkich możliwych kolorów, aby blaknące obiekty i inne elementy nie miały płynnego wygaszania z kanałami alfa . (Wiem, że obiekty nie mogły tak naprawdę zanikać w systemie NES, ale mam wszystkie obiekty, które zanikają i zanikają na jednolitym czarnym tle, co byłoby możliwe przy zamianie palet. Ponadto ekran zanika i zanika po wstrzymaniu co, jak wiem, było możliwe również przy zamianie palet, jak miało to miejsce w kilku grach Mega Man.) Problem polega na tym, że nie wiem nic o shaderach HLSL.

Jak mam to zrobić?

Michael Allen Crain
źródło
Poniższy komentarz opisuje podejście wykorzystujące moduł cieniujący piksele, ale po prostu takie ograniczenie kolorów można osiągnąć za pomocą odpowiedniego przewodnika po stylu dla zespołu artystycznego.
Evan
czy chcesz po prostu zmniejszyć paletę, czy też chcesz wykonać dithering (możliwe również za pomocą shaderów). w przeciwnym razie odpowiedź zezba9000 wydaje mi się najlepsza
tigrou
Chcę po prostu zredukować możliwe kolory do palety NES. Potrzebowałem go głównie do przechwytywania efektów kanału alfa i innych efektów atramentu, ponieważ w trybie 256 kolorów łapie je, ale Direct3D nie obsługuje już trybu 256 kolorów, więc efekty są wygładzone w prawdziwych kolorach.
Michael Allen Crain,

Odpowiedzi:

4

W module cieniującym piksele można przekazać teksturę Texture2D 256 x 256 z kolorami palet ustawionymi poziomo w rzędzie. Następnie tekstury NES zostaną przekonwertowane na Direct3D Texture2D z każdym pikselem przekonwertowanym na wartość indeksu 0-255. Istnieje format tekstury, który używa tylko czerwonej wartości w D3D9. Zatem tekstura zajmowałaby tylko 8 bitów na piksel, ale dane, które wchodzą do modułu cieniującego, wynosiłyby od 0-1.

// Moduł cieniujący piksele może wyglądać tak:

float4 mainPS() : COLOR0
{
    float4 colorIndex = tex2D(MainTexture, uv);
    float4 palletColor = tex2D(PalletTexture, float2(colorIndex.x, 0);
    return palletColor;
}

EDYCJA: Bardziej poprawnym sposobem byłoby dodanie we wszystkich mieszanych wersjach palet wyrównanych pionowo w teksturze i odniesienie ich do wartości „alpha” koloru colorIndex:

float4 mainPS() : COLOR0
{
    float4 colorIndex = tex2D(MainTexture, uv);
    float4 palletColor = tex2D(PalletTexture, float2(colorIndex.x, colorIndex.a);
    return palletColor;
}

Trzecim sposobem byłoby po prostu sfałszowanie niskiej jakości zanikania NES poprzez przyciemnianie koloru alfa:

float4 mainPS() : COLOR0
{
    float4 colorIndex = tex2D(MainTexture, uv);
    float4 palletColor = tex2D(PalletTexture, float2(colorIndex.x, 0);
    palletColor.a = floor(palletColor.a * fadeQuality) / fadeQuality;
    //NOTE: If fadeQuality where to equal say '3' there would be only 3 levels of fade.
    return palletColor;
}
zezba9000
źródło
1
Masz na myśli 256x1, a nie 256x256, zgaduję? Pamiętaj też o wyłączeniu filtrowania dwuliniowego dla obu tekstur, w przeciwnym razie będziesz mieszał się z „wpisami” palety.
Nathan Reed
W tym schemacie nie można również wykonywać żadnego oświetlenia ani matematyki na wartościach tekstury, ponieważ wszystko, co zrobisz, może po prostu wysłać cię do zupełnie innej części palety.
Nathan Reed
@Nathan Reed Możesz zrobić oświetlenie. Po prostu obliczasz światło na „palletColor”, zanim zwrócisz jego wartość koloru. Możesz także utworzyć teksturę 256x1, ale sprzęt może i tak użyć 256x256, a 256x256 to najszybszy rozmiar do użycia na większości urządzeń. Chyba że to zmieniło idk.
zezba9000,
Jeśli palisz po paletyzacji, prawdopodobnie nie będzie to nawet jeden z kolorów NES. Jeśli tego właśnie chcesz, to w porządku - ale to nie brzmi jak to, o co pytało pytanie. Jeśli chodzi o 256, jest to możliwe, jeśli masz GPU ponad 10 lat ... ale cokolwiek nowszego będzie z pewnością obsługiwać prostokątne tekstury 2-bitowe, takie jak 256x1.
Nathan Reed
Jeśli po prostu nie chce płynnego wygaszania, mógłby zrobić: „palletColor.a = (float) floor (palletColor.a * fadeQuality) / fadeQuality;” Mógłby nawet zrobić to samo co metoda tekstury 3D, ale z teksturą 2D, zmieniając linię 4 na: „float4 palletColor = tex2D (PalletTexture, float2 (colorIndex.x, colorIndex.a);” Kanał alfa indeksuje tylko inną paletę warstw na pojedynczej fakturze 2D
zezba9000,
3

Jeśli tak naprawdę nie obchodzi Cię użycie pamięci tekstur (a pomysł, aby uzyskać niesamowitą ilość pamięci tekstur, aby uzyskać retro wygląd, ma pewien przewrotny urok), możesz zbudować teksturę 3D 256x256x256 3D, odwzorowującą wszystkie kombinacje RGB na wybraną paletę . Następnie w twoim module cieniującym staje się tylko jedną linią kodu na końcu:

return tex3d (paletteMap, color.rgb);

Może nawet nie być konieczne przejście do 256x256x256 - coś takiego jak 64x64x64 może być wystarczające - i możesz nawet zmieniać mapowania palet w locie za pomocą tej metody (ale przy znacznych kosztach ze względu na dużą dynamiczną aktualizację tekstur).

Maximus Minimus
źródło
Może to być najlepsza metoda, jeśli chcesz zastosować oświetlenie, mieszanie alfa lub inną matematykę na swoich teksturach, a następnie przyciągnąć wynik końcowy do najbliższego koloru NES. Mógłbyś wstępnie obliczyć teksturę woluminu za pomocą obrazu referencyjnego takiego jak ten ; z ustawieniem filtrowania najbliższego sąsiada, możesz uciec od czegoś tak małego jak 16x16x16, co wcale nie byłoby dużo pamięci.
Nathan Reed
1
To fajny pomysł, ale będzie znacznie wolniejszy i nie będzie tak kompatybilny ze starszym sprzętem. Tekstury 3D będą próbkować znacznie wolniej niż 2D, a tekstury 3D będą wymagały znacznie większej przepustowości, co jeszcze bardziej spowolni. Nowsze karty to nie będzie miało znaczenia, ale nadal.
zezba9000,
1
Zależy, ile masz lat. Wierzę, że obsługa tekstur 3D wraca do oryginalnej GeForce 1 - czy ma 14 lat?
Maximus Minimus
Lol Cóż, może tak, nigdy nie używał tych kart, myślę, że myślałem bardziej zgodny z procesorami graficznymi telefonu. Obecnie istnieje wiele celów, które nie mają obsługi tekstur 3D. Ale ponieważ używa D3D, a nie OpenGL, nawet WP8 to obsługuje. Mimo to tekstura 3D zajmowałaby większą przepustowość niż 2D.
zezba9000,
1

(oba moje rozwiązania działają tylko wtedy, gdy nie obchodzi Cię zmiana palet w locie za pomocą shaderów)

Możesz użyć dowolnego rodzaju tekstury i wykonać proste obliczenia na module cieniującym. Sztuczka polega na tym, że masz więcej informacji o kolorze niż potrzebujesz, więc po prostu pozbędziesz się informacji, których nie chcesz.

8- bitowy kolor ma formalną RRRGGGBB . Co daje 8 odcieni czerwieni i zieleni oraz 4 odcienie niebieskiego.

To rozwiązanie będzie działać dla dowolnych tekstur w formacie RGB (A).

float4 mainPS() : COLOR0
{
    const float oneOver7 = 1.0 / 8.0;
    const float oneOver3 = 1.0 / 3.0;

    float4 color = tex2D(YourTexture, uvCoord);
    float R = floor(color.r * 7.99) * oneOver7;
    float G = floor(color.g * 7.99) * oneOver7;
    float B = floor(color.b * 3.99) * oneOver3;

    return float4(R, G, B, 1);
}

Uwaga: napisałem to z góry mojej głowy, ale jestem pewien, że to się dla ciebie skompiluje i zadziała


Inną możliwością byłoby użycie formatu tekstury D3DFMT_R3G3B2 , który jest faktycznie taki sam jak grafika 8- bitowa . Kiedy wstawiasz dane do tej tekstury, możesz użyć prostej operacji bitowej na bajt.

tex[index] = (R & 8) << 5 + ((G & 8) << 2) + (B & 4);
Notabene
źródło
To dobry przykład. Tylko myślę, że musiał wymieniać paletę kolorów. W takim przypadku musiałby użyć czegoś takiego jak mój przykład.
zezba9000,
To wcale nie jest paleta kolorów NES. NES nie używał 8-bitowego RGB, użył stałej palety około 50 do 60 kolorów w przestrzeni YPbPr.
sam hocevar