在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的调色板交换一样具有魅力!

Answers:


41

我不会担心为某些字符纹理浪费VRAM。

对我来说,使用选项2(可以使用不同的纹理或不同的UV偏移)是更合适的方法:更灵活,数据驱动,对代码的影响更少,错误更少,烦恼更少。


撇开这一点,如果您开始在内存中积累大量的精灵动画和大量的精灵动画,也许您可​​以开始使用OpenGL推荐的,自己动手的调色板:

调色板纹理

主要的GL供应商已放弃对EXT_paletted_texture扩展的支持。如果确实需要新硬件上的调色板纹理,则可以使用着色器来达到该效果。

着色器示例:

//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使用MyIndexTexture(索引颜色的8位正方形纹理)进行采样(RGBA8中的调色板)。只是再现了复古调色板的工作方式。


上面引用的示例使用了2 sampler2D,而实际上可以使用1 sampler1D+ 1 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。
Ilmari Karonen 2012年

我在使它适用于我时遇到了一些问题,主要是因为我不了解颜色的索引是如何确定的-您能否再扩大一点呢?
Zack The Human

您可能应该阅读索引颜色。您的纹理变成2D索引数组,这些索引对应于调色板中的颜色(通常是一维纹理)。我将编辑答案以简化OpenGL网站给出的示例。
Laurent Couvidou 2012年

抱歉,我的原始评论不清楚。我熟悉索引颜色及其工作方式,但是我不清楚它们是如何在着色器或32位纹理的上下文中工作的(因为这些索引没有被索引-对吗?)。
Zack The Human

好吧,问题在于,这里有一个32位调色板,其中包含256种颜色,以及一个纹理8位索引(从0到255)。见我的编辑;)
Laurent Couvidou 2012年

10

我可以考虑两种方法来做到这一点。

方式1:GL_MODULATE

您可以将精灵设置为灰色阴影,并GL_MODULATE使用单一单色绘制纹理时。

例如,

在此处输入图片说明

在此处输入图片说明

但是,这并没有太大的灵活性,基本上,您只能在相同的阴影下变暗。

方法2:if陈述(不利于表现)

您可以做的另一件事是使用一些涉及R,G和B的关键值为纹理着色。例如,第一种颜色是(1,0,0),第二种颜色是(0,1,0),第三种是颜色是(0,0,1)。

你申报几套制服

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

然后在着色器中,最终的像素颜色类似于

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

color1color2color3,是制服,使他们能够着色器运行之前进行设置(取决于什么“西服”洛克人是),那么着色器根本没有休息。

if如果需要更多颜色,也可以使用语句,但是必须进行相等性检查正常工作(纹理R8G8B8文件中的纹理通常在0到255之间,但是在着色器中,rgba浮点数)

方法3:只是将不同颜色的图像冗余地存储在精灵图中

在此处输入图片说明

如果您不是疯狂地尝试保存内存,这可能是最简单的方法


2
#1可以很整洁,但是#2和#3是可怕的坏建议。GPU能够从地图(基本上是调色板)进行索引,并且比这两个建议都更好。
Skrylar

6

我会使用着色器。如果您不想使用(或不能使用)它们,则我会为每种颜色使用一个纹理(或纹理的一部分),并且仅使用干净的白色。这将使您可以为纹理着色(例如通过glColor()),而不必担心为颜色创建其他纹理。您甚至可以在运行时交换颜色,而无需其他纹理工作。


这是一个很好的主意。我实际上早就想到了这一点,但是当我发布此问题时却没有想到。这会带来额外的复杂性,因为任何使用这种复合纹理的子画面都将需要更复杂的绘制逻辑。感谢您提出的建议!
Zack The Human

是的,每个子画面还需要x次通过,这会增加一些复杂性。考虑到当今的硬件通常至少支持基本的像素着色器(甚至是上网本GPU),因此我会选择它们。如果您只是想着色特定的东西,例如健康栏或图标,那可能会很整洁。
马里奥(Mario)

3

“并不是所有精灵的颜色都会改变,只有某些会改变。”

老游戏做了调色板技巧,因为那是全部。如今,您可以使用多种纹理,并以多种方式将它们组合。

您可以创建单独的灰度蒙版纹理,用于确定哪些像素将改变颜色。纹理中的白色区域会接受完整的颜色修改,而黑色区域则保持不变。

在着色器语言中:

vec3 baseColor = texture(BaseSampler,att_TexCoord).rgb;
浮动量= Texture(MaskSampler,att_TexCoord).r;
vec3 color = mix(baseColor,baseColor * ModulationColor,数量);

或者,您可以将固定功能的多重纹理与各种纹理组合器模式一起使用。

显然,您可以采用许多不同的方法来进行此操作。您可以在每个RGB通道中放置不同颜色的蒙版,或通过在HSV颜色空间中进行色移来调制颜色。

另一个也许更简单的选择是为每个组件使用单独的纹理简单地绘制精灵。一种纹理适合头发,一种适合盔甲,一种适合靴子,等等。每种颜色可以单独着色。


2

首先,通常将其称为循环(托盘循环),这样可能会对您的Google-fu有所帮助。

这将取决于您要产生什么样的效果,以及是否要处理静态2D内容或动态3D内容(即,您是否只想处理恶心/纹理,还是渲染整个3D场景,然后进行托盘交换)。另外,如果您要限制自己使用多种颜色或使用全彩色。

使用着色器的一种更完整的方法是实际生成带有托盘纹理的颜色索引图像。制作纹理,但不要使用颜色,而应使用代表代表索引的颜色,然后使用另一种颜色纹理即印版。例如,红色可以是纹理的t坐标,而绿色可以是s坐标。使用着色器进行查找。然后动画/修改该托盘纹理的颜色。这将非常接近复古效果,但仅适用于静态2D内容,例如精灵或纹理。如果您制作的是真正的复古图形,则可以生成实际索引的彩色精灵,并编写一些内容以导入它们和托盘。

如果您要使用“全局”托盘,您也将需要解决。就像旧硬件的工作方式一样,用于托盘的内存更少,因为您只需要一个内存,但是就很难为您的作品制作作品,因为您必须在所有图像之间同步托盘)或为每个图像提供自己的托盘。这将为您提供更大的灵活性,但会使用更多的内存。当然,您既可以做这两种事情,也可以只对颜色循环的物品使用托盘。还可以计算出您不希望将颜色限制到什么程度,例如,旧游戏将使用256种颜色。如果您使用的是纹理,那么您将获得像素数,因此256 * 256将为您提供

如果只需要便宜的假效果(例如流水),则可以创建第二个纹理,该纹理包含要修改的区域的灰度蒙版,然后使用修饰过的纹理修改色相/饱和度/亮度。灰度蒙版纹理。您甚至可能变得懒惰,只需更改颜色范围内任何东西的色相/亮度/饱和度即可。

如果确实需要全3D图像,则可以尝试动态生成索引图像,但是由于必须查找托盘,它会很慢,因此颜色范围也可能会受到限制。

否则,您可以在着色器中伪造它,如果color ==交换颜色,则将其交换。这将很慢,并且仅适用于有限数量的交换颜色,但不应对当今的任何硬件造成压力,尤其是如果您仅处理2D图形,并且始终可以标记哪些子对象具有颜色交换并使用其他着色器时,尤其如此。

否则,请真正伪造它们,为每个状态制作一堆动画纹理。

这是用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(将它们乘以零或一来进行乘法运算...)。

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.