Shader Request : Palette replacer ( permute a set of colors in scene with other input colors)

Hello community of developers,
I’m in need of a shader that replaces a set of selected colors from a scene with other equivalent colors ( sort of palette swapping in a graphics editing program).
This was achieved in Joylancer :

I’d really appreciate if someone were to make this shader, and I apologize for my lack of knowledge about shaders, I’ve found them hard enough to quit learning them.
Note : my game is 2D, if that has any effect on the shader.

You’ll need your source sprites to be setup in specific way, like all gray scale, to mark what what palette ID to use where. After that it’s pretty straight forward.

Would that work?

Of course that would work! I’m very flattered to see that you’re ready to work on this, I posted this thread with a 5% chance that someone would put their time making this.
Just to make sure you’ve got the idea, I’ll make my sprites with a unified color palette, import those sprites into Unity, and then select each color of the palette and choose an output color for that input color.

Also, setting the colors from a shader attached to the camera would be fine as well ( i.e. I don’t have to attach the shader to each imported sprite, the camera unifies the whole scene colors)

Yeah, a post process would work too. Although an advantage to having a shader do it per object is that you can apply different palettes to different object, but you can still set a global palatte to use too.

Also, a post effect it make get difficult to differeciate sections you don’t want to change, like a sky for example, and if you use any other effects they could be in advertantly colored.

Are you looking for the palette to be controlled procedurally or with another texture?

I’d really prefer to set the colors from the Unity editor. That is, using the color window to set the input color, and another color window to set the output one :

Well, in my case, if I wanted to isolate a sprite from the post-processing, I’d just create another camera and disable the palette swapping ( I already have a separate camera for the background in my game).
I’m saying this because I’m afraid it’d be impractical to set the colors each time I import a sprite, correct me otherwise as I’m not too familiar with this.

This should work. It might not be the most efficient for large amounts of colors, but it works well. I did find that unless the sprites were set to Point filter there would be artifacts.

Shader "Hidden/PaletteSwap"
{
    Properties {
        _MainTex ("", 2D) = "" {}
    }
    SubShader {

        Pass{
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct v2f {
                float4 pos : POSITION;
                half2 uv : TEXCOORD0;
            };

            v2f vert(appdata_img v)
            {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = MultiplyUV(UNITY_MATRIX_TEXTURE0, v.texcoord.xy);
                return o;
            }

            sampler2D _MainTex;

            uniform float4 _InColorA;
            uniform float4 _InColorB;
            uniform float4 _InColorC;
            uniform float4 _InColorD;

            uniform float4 _OutColorA;
            uniform float4 _OutColorB;
            uniform float4 _OutColorC;
            uniform float4 _OutColorD;

            fixed4 frag(v2f i) : COLOR
            {
                float4 start = tex2D(_MainTex, i.uv);

                float4 final = start;

                float testA = saturate(abs(start.x - _InColorA.x) + abs(start.y - _InColorA.y) + abs(start.z - _InColorA.z));
                final = lerp(final, _OutColorA, 1-testA);

                float testB = saturate(abs(start.x - _InColorB.x) + abs(start.y - _InColorB.y) + abs(start.z - _InColorB.z));
                final = lerp(final, _OutColorB, 1 - testB);

                float testC = saturate(abs(start.x - _InColorC.x) + abs(start.y - _InColorC.y) + abs(start.z - _InColorC.z));
                final = lerp(final, _OutColorC, 1 - testC);

                float testD = saturate(abs(start.x - _InColorD.x) + abs(start.y - _InColorD.y) + abs(start.z - _InColorD.z));
                final = lerp(final, _OutColorD, 1 - testD);

                return final;
            }
            ENDCG
        }
    }
}

And this is an example of a script that would go on the camera, you can set this up how you would like:

using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
public class PaletteSwap : MonoBehaviour
{
    [SerializeField]
    private Color[] input;
    [SerializeField]
    private Color[] output;

    private Material paletteswapmat;

    public bool reset = false;

    void Init()
    {
        paletteswapmat = new Material(Shader.Find("Hidden/PaletteSwap"));
    }

    void Awake()
    {
        Init();
        SetColors();
    }

    public void SetColors()
    {
        paletteswapmat.SetColor("_InColorA", input[0]);
        paletteswapmat.SetColor("_InColorB", input[1]);
        paletteswapmat.SetColor("_InColorC", input[2]);
        paletteswapmat.SetColor("_InColorD", input[3]);

        paletteswapmat.SetColor("_OutColorA", output[0]);
        paletteswapmat.SetColor("_OutColorB", output[1]);
        paletteswapmat.SetColor("_OutColorC", output[2]);
        paletteswapmat.SetColor("_OutColorD", output[3]);
    }

    void Update()
    {
        if(reset)
        {
            SetColors();
            reset = false;
        }
    }

    void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        Graphics.Blit(source, destination, paletteswapmat);
    }

}

This only does 4 colors, to add more you’ll have to manually add more colors to the shader and script.

I actually think this is kind of a poor way to accomplish this, however, since there is a cap on the number of colors you can use depending on your platform this way.

1 Like

Thanks a bunch!! This is working really good.
I appreciate your work on this, and sorry for any inconvenience.

1 Like