Neste blog, mostrarei minha técnica favorita que uso ativamente no meu jogo Vagabond: substituindo paletas.
A troca de paleta é uma alteração na paleta de textura. No artigo, implementamos usando shaders. Antigamente, essa era uma técnica útil, permitindo adicionar variabilidade aos recursos sem desperdício de memória desnecessário. Hoje é usado na geração de procedimentos para criar novos recursos.
Preparação da imagem
O primeiro passo é preparar as imagens para substituir as paletas. Em um
bitmap, cada pixel contém uma cor, mas precisamos conter seu índice de cores na paleta. Por esse motivo, separaremos a estrutura da imagem (áreas da mesma cor) das cores reais.
De fato, alguns formatos de imagem suportam esse método de armazenamento. Por exemplo,
o formato PNG tem a capacidade de salvar cores indexadas. Infelizmente, muitas bibliotecas de carregamento de imagens criam uma matriz de cores, mesmo que a imagem tenha sido salva no modo indexado. Isso também se aplica à biblioteca SFML que eu uso. No interior, ele usa
stb_image , que "remove automaticamente a paleta" das imagens, ou seja, substitui índices pela cor da paleta correspondente.
Portanto, para evitar esse problema, você precisa armazenar a imagem e a paleta separadamente. A imagem é gravada em tons de cinza, e o nível de cinza de cada pixel corresponde ao seu índice de cores na paleta.
Aqui está um exemplo do que esperamos receber:
Para conseguir isso, eu uso uma pequena função Python que usa a biblioteca
Pillow :
import io import numpy as np from PIL import Image def convert_to_indexed_image(image, palette_size):
Primeiro, a função converte a imagem no modo de paleta. Em seguida, ela a reinterpreta como uma imagem em escala de cinza. Em seguida, recupera a paleta. Nada complicado, o trabalho principal é realizado pela biblioteca Pillow.
Shader
Depois de preparar as imagens, estamos prontos para escrever um shader para substituir as paletas. Existem duas estratégias para transferir uma paleta para um sombreador: você pode usar uma textura ou uma matriz homogênea. Eu descobri que é mais fácil usar uma matriz uniforme, então eu a usei.
Aqui está o meu shader, escrevi no GLSL, mas acho que pode ser facilmente transferido para outro idioma para criar shaders:
#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)]; }
Nós apenas usamos uma textura para ler o canal vermelho do pixel atual. O canal vermelho é um valor de ponto flutuante no intervalo de 0 a 1, então o multiplicamos por 255 e o convertemos em
int
para obter o nível de cinza original de 0 a 255, que é armazenado na imagem. Em seguida, usamos para obter a cor da paleta.
A animação no início do artigo é tirada de capturas de tela no jogo, nas quais utilizo as paletas a seguir para alterar a cor do corpo do personagem: