Asset - Shaders Retro Palette Swapper


Check out the HTML5 interactive demo over on Itch.io.
If you like this Asset and want to support me by purchasing it, you can get it at the above link or on the Marketplace.
If you'd like a free copy, send me a DM!

Retro Palette Swapper is a bit more than just a shader; it is an entire system for drawing sprites, surfaces, tiles, and backgrounds with a manipulated palette in your Game Maker Studio 1 or 2 project.

Have you ever created a character in your game and wished you could change the character's hair or clothing color without adding a complete new set of sprites to your project for every single one of that character's animations?
What about making a sprite appear on fire in a much more vibrant way than just putting a red or orange image_blend on it?
Maybe you've got a black outline around your sprite and want to make it red or green when the player mouses over it?

All of this and much more is possible with Retro Palette Swapper!

Features
* Draw any sprite, surface, or layer with a specific palette!
* Use surfaces to build dynamic palettes on the fly!
* Gradually shift between palettes to create dynamic effects!
* Easy to drop into any existing project!
* Fully commented example code!
* Helpful scripts for manipulating palettes!
* Full featured tool available to help non-artists build palette sprites! (Free to download for everyone)

Demo
Before you buy, try out the demo available on itch and make sure it runs for you.

Requirements
All platforms supported (HTML has a few extra hoops to jump through)


This shader will make your project require a video card that supports Shader Model 3.0 (which was released in 2004... so that really shouldn't be a problem)
Additionally, your graphics need to be "retro"... or at the very least you should NOT be using the "interpolate colors between pixels" graphical option.

FAQ
Q
: "I got it, but it isn't working."
A: Well, post here and I'll do my best to help you get it working. You may also want to scan this post to see if your issue has already been discussed.

Q: "The example project doesn't seem to do anything"
A: Make sure you've installed the latest version of direct-x and video card drivers. I promise this works.

Q :"My sprite doesn't look any different when swapped."
A: Make sure the colors in your palette are EXACT MATCHES of the colors in your sprite. There is no "approximation"; the colors must be identical. Use the palette builder to help you find color inconsistencies in your sprites.Additionally, make sure you've imported the shader properly. Double check the fragment and vertex tabs in the shader asset itself compared to the example project.

Q: "Only some of my colors are changing."
A: See previous answer.

Q: "I'm trying to get it to work with OperaGX. What's wrong?"
A: OperaGX is not a "browser" type in GameMaker. It is an OS Type. So when you build for Opera, it tries to use the Desktop version of the shader rather than the HTML5 version. So you can either go into the logic and make the change yourself in the system init function, or just use the HTML5 shader in both arguments of the init function. Hopefully I'll have this fixed in a future update when I'm feeling less lazy.

Q: "Everything looks SUPER weird."
A: Did you turn off the "Interpolate Colors Between Pixels" option in the global game settings? This will not work at all with that option on, so if your game's aesthetic relies on that, you are out of luck; this won't work for you.

Q: "I'm using GMS2, and I've copied the code into a new shader resource, but it won't work."
A: You need to set the shader type to GLSL ES. Right click on the shader in your resource tree and expand the "Shader Type" option.

Q: When I run the Palette Builder Tool, I get an error about the shader not working correctly. How do I fix that?
A: You need to install the latest DirectX end-user runtime from Microsoft. https://www.microsoft.com/en-us/download/details.aspx?id=35 Why is that required to run the pal builder but not the demo project? I honestly have no idea.
 
Last edited:

Zerb Games

Member
So, is there anyway this could be applied to a FPS to make it look more retro? Or is it just for specific sprite pallet swapping? Also, I downloaded the example, and saw no difference in the sprites. Why is that?
 
So, is there anyway this could be applied to a FPS to make it look more retro? Or is it just for specific sprite pallet swapping?
Nope. This is only for 2D "retro" style games. Pixel art only.

Also, I downloaded the example, and saw no difference in the sprites. Why is that?
Odd.

Make sure you have your videocards latest drivers installed and your card supports shader model 3.0 (unless your computer is ancient it probably does). You also need the latest version of DirectX.

I promise, it works.
 

Zerb Games

Member
Odd.

Make sure you have your videocards latest drivers installed and your card supports shader model 3.0 (unless your computer is ancient it probably does). You also need the latest version of DirectX.

I promise, it works.
Yeah I was guessing it's a hardware problem, my computer is ancient :/
 
T

Tronco

Guest
Hi Pixelated_Pope

First, i wanna congratulate with you for your wonderful shader. It's very easy to use and it gets the job done perfectly

But... as you already know, setting and resetting the shader for every drawn sprite, breaks vertex batches. If you don't have a dedicated video card (and unfortunately it's still a common case) and you use it a lot, it can get really heavy on the FPS and the performance of the game.

Your shader can be in some way adapted to be drawn in a single vertex batch for all the game? There is an asset on the marketplace by GamerXP that can do this using surfaces maybe (anyway it crashes on the latest version of GM) but it'd really prefeer to adapt yours, because i really love the way it works.

Or can you suggest me some optimizations to use less batches and palette swap more objects at the same time? I'm experimenting a bit but i'm having an hard time to really get a good method (for example: i have two row of ten enemies and i want to palette swap every enemy of the second row in just one vertex batch using a control object)

Thank you
 
Your shader can be in some way adapted to be drawn in a single vertex batch for all the game?
It certainly can. I haven't used GamerXP's solution personally, so I can't say how it compares (Just a note, GamerXP wrote the actual shader for this solution).

I can't imagine using this for a very colorful game, but if you have a very limited game palette (such as Downwell) it wouldn't be too bad. You can use the shader to draw your application surface to the screen, and use one giant palette that will work for any color that may appear in your game. This would swap every color in the app surface in one vertex batch.

The only way to really optimize it would depend on the type of game you are making. If you can guarantee that all of your enemies will be on the same depth, you could swap their palettes all at once as if they were tiles. This would never work for any of the projects I work on as I need to use depth=-y, but if you don't rely on that trick and can group all of your enemies into a specific depth layer, it could work.
 
T

Tronco

Guest
You're right! The depth trick is great, it works real good. My game is a side scrolling shmup and i have room to play quite well with depth, so i can easily group enemies or elements that need a swap with this trick, i haven't really thought about it!

About using one giant palette, the game uses 16-bit palette but it's quite a colorful one, kind of Neo Geo palette, and i think it will be a problem if i want to swap a colour only for a determinated object; the same colour will be swapped in every other object i think.

Anyway, i suggest to not understimate this vertex batch thing, because other than that the shader is 100% perfect and it's a MUST for every neo-retro game like mine. Unfortunately, we noticed that a lot of people interested in these kind of games are playing on old laptops or old PCs without a proper gpu, or in some cases use minimal configurations to play these games on arcade cabinets. So we have to take the GPU performance very seriously.

Thanks again
 
Glad that will work well for you. That should help quite a bit.

Anyway, i suggest to not understimate this vertex batch thing
Yup. Anyone considering using this shader for a retail release should carefully consider how you use it, as without a dedicated video card it can get quite demanding.
 

Aviox

Member
@Pixelated_Pope I've tried your shader in GMS2.0, and it seems it doesn't (yet?) support HLSL, which is what the shader is written in. I don't recall why you made the language switch to HLSL, but I was able to translate it to GLSL ES and get it successfully working. (I didn't unroll the for loop, so that might cause issues for some)

With your permission, I'll post it here as a "workaround" for others to use. (I imagine that would be fine, since I'd argue what people are "paying for" is more the scripts around the shader than the shader itself. But I'll still wait for your permission.:))
 
