Создание эффекта замены палитры в стиле ретро в OpenGL

37

Я работаю над Megaman- подобной игрой, где мне нужно изменить цвет определенных пикселей во время выполнения. Для справки : в Megaman, когда вы меняете выбранное оружие, палитра главного героя меняется, отражая выбранное оружие. Меняются не все цвета спрайта, а только определенные .

Этот вид эффекта был обычным и довольно простым в NES, поскольку у программиста был доступ к палитре и логическому отображению между пикселями и индексами палитры. Однако на современном оборудовании это немного сложнее, потому что концепция палитр не одинакова.

Все мои текстуры являются 32-битными и не используют палитры.

Есть два способа достижения желаемого эффекта, но мне любопытно, есть ли лучшие способы легко достичь этого эффекта. Мне известны два варианта:

  1. Используйте шейдер и напишите немного GLSL, чтобы выполнить поведение «замена палитры».
  2. Если шейдеры недоступны (скажем, потому что видеокарта их не поддерживает), то можно клонировать «оригинальные» текстуры и генерировать разные версии с предварительно примененными изменениями цвета.

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

Изменить : я в конечном итоге использовал прием принятого ответа и вот мой шейдер для справки.

uniform sampler2D texture;
uniform sampler2D colorTable;
uniform float paletteIndex;

void main()
{
        vec2 pos = gl_TexCoord[0].xy;
        vec4 color = texture2D(texture, pos);
        vec2 index = vec2(color.r + paletteIndex, 0);
        vec4 indexedColor = texture2D(colorTable, index);
        gl_FragColor = indexedColor;      
}

Обе текстуры являются 32-битными, одна текстура используется в качестве справочной таблицы, содержащей несколько палитр одинакового размера (в моем случае 6 цветов). Я использую красный канал исходного пикселя в качестве указателя на таблицу цветов. Это сработало как шарм для достижения Megaman-подобной замены палитры!

Зак Человек
источник

Ответы:

41

Я не буду беспокоиться о том, чтобы тратить VRAM на несколько текстур персонажей.

Для меня использование варианта 2. (с разными текстурами или различными смещениями УФ-лучей, если это подходит) - это путь: более гибкий, управляемый данными, меньшее влияние на код, меньше ошибок, меньше беспокойств.


Это оставит в стороне, если вы начнете накапливать в памяти тонны символов с тоннами спрайтовых анимаций, возможно, вы могли бы начать использовать то, что рекомендует OpenGL , палитры "сделай сам":

Палитра текстур

Поддержка расширения EXT_paletted_texture была прекращена основными поставщиками GL. Если вам действительно нужны палитровые текстуры на новом оборудовании, вы можете использовать шейдеры для достижения этого эффекта.

Пример шейдера:

//Fragment shader
#version 110
uniform sampler2D ColorTable;     //256 x 1 pixels
uniform sampler2D MyIndexTexture;
varying vec2 TexCoord0;

void main()
{
  //What color do we want to index?
  vec4 myindex = texture2D(MyIndexTexture, TexCoord0);
  //Do a dependency texture read
  vec4 texel = texture2D(ColorTable, myindex.xy);
  gl_FragColor = texel;   //Output the color
}

Это просто выборка ColorTable(палитра в RGBA8) с использованием MyIndexTexture(квадратная текстура 8 бит в индексированных цветах). Просто воспроизводит, как работают палитры в стиле ретро.


В приведенном выше примере используется два sampler2D, где он может фактически использовать один sampler1D+ один sampler2D. Я подозреваю, что это из соображений совместимости ( нет одномерных текстур в OpenGL ES ) ... Но не имеет значения, для настольных OpenGL это может быть упрощено до:

uniform sampler1D Palette;             // A palette of 256 colors
uniform sampler2D IndexedColorTexture; // A texture using indexed color
varying vec2 TexCoord0;                // UVs

void main()
{
    // Pick up a color index
    vec4 index = texture2D(IndexedColorTexture, TexCoord0);
    // Retrieve the actual color from the palette
    vec4 texel = texture1D(Palette, index.x);
    gl_FragColor = texel;   //Output the color
}

Paletteпредставляет собой одномерную текстуру «реальных» цветов (например GL_RGBA8) и IndexedColorTextureпредставляет собой двумерные текстуры индексированных цветов (как правило GL_R8, которые дают 256 индексов). Чтобы создать их, существует несколько инструментов разработки и форматов файлов изображений , которые поддерживают палитровые изображения, поэтому можно найти тот, который соответствует вашим потребностям.

