Comment puis-je reproduire les limitations de couleurs de la NES avec un pixel shader HLSL?

13

Donc, comme le mode 256 couleurs est déprécié et n'est plus pris en charge en mode Direct3D, j'ai eu l'idée d'utiliser un pixel shader à la place pour simuler la palette NES de toutes les couleurs possibles afin que les objets en décoloration et ainsi de suite n'aient pas de fondus en douceur avec les canaux alpha . (Je sais que les objets ne peuvent pas vraiment disparaître sur le NES, mais j'ai tous les objets qui apparaissent et disparaissent sur un fond noir uni, ce qui serait possible avec l'échange de palette. En outre, l'écran apparaît et disparaît lorsque vous faites une pause ce que je sais aussi possible avec le changement de palette comme cela a été fait dans quelques jeux Mega Man.) Le problème est que je ne connais pratiquement rien des shaders HLSL.

Comment fait-on ça?

Michael Allen Crain
la source
Le commentaire ci-dessous décrit l'approche utilisant le pixel shader, mais simplement une limitation de couleur comme celle-ci pourrait être obtenue en utilisant un guide de style approprié pour votre équipe artistique.
Evan
voulez-vous simplement réduire la palette ou également effectuer un tramage (possible en utilisant des shaders aussi). sinon la réponse de zezba9000 me semble la meilleure
tigrou
Je veux juste réduire les couleurs possibles à la palette NES. J'en avais principalement besoin pour capturer les effets de canal alpha et d'autres effets d'encre car en mode 256 couleurs, il les attrape mais Direct3D ne prend plus en charge le mode 256 couleurs, donc les effets sont lissés en vraies couleurs.
Michael Allen Crain

Réponses:

4

Dans le pixel shader, vous pouvez passer dans un 256x256 Texture2D avec les couleurs de palette alignées horizontalement dans une rangée. Ensuite, vos textures NES seraient converties en Texture2Ds direct3D avec toujours des pixels convertis en une valeur d'index 0-255. Il existe un format de texture qui utilise uniquement la valeur rouge dans D3D9. Ainsi, la texture ne prendrait que 8 bits par pixel, mais les données qui entrent dans le shader seraient de 0 à 1.

// Le pixel shader pourrait ressembler à ceci:

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

EDIT: Une manière plus correcte serait d'ajouter toutes les versions de palette de mélange dont vous avez besoin alignées verticalement dans la texture et de les référencer avec la valeur alpha de votre colorIndex:

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

Une troisième façon serait de simuler la faible qualité de fondu NES en ombrant la couleur alpha:

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
la source
1
Tu veux dire 256x1, pas 256x256, je suppose? Assurez-vous également de désactiver le filtrage bilinéaire sur les deux textures, sinon vous obtiendrez un mélange entre les "entrées" de la palette.
Nathan Reed
De plus, vous ne pouvez pas faire n'importe quel type d'éclairage ou de calcul sur les valeurs de texture avec ce schéma, car tout ce que vous faites est susceptible de simplement vous envoyer vers une partie complètement différente de la palette.
Nathan Reed
@Nathan Reed Vous pouvez faire de l'éclairage. Vous calculez juste la lumière sur le "palletColor" avant de retourner sa valeur de couleur. Vous pouvez également créer une texture 256x1, mais le matériel peut juste sous le capot utiliser 256x256 de toute façon et 256x256 est la taille la plus rapide à utiliser sur la plupart des matériels. A moins que cela ne change d'idk.
zezba9000
Si vous faites de l'éclairage après la palettisation, ce ne sera probablement plus l'une des couleurs NES. Si c'est ce que vous voulez, c'est bien - mais cela ne ressemble pas à ce que la question demandait. En ce qui concerne le 256, c'est possible si vous avez un GPU de plus de 10 ans ... mais tout ce qui est plus récent prendra certainement en charge des textures rectangulaires de puissance de 2, comme 256x1.
Nathan Reed
S'il ne veut tout simplement pas de fondus en douceur, il peut faire: "palletColor.a = (float) floor (palletColor.a * fadeQuality) / fadeQuality;" Il pourrait même faire la même chose que la méthode de texture 3D mais avec une texture 2D en changeant la ligne 4 en: "float4 palletColor = tex2D (PalletTexture, float2 (colorIndex.x, colorIndex.a);" Le canal alpha indexe simplement différentes palettes couches sur une seule texture 2D
zezba9000
3