Last edited:
@Aviox Absolutely! Post it here, and I'll edit the main post so people can find the work around easier. Thanks a bunch. And, yeah, it's definitely the scripts around the shader. Again, the shader itself was written by GamerXP (who I'm not sure has made the transition to the new forums yet).
 

Aviox

Member
Alright, here's the shader translated from HLSL into GLSL ES.

Vertex Shader: (just a default passthrough shader)
Code:
//
// Simple passthrough vertex shader
//
attribute vec3 in_Position;                  // (x,y,z)
//attribute vec3 in_Normal;                  // (x,y,z)     unused in this shader.   
attribute vec4 in_Colour;                    // (r,g,b,a)
attribute vec2 in_TextureCoord;              // (u,v)

varying vec2 v_vTexcoord;
varying vec4 v_vColour;

void main()
{
    vec4 object_space_pos = vec4( in_Position.x, in_Position.y, in_Position.z, 1.0);
    gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * object_space_pos;
    
    v_vColour = in_Colour;
    v_vTexcoord = in_TextureCoord;
}

Fragment Shader:
Code:
varying vec2 v_vTexcoord;
varying vec4 v_vColour;

uniform sampler2D palette_texture;
uniform vec2 texel_size;
uniform vec4 palette_UVs;
uniform float palette_index;

void main()
{
    vec4 source = texture2D( gm_BaseTexture, v_vTexcoord );
    for(float i = palette_UVs.y; i < palette_UVs.w; i+=texel_size.y )
    {
        if (distance(source, texture2D(palette_texture, vec2(palette_UVs.x, i))) <= 0.004)
        {
            float palette_V = palette_UVs.x + texel_size.x * palette_index;
            source = texture2D(palette_texture, vec2(palette_V, i));
            break;
        }
    }
    gl_FragColor = v_vColour * source;
}
 
For those of you who have noticed the increase in load times by a few seconds and want to not have that happen, there IS a work around.

It requires converting the shader to HLSL 9 and taking advantage of the unroll() command. So if the extra load time is bothering you, try this shader.

Remember to set the language drop down to HLSL 9
struct VS_INPUT { // Input to VS
float4 Position : POSITION;
float4 Color : COLOR0;
float2 Texcoord : TEXCOORD0;
};

struct VS_OUTPUT { // Output to PS from VS
float4 Position : SV_POSITION;
float4 Color : COLOR0;
float2 Texcoord : TEXCOORD0;
};

VS_OUTPUT main(VS_INPUT IN)
{
VS_OUTPUT OUT;
OUT.Position = mul(gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION], IN.Position);
OUT.Color = IN.Color;
OUT.Texcoord = IN.Texcoord;
return OUT;
}
struct PS_INPUT { // Input from VS to PS
float4 Color : COLOR0;
float2 Texcoord : TEXCOORD0;
};

uniform sampler2D palette_texture;
uniform float2 texel_size;
uniform float4 palette_UVs;
uniform float palette_index;

float4 main(PS_INPUT IN): SV_TARGET
{
float4 source = tex2D( gm_BaseTexture, IN.Texcoord );

[unroll(32)]
for(float i = palette_UVs.y; i < palette_UVs.w; i+=texel_size.y )
{
if (distance(source, tex2D(palette_texture, float2(palette_UVs.x, i))) <= 0.004)
{
float palette_V = palette_UVs.x + texel_size.x * palette_index;
source = tex2D(palette_texture, float2(palette_V, i));
break;
}
}

return source * IN.Color;
}

Notice the "[unroll(32)]" in the Fragment code (about line 15). If this shader doesn't compile for you or doesn't seem to work quite right, try adjusting this value up or down.

I can't speak too much for the full effects of this. It seems to work great for the Windows platform, but I'm not sure if this will work on Android, iOS or other desktop platforms... so test it out and let me know if you find any other issues with it.
 
S

Sirrico

Guest
This looks like a great extension of the drawing features in GMS. A quick question, though. Do the drawing and positioning commands _have_ to be used to have a palette swapable object, or can I simply switch up the palette of an existing object/sprite on the fly after initialization?
 
S

Sirrico

Guest
Retro Palette Swap shader is a bit more than just a shader; it is an entire system for managing, manipulating, and drawing sprites, surfaces, backgrounds, and tiles with a swapped palette.
@Pixelated_Pope I took this line to mean that you had custom functions for drawing and moving sprites about the screen. If that isn't the case (as it seems!) then disregard my question. Thanks!
 
@Sirrico

