Створення ефекту заміни палітри в стилі ретро в OpenGL


37

Я працюю над грою на 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 кольорів). Я використовую червоний канал вихідного пікселя як індекс до таблиці кольорів. Це спрацювало як шарм для досягнення мегаманської заміни палітри!

Відповіді:


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

Є два способи, як я можу зробити це.

Шлях №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: Просто зайве зберігання різних кольорових зображень у спрайт-карті

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

Це може бути найпростіший спосіб, якщо ви не шалено намагаєтеся зберегти пам'ять


2
# 1 може бути акуратним, але №2 і №3 жахливо погані поради. Графічний процесор здатний індексувати з карт (в основному це палітра) і робить це краще, ніж обидві пропозиції.
Скрилар

6

Я б використовував шейдери. Якщо ви не хочете їх використовувати (або не можете), я б перейшов з однією текстурою (або частиною текстури) на колір і використовував лише чистий білий колір. Це дозволить тонувати текстуру (наприклад, наскрізь glColor()), не турбуючись про створення додаткових текстур для кольорів. Ви навіть можете міняти кольори на ходу без додаткової роботи з текстурою.


Це досить гарна ідея. Я насправді про це думав деякий час назад, але це не прийшло в голову, коли я опублікував це питання. У цьому є додаткова складність, оскільки будь-який спрайт, який використовує такий тип композитної текстури, потребує більш складної логіки малювання. Дякуємо за цю дивовижну пропозицію!
Zack The Human

Так, вам також знадобляться х пропусків на спрайт, що може додати певної складності. Враховуючи сучасне обладнання, яке зазвичай підтримує хоча б базові піксельні шейдери (навіть нетбукові графічні процесори), я б замість цього перейшов. Це може бути акуратно, якщо ви просто хочете підфарбувати конкретні речі, наприклад, батончики для здоров'я або значки.
Маріо

3

"Не всі кольори спрайта змінюються, лише певні."

Старі ігри робили палетні трюки, тому що це було все, що вони мали. У ці дні ви можете використовувати кілька текстур і комбінувати їх різними способами.

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

Шейдерною мовою:

vec3 baseColor = текстура (BaseSampler, att_TexCoord) .rgb;
кількість поплавця = текстура (MaskSampler, att_TexCoord) .r;
vec3 color = змішати (baseColor, baseColor * ModulationColor, кількість);

Або ви можете використовувати багатотекстурну фіксовану функцію з різними режимами комбінації текстур.

Очевидно, існує багато різних способів, як ви можете піти з цього приводу; ви можете розмістити маски для різних кольорів у кожному з RGB-каналів або модулювати кольори, змінюючи відтінок у просторі кольорів HSV.

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


2

По-перше, це зазвичай називають їздою на велосипеді (велосипед на піддоні), так що це може допомогти вашому Google-фу.

Це буде залежати від того, які саме ефекти ви бажаєте створити, і якщо ви маєте справу зі статичним 2D-вмістом або динамічними 3D-речами (тобто ви просто хочете мати справу з косами / текстурами, або візуалізувати цілу 3D-сцену, а потім міняйте піддон). Також якщо ви обмежуєте себе кількома кольорами або використовуєте повноколірний колір.

Більш повним підходом із застосуванням шейдерів було б фактичне створення кольорових індексованих зображень із текстурою піддону. Зробіть текстуру, але замість кольорів, майте колір, який представляє індекс для пошуку, а потім іншу текстуру кольорів, яка є пластиною. Наприклад, червоний може бути координатою текстур t, а зелений - координатою s. Використовуйте шейдер для пошуку. І оживити / змінити кольори текстури піддону. Це було б дуже близько до ефекту ретро, ​​але працювало б лише для статичного 2D-вмісту, такого як спрайти або текстури. Якщо ви робите справжню ретро-графіку, то, можливо, ви зможете створити справжні індексовані кольори спрайтів і написати щось для імпортування їх та піддонів.

Ви також хочете розібратися, якщо ви збираєтеся використовувати "глобальний" піддон. Як, наприклад, як би працювало старе обладнання, менше пам’яті для піддонів, тому що вам потрібна лише одна, але важче для виготовлення твору мистецтва, оскільки вам доведеться синхронізувати піддон на всіх зображеннях) або надавати кожному зображенню власний піддон. Це дасть вам більше гнучкості, але використовує більше пам’яті. Звичайно, ви можете зробити і те, і інше, і ви можете використовувати піддони лише для речей, які ви кольорові. Також попрацюйте, наскільки ви не хочете обмежувати свої кольори, наприклад, у старих іграх буде використано 256 кольорів. Якщо ви використовуєте текстуру, то ви отримаєте кількість пікселів, тому 256 * 256 дасть вам

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

Якщо вам це знадобиться у повному 3D-режимі, ви можете спробувати генерувати індексоване зображення на льоту, але це буде повільно, оскільки вам доведеться шукати піддон, ви також, ймовірно, будете обмежені в кольоровій гамі.

Інакше ви можете просто підробити це в шейдері, якщо color == поміняючи колір, поміняйте його. Це було б повільно і працювало б лише з обмеженою кількістю кольорів, що змінюються, але не повинно наголошувати на будь-якому сьогоднішньому апаратному забезпеченні, особливо якщо ви просто маєте справу з двовимірною графікою, і ви завжди можете відзначити, які спрайти мають міняти кольори та використовувати інший шейдер.

Інакше по-справжньому підробляйте їх, складіть купу анімованих текстур для кожного штату.

Ось чудова демонстрація їзди на велосипеді з піддонів у HTML5 .


0

Я вважаю, що ifs досить швидкий, щоб кольоровий простір [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), щоб повністю видалити ifs (торгуючи ними на множення на нуль чи один ...).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.