Jump to content

Palette Cycling in Pixi.js


mikel
 Share

Recommended Posts

I'm developing a project that uses png indexed sprites with 256-colors palette

And I need those sprites to have different palettes, problem is that I couldn't find a way to do that in PIXI.

I understand that modern hardware doesnt have a way to access the color palette and simple change it.

I know I could do it by javascript, create an array with the sprite original colors and another with the new palette colors, draw the sprite to a canvas and check pixel by pixel, find what index that color belongs to and replace with the corresponding index in the other color array, then import that canvas to PIXI renderer a sprite texture.

But that is CPU based and may work with one image once, but seems a bit overkill for many sprites in the screen with animations changing every tick, more than once per second.

Each spritesheet can have up to 10 color palettes, so storing 10 versions of spritesheets isn't a good option too.

I did a lot of research and read somewhere that I need to use fragment shaders to do that.

Problem is that I have about 0 WebGL knowlegde. I think the correct method would be storing the palettes as 256x1 indexed png files, and each coordinate of the image would be a 1x1 pixel of one color, something like represented in the image (I flipped the palettes in the image). Example:

Coord 1x1: red
Coord 2x1: green
Coord 3x1: blue

I need to know how I can pass the current sprite and the alternative palette as a parameter to the shader and if it's possible that the shader find out what index the current pixel color correspond in the original palette and then replace by the color in that index of same number on the new palette that was passed as an parameter.

If I got it correctly, the shader work in all the pixels of the image at the same time, so the logic would be something like this:

Current working pixel coordinates (x,y) are 5x5, color of the current pixel is red, and red color index is 2 (0-255) in the original palette.

Based on the that, I need to look up the index 2 of the alternative palette to find out the color it contains, that would be green. And then return it to the renderer.

LvxFHnC.png

Link to comment
Share on other sites

Basically you need to replace the sprite shader with one that draws based on two textures: The map, and the colors. It is actually pretty simple, and I did this a long time ago for a project (back in v1?).

I can't for the life of me find the old branch I had with this shader in it, but it was pretty straight forward to write. At the time I found this question helpful: http://gamedev.stackexchange.com/questions/43294/creating-a-retro-style-palette-swapping-effect-in-opengl

Link to comment
Share on other sites

Thanks, I did find that link in my research before, but managed to go futher looking at it again.

I managed to send the palette as an uniform sampler2d to my shader, but I'm getting mostly black lines now.

Here's what my code looks like:

//create filter
function paletteSwap(newpal) {
    PIXI.Filter.call(this,
            //vs
            null,
            //fs
            'varying vec2 vTextureCoord;' +                    
            'uniform sampler2D uSampler;' +
            'uniform sampler2D Palette;' +
            'void main()' +
            '{' +
            'vec4 color = texture2D(uSampler, vTextureCoord);' +
            'vec4 indexedColor = texture2D(Palette, color.xy);' +
            'gl_FragColor = indexedColor;' +
            '}'

            );
    this.uniforms.Palette = newpal;
}

paletteSwap.prototype = Object.create(PIXI.Filter.prototype);
paletteSwap.prototype.constructor = paletteSwap;


// APPLY

var palette = new PIXI.Texture.fromImage('Content/Palettes/10.png');

var filter = new paletteSwap(palette);

//add filter
SprTest.filters = [filter];

I've attached some images from my tests, the test sprite, the palette to be applied and the result of the above code.

It's retrieving colors from the palette, as can be seen by the purple tones at the hair area, but absolutely out of order. I have no idea how to check for the correct indexes.

I've tried following examples from that link and messing a lot with the code, but no real success. By the way, I'm using Pixi.js 4.4.3

10.png

SprTest.png

SprPal.PNG

Link to comment
Share on other sites

If you could create a code pen with your code and images I would be willing to help you get it working :)

