• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

how to swap the color palette using shader and texture

Heavybrush

Member
how to change the color of the application surface, using the texture??

https://en.wikipedia.org/wiki/List_of_8-bit_computer_hardware_palettes

https://en.wikipedia.org/wiki/List_...#/media/File:CGA_palette_color_test_chart.png

https://upload.wikimedia.org/wikipedia/commons/c/c5/CGA_palette_color_test_chart.png

my fragment shader start in this way:

Code:
varying vec2 v_vTexcoord;
varying vec4 v_vColour;

void main()
{
    gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
}
thanks
 
I can explain how my palette swap shader works, which is very basic. That means it should be easy for you to understand and to replicate. There are other ways of doing it.

First look at this fragment shader:
Code:
varying vec2 v_vTexcoord;
varying vec4 v_vColour;

uniform sampler2D palette;
uniform float row;

void main()
{

    vec4 base_color = texture2D( gm_BaseTexture, v_vTexcoord );
    vec4 new_color_1 = vec4(texture2D( palette, vec2(base_color.b,row) ).rgb,base_color.a);
    gl_FragColor = v_vColour * new_color_1;
}
What's going on here is:

Two uniforms are received.

"palette" is a texture that contains multiple palettes. Each palette contains up to 256 colors, and each color is contained in a single pixel. Each row of pixels is a single palette. You can have any number of palettes but the max number has to be a power of 2 just to simplify the math. Therefore the palette texture image is 256 x 2^n in size.

"row" indicates to the shader which palette (row) should be used to draw the current image. When you pass this number into the shader uniform, divided it by the height (pixels) of the palette image. So if my palette image is 256 pixels tall, and I want to use the fourth (row) as my palette, I pass in this value: 3 / 256;

Note: The first palette is in row zero, therefore the fourth palette as in the example above, is in row 3.

I should probably mention before I go any further that texture interpolation must be deactivated when drawing with this shader or you will get wrong results.

The shader reads the base texture color, and the blue channel (blue is an arbitrary choice by me, could use any of them), the blue channel of that color contains which color in the current palette to use.

For example if I wanted this pixel in the base image to draw using the color in the 3rd column of the color palette, I would have the blue channel contain the value 2. (The first color in each color palette is in column zero).

The shader then uses the coordinates (base_color.b, row) to read the palette texture, which will determine the color to draw for the current pixel.

new_color1 is multiplied by v_vColour in case you want to use the color and alpha blending.

It is required that the blue channel of any image you draw using this shader contains the column number of the color in the palette texture image. Drawing the blue channel by hand would be very difficult, especially since you could barely be able to tell the difference between slight variations. Therefore you will need a script which automatically takes an image and converts it into a format that this shader can understand. You can use the script I created for this task if you don't care to write one yourself:

Code:
    //background  to use as palette
    var _background_palette = argument0;
 
    //background to convert
    var _background_to_convert = argument1;
 
    //the row (palette) you used to draw your image
    //first row is zero, last row is 255.
    var _palette_row = argument2;
 
    //load palette colors for this row
    ///////////////////////////////////////////////////////////////
    var _w = background_get_width(_background_palette);
    var _h = background_get_height(_background_palette);
    var _surf = surface_create(_w,_h);
    surface_set_target(_surf);
    draw_background(_background_palette,0,0);
    surface_reset_target();
    var _buff = buffer_create(4 * _w * _h, buffer_fast, 1);
    buffer_get_surface(_buff,_surf,0,0,0);
    var _palette_map = ds_map_create();
    buffer_seek(_buff,buffer_seek_start,4 * _w * _palette_row);
    var _b, _g, _r, _a;
    for (var i = 0; i < _w; i++)
    {
        _b = buffer_read(_buff,buffer_u8);
        _g = buffer_read(_buff,buffer_u8);
        _r = buffer_read(_buff,buffer_u8);
        ds_map_add(_palette_map, (_b << 16) + (_g << 8) + _r, i);
        buffer_read(_buff,buffer_u8);
    }
    surface_free(_surf);
    buffer_delete(_buff);
    ///////////////////////////////////////////////////////////////

 
    //convert background
    ///////////////////////////////////////////////////////////////
    var _w = background_get_width(_background_to_convert);
    var _h = background_get_height(_background_to_convert);
    var _surf = surface_create(_w,_h);
    surface_set_target(_surf);
    draw_background(_background_to_convert,0,0);
    surface_reset_target();
    var _buff_size = _w * _h;
    var _buff = buffer_create(4 * _buff_size, buffer_fixed, 1);
    var _buff2 = buffer_create(4 * _buff_size, buffer_fixed, 1);
    buffer_get_surface(_buff,_surf,0,0,0);
    buffer_seek(_buff,buffer_seek_start,0);
    buffer_seek(_buff2,buffer_seek_start,0);
    var _index, _brightness;
    for (var i = 0; i < _buff_size; i++)
    {
        _b = buffer_read(_buff,buffer_u8);
        _g = buffer_read(_buff,buffer_u8);
        _r = buffer_read(_buff,buffer_u8);
        _a = buffer_read(_buff,buffer_u8);
        _index = ds_map_find_value(_palette_map, (_b << 16) + (_g << 8) + _r);
        _brightness = (0.114*_b + 0.587*_g + 0.299*_r);
        if (is_undefined(_index)) { _index = 0; }
        buffer_write(_buff2,buffer_u8,_index);
        buffer_write(_buff2,buffer_u8,_brightness);
        buffer_write(_buff2,buffer_u8,_brightness);
        buffer_write(_buff2,buffer_u8,_a);
    }
    buffer_set_surface(_buff2,_surf,0,0,0);
 
    ///////////////////////////////////////////////////////////////

    //save new image as png
    var _file_name = background_get_name(_background_to_convert) + ".png";
    surface_save(_surf,_file_name);
 
    //create a sprite out of the new image
    converted_image = sprite_create_from_surface(_surf,0,0,_w,_h,0,0,0,0);
 
    ///////////////////////////////////////////////////////////////
 
    //clean-up
    ///////////////////////////////////////////////////////////////
    surface_free(_surf);
    buffer_delete(_buff);
    buffer_delete(_buff2);
    ds_map_destroy(_palette_map);
    ///////////////////////////////////////////////////////////////