Ah, I could see how that could be misinterpreted. What I mean by that is "there are a bunch of built in scripts to help you do stuff and a tool to help you build palettes". Yeah, you just use the standard drawing functions to use this. It's very, very simple.
 
S

Stuart

Guest
Wow, this is great! Thanks. Quick question: I'm working on a game that only uses 4 colours (like game boy). I'd like to include the ability to swap palette similar to games like downwell. Do I need to add the draw code to every object in the game or is there a blanket way I can manipulate all colourss that are currently on the screen (the game only ever uses these same four colours). I've never used surfaces before, so not sure if that would be the solution? Thanks
 
S

Stuart

Guest
Hi! Thanks for getting back to me so promptly.

I'm not sure your video link worked as I don't see a link to any video in your comment?

Thanks
 
R

Renante Silvesttre

Guest
Hi! I bought this in Game Maker, it all works great. Then somehow when testing on Android and iOS, there is some part that is not colorized (the player should be in blue palette and the default is orange and it is showing here) http://imgur.com/a/Cue85 It works ok during character selection, or when new character color is unlocked but during playthrough, when player switches animation from idle to run to jump to attack, the palette shader breaks somehow.
 
S

Stuart

Guest
Hi! I bought this in Game Maker, it all works great. Then somehow when testing on Android and iOS, there is some part that is not colorized (the player should be in blue palette and the default is orange and it is showing here) http://imgur.com/a/Cue85 It works ok during character selection, or when new character color is unlocked but during playthrough, when player switches animation from idle to run to jump to attack, the palette shader breaks somehow.
"What platforms are supported?"
I've tested it on Windows and Android and they both work great. I assume any desktop platform and iOS "should" work, but I don't have the means to test it. HTML5 IS NOT SUPPORTED. The for loop in the shader prevents it from being webGL compatible. Sorry.
Hi both. I recently bought this after trying it out and it looks great, but on Android, there is some serious lag! I'm developing a game that has unlockable palette swaps (like downwell) and am using draw_surface and application_surface to redraw the surface with the selected palette. As you guys have had some success on android, I figured I'd ask for some advice.

Here is a video of how it looks on PC:

This thread: https://forum.yoyogames.com/index.p...s-and-all-ios-ones-to-lag-with-surfaces.3603/ discusses some issues with this on Android, but I just wondered if there was some other work around you might suggest to have unlockable palettes in my game? I suppose I could use the set palette function in each individual object and just have it draw_self, but what about things like drawing text and drawing backgrounds? Would this still work?
 
@Renante Silvesttre Are you sure it works perfectly on windows and the issue only happens on Android? Would you be willing to let me see your project so I could reproduce it locally? Even just giving me a test project with your sprites and a simple swap would be sufficient. I have yet to see any inconsistencies between android and windows.

@Stuart Without seeing your code, I imagine you are drawing the application surface using draw_surface_ext and scaling up. This can be a serious bottleneck especially on high resolution mobile displays. What you want to do is draw the appsurface to a second surface with the palette swap, and then draw THAT surface to the screen scaled to fit. This will reduce the number of pixels the shader has to run on dramatically and should speed things up considerably.
 
R

Renante Silvesttre

Guest
Hi Pixelated Pope
Here is a video of what's happening

When I first tried it, it worked on all platforms ok, pc android and ios. Then suddenly the problem arised during gameplay after adding more graphics. I noticed that texture atlas size affects it so I decreased to 512x512. It kind of fixed it but there is probably something else going on. Now that I am using background taller than 512px the fix is not working anymore. I just mentioned that just in case you encountered same thing with texture pages before.

Basically everything is perfect on pc and mac.
On android and ios, they exhibit the same problem. Character select is ok, colors are replaced perfectly. But when I am playing the game it is as if the new color wears off and on.
 
Last edited by a moderator:
@Renante Silvesttre I have never seen any issues like this before. If you want me to trouble shoot, I'd be happy to. I can build Android locally. Beyond that, I would say really scrutinize your color choices on your in game sprites. It may not be as exact as you may think.
 
R

Renante Silvesttre

Guest
I'll send over a file where you can build for Android. The sprites in my game uses only 5 colors and it actually worked before until this happened.
 