Лоран Кувиду
источник
2
Точно ответ я бы дал, только более подробный. +2, если бы я мог.
Илмари Каронен
У меня возникли некоторые проблемы с тем, чтобы это сработало для меня, в основном потому, что я не понимаю, как определяется индекс в цвете - не могли бы вы подробнее остановиться на этом?
Зак Человек
Вы, вероятно, должны прочитать об индексированных цветах . Ваша текстура становится двумерным массивом индексов, то есть индексами, соответствующими цветам в палитре (обычно это одномерная текстура). Я отредактирую свой ответ, чтобы упростить пример, приведенный на сайте OpenGL.
Лоран Кувиду
Извините, я не совсем понял в своем первоначальном комментарии. Я знаком с индексированными цветами и тем, как они работают, но мне неясно, как они работают в контексте шейдера или 32-битной текстуры (так как они не проиндексированы - верно?).
Зак Человек
Дело в том, что здесь у вас есть одна 32-битная палитра, содержащая 256 цветов, и одна текстура из 8-битных индексов (от 0 до 255). Смотрите мои правки;)
Лоран Кувиду
10

Есть 2 способа, которыми я могу думать, чтобы сделать это.

Способ № 1: GL_MODULATE

Вы можете сделать спрайт в оттенках серого, а GL_MODULATEтекстуру, когда он нарисован одним сплошным цветом.

Например,

введите описание изображения здесь

введите описание изображения здесь

Это не дает вам большой гибкости, в основном вы можете становиться только светлее и темнее одного и того же оттенка.

Способ № 2: ifпостановка (плохо для производительности)

Другая вещь, которую вы могли бы сделать, это закрасить ваши текстуры некоторыми ключевыми значениями, включающими R, G и B. Так, например, первый цвет (1,0,0), 2-й цвет (0,1,0), 3-й цвет (0,0,1).

Вы объявляете пару униформ как

uniform float4 color1 ;
uniform float4 color2 ;
uniform float4 color3 ;

Тогда в шейдере конечный цвет пикселя будет что-то вроде

float4 pixelColor = texel.r*color1 + texel.g*color2 + texel.b*color3 ;

color1, color2, color3, Являются формы , так что они могут быть установлены до запуска шейдера ( в зависимости от того, что «костюм» Megaman в), то шейдер просто делает все остальное.

Вы также можете использовать ifоператоры, если вам нужно больше цветов, но вам нужно, чтобы проверки на равенство работали правильно (текстуры обычно находятся R8G8B8в диапазоне от 0 до 255 каждая в файле текстур, но в шейдере у вас есть плавающие rgba)

Способ № 3: просто избыточно хранить разноцветные изображения в спрайт-карте

введите описание изображения здесь

Это может быть самый простой способ, если вы не безумно пытаетесь сохранить память

bobobobo
источник
2
№ 1 может быть аккуратным, но № 2 и № 3 ужасно плохие советы. Графический процессор способен индексировать из карт (из которых палитра в основном) и делает это лучше, чем оба этих предложения.
Скрылар
6

Я бы использовал шейдеры. Если вы не хотите их использовать (или не можете), я бы использовал одну текстуру (или часть текстуры) на цвет и использовал только чистый белый. Это позволит вам тонировать текстуру (например, сквозную glColor()), не беспокоясь о создании дополнительных текстур для цветов. Вы даже можете менять цвета на ходу без дополнительной работы с текстурами.

Марио
источник
Это довольно хорошая идея. Я действительно думал об этом некоторое время назад, но это не пришло в голову, когда я отправил этот вопрос. В этом есть некоторая дополнительная сложность, поскольку любому спрайту, использующему этот вид составной текстуры, потребуется более сложная логика рисования. Спасибо за это потрясающее предложение!
Зак Человек
Да, вам также понадобится x проходов на спрайт, что может добавить некоторую сложность. Учитывая то, что современное оборудование обычно поддерживает как минимум базовые пиксельные шейдеры (даже графические процессоры для нетбуков), я бы выбрал их. Это может быть аккуратно, если вы просто хотите подкрашивать определенные вещи, например, столбики здоровья или значки.
Марио
3

«Меняются не все цвета спрайта, а только некоторые».

Старые игры делали палитры, потому что это было все, что у них было. В наши дни вы можете использовать несколько текстур и комбинировать их различными способами.

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

В шейдерном языке:

vec3 baseColor = texture (BaseSampler, att_TexCoord) .rgb;
количество с плавающей точкой = текстура (MaskSampler, att_TexCoord) .r;
vec3 color = mix (baseColor, baseColor * ModulationColor, количество);

