使用着色器替换游戏中的调色板

在此博客中,我将向您展示我在Vagabond游戏中积极使用的最喜欢的技术:更换调色板。

调色板交换是对纹理调色板的更改。 在本文中,我们使用着色器实现它。 在过去,这是一种有用的技术,可让您在不浪费内存的情况下增加资源的可变性。 今天,它已用于程序生成中以创建新资源。



影像准备


第一步是准备图像以替换调色板。 在位图中,每个像素都包含一种颜色,但是我们需要在调色板中包含其颜色索引。 因此,我们将图像结构(相同颜色的区域)与真实颜色分开。

实际上,某些图像格式支持此存储方法。 例如, PNG格式可以保存索引颜色。 不幸的是,即使图像以索引模式保存,许多图像加载库也会创建颜色数组。 这也适用于我使用的SFML库。 在内部,它使用stb_image ,它会自动“删除调色板”,即 用相应的调色板颜色替换索引。

因此,为避免此问题,您需要分别存储图像和调色板。 图像以灰色阴影记录,每个像素的灰度级与其调色板中的颜色索引相对应。

这是我们期望收到的示例:


为此,我使用了一个使用Pillow库的小型Python函数:

import io import numpy as np from PIL import Image def convert_to_indexed_image(image, palette_size): # Convert to an indexed image indexed_image = image.convert('RGBA').convert(mode='P', dither='NONE', colors=palette_size) # Be careful it can remove colors # Save and load the image to update the info (transparency field in particular) f = io.BytesIO() indexed_image.save(f, 'png') indexed_image = Image.open(f) # Reinterpret the indexed image as a grayscale image grayscale_image = Image.fromarray(np.asarray(indexed_image), 'L') # Create the palette palette = indexed_image.getpalette() transparency = list(indexed_image.info['transparency']) palette_colors = np.asarray([[palette[3*i:3*i+3] + [transparency[i]] \ for i in range(palette_size)]]).astype('uint8') palette_image = Image.fromarray(palette_colors, mode='RGBA') return grayscale_image, palette_image 

首先,该功能将图像转换为调色板模式。 然后,她将其重新解释为灰度图像。 然后检索调色板。 没什么复杂的,主要工作由Pillow库完成。

着色器


准备好图像后,我们准备编写一个着色器来替换调色板。 有两种将调色板转移到着色器的策略:可以使用纹理或齐次阵列。 我发现使用齐整数组更容易,因此我使用了它。

这是我的着色器,是用GLSL编写的,但是我认为可以轻松地将其转移到另一种用于创建着色器的语言中:

 #version 330 core in vec2 TexCoords; uniform sampler2D Texture; uniform vec4 Palette[32]; out vec4 Color; void main() { Color = Palette[int(texture(Texture, TexCoords).r * 255)]; } 

我们仅使用纹理来读取当前像素的红色通道。 红色通道是一个介于0到1之间的浮点值,因此我们将其乘以255并将其转换为int以获得从0到255的原始灰度级,该灰度级存储在图像中。 接下来,我们使用它从调色板中获取颜色。

本文开头的动画取自游戏中的屏幕截图,其中,我使用以下调色板来更改角色身体的颜色:

Source: https://habr.com/ru/post/zh-CN480836/


All Articles