0
\$\begingroup\$

Basically, what I want to do is take a primary input image texture (which is a render texture in-game) and a secondary pallete texture (contains X number of colors, and is X by 1 pixels in size). What I want to do is replace all of the pixels in the image with the most similar color in the color pallete texture. I made a stab at producing the effect, but at the moment, it only seems to return the first color in the pallete. Below is my (nonfunctional) version. I'm new to shaders, so sorry for the potentially very sloppy/basic shader code.

Shader "Custom/Limiter" {
Properties
{
    _MainTex ("Texture", 2D) = "white" {}
    _PalTex ("Pallete", 2D) = "white" {}
}
SubShader
{
    // No culling or depth
    Cull Off ZWrite Off ZTest Always

    Pass
    {
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag

        #include "UnityCG.cginc"

        struct appdata
        {
            float4 vertex : POSITION;
            float2 uv : TEXCOORD0;
        };

        struct v2f
        {
            float2 uv : TEXCOORD0;
            float4 vertex : SV_POSITION;
        };

        v2f vert (appdata v)
        {
            v2f o;
            o.vertex = UnityObjectToClipPos(v.vertex);
            o.uv = v.uv;
            return o;
        }

        sampler2D _MainTex;
        sampler2D _PalTex;

        fixed4 frag (v2f i) : SV_Target
        {
            float4 baseCol = tex2D(_MainTex, i.uv);
            float4 palCol = tex2D(_PalTex, 0);
            float palDiff = 1000;

            for (int p = 0; p < 37; p++) {
                float baseColDiff = abs(tex2D(_PalTex, p).r - baseCol.r) + abs(tex2D(_PalTex, p).g - baseCol.g) + abs(tex2D(_PalTex, p).b - baseCol.b);
                palDiff = min(palDiff, baseColDiff);
                palCol = (abs(palDiff - baseColDiff) < 0.001) ? tex2D(_PalTex, p) : palCol;
            }

            return palCol;
        }
        ENDCG
    }
}
}

Any help would be greatly appreciated! I feel like it's a relatively simple problem to solve, but I couldn't find any examples of existing shaders like it, or any resources describing it.

Edit: This shader works... sort of. It has been picking the nearest color, but it has only been picking two yellow colors out of the pallete. I tried removing those colors, and it then chose to use only an orange. I don't know if this will help anyone, but I thought it was at least mildly interesting.

\$\endgroup\$
  • \$\begingroup\$ I may have figured out the issue. It might have something to do with the fact that I'm comparing RGB values, which means that mathematically similar values don't necessarily look similar at all. I'm going to try using HSV values in some places instead. \$\endgroup\$ – Koala Squad Apr 26 at 17:28
  • \$\begingroup\$ I wonder if the color space could also be contributing to the problem? I've no concrete suspicion as to how this might interfere, but I know Gamma space has caused problems with my shader code in the past. \$\endgroup\$ – Chaosed0 Apr 26 at 21:03
  • \$\begingroup\$ Nope, sadly. I wish it was that easy. Also, it turns out that comparing HSV values has the exact same result as comparing RGB values, or at least comparing them directly. Surprisingly complicated. \$\endgroup\$ – Koala Squad Apr 26 at 21:54
  • \$\begingroup\$ Whoops, comparing HSV values has a different result to comparing RGB values, but now it's still wrong, and it's still using only the two yellow hues. \$\endgroup\$ – Koala Squad Apr 26 at 22:09
  • \$\begingroup\$ What color palette are you using? \$\endgroup\$ – Alex F Apr 26 at 22:58
0
\$\begingroup\$

What I would do is loop through each palette color, looking for the closest one to the base color. Something like:

float4 bestColor = float4(0, 0, 0, 0);
float3 bestDiff = float3(1000, 1000, 1000);
for (float i = 0; i < X /*number of colors in palette*/; ++i) {
    float4 palCol = tex2D(_PalText, float2(i, 0));
    float3 diff = abs(baseCol.rgb, palCol.rgb);
    if (length(diff) < length(bestDiff)) {
        bestDiff = diff;
        bestColor = palCol;
    }
}
return bestColor;

(Apologies for any typos)

\$\endgroup\$
  • \$\begingroup\$ That does exactly the same thing as my shader. Mine just doesn't use an if statement. Oh, and I realized the for loop in mine was messed up because I was debugging. \$\endgroup\$ – Koala Squad Apr 26 at 15:31
  • \$\begingroup\$ I thought that the problem in your shader was that you were using the sum of the differences of the colors, instead of the length (in color space). This will make it so that bright colors (pink, cyan, yellow, white) and dark colors (black) get more biased than they should. \$\endgroup\$ – Alex F Apr 26 at 22:57
  • \$\begingroup\$ I have no idea if that's the case, since I've only seen two light colors being used the whole time, but my point is that your shader is actually functionally identical to mine. It's written differently, but the end result is the same. \$\endgroup\$ – Koala Squad Apr 27 at 14:18
  • \$\begingroup\$ Does mine also produce only yellow colors? \$\endgroup\$ – Alex F Apr 27 at 18:11
  • 1
    \$\begingroup\$ Yes. Sorry I didn't make it more clear in the first comment. \$\endgroup\$ – Koala Squad Apr 28 at 0:51
0
\$\begingroup\$

Ok, so I ended up getting the answer anyway. Here's a GitHub repository that does the effect by using lists of colors. Not sure why it works when this shader doesn't, but it works. https://github.com/oxysoft/RetroSuite3D

And here's the shader in that repository doing the job. Don't directly copy-paste it, as it has to use a script to assign the colors to it. https://github.com/oxysoft/RetroSuite3D/blob/master/Assets/Shaders/Custom/RetroPixelMax.shader

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.