Crear un efecto de intercambio de paleta de estilo retro en OpenGL


37

Estoy trabajando en un juego similar a Megaman donde necesito cambiar el color de ciertos píxeles en tiempo de ejecución. Como referencia : en Megaman cuando cambias tu arma seleccionada, la paleta del personaje principal cambia para reflejar el arma seleccionada. No todos los colores del sprite cambian, solo algunos cambian .

Este tipo de efecto era común y bastante fácil de hacer en el NES ya que el programador tenía acceso a la paleta y al mapeo lógico entre píxeles e índices de paleta. Sin embargo, en el hardware moderno, esto es un poco más desafiante porque el concepto de paletas no es el mismo.

Todas mis texturas son de 32 bits y no usan paletas.

Hay dos maneras que conozco para lograr el efecto que quiero, pero tengo curiosidad por saber si hay mejores maneras de lograr este efecto fácilmente. Las dos opciones que conozco son:

  1. Use un sombreador y escriba GLSL para realizar el comportamiento de "intercambio de paleta".
  2. Si los sombreadores no están disponibles (por ejemplo, porque la tarjeta gráfica no los admite), entonces es posible clonar las texturas "originales" y generar diferentes versiones con los cambios de color aplicados previamente.

Idealmente, me gustaría usar un sombreador, ya que parece sencillo y requiere poco trabajo adicional en oposición al método de textura duplicada. Me preocupa que duplicar texturas solo para cambiar un color en ellas está desperdiciando VRAM, ¿no debería preocuparme por eso?

Editar : Terminé usando la técnica de respuesta aceptada y aquí está mi sombreador para referencia.

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

Ambas texturas son de 32 bits, una textura se usa como tabla de búsqueda que contiene varias paletas que son todas del mismo tamaño (en mi caso, 6 colores). Utilizo el canal rojo del píxel de origen como índice de la tabla de colores. ¡Esto funcionó a las mil maravillas para lograr el intercambio de paletas como Megaman!

Respuestas:


41

No me preocuparía desperdiciar VRAM por algunas texturas de caracteres.

Para mí, usar su opción 2. (con diferentes texturas o diferentes compensaciones UV si eso encaja) es el camino a seguir: más flexible, basado en datos, menos impacto en el código, menos errores, menos preocupaciones.


Dejando esto de lado, si comienza a acumular toneladas de caracteres con toneladas de animaciones de sprites en la memoria, tal vez podría comenzar a usar lo que recomienda OpenGL , paletas de bricolaje:

Texturas paletizadas

La compatibilidad con la extensión EXT_paletted_texture ha sido eliminada por los principales proveedores de GL. Si realmente necesita texturas paletizadas en un nuevo hardware, puede usar sombreadores para lograr ese efecto.

Ejemplo de sombreador:

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

Esto es simplemente un muestreo en ColorTable(una paleta en RGBA8), usando MyIndexTexture(una textura cuadrada de 8 bits en colores indexados). Simplemente reproduce la forma en que funcionan las paletas de estilo retro.


El ejemplo citado anteriormente usa dos sampler2D, donde en realidad podría usar uno sampler1D+ uno sampler2D. Sospecho que esto es por razones de compatibilidad ( no hay texturas unidimensionales en OpenGL ES ) ... Pero no importa, para OpenGL de escritorio esto se puede simplificar para:

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
}

Palettees una textura unidimensional de colores "reales" (p GL_RGBA8. ej. ), y IndexedColorTexturees una textura bidimensional de colores indexados (típicamente GL_R8, que le da 256 índices). Para crearlos, existen varias herramientas de creación y formatos de archivo de imagen que admiten imágenes en paleta, debería ser posible encontrar el que se ajuste a sus necesidades.


2
Exactamente la respuesta que habría dado, solo que más detallada. Haría +2 si pudiera.
Ilmari Karonen

Tengo algunos problemas para hacer que esto funcione para mí, principalmente porque no entiendo cómo se determina el índice en el color, ¿podría expandirlo un poco más?
Zack The Human