To use this script, you first have to create an image by drawing it using only the colors in the palette row that you want to use to encode the image.

That this script does is first look up all the colors contained in the target palette row. It makes a map of those colors, where each color contains the column index of that color.

It then reads the color of every pixel in the image to be converted, finds that color in the map that was created above, and writes into the blue channel of that image the column index for that color.

The overall brightness of the original color is recorded into the red and green channels so that it is easy for you to visually identify the image at a glance.

The alpha channel is unchanged from its original value.

The script saves the newly converted image as a png file, and also creates a sprite of the newly converted image. Delete those lines if you don't need one of them.

This script assumes the palette and image to convert are both backgrounds, but you can change it to use sprites if you need to.

Drawing the converted image using the same palette texture and palette row which was used to convert the image will result in an image which is identical to the orignal image.

Hopefully it is clear that any image only needs be converted once. You can then load the converted images into your project and use them from then on. So that the images do not need to be converted at game run-time.
 
Last edited:

Heavybrush

Member
flyingsaucerinvasion:
I love your answer, is very detailed and well explained, I really thank you for all explanations and I think you really gived me an help, but I'm searching to write a shader for the game run-time conversion
to convert colors in your way and save it in png I just use photoshop, I don't need to convert to make a snapshot converted and save; I'm developing a retro style side scroller game and I need to have a CGA conversion during the game run-time
this conversion must change in base of what kind of palette I want to use

I've been looking for theese cga colors
https://it.wikipedia.org/wiki/Color_Graphics_Adapter
and here I found the palettes to be converted as sprite to be used
https://en.wikipedia.org/wiki/List_of_8-bit_computer_hardware_palettes

juju and pixelated:
I already saw those links, but I don't want to buy the plugin, I want to learn how to write it
 
Hi again, I should clarify, the conversion tool is for converting the image to indexed form, and then you can use any palette you want with those converted images. The conversion process would only have to be done once, by you, before distributing the game.

The conversion could be done at runtime (but I'm not sure why that needs to happen). In order to do that you'd probably want to use one of the tools posted by the other users. There's one that builds its own "texture pages" which should cut down on expensive texture swaps.
 

Heavybrush

Member
yes, I want to write a shader to do the thing in the links
but I need a shader because it have to change dynamically by the uniform variable

first I need to have different palettes for different choices in different stages and contexts
second I will need to change colors of the application surface because in my game the hero use a crafting skill to mix potions and use drugs on himself, the drugs change the skills but change also the way the hero see the world, so I have to change everything dynamically by the shader
third I have to discard all the alpha information, to delete the antialias informations and the alpha of all the particle effects inside game maker, because my game need to be retro style and in past the antialias and alpha didn't exists

I want to try to use more tool I have to give more impression of the life, my game for example is a random procedurally room generator and it give a lot of life, it have very good animation, it's based on the animation to be very fluid and live

In my team I decided to take the shader task, because I like it, I'm curious about it, but I have to be fast and I cannot follow the things step by step for now, also if I'm going to do it in the right time

for this reason I need to do a shader and I need to swap the palette of the entire application surface in the game run-time
 
you can disable anti-aliasing and texture-interpolation without having to modify the shader. Alpha, you don't really need to worry about as long as you are drawing things with either full alpha or zero alpha. If you must discard alpha information, then you could enable draw_set_alpha_test, which will use a threshold test. Any alpha above the thresold is drawn at full opacity, any alpha below the threshold is discarded.
http://docs.yoyogames.com/source/da...g/color and blending/draw_set_alpha_test.html

I'm still not seeing where the need to convert images at run time comes from. As long as images you are drawing are indexed to use with the shader, then they will draw using whatever palette the shader is currently set to use. The only reason that I can think of that you would need to defer image conversion until runtime is if the player loads their own image assets into the game.
 

Heavybrush

Member
I said, I need the shader to convert all the application surface dynamically and change the palette of entire game whenever I want
 
when you say convert, what do you mean? Indexing the colors on the application surface in real time isn't an option. Your game would grind to a halt. But you can draw already indexed images into the game using the shader to "convert" those images into the palette that you want to use.
 

Heavybrush

Member
I already did with shader, but I did a shader that convert the colors if theese are close to my 4 colors
for instance this shader convert in cga cyan magenta black and white, and if i have a green surface it become only black and white
 
Last edited:
Top