Или вы можете использовать фиксированную функцию мультитекстурирования с различными режимами объединения текстур.

Очевидно, есть много разных способов сделать это; Вы можете поместить маски для разных цветов в каждый из каналов RGB или модулировать цвета путем изменения оттенка в цветовом пространстве HSV.

Другой, возможно, более простой вариант - просто нарисовать спрайт, используя отдельную текстуру для каждого компонента. Одна текстура для волос, одна для доспехов, одна для сапог и т. Д. Каждая из них окрашивается отдельно.

ccxvii
источник
2

Во-первых, это обычно называется езда на велосипеде (паллетный езда на велосипеде), так что это может помочь вашему Google-фу.

Это будет зависеть от того, какие именно эффекты вы хотите создать, и от того, имеете ли вы дело со статическим 2D-контентом или динамическим 3D-материалом (т. Е. Хотите ли вы иметь дело со спайтами / текстурами или визуализировать целую 3D-сцену, а затем поменять ее местами). Также, если вы ограничиваете себя количеством цветов или используете полный цвет.

Более полный подход, использующий шейдеры, заключается в том, чтобы на самом деле создавать индексированные по цвету изображения с текстурой поддона. Создайте текстуру, но вместо того, чтобы иметь цвета, выберите цвет, который представляет индекс для поиска, а затем используйте другую текстуру цветов, которая является пластиной. Например, красный может быть координатой t текстур, а зеленый - координатой s. Используйте шейдер для поиска. И Анимировать / Изменить цвета этой текстуры поддона. Это будет очень близко к ретро-эффекту, но будет работать только для статического 2D-контента, такого как спрайты или текстуры. Если вы работаете с подлинной ретро-графикой, то вы можете создавать фактические индексированные цветные спрайты и писать что-то для импорта их и поддонов.

Вы также захотите потренироваться, если собираетесь использовать «глобальный» поддон. Например, сколько старого оборудования будет работать, меньше памяти для поддонов, так как вам нужен только один, но сложнее для создания ваших иллюстраций, поскольку вы должны синхронизировать поддон по всем изображениям) или для каждого изображения свой собственный поддон. Это даст вам больше гибкости, но использует больше памяти. Конечно, вы можете сделать и то и другое, также вы можете использовать только поддоны для вещей, которые вы катаете на велосипеде. Также определите, в какой степени вы не хотите ограничивать свои цвета, например, в старых играх было бы 256 цветов. Если вы используете текстуру, то вы получите количество пикселей, поэтому 256 * 256 даст вам

Если вы просто хотите дешевый эффект фальшивки, такой как текущая вода, вы можете создать 2-ю текстуру, которая будет содержать маску серого в области, которую вы хотите изменить, а затем использовать мачтовые текстуры, чтобы изменить оттенок / насыщенность / яркость на величину в текстура маски в оттенках серого. Вы можете быть еще ленивее и просто изменить оттенок / яркость / насыщенность всего, что находится в цветовой гамме.

Если вам это нужно в полном 3D, вы можете попробовать сгенерировать индексированное изображение на лету, но это будет медленным, так как вам придется искать паллету, вы также, вероятно, будете ограничены в диапазоне цветов.

В противном случае вы можете просто подделать его в шейдере, если color == поменять цвет, поменять его местами. Это было бы медленно и работало бы только с ограниченным числом мест замены цветов, но не должно вызывать стресса у любого современного оборудования, особенно если вы работаете только с 2D-графикой и всегда можете пометить, какие спрайты меняют цвета и используют другой шейдер.

В противном случае действительно подделайте их, сделайте кучу анимированных текстур для каждого состояния.

Вот отличная демонстрация циклического перемещения паллет в HTML5 .

Дэвид С. Бишоп
источник
0

Я считаю, что если бы достаточно быстро, чтобы цветовое пространство [0 ... 1] было разделено на два:

if (color.r > 0.5) {
   color.r = (color.r-0.5) * uniform_r + uniform_base_r;
} else {
  color.r *=2.0;
} // this could be vectorised for all colors

Таким образом, значения цвета от 0 до 0,5 зарезервированы для нормальных значений цвета; и 0.5 - 1.0 зарезервированы для «модулированных» значений, где 0.5..1.0 соответствует любому диапазону, выбранному пользователем.

Только цветовое разрешение усекается от 256 значений на пиксель до 128 значений.

Возможно, можно использовать встроенные функции, такие как max (color-0.5f, 0.0f), чтобы полностью удалить if (обмен их на умножения на ноль или единицу ...).

Аки Суйхконен
источник