Note that you don't go from original image + palette = result. You create a map texture, something like x/y == r/g. Then you upload map texture + palette and get result. Finally, make sure you are using nearest scaling since linear scaling will blur the colors and mess up the mapping.

Link to comment
Share on other sites

Hey, thanks again. I'm using nearest, I've set it up like this:

PIXI.SCALE_MODES.DEFAULT = PIXI.SCALE_MODES.NEAREST;
PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST;

Here's a codepen of what I got so far: codepen.io/mikelspr/pen/XMGdqN

Quote

Note that you don't go from original image + palette = result. You create a map texture, something like x/y == r/g. Then you upload map texture + palette and get result. Finally, make sure you are using nearest scaling since linear scaling will blur the colors and mess up the mapping.

I'm currently trying to find out how I can create a map texture for this...

Link to comment
Share on other sites

Quote

I'm currently trying to find out how I can create a map texture for this...

Make a tool! Here is something I threw together quickly: https://github.com/englercj/paletter

I haven't gotten the shader working 100%, there is still some bleeding but hopefully this is a good place for you to start: 

The image on the left is the original sprite image, the one on the right is the output of the map + palette shader. Hope this helps!

Link to comment
Share on other sites

Thank you guys sooooo much! You're awesome! I finally did it! After xerver's second reply, I tried to come up with a way to create a texture map, but my method wasn't quite working properly, but after setting the mipmap to false on the palette texture, it worked like a charm!

I'll leave a small tutorial here detailing my method, maybe it can help someone else and maybe you guys can give me hints to improve it. :)

The solution I reached was this:

I would analyse the sprite main palette (which is the same in all sprites), then create a formula to generate coordinates based on the rgb values of those colors.

The idea is to create a 512x512 png and paint 1x1 pixels of the colors based on their rgb values. Like:

The color (255, 120, 10) would be painted on the coordinates X: 255 Y: 120 of the 512x512 palette. Then there's the problem of repeated colors when a color Red and Green channels are the same, since I have 3 values for rgb and only 2 for X and Y. My solution was to get the blue value, divide it by two, round it up (ceil) and sum it to the previous obtained X & Y coordinates. Then:

Offset = ceil(10 (Blue) / 2) = 5

X = (255 (Red) + 5) = 260

Y = (120 (Green) + 5) = 125

After proccessing the 256 colors with this method, I now have a map with the coordinates of where each color needs to be painted for those set of sprites, I can now have 100 distinct palettes, as long as the colors are painted on those exact coordinates.

Here's a finished palette png example: http://i.imgur.com/94mnHAb.png

This method will allow me to upload directly to the shader the original sprite + the 512x512 palettes and swap the colors accordingly. Here's the working fragment shader:

varying vec2 vTextureCoord;
uniform sampler2D uSampler;
uniform sampler2D Palette;
void main()
{
    vec4 color = texture2D(uSampler, vTextureCoord);
    if (color.a == 0.0) discard;
    float red = (color.r*255.0);
    float green = (color.g*255.0);
    float blue = (color.b*255.0);
    float diag = ceil(blue / 2.0);
    float divideby = 512.0;
    float sum = 0.5;
    vec2 coord = vec2((((red + diag) + sum) / divideby), ((((green + diag)) + sum) / divideby));
    vec4 indexedColor = texture2D(Palette, coord);
    gl_FragColor = indexedColor;
}

As the fragment shader works on all the pixels of the image at once, what this does is get the RGB values of the current working pixel, (Something to be aware here is that the shader represent colors in a range of 0 ~ 1 and not 0 ~ 255, so it's necessary to multiply it by 255 to get the values we need), does the same calculations as I detailed before to convert color (RGB) to coordinates (XY). Then it'll look at the given coordinates on the palette image and return whatever color is in there.

I hope I managed to explain this in a understandable way. Finally here's a working codepen (click on the sprite to swap palettes). https://codepen.io/mikelspr/pen/RpzZMX

 

 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...