Как я могу повторить цветовые ограничения NES с пиксельным шейдером HLSL?

13

Так как 256-цветный режим устарел и больше не поддерживается в режиме Direct3D, мне пришла в голову идея использовать вместо этого пиксельный шейдер для имитации палитры NES всех возможных цветов, чтобы объекты с затуханием и тому подобное не имели плавных затуханий с альфа-каналами. , (Я знаю, что объекты не могли действительно исчезать в NES, но у меня есть все объекты, которые появляются и исчезают на сплошном черном фоне, что было бы возможно при смене палитры. Кроме того, экран затухает и исчезает при паузе что, как я знаю, было возможно и при смене палитры, как это было сделано в нескольких играх Mega Man.) Проблема в том, что я почти ничего не знаю о шейдерах HLSL.

Как это сделать?

Майкл Аллен Крейн
источник
Комментарий ниже описывает подход с использованием пиксельного шейдера, но простого ограничения цвета, подобного этому, можно добиться с помощью соответствующего руководства по стилю для вашей команды художников.
Эван
Вы просто хотите уменьшить палитру или также хотите выполнить дизеринг (возможно также с использованием шейдеров). в противном случае ответ zezba9000 кажется мне лучшим
tigrou
Я хочу просто уменьшить возможные цвета до палитры NES. Он мне нужен был в основном для захвата эффектов альфа-канала и других эффектов чернил, потому что в режиме 256 цветов он их ловит, но Direct3D больше не поддерживает режим 256 цветов, поэтому в нем эффекты сглаживаются в реальных цветах.
Майкл Аллен Крэйн

Ответы:

4

В пиксельном шейдере вы можете передать 256x256 Texture2D с цветами палитры, выстроенными в ряд горизонтально. Тогда ваши текстуры NES будут преобразованы в прямые 3DD текстуры2D с преобразованием каждого пикселя в значение индекса 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:

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

Третий способ состоит в том, чтобы просто подделать низкокачественное изображение 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;
}
zezba9000
источник
1
Я имею в виду 256x1, а не 256x256? Также обязательно отключите билинейную фильтрацию для обеих текстур, иначе вы получите смешение между «записями» палитры.
Натан Рид
Кроме того, с помощью этой схемы вы не можете использовать освещение или математические вычисления для значений текстуры, поскольку все, что вы делаете, скорее всего просто отправит вас в совершенно другую часть палитры.
Натан Рид
@ Натан Рид Вы можете сделать освещение. Вы просто рассчитываете свет на "palletColor", прежде чем вернуть его значение цвета. Также вы можете сделать текстуру 256x1, но аппаратное обеспечение может использовать 256x256 в любом случае, и 256x256 - самый быстрый размер для использования на большинстве аппаратных средств. Если только это не изменило idk.
zezba9000
Если вы делаете освещение после палитризации, то это, вероятно, даже не будет одним из цветов NES. Если это то, что вы хотите, это нормально - но это не похоже на то, о чем спрашивал вопрос. Что касается 256, это возможно, если у вас есть графический процессор старше 10 лет ... но все, что новее, будет поддерживать прямоугольные текстуры степени 2, например 256x1.
Натан Рид
Если ему просто не нужны плавные затухания, он может сделать следующее: "palletColor.a = (float) floor (palletColor.a * fadeQuality) / fadeQuality;" Он мог бы сделать то же самое, что и метод 3D-текстуры, но с 2D-текстурой, изменив строку 4 на: "float4 palletColor = tex2D (PalletTexture, float2 (colorIndex.x, colorIndex.a);" Альфа-канал просто индексирует другой поддон слои на одной 2D текстуре
zezba9000
3

Если вы на самом деле не заботитесь об использовании текстурной памяти (и идея использовать безумный объем текстурной памяти для получения ретро-стиля имеет своего рода извращенную привлекательность), вы можете создать трехмерную текстуру 256x256x256, отображающую все комбинации RGB в выбранную палитру. , Тогда в вашем шейдере он просто становится одной строкой кода в конце:

return tex3d (paletteMap, color.rgb);

Может даже не потребоваться пройти весь путь до 256x256x256 - может быть достаточно что-то вроде 64x64x64 - и вы даже можете изменить отображения палитры на лету, используя этот метод (но при значительных затратах из-за большого динамического обновления текстуры).

Максимус Минимус
источник
Это может быть лучшим методом, если вы хотите сделать освещение, альфа-смешивание или любой другой тип математики на ваших текстурах, а затем привязать конечный результат к ближайшему цвету NES. Вы можете предварительно рассчитать объемную текстуру, используя эталонное изображение, подобное этому ; если он настроен на фильтрацию ближайших соседей, вы можете получить что-то такое маленькое, как 16x16x16, что совсем не будет много памяти.
Натан Рид
1
Это крутая идея, но она будет намного медленнее и не будет совместима со старым оборудованием. 3D-текстуры будут сэмплировать намного медленнее, чем 2D-текстуры, а 3D-текстуры также потребуют гораздо большей пропускной способности, что еще больше замедлит их. Новые карты это не имеет значения, хотя, но все же.
zezba9000
1
Зависит от того, сколько лет вы хотите пойти. Я считаю, что поддержка 3D-текстуры восходит к оригинальной GeForce 1 - достаточно ли ей 14 лет?
Максимус Минимус
Lol Ну, да, может быть, они никогда не использовали эти карты, думаю, я думал больше о GPU Phone. Сегодня существует множество целей, которые не поддерживают 3D-текстуры. Но поскольку он использует D3D, а не OpenGL, думаю, даже WP8 поддерживает это. Тем не менее, 3D текстура будет занимать большую полосу пропускания, чем 2D.
zezba9000
1

(оба моих решения работают, только если вы не заботитесь о смене паллет на лету с помощью шейдеров)

Вы можете использовать любой тип текстуры и просто вычислить шейдер. Хитрость в том, что у вас больше информации о цвете, чем вам нужно, поэтому вы просто избавитесь от информации, которая вам не нужна.

8-битный цвет находится в формате RRRGGGBB . Что дает вам 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);
}

примечание: я написал это из головы, но я действительно уверен, что он скомпилируется и будет работать для вас


Другая возможность - использовать формат текстуры D3DFMT_R3G3B2 , который на самом деле совпадает с 8-битной графикой. Когда вы помещаете данные в эту текстуру, вы можете использовать простую битовую операцию на байт.

tex[index] = (R & 8) << 5 + ((G & 8) << 2) + (B & 4);
NotaBene
источник
Это хороший пример. Только я думаю, что ему нужно было поменять цветную палитру. В этом случае ему придется использовать что-то вроде моего примера.
zezba9000
Это не цветовая палитра NES вообще. NES не использовал 8-битный RGB, он использовал фиксированную палитру из 50–60 цветов в пространстве YPbPr.
Сэм Хоцевар