Advanced Palette (Godot 3)

Advanced Palette, Godot 3 version.

Enables the use of swappable 256-color palettes and partial or full sprite color cycling, with a toggle for blending between each cycle step.

Requires recoloring the sprite. In return, you can supply palettes as PNG files of any width and height and with more or fewer colors than the sprite was designed for, and they will be handled automatically.

An alternative version that allows for multiple palettes to be defined in a single PNG file is available from Github.

A tutorial on how to use Advanced Palette is available at the below “See more” link.

Shader code
//	Godot 3 Advanced Palette Shader

shader_type canvas_item;

uniform bool		disableProcessing;

uniform sampler2D	palette;

uniform int			cycleMode: hint_range(0, 2, 1);
uniform float		cycleRate: hint_range(-120, 120, 1);
uniform float		cycleOffset: hint_range(0, 256, 0.1);
uniform bool		colorBlending;

vec4 applyPalette(vec4 sprTex)
{	// Palette data
	ivec2	palSize	 = textureSize(palette, 0);
	int		palCount = palSize.x * palSize.y;
	
	// Color channel data
	float idxPalette = floor(sprTex.r * 256.00);	// Color index
	float idxCycEnds = floor(sprTex.g * 256.00);	// Color cycle end index
	float idxCycLeng = floor(sprTex.b * 256.00);	// Color cycle length
	
	// Cycling
	float idxCycle	 = cycleOffset + TIME * cycleRate;	// Cycle frame
	float idxCycNext = idxPalette;
	
	// Ignore index #0
	if (idxPalette > 0.0)
	{
		switch (cycleMode)
		{
			case 1:		// Localized color cycling
				if (idxCycEnds * idxCycLeng > 0.0)
				{
					idxPalette = floor(idxPalette + mod(idxCycle, idxCycLeng));
					idxCycNext = idxPalette + 1.0;
					
					// Wrap indices around if they exceed end of cycle
					if (idxPalette > idxCycEnds)
						idxPalette -= idxCycLeng;

					if (idxCycNext > idxCycEnds)
						idxCycNext -= idxCycLeng;
				}
				break;

			case 2:		// Palette-wide color cycling
				idxPalette = floor(idxPalette + idxCycle);
				idxCycNext = idxPalette + 1.0;
				break;
		}
		// Wrap indices back within palette bounds, skipping index 0
		idxPalette = mod(idxPalette - 1.0, float(palCount - 1)) + 1.0;
		idxCycNext = mod(idxCycNext - 1.0, float(palCount - 1)) + 1.0;
	}
	
	// Palette sampler UV
	float yxUV = idxPalette / float(palSize.x);
	
	// Sample palette from center of pixel (maximum result accuracy)
	float xUV = fract(yxUV) + (0.5 / float(palSize.x));
	float yUV = floor(yxUV) / float(palSize.y) + (0.5 / float(palSize.y));
	vec2 palUV = vec2(xUV, yUV);
	
	if (!colorBlending)
	{
		// Unblended result
		return texture(palette, palUV);
	}
	else
	{
		// Next cycle step sampler UV
		float nyxUV = idxCycNext / float(palSize.x);
		
		// Center next index sampler
		float nxUV = fract(nyxUV) + (0.5 / float(palSize.x));
		float nyUV = floor(nyxUV) / float(palSize.y) + (0.5 / float(palSize.y));
		vec2 nxtUV = vec2(nxUV, nyUV);
		
		// Blend result
		vec4 pixel	= texture(palette, palUV);
		vec4 next	= texture(palette, nxtUV);
		return mix(pixel, next, fract(idxCycle));
	}
}

void fragment()
{
	if (!disableProcessing)
	{
		COLOR = applyPalette(texture(TEXTURE, UV));
	}
	else
	{
		COLOR = texture(TEXTURE, UV);
	}
}
Tags
palette, swap
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from ALT_OhDude

Advanced Palette (Godot 4)

Related shaders

Advanced Palette (Godot 4)

GLES 2.0 2D Fragment Shader Tutorial Series in a Single Godot Project from Beginner to Advanced

Earthbound-like battle background shader w/scroll effect and palette cycling

guest

0 Comments
Inline Feedbacks
View all comments