Wie kann ich die Farbbeschränkungen des NES mit einem HLSL-Pixelshader replizieren?

13

Da der 256-Farben-Modus im Direct3D-Modus nicht mehr unterstützt wird, kam mir die Idee, stattdessen einen Pixel-Shader zu verwenden, um die NES-Palette aller möglichen Farben zu simulieren, damit Objekte ausgeblendet werden und bei Alphakanälen keine glatten Ausblendungen auftreten . (Ich weiß, dass Objekte auf dem NES nicht wirklich ausgeblendet werden konnten, aber ich habe alle Objekte, die eingeblendet und ausgeblendet werden, auf einem festen schwarzen Hintergrund, der mit Palettentausch möglich wäre. Außerdem wird der Bildschirm eingeblendet und ausgeblendet, wenn Sie anhalten Ich weiß, dass dies auch mit Palettentausch möglich war, wie es in einigen Mega-Man-Spielen der Fall war.) Das Problem ist, dass ich so gut wie nichts über HLSL-Shader weiß.

Wie mache ich es?

Michael Allen Crain
quelle
Der folgende Kommentar beschreibt die Vorgehensweise mit dem Pixel-Shader. Eine solche Farbbeschränkung kann jedoch mit einem geeigneten Style-Guide für Ihr Art-Team erreicht werden.
Evan
Wollen Sie nur die Palette verkleinern oder auch das Dithering ausführen (auch mit Shadern möglich)? ansonsten scheint mir die
antwort von
Ich möchte nur die möglichen Farben auf die NES-Palette reduzieren. Ich brauchte es hauptsächlich, um Alphakanaleffekte und andere Tinteneffekte einzufangen, da es sie im 256-Farben-Modus einfängt, aber Direct3D den 256-Farben-Modus nicht mehr unterstützt, sodass die Effekte in echten Farben geglättet werden.
Michael Allen Crain

Antworten:

4

Im Pixel-Shader können Sie eine 256x256-Textur2D mit den Palettenfarben übergeben, die alle horizontal in einer Reihe angeordnet sind. Dann würden Ihre NES-Texturen in Direct3D-Texture2Ds konvertiert, wobei jedes Pixel in einen Indexwert von 0-255 konvertiert würde. Es gibt ein Texturformat, das nur den roten Wert in D3D9 verwendet. Die Textur würde also nur 8 Bit pro Pixel einnehmen, aber die Daten, die in den Shader gelangen, wären von 0-1.

// Der Pixel Shader könnte so aussehen:

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

BEARBEITEN: Richtiger wäre es, alle Mischpalettenversionen, die Sie benötigen, vertikal in der Textur auszurichten und mit dem Alpha-Wert Ihres colorIndex zu referenzieren:

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

Ein dritter Weg wäre, einfach die niedrige Fade-Qualität von NES zu fälschen, indem man die Alpha-Farbe schattiert:

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
quelle
1
Du meinst 256x1, nicht 256x256, vermute ich? Stellen Sie außerdem sicher, dass die bilineare Filterung für beide Texturen deaktiviert ist, da Sie sonst eine Überblendung zwischen den "Paletteneinträgen" erhalten.
Nathan Reed
Außerdem können Sie mit diesem Schema keinerlei Beleuchtungs- oder Berechnungsaufgaben für die Texturwerte ausführen, da Sie wahrscheinlich nur zu einem ganz anderen Teil der Palette weitergeleitet werden.
Nathan Reed
@ Nathan Reed Sie können Beleuchtung tun. Sie berechnen nur das Licht auf der "palletColor", bevor Sie den Farbwert zurückgeben. Sie könnten auch eine 256x1-Textur erstellen, aber die Hardware kann ohnehin nur 256x256 verwenden, und 256x256 ist die schnellste Größe, die auf den meisten Hardwarekomponenten verwendet werden kann. Es sei denn, das ist idk geändert.
Zezba9000
Wenn Sie nach dem Palettieren beleuchten, wird es wahrscheinlich nicht einmal mehr eine der NES-Farben sein. Wenn Sie das wollen, ist das in Ordnung - aber das hört sich nicht so an, wie die Frage es verlangt. Was die 256 betrifft, ist das möglich, wenn Sie eine GPU haben, die älter als 10 Jahre ist ... aber alles, was neuer ist, wird sicherlich rechteckige Power-of-2-Texturen wie 256x1 unterstützen.
Nathan Reed
Wenn er nur keine glatten Ausblendungen möchte, kann er Folgendes tun: "palletColor.a = (schwimmender) Boden (palletColor.a * fadeQuality) / fadeQuality;" Er könnte sogar das Gleiche tun wie die 3D-Texturmethode, jedoch mit einer 2D-Textur, indem er Zeile 4 wie folgt ändert: "float4 palletColor = tex2D (PalletTexture, float2 (colorIndex.x, colorIndex.a);" Der Alphakanal indiziert nur eine andere Palette Ebenen auf einer einzigen 2D-Textur
Zezba9000
3

