如何使用HLSL像素着色器复制NES的颜色限制?


13

因此,由于256色模式已被弃用,并且在Direct3D模式下不再受支持,因此我有了一个想法,改为使用像素着色器来模拟所有可能颜色的NES调色板,以便淡出的对象以及不具有alpha通道的平滑淡出效果。(我知道对象在NES上并不能真正淡出,但是我的所有对象都会在纯黑色背景上淡入和淡出,这可以通过调换调色板来实现。此外,当您暂停时,屏幕也可以淡入和淡出。我知道通过调色板交换也可以实现,就像在《洛克人》游戏中所做的那样。)问题是,我几乎不了解HLSL着色器。

我该怎么做?


下面的注释描述了使用像素着色器的方法,但是可以通过为您的艺术团队使用适当的样式指南来简单地实现这种颜色限制。
伊万

您是要缩小调色板还是要进行抖动处理(也可以使用着色器)?否则zezba9000答案对我而言似乎是最好的答案
tigrou

我只想将可能的颜色减少到NES调色板。我主要需要它来捕获alpha通道效果和其他墨水效果,因为在256色模式下它可以捕获它们,但是Direct3D不再支持256色模式,因此在其中效果可以用真实的颜色进行平滑处理。
Michael Allen Crain

Answers:


4

在像素着色器中,您可以传入256x256 Texture2D,并且托盘颜色连续水平排列。然后,您的NES纹理将被转换为direct3D Texture2Ds,并且将像素转换为0-255的索引值。有一种纹理格式仅在D3D9中使用红色值。因此,纹理每个像素仅占用8位,但是进入着色器的数据将是0-1。

//像素着色器可能看起来像这样:

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

编辑:一种更正确的方法是添加您需要在纹理中垂直对齐的所有混合托盘版本,并使用colorIndex的“ alpha”值引用它们:

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

第三种方法是通过用卡通色调遮盖Alpha颜色来伪造NES低淡变质量:

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

1
我是说256x1,不是256x256。另外,请确保对两个纹理都禁用双线性过滤,否则将在调色板“条目”之间进行混合。
内森·里德

另外,使用这种方案您无法对纹理值进行任何形式的光照或数学运算,因为您所做的任何事情都可能会将您发送到调色板的完全不同的部分。
内森·里德

@Nathan Reed您可以进行照明。您只需在“ palletColor”上计算光,然后再返回其颜色值。您也可以制作256x1纹理,但是硬件可能仍然使用256x256,而256x256是大多数硬件上使用最快的尺寸。除非多数民众赞成更改IDK。
zezba9000

如果在着色之后进行照明,那么它甚至可能不再是NES颜色之一。如果那是您想要的,那很好-但这听起来好像不是问题的要求。至于256位数,如果您拥有超过10年的GPU,这是可能的...但是比这更新的版本肯定会支持矩形2幂纹理,例如256x1。
内森·里德

如果他只是不想平滑的淡入淡出,他可以这样做:“ palletColor.a =(float)floor(palletColor.a * fadeQuality)/ fadeQuality;” 他甚至可以执行与3D纹理方法相同的操作,但通过将第4行更改为2D纹理即可:“ float4 PalttureColor = tex2D(PalletTexture,float2(colorIndex.x,colorIndex.a);” Alpha通道仅索引了不同的调色板单个2D纹理上的多个图层
zezba9000 2013年

3

如果您并不真正在意纹理内存的使用(以及疯狂地消耗大量纹理内存以实现复古外观的想法具有某种反常的吸引力),则可以构建256x256x256 3d纹理,将所有RGB组合映射到所选调色板。然后在您的着色器中,它最后只是一行代码:

return tex3d (paletteMap, color.rgb);

甚至不必完全达到256x256x256的水平-可能像64x64x64之类的东西就足够了-您甚至可以使用此方法即时更改调色板映射(但由于动态纹理更新较大,因此成本很高)。


如果要对纹理进行照明,alpha混合或任何其他类型的数学运算,然后将最终结果捕捉到最接近的NES颜色,则这可能是最好的方法。您可以使用像这样的参考图像来预先计算体积纹理;通过将其设置为最近邻居过滤,您很可能会获得16x16x16的小内存,这根本就不会占用太多内存。
内森·里德

1
这是一个很不错的主意,但是会慢很多,并且与旧版硬件不兼容。3D纹理的采样速度要比2D纹理的采样速度慢得多,而3D纹理的采样带宽也要高得多,这将使其速度降低得更多。较新的卡虽然无关紧要,但仍然如此。
zezba9000

1
取决于您要多大年龄。我相信3d纹理支持可以追溯到最初的GeForce 1-14岁就足够了吗?
Maximus Minimus

大声笑,是的,也许他们这样做了,从未使用过这些卡,我想我是在考虑与Phone GPU更加兼容。今天,有许多目标不支持3D纹理。但是因为他使用的是D3D,而不是OpenGL,所以即使WP8也支持此功能。3D纹理仍然比2D纹理占用更多的带宽。
zezba9000

1

(仅当您不关心使用着色器动态更改调色板时,我的解决方案均有效)

您可以使用任何类型的纹理,并且只需在着色器上进行简单的计算即可。窍门是您拥有比所需更多的颜色信息,因此您将摆脱掉不需要的信息。

8位彩色格式为RRGGGGGB。这将为您提供8种红色和绿色阴影以及4种蓝色阴影。

该解决方案适用于任何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);
}

注意:我从头顶上写了那句话,但是我真的确定它会为您编译和使用


另一种可能性是使用实际上与8位图形相同的D3DFMT_R3G3B2纹理格式。将数据放入此纹理时,可以对每个字节使用简单的位操作。

tex[index] = (R & 8) << 5 + ((G & 8) << 2) + (B & 4);

那是一个很好的例子。只有我认为他需要能够更换调色板。在这种情况下,他将不得不使用类似于我的示例的方法。
zezba9000

这根本不是NES调色板。NES没有使用8位RGB,而是在YPbPr空间中使用了大约50至60种颜色的固定调色板。
sam hocevar
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.