Como replicar as limitações de cores do NES com um sombreador de pixel HLSL?

13

Portanto, como o modo 256 cores é depreciado e não é mais suportado no modo Direct3D, tive a ideia de usar um sombreador de pixel para simular a paleta NES de todas as cores possíveis, para que os objetos desbotados e outros objetos não tenham desbotamentos suaves com canais alfa . (Eu sei que os objetos não podem desaparecer no NES, mas tenho todos os objetos que aparecem e desaparecem em um fundo preto sólido, o que seria possível com a troca de paleta. Além disso, a tela aparece e desaparece quando você pausa o que eu sei também era possível com a troca de paleta, como foi feito em alguns jogos do Mega Man.) O problema é que não sei quase nada sobre os shaders HLSL.

Como eu faço isso?

Michael Allen Crain
fonte
O comentário abaixo descreve a abordagem usando o sombreador de pixels, mas uma limitação de cores como essa pode ser obtida usando um guia de estilo apropriado para sua equipe de arte.
Evan
você só quer reduzir a paleta ou também deseja fazer pontilhamento (possível usando shaders também). caso contrário zezba9000 resposta parece o melhor para mim
tigrou
Estou querendo reduzir as cores possíveis para a paleta NES. Eu precisava dele principalmente para capturar efeitos de canal alfa e outros efeitos de tinta, porque no modo 256 cores, ele os captura, mas o Direct3D não suporta mais o modo 256 cores, de modo que os efeitos são suavizados em cores verdadeiras.
Michael Allen Crain

Respostas:

4

No sombreador de pixels, você pode passar um Texture2D de 256x256 com as cores do palete alinhadas horizontalmente em uma linha. Em seguida, suas texturas NES seriam convertidas em direct3D Texture2Ds com cada pixel convertido em um valor de índice de 0 a 255. Há um formato de textura que usa apenas o valor vermelho em D3D9. Portanto, a textura ocuparia apenas 8 bits por pixel, mas os dados que entram no shader seriam de 0 a 1.

// O pixel shader pode ficar assim:

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

EDIT: Uma maneira mais correta seria adicionar todas as versões de paletes de mesclagem que você precisa alinhadas verticalmente na textura e referenciá-las com o valor 'alpha' do seu colorIndex:

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

Uma terceira maneira seria falsificar a baixa qualidade de desbotamento do NES ao sombrear a cor 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
fonte
1
Você quer dizer 256x1, não 256x256, eu acho? Além disso, certifique-se de desativar a filtragem bilinear nas duas texturas, caso contrário, você poderá misturar-se entre as "entradas" da paleta.
Nathan Reed
Além disso, você não pode fazer nenhum tipo de iluminação ou matemática nos valores de textura com esse esquema, pois qualquer coisa que você faça provavelmente o enviará para uma parte completamente diferente da paleta.
Nathan Reed
@ Nathan Reed Você pode fazer a iluminação. Você apenas calcula a luz na "palletColor" antes de retornar seu valor de cor. Você também pode criar uma textura de 256x1, mas o hardware pode usar 256x256 de qualquer maneira e 256x256 é o tamanho mais rápido a ser usado na maioria dos hardwares. A menos que isso mude idk.
Zezba9000
Se você iluminar após a paletização, provavelmente não será mais uma das cores do NES. Se é isso que você quer, tudo bem - mas isso não soa como o que a pergunta estava pedindo. Quanto aos 256, isso é possível se você tiver uma GPU com mais de 10 anos de idade ... mas algo mais recente do que certamente suportará texturas retangulares de potência de 2, como 256x1.
Nathan Reed
Se ele simplesmente não quiser um desbotamento suave, ele poderá fazer: "palletColor.a = (flutuante) piso (palletColor.a * fadeQuality) / fadeQuality;" Ele poderia fazer o mesmo que o método de textura 3D, mas com uma textura 2D, alterando a linha 4 para: "float4 palletColor = tex2D (PalletTexture, float2 (colorIndex.x, colorIndex.a);" O canal alfa apenas indexa paletes diferentes camadas em uma única textura 2D.
zezba9000
3

Se você realmente não se importa com o uso da memória de textura (e a idéia de soprar uma quantidade insana de memória de textura para obter uma aparência retrô tem um tipo de apelo perverso), você pode criar uma textura 3D de 256x256x256 mapeando todas as combinações RGB para a paleta selecionada . Então, no seu sombreador, ele se torna uma linha de código no final:

return tex3d (paletteMap, color.rgb);

Pode até não ser necessário ir até 256x256x256 - algo como 64x64x64 pode ser suficiente - e você pode até mudar os mapeamentos de paleta rapidamente usando esse método (mas a um custo significativo devido a uma grande atualização dinâmica de textura).

Maximus Minimus
fonte
Esse pode ser o melhor método se você quiser fazer iluminação, mistura alfa ou qualquer outro tipo de matemática em suas texturas e, em seguida, encaixe o resultado final na cor NES mais próxima. Você pode pré-calcular sua textura de volume usando uma imagem de referência como esta ; com ele definido para a filtragem do vizinho mais próximo, você pode se dar bem com algo tão pequeno quanto 16x16x16, o que não seria muita memória.
Nathan Reed
1
Essa é uma ideia interessante, mas será muito mais lenta e não será compatível com o hardware mais antigo. As texturas 3D terão uma amostra muito mais lenta que as 2D e as texturas 3D também exigirão muito mais largura de banda, o que diminuirá ainda mais a velocidade. Cartões mais novos isso não importa, mas ainda assim.
Zezba9000
1
Depende de quantos anos você deseja ir. Acredito que o suporte à textura 3D remonta à GeForce 1 original - tem 14 anos o suficiente?
Maximus Minimus
Lol Bem, sim, talvez eles usem, nunca usaram esses cartões, acho que eu estava pensando mais em linha com as GPUs do telefone. Atualmente, existem muitos alvos que não têm suporte para textura 3D. Mas porque ele está usando o D3D e não o OpenGL, acho que até o WP8 suporta isso. Ainda assim, uma textura 3D ocuparia mais largura de banda do que a 2D.
Zezba9000
1

(a minha solução funciona apenas se você não se importar em trocar paletes em tempo real usando shaders)

Você pode usar qualquer tipo de textura e fazer apenas um cálculo simples em um shader. O truque é que você tem mais informações sobre cores do que precisa, então você apenas se livrará das informações que não deseja.

A cor 8bit está em formar RRRGGGBB . O que lhe dá 8 tons de vermelho e verde e 4 tons de azul.

Esta solução funcionará para qualquer textura de formato de cor 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: eu escrevi isso do alto da minha cabeça, mas eu tenho certeza que ele irá compilar e trabalhar para você


Outra possibilidade seria usar o formato de textura D3DFMT_R3G3B2 que é realmente o mesmo que os gráficos de 8 bits. Quando você coloca dados nessa textura, pode usar uma operação de bit simples por byte.

tex[index] = (R & 8) << 5 + ((G & 8) << 2) + (B & 4);
Notabene
fonte
Esse é um bom exemplo. Só acho que ele precisava trocar o palete de cores. Nesse caso, ele teria que usar algo como o meu exemplo.
Zezba9000
Esta não é a paleta de cores do NES. O NES não usava RGB de 8 bits, usava uma paleta fixa de 50 a 60 cores no espaço YPbPr.
21813 Sam Hocevar