R

Renante Silvesttre

Guest
I just wanted to ask if color palette is affected by screen resizing? Maybe it's compatibility that affects the artifacts. Do you have recommended screen resizing for mobile screens?

Here's what I use

Code:
scr_resolution();
window_width = view_wview[0];
window_height = view_hview[0];
window_set_size(window_width, window_height);
Script scr_resolution:
Code:
base_w = 160;
base_h = 256;
max_w = display_get_width();
max_h = display_get_height();
aspect = max_h/max_w;
VIEW_WIDTH = min(base_w,max_w);
VIEW_HEIGHT = VIEW_WIDTH*aspect;

view_wview[0] = floor(VIEW_WIDTH);
view_hview[0] = floor(VIEW_HEIGHT);
view_wport[0] = max_w;
view_hport[0] = max_h;

surface_resize(application_surface,view_wview[0],view_hview[0]);

var i = true;
var rm = room_next(room);
while(i){
    room_set_view(rm,0,true,0,0,view_wview[0],view_hview[0],0,0,view_wport[0],view_hport[0],0,0,-1,-1,-1);
    room_set_view_enabled(rm,true);
    if(rm == room_last)i = false;
    else rm = room_next(rm);
}
 
@Renante Silvesttre
Screen scaling should only affect overall performance (because you are drawing more pixels to the app surface, that's more pixels the shader has to run on). But it shouldn't affect how the shader itself works.
 
R

Renante Silvesttre

Guest
Ok, so somehow the sizes of background affected it. I changed the background I am using from 158x401 to 160x400. I also then use 512x512 Texture pages instead of 1024
 
R

Renante Silvesttre

Guest
Yeah it is super strange. It's actually what I wont call a suggested fix. It also happened again when I added a new background of 160 x 400.
 
Yeah it is super strange. It's actually what I wont call a suggested fix. It also happened again when I added a new background of 160 x 400.
This "sounds" like a texture page issue, honestly. Go through your compilation log and see if you get some lines like "Warning : resource bah blah rescaled from <w,h> to <w,h>"

If so, that's what's causing the issue. I would have expected that resize to be "nearest neighbor" scaling so no "new" colors would be created in the process, but maybe it's bicubic or something.
 
R

Renante Silvesttre

Guest
@Renante Silvesttre That is... super strange. But I'm glad you got it figured out.
Ok, thanks for that. I checked the warnings and I get this
could not open file C:\Users\Bari\Documents\GameMaker\Projects\TowerFortress.gmx\sprites\images\spr_player_fall_stop_0.png, using blank 16x16 replacement
At first I thought because it is a 0x0 px that is resized to 16x16 it is the cause of the problem. But after removing the unused sprite, the same error still occurs.
EDIT: I fixed it :D
 
Last edited by a moderator:
R

Renante Silvesttre

Guest
I think I fixed it! I just put all player sprites in its own texture group. Thank you for your replies. I was tuck into thinking the fix will be somewhere in the code or your shader. Looks like something gets rescaled somewhere, and moving all player sprites in its own texture group keeps it from being affected. Thank you! This is really awesome color palette changer!
 
I think I fixed it! I just put all player sprites in its own texture group. Thank you for your replies. I was tuck into thinking the fix will be somewhere in the code or your shader. Looks like something gets rescaled somewhere, and moving all player sprites in its own texture group keeps it from being affected. Thank you! This is really awesome color palette changer!
Yay! Glad you got it working! Good luck with your project.
 
A

ajan-ko

Guest
@Pixelated_Pope
Thank you for the assets.

I tried to interpolate between pixel using your pallete shader,

Make the shader swap loosely...

Code:
//changed the code to loosely shader swap 0.16
if (distance(source, texture2D(palette_texture, vec2(palette_UVs.x, i))) <= 0.16)

upload_2017-4-17_13-5-8.png
after that, try to manually mapping the interpolate pixel.
It's not perfect but...

 
A

ajan-ko

Guest
@ajan-ko Can I ask why you are using interpolate colors between pixels? Your art style is very pixel art-y, so you really shouldn't be using it.
Yeah, you're right.

Well, the problem is my game have zoom in zoom out features (but now I realized my zoom feature screw up the pixel art).

And some my image using 0.7 stretched out.

Basically, I screwed up.

Now, I need to turn of interpolation, remove zoom feature. Fix some Stretched image, and hopefully learn from my mistake.
 
@ajan-ko Definitely turn off interpolation, but it's not impossible to do zooming and scaling with pixel art and make it look good if you take advantage of sub pixels. Resize your application surface to be the size of your game window and your pixel art will be able to scale and rotate with minimal distortion (although you are effectively increasing the size of your game resolution, and just like in a 3D game, higher resolution means more pixels, means worse performance.

If you are curious about the topic, I have an indepth tutorial on my youtube channel you should check out. I even show zooming on pixel art specifically in part 3.

https://www.youtube.com/playlist?list=PLXkVsacazW2qvdnKNzgBLkUwlgi3FU-VO
 
A

ajan-ko

Guest
@Pixelated_Pope
Edit: hmm... can we fixed the default hview?
Code:
global.default_wview=1280;
global.default_hview=720;
window_set_size(global.default_wview,global.default_hview);
surface_resize(application_surface, global.default_wview*4,global.default_hview*4);
obj_camera
Code:
view_wview[0]= global.default_wview * zoom ;
view_hview[0]= global.default_hview * zoom ;
it still broken when the zoom 0.75
upload_2017-4-18_11-46-6.png
 
Last edited:
S

Shadowblitz16

Guest
thankyou so much pixelated pope. this shader is awesome.
 

K3N_

Member
Realy thank you Pixelated, you are awesome, that works fine in GMS2
 
Last edited:
S

Skaz

Guest
Just purchased your tools, that's really great! I'll use it to swap character colours for player customization, and swap enemy palette to represent changes in their A.I. behaviours. Not much else I think, but we'll see! Thanks! The palette tool is great, only thing I could see different is a colour pick instead of colour change, and copy/paste in case you want to use the same colour for different colour slots. But hey, I can copy and paste 2 pixel :) I'll post a few palette swap on my Twitter latter today, will make a swap to Samus Aran blue suit and blond hair ^^' (@Skaz_)
 
A

Ali-TP

Guest
Hello, sorry to bother you but I’m having some difficulty understanding how to implement this with GMS2 tiles. Otherwise I’ve got it working well with objects and it’s wonderful. Thank you. This is going to save me a huge amount of time and it's so easy and quick to fiddle colours.

@Shadowblitz16 There is a feature in GMS 2 called "layer scripts" which run before and after each layer is drawn. Set the palette swap before, reset after.
This is the code I’ve put in the draw event of the palette manager obj in an arbitrary instance layer.

//------------------ Tiles
var lay_id = layer_get_id("Tiles_1");
if room = rTitle {
var my_pal_sprite = sTitlePalette;
}
if room = rForest1 {
my_pal_sprite = sForest1Palette;
}
var current_pal = global.eth?1:0;
pal_swap_set(my_pal_sprite,current_pal,false);
pal_swap_set_tiles(my_pal_sprite,current_pal,1000000,999998,false); // I know this bit must be wrong :(
pal_swap_reset();
(“global.eth” is for changing lots of things including palettes to make the world look ethereal - my game is about ghosts and possessing people.)

I assume "obj_tile_swapper_start" and swapper_end arn't needed for GMS2?

This is my first game btw (I don't have any experience with GMS1) and my first post in the forum. I hope this is the right place to post rather than messaging directly.
 
I assume "obj_tile_swapper_start" and swapper_end arn't needed for GMS2?
I mean, it's not "needed" but it is one way to handle it, and if you are using pal_swap_set_tile, then yes they are required. In fact, I recently (like two days ago) published a GMS2.0 specific version to the marketplace that works perfectly using the pal_swap_set_tiles() script.

First off, it's recommended that you use pal_swap_set_tiles() in a step event.
What "depth" are your tiles on? Because it was my example project that used the depth 999,999. If your tiles are on depth 250, then your arguments should be 249,251.
 
Top