Hello guys, I know this probably looks like a silly question, but I haven’t found anything in the forum that can address my issue.
I want a Palette swapping shader, with separate channels for Red, Green and Blue. This means I will use a 3x256 palette texture (line 1 for red, line 2 for green, line 3 for blue). What I want to achieve is a simple shader that will check each pixel from the main texture, and swap it with the correct palette pixel.
So for example, if Main Texture Pixel is (201,14,102), the shader will find the value from each row.
I am testing several solutions, but nothing…
here’s what I’m trying to do from a downloaded shader:
Why are you using 3 rows? Why not a 1x256 RGB texture? That’ll make this much easier to handle in the shader.
To understand why the current shader may not be working like you expect, a UV value of “0.0” is on the edge of the texture between two pixels. A UV value of “1.0” is on the opposite edge, and is the same color! With bilinear (or better) filtering w/o clamped wrapping enabled you’re going to get a color that’s 50% of the first pixel and 50% of the last pixel. With clamping, point filtering, and no mip maps it’ll act a bit more like you expect, but you’re still technically sampling on the border between pixel colors so you may get the wrong color sometimes.
Lets look at a 4x1 palette of red, green, blue, and black.
Erm, okay, let’s look at that a little bigger.
This is what that 4x1 texture looks like blown up to 512x128 with bilinear filtering and wrapping set to repeat (the default settings). Notice how the color at “0.0” on the left isn’t fully red? Also notice how “0.5” in this case is also half way between the green and blue? Just enabling clamping will remove the blending on the ends, but between the other pixels.
This is probably more how you’re thinking about the texture.
This is clamped & point filtered. However the UVs of “0.0”, “0.25”, “0.5” and 0.75" are still on that border between colors, and due to floating point precision it can end up one side or the other in sometimes unpredictable ways.
What you really want to do, even when using point sampling, is offset your “lookup” UV’s by half a pixel so it’s sampling from the center, like this.
Interestingly, if you do this offset, even the bilinear filtered image will get you the values you actually want.
So going back to the original shader code, the thing people usually do to sample a texture is construct a texture UV with a range from “0.0” and “1.0” on the x, and then sample “0.0” for y. But as you can see above that’s going to get you values you don’t want. Instead you want to sample from a range of “half pixel” to “1 - half pixel” on x. If this is a palette that’s more than 1 pixel high you would want to do the same mid-pixel sampling for y too!
The other thing most people don’t realize is Unity passes all the necessary information about texture resolutions to do this with out needing to hard code anything. Adding the variable _TextureName_TexelSize to your shader will give you the 1/texturedimensions.xy and texturedimensions.xy values.
It’s probably worth looking at the Unity Post Processing Stack for how the LUT Grading is handled. There are 2D and 3D texture versions. Generating a palette for one of those might be a simpler solution than a custom shader. You can see exactly how UT handle this type of palette lookup at the end of ColorGrading.cginc though of course, it’ll use more memory this way but saves a texture lookup or two.
First of all, thank you very much bgolus, I couldn’t expect anything better, your explanation is satisfactory and well made. Thank you DominoM too. You are amazing guys
I’ll get to see if I can get it to work now.
Concerning the use of a 3x256 palette, it was the first thing that came in to my mind: what I want to achieve is instagram-like filters for screenshots, but I see there’s no need probably for them.
Will let you know soon
Fyi, something like Instagram filters are usually more complex than a single 2d texture lookup. Generally these use a 3d matrix transform or 3d texture lookup.
This is scarier sounding than it actually is.
@DominoM 's suggestion of looking at color grading LUT creation and usage is spot on here.
Basically there’s a simple texture, the neutral LUT, you take into Photoshop, and paste it on top of an under to use as reference. Then use what ever color manipulation tools you want to modify the image and save it the new LUT with those same modifications.
I took your code and slightly modified it in order to look up on the 3*256 palettes I prepared. It surely is a simplicistic approach to the problem, but I am quite satisfact with the result.
It’s also easy to generate LUTs from code - they are basically a repeated square texture where u = red, v = green and w = blue. The 2D version has one blue value for each square horizontally, so you get a 32 x 1024 texture (for 32 steps in each of RGB) that splits into 32 pixel square for each blue value. The 3D version maps blue to depth, which again gives squares with constant blue and the red / green ramps on UV. So the lookup is done on a specific colour and returns an RGB value to use instead.
The advantage of a LUT over the individual RGB ramps is that any colour can be mapped to any other colour, so you could desaturate blues and greens without turning everything pink if you wanted.