Wenn Ihnen die Verwendung des Texturspeichers nicht wirklich wichtig ist (und die Idee, eine verrückte Menge an Texturspeicher zu verwenden, um einen Retro-Look zu erzielen, hat eine perverse Ausstrahlung), können Sie eine 3D-Textur mit 256 x 256 x 256 erstellen, die alle RGB-Kombinationen Ihrer ausgewählten Palette zuordnet . Dann wird es in Ihrem Shader nur eine Codezeile am Ende:

return tex3d (paletteMap, color.rgb);

Es ist möglicherweise nicht einmal erforderlich, bis zu 256x256x256 zu wechseln - so etwas wie 64x64x64 ist möglicherweise ausreichend - und Sie können sogar Palettenzuordnungen mit dieser Methode ändern (jedoch mit erheblichen Kosten aufgrund eines großen dynamischen Texturupdates).

Maximus Minimus
quelle
Dies ist möglicherweise die beste Methode, wenn Sie Ihre Texturen beleuchten, Alpha-Blending oder eine andere Art von Mathematik ausführen und das Endergebnis dann an der nächstgelegenen NES-Farbe ausrichten möchten. Sie können Ihre Volumentextur mit einem Referenzbild wie diesem vorberechnen . Wenn die Filterung auf "Nächster Nachbar" eingestellt ist, könnten Sie mit einer Größe von 16 x 16 x 16 davonkommen, was überhaupt nicht viel Arbeitsspeicher bedeutet.
Nathan Reed
1
Dies ist eine coole Idee, wird aber viel langsamer und nicht so kompatibel mit älterer Hardware sein. 3D-Texturen werden viel langsamer abgetastet als 2D-Texturen, und 3D-Texturen erfordern auch viel mehr Bandbreite, wodurch sie noch langsamer werden. Neuere Karten sind zwar unwichtig, aber immer noch.
Zezba9000
1
Kommt drauf an, wie alt du sein willst. Ich glaube, dass die 3D-Textur-Unterstützung auf die ursprüngliche GeForce 1 zurückgeht - sind 14 Jahre alt genug?
Maximus Minimus
Lol Nun ja, vielleicht haben sie diese Karten nie benutzt, ich schätze, ich habe mehr an Telefon-GPUs gedacht. Es gibt heutzutage viele Ziele, die keine 3D-Textur unterstützen. Aber weil er D3D und nicht OpenGL verwendet, wird dies auch von WP8 unterstützt. Dennoch würde eine 3D-Textur mehr Bandbreite beanspruchen als eine 2D-Textur.
Zezba9000
1

(Beide meiner Lösungen funktionieren nur, wenn Sie sich nicht darum kümmern, Paletten im laufenden Betrieb mit Shadern zu wechseln.)

Sie können jede Art von Textur verwenden und nur eine einfache Berechnung für einen Shader durchführen. Der Trick ist, dass Sie mehr Farbinformationen haben, als Sie benötigen. So werden Sie Informationen, die Sie nicht möchten, einfach los.

Die 8-Bit-Farbe ist in der Form RRRGGGBB . Das gibt Ihnen 8 Schattierungen von Rot und Grün und 4 Schattierungen von Blau.

Diese Lösung funktioniert für alle RGB (A) -Farbformat-Texturen.

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

Anmerkung: Ich habe das von oben geschrieben, aber ich bin mir wirklich sicher, dass es für Sie kompiliert und funktioniert


Eine andere Möglichkeit wäre die Verwendung des Texturformats D3DFMT_R3G3B2 , das tatsächlich mit 8-Bit-Grafiken identisch ist. Wenn Sie Daten in diese Textur einfügen, können Sie eine einfache Bitoperation pro Byte verwenden.

tex[index] = (R & 8) << 5 + ((G & 8) << 2) + (B & 4);
Notabene
quelle
Das ist ein gutes Beispiel. Nur ich denke, er musste in der Lage sein, die Farbpalette auszutauschen. In diesem Fall müsste er so etwas wie mein Beispiel verwenden.
zezba9000
Dies ist überhaupt nicht die NES-Farbpalette. Das NES verwendete kein 8-Bit-RGB, sondern eine feste Palette von etwa 50 bis 60 Farben im YPbPr-Bereich.
Sam Hocevar