Si vous ne vous souciez pas vraiment de l'utilisation de la mémoire de texture (et l'idée de souffler une quantité folle de mémoire de texture pour obtenir un look rétro a une sorte d'attrait pervers), vous pouvez créer une texture 3D 256x256x256 mappant toutes les combinaisons RVB à votre palette sélectionnée . Ensuite, dans votre shader, il ne devient qu'une ligne de code à la fin:

return tex3d (paletteMap, color.rgb);

Il n'est peut-être même pas nécessaire d'aller jusqu'au 256x256x256 - quelque chose comme 64x64x64 peut être suffisant - et vous pouvez même changer les mappages de palette à la volée en utilisant cette méthode (mais à un coût important en raison d'une grande mise à jour de texture dynamique).

Maximus Minimus
la source
Cela peut être la meilleure méthode si vous souhaitez effectuer un éclairage, un mélange alpha ou tout autre type de calcul sur vos textures, puis accrocher le résultat final à la couleur NES la plus proche. Vous pouvez précalculer la texture de votre volume en utilisant une image de référence comme celle-ci ; avec le filtrage sur le plus proche voisin, vous pourriez bien vous en sortir avec quelque chose d'aussi petit que 16x16x16, ce qui ne serait pas beaucoup de mémoire du tout.
Nathan Reed
1
C'est une bonne idée, mais elle sera beaucoup plus lente et pas aussi compatible avec du matériel plus ancien. Les textures 3D échantillonneront beaucoup plus lentement que les textures 2D et les textures 3D nécessiteront également beaucoup plus de bande passante, ce qui la ralentira encore plus. Les cartes plus récentes ne comptent pas, mais quand même.
zezba9000
1
Cela dépend de l'âge auquel vous voulez aller. Je crois que la prise en charge de la texture 3D remonte à la GeForce 1 d'origine - est-ce 14 ans assez?
Maximus Minimus
Lol Eh bien oui peut-être qu'ils le font, jamais utilisé ces cartes, je suppose que je pensais plus en ligne avec les GPU du téléphone. Il existe aujourd'hui de nombreuses cibles qui ne prennent pas en charge les textures 3D. Mais parce qu'il utilise D3D et non OpenGL, même WP8 le supporte. Une texture 3D occuperait encore plus de bande passante qu'une texture 2D.
zezba9000
1

(ma solution ne fonctionne que si vous ne vous souciez pas de changer les palettes à la volée à l'aide de shaders)

Vous pouvez utiliser n'importe quel type de texture et effectuer un simple calcul sur un shader. L'astuce est que vous avez plus d'informations sur les couleurs que vous n'en avez besoin, donc ce que vous allez simplement vous débarrasser des informations que vous ne voulez pas.

La couleur 8 bits est au format RRRGGGBB . Ce qui vous donne 8 nuances de rouge et de vert et 4 nuances de bleu.

Cette solution fonctionnera pour toutes les textures de format de couleur RVB (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);
}

note: j'ai écrit cela du haut de ma tête, mais je suis vraiment sûr que cela compilera et fonctionnera pour vous


Une autre possibilité serait d'utiliser le format de texture D3DFMT_R3G3B2 qui est en fait le même que les graphiques 8 bits. Lorsque vous placez des données dans cette texture, vous pouvez utiliser une opération de bits simple par octet.

tex[index] = (R & 8) << 5 + ((G & 8) << 2) + (B & 4);
Notabene
la source
C'est un bon exemple. Seulement, je pense qu'il avait besoin de pouvoir échanger la palette de couleurs. Dans ce cas, il devrait utiliser quelque chose comme mon exemple.
zezba9000
Ce n'est pas du tout la palette de couleurs NES. Le NES n'utilisait pas de RVB 8 bits, il utilisait une palette fixe d'environ 50 à 60 couleurs dans l'espace YPbPr.
sam hocevar