Probablemente debería leer sobre los colores indexados . Su textura se convierte en una matriz 2D de índices, esos índices correspondientes a los colores en una paleta (generalmente una textura unidimensional). Editaré mi respuesta para simplificar el ejemplo dado por el sitio web de OpenGL.
Laurent Couvidou

Lo siento, no estaba claro en mi comentario original. Estoy familiarizado con los colores indexados y cómo funcionan, pero no tengo claro cómo funcionan dentro del contexto de un sombreador o una textura de 32 bits (ya que esos no están indexados, ¿verdad?).
Zack The Human

Bueno, lo que pasa es que aquí tienes una paleta de 32 bits que contiene 256 colores y una textura de índices de 8 bits (que va de 0 a 255). Ver mi edición;)
Laurent Couvidou

10

Hay 2 formas en que puedo pensar en hacer esto.

Camino # 1: GL_MODULATE

Puede hacer el sprite en tonos de gris y GL_MODULATEla textura cuando se dibuja con un solo color sólido.

Por ejemplo,

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

Sin embargo, esto no le permite mucha flexibilidad, básicamente solo puede ir más claro y más oscuro del mismo tono.

Camino # 2: ifdeclaración (malo para el rendimiento)

Otra cosa que podría hacer es colorear sus texturas con algunos valores clave que involucran R, G y B. SO, por ejemplo, el primer color es (1,0,0), el segundo color es (0,1,0), el tercero el color es (0,0,1).

Declaras un par de uniformes como

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

Luego, en el sombreador, el color de píxel final es algo así como

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

color1, color2, color3, Son uniformes por lo que se pueden establecer antes de las carreras de sombreado (dependiendo de lo que Megaman "traje" está en), entonces el shader simplemente hace el resto.

También podría usar ifdeclaraciones si necesitara más colores, pero tendría que hacer que las comprobaciones de igualdad funcionen correctamente (las texturas generalmente están R8G8B8entre 0 y 255 en el archivo de textura, pero en el sombreador, tiene flotantes rgba)

Forma # 3: simplemente almacena de forma redundante las diferentes imágenes de colores en el mapa de sprites

ingrese la descripción de la imagen aquí

Esta puede ser la forma más fácil, si no estás tratando de conservar la memoria


2
# 1 puede ser ordenado, pero # 2 y # 3 son horriblemente malos consejos. La GPU es capaz de indexar desde mapas (de los cuales básicamente es una paleta) y lo hace mejor que ambas sugerencias.
Skrylar

6

Yo usaría sombreadores. Si no quiere usarlos (o no puede), iría con una textura (o parte de una textura) por color y usaría solo blanco limpio. Esto le permitirá teñir la textura (por ejemplo, a través glColor()) sin tener que preocuparse por crear texturas adicionales para los colores. Incluso puede intercambiar colores en la ejecución sin trabajo de textura adicional.


Esta es una muy buena idea. De hecho, había pensado en esto hace un tiempo, pero no me vino a la mente cuando publiqué esta pregunta. Hay algo de complejidad adicional con esto ya que cualquier sprite que use este tipo de textura compuesta necesitará una lógica de dibujo más compleja. ¡Gracias por esta increíble sugerencia!
Zack The Human

Sí, también necesitará x pases por sprite, lo que puede agregar bastante complejidad. Teniendo en cuenta que el hardware de hoy en día generalmente admite al menos sombreadores de píxeles básicos (incluso GPU de netbook), preferiría estos. Sin embargo, puede ser interesante si solo desea teñir cosas específicas, por ejemplo, barras de salud o íconos.
Mario

3

"No todos los colores del sprite cambian, solo algunos cambian".

Los viejos juegos hacían trucos de paleta porque eso era todo lo que tenían. En estos días, puede usar múltiples texturas y combinarlas de varias maneras.

Puede crear una textura de máscara de escala de grises separada que use para decidir qué píxeles cambiarán de color. Las áreas blancas en la textura reciben la modificación a todo color, y las áreas negras permanecen sin cambios.

En el idioma del sombreador:

vec3 baseColor = textura (BaseSampler, att_TexCoord) .rgb;
cantidad flotante = textura (MaskSampler, att_TexCoord) .r;
vec3 color = mix (baseColor, baseColor * ModulationColor, cantidad);

O puede usar texturizado de función fija con varios modos de combinación de texturas.

Obviamente, hay muchas maneras diferentes de hacerlo; puede poner máscaras para diferentes colores en cada uno de los canales RGB, o modular los colores cambiando el tono en un espacio de color HSV.

Otra opción, quizás más simple, es simplemente dibujar el sprite usando una textura separada para cada componente. Una textura para el cabello, una para la armadura, una para las botas, etc. Cada color se puede colorear por separado.


2

En primer lugar, generalmente se conoce como ciclismo (ciclado de paletas), por lo que podría ayudar a su Google-fu.

Esto dependerá exactamente de los efectos que desee producir y si se trata de contenido 2D estático o material 3D dinámico (es decir, si solo desea lidiar con spites / texturas, o renderizar una escena 3D completa y luego cambiar de paleta). Además, si se limita a una serie de colores o utiliza todo color.

Un enfoque más completo, usando sombreadores, sería producir imágenes indexadas en color con una textura de paleta. Haga una textura, pero en lugar de tener colores, tenga un color que represente un índice para buscar y luego tenga otra textura de colores que sea la placa. Por ejemplo, el rojo podría ser la coordenada t de las texturas y el verde podría ser la coordenada s. Use un sombreador para hacer la búsqueda. Y animar / modificar los colores de esa textura de paleta. Esto estaría bastante cerca del efecto retro, pero solo funcionaría para contenido 2D estático como sprites o texturas. Si está haciendo gráficos retro genuinos, es posible que pueda producir sprites de color indexados reales y escribir algo para importarlos y las paletas.

También querrá hacer ejercicio si va a utilizar una paleta 'global'. Al igual que la antigüedad del hardware, menos memoria para las paletas, ya que solo necesita una, pero es más difícil producir su obra de arte, ya que tiene que sincronizar la paleta en todas las imágenes) o dar a cada imagen su propia paleta. Eso le daría más flexibilidad pero usaría más memoria. Por supuesto, puedes hacer ambas cosas, también puedes usar paletas para las cosas en las que cambias de color. También descubra en qué medida desea limitar sus colores, los juegos antiguos usarían 256 colores, por ejemplo. Si está utilizando una textura, obtendrá el número de píxeles, por lo que un 256 * 256 le dará

Si solo desea un efecto falso barato, como el flujo de agua, puede crear una segunda textura que contenga una máscara de escala de grises en el área que desea modificar, luego use las texturas enmascaradas para modificar el tono / saturación / brillo por la cantidad en el Textura de máscara de escala de grises. Podría ser aún más vago y simplemente cambiar el tono / brillo / saturación de cualquier cosa en un rango de color.

Si lo necesitara en 3D completo, podría intentar generar la imagen indexada sobre la marcha, pero sería lento ya que tendría que buscar el palet, también es probable que tenga una gama limitada de colores.

De lo contrario, podría simularlo en el sombreador, si color == intercambiando color, cámbielo. Eso sería lento y solo funcionaría con un número limitado de intercambio de colores, pero no debería estresar a ninguno de los equipos actuales, especialmente si solo se trata de gráficos 2D y siempre puede marcar qué sprites tienen intercambio de color y usar un sombreador diferente.

De lo contrario, fingirlos realmente, hacer un montón de texturas animadas para cada estado.

Aquí hay una gran demostración del ciclo de pallets realizado en HTML5 .


0

Creo que si son lo suficientemente rápidos para que el espacio de color [0 ... 1] se divida en dos:

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

De esta manera, los valores de color entre 0 y 0.5 se reservan para valores de color normales; y 0.5 - 1.0 están reservados para valores "modulados", donde 0.5..1.0 se asigna a cualquier rango seleccionado por el usuario.

Solo la resolución de color se trunca de 256 valores por píxel a 128 valores.

Probablemente uno puede usar funciones incorporadas como max (color-0.5f, 0.0f) para eliminar completamente los ifs (intercambiándolos por multiplicaciones por cero o uno ...).

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.