¿Cómo puedo replicar las limitaciones de color del NES con un sombreador de píxeles HLSL?

13

Entonces, dado que el modo de 256 colores se deprecia y ya no es compatible con el modo Direct3D, tuve la idea de usar un sombreador de píxeles en su lugar para simular la paleta NES de todos los colores posibles para que los objetos que se desvanecen y otros no se desvanezcan con canales alfa . (Sé que los objetos realmente no se pueden desvanecer en el NES, pero tengo todos los objetos que se desvanecen en un fondo negro sólido, lo que sería posible con el intercambio de paletas. Además, la pantalla se desvanece cuando se detiene. que sé que también fue posible con el intercambio de paletas como se hizo en algunos juegos de Mega Man.) El problema es que no sé casi nada sobre los sombreadores HLSL.

¿Cómo lo hago?

Michael Allen Crain
fuente
El comentario a continuación describe el enfoque utilizando el sombreador de píxeles, pero simplemente se podría lograr una limitación de color como esta utilizando una guía de estilo adecuada para su equipo de arte.
Evan
¿solo desea reducir la paleta o también desea difuminar (también es posible usar sombreadores). de lo contrario, la respuesta de zezba9000 me parece la mejor
tigrou
Solo quiero reducir los posibles colores a la paleta NES. Lo necesitaba principalmente para captar los efectos del canal alfa y otros efectos de tinta porque en el modo de 256 colores los capta, pero Direct3D ya no admite el modo de 256 colores, por lo que los efectos se suavizan en colores reales.
Michael Allen Crain

Respuestas:

4

En el sombreador de píxeles, puede pasar un Texture2D de 256x256 con los colores de la paleta alineados horizontalmente en una fila. Luego, sus texturas NES se convertirán en direct3D Texture2Ds con píxeles convertidos a un valor de índice 0-255. Hay un formato de textura que solo usa el valor rojo en D3D9. Por lo tanto, la textura solo ocuparía 8 bits por píxel, pero los datos que entran en el sombreador serían de 0-1.

// El sombreador de píxeles podría verse así:

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

EDITAR: Una forma más correcta sería agregar todas las versiones de paletas de mezcla que necesita alineadas verticalmente en la textura y hacer referencia a ellas con el valor 'alfa' de su colorIndex:

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

Una tercera forma sería simular la baja calidad de desvanecimiento de NES sombreando el color 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
fuente
1
¿Te refieres a 256x1, no 256x256, supongo? Además, asegúrese de deshabilitar el filtrado bilineal en ambas texturas, de lo contrario obtendrá una mezcla entre las "entradas" de la paleta.
Nathan Reed
Además, no puede hacer ningún tipo de iluminación o matemática en los valores de textura con este esquema, ya que cualquier cosa que haga es probable que lo envíe a una parte completamente diferente de la paleta.
Nathan Reed
@Nathan Reed Puedes hacer la iluminación. Simplemente calcule la luz en el "palletColor" antes de devolver su valor de color. También puede hacer una textura de 256x1, pero el hardware puede usar 256x256 de todos modos y 256x256 es el tamaño más rápido para usar en la mayoría del hardware. A menos que eso haya cambiado idk.
zezba9000
Si enciende después de la paletización, probablemente ya no sea uno de los colores NES. Si eso es lo que quieres, está bien, pero eso no suena como lo que preguntaba. En cuanto a lo 256, eso es posible si tiene una GPU de más de 10 años ... pero cualquier cosa más reciente que eso sin duda admitirá texturas rectangulares de potencia de 2, como 256x1.
Nathan Reed
Si simplemente no quiere desvanecimientos suaves, podría hacer: "palletColor.a = (float) floor (palletColor.a * fadeQuality) / fadeQuality;" Incluso podría hacer lo mismo que el método de textura 3D pero con una textura 2D cambiando la línea 4 a: "float4 palletColor = tex2D (PalletTexture, float2 (colorIndex.x, colorIndex.a);" El canal alfa solo indexa diferentes paletas capas en una sola textura 2D.
zezba9000
3

Si realmente no le importa el uso de la memoria de textura (y la idea de soplar una cantidad increíble de memoria de textura para lograr un aspecto retro tiene un tipo de atractivo perverso), puede construir un mapeo de texturas 3D de 256x256x256 todas las combinaciones RGB a su paleta seleccionada . Luego, en su sombreador, se convierte en una línea de código al final:

return tex3d (paletteMap, color.rgb);

Puede que ni siquiera sea necesario llegar hasta 256x256x256, algo como 64x64x64 puede ser suficiente, e incluso podría cambiar las asignaciones de paletas sobre la marcha utilizando este método (pero a un costo significativo debido a una gran actualización dinámica de textura).

Maximus Minimus
fuente
Este puede ser el mejor método si desea iluminar, mezclar alfa o cualquier otro tipo de matemática en sus texturas, y luego ajustar el resultado final al color NES más cercano. Puede calcular previamente la textura de su volumen utilizando una imagen de referencia como esta ; con el filtro de vecino más cercano, podría salirse con la suya con algo tan pequeño como 16x16x16, que no tendría mucha memoria.
Nathan Reed
1
Esta es una idea genial, pero será mucho más lenta y no tan compatible con hardware antiguo. Las texturas 3D tomarán muestras mucho más lentas que las 2D y las texturas 3D también requerirán mucho más ancho de banda, lo que lo ralentizará aún más. Sin embargo, las cartas más nuevas no importan, pero aún así.
zezba9000
1
Depende de la edad que quieras tener. Creo que el soporte de texturas 3D se remonta a la GeForce 1 original: ¿tiene 14 años de edad suficiente?
Maximus Minimus
Lol Bueno, sí, tal vez lo hacen, nunca usé esas tarjetas, supongo que estaba pensando más en línea con las GPU del teléfono. Hoy en día hay muchos objetivos que no tienen soporte de textura 3D. Pero debido a que está usando D3D y no OpenGL, supongo que incluso WP8 lo admite. Aún así, una textura 3D ocuparía más ancho de banda que una 2D.
zezba9000
1

(ambas soluciones funcionan solo si no le importa cambiar paletas sobre la marcha con sombreadores)

Puede usar cualquier tipo de textura y hacer un simple cálculo en un sombreador. El truco es que tienes más información de color de la que necesitas, por lo que simplemente eliminarás la información que no deseas.

El color de 8 bits está en forma bits RRRGGGBB . Lo que te da 8 tonos de rojo y verde y 4 tonos de azul.

Esta solución funcionará para cualquier textura de formato de color 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);
}

nota: escribí eso desde la parte superior de mi cabeza, pero estoy realmente seguro de que compilará y funcionará para usted


Otra posibilidad sería utilizar el formato de textura D3DFMT_R3G3B2 que en realidad es el mismo que los gráficos de 8 bits. Cuando coloca datos en esta textura, puede usar una operación de bits simple por byte.

tex[index] = (R & 8) << 5 + ((G & 8) << 2) + (B & 4);
Notabene
fuente
Ese es un buen ejemplo. Solo que creo que necesitaba poder cambiar la paleta de colores. En ese caso, tendría que usar algo como mi ejemplo.
zezba9000
Esta no es la paleta de colores NES en absoluto. El NES no usó RGB de 8 bits, usó una paleta fija de aproximadamente 50 a 60 colores en el espacio YPbPr.
sam hocevar