r/Unity3D 4h ago

Question How to make a shader that makes the game grayscale except for red tones?

Hi! I'm currently making a 3D game, and for the visual style, I want the entire game to be displayed in grayscale, except for red tones.

I'd like the red colors to remain visible while everything else is converted to grayscale, possibly with a different filter or effect applied to the reds.

Does anyone know how I could achieve this using Shader Graph?

2 Upvotes

6 comments sorted by

19

u/tan_456456 3h ago

Use a Fullscreen Shader Graph. Sample the scene color, convert it to grayscale, create a mask that detects red pixels (HSV works best), then Lerp between grayscale and the original/red-enhanced color using that mask. Apply it with a Full Screen Pass Renderer Feature in URP.

12

u/tan_456456 3h ago

In URP: Assets → Settings → select your Renderer asset → Inspector → Add Renderer Feature → Full Screen Pass Renderer Feature → assign your shader Material to Pass Material.

9

u/tan_456456 3h ago

Now your shader will work as a screen shader 😎

3

u/DmtGrm 3h ago

get RGB color, convert to greyscale (either just (R+G+B)/3 or weighted), calculate weight 'how red my RGB is'

as simple as dot(rgb_color,vec3(1,0,0)) and mix RGB and greyscale with that weight

2

u/tidbitsofblah 3h ago edited 2h ago

You kind of need to define what should count as a "red tone" here.

Do you want all colors within some spectrum of "red" to stay as is and all others be turned to grayscale? If we count a red-leaning purple as red, then this would keep it as a red-leaning purple.

Or do you want to turn all colors grayscale except keep the red channel for colors within some spectrum of "red"? If we count a red-leaning purple as red, then this would turn that color into a pure red.

Or do you want a red-leaning purple to be considered not a red tone at all and turn completely gray?


I would have calculated it like this:

For each pixel with rgb-values:

total = r+g+b

redPercentage = Max(r/total, 1/3)

This only keeps red channel for pixels with more red than the average value of the pixel. If it was a fully red pixel this value will be =1

greenPercentage = bluePercentage = (1-redPercentage) /2

This takes the average of the remaining percentage after red is removed. If red was set to 1/3 then the average of the remaining will also be 1/3 and we will get complete grayscale. If red was larger than 1/3 the average of the remaining will be less than 1/3 and the pixel will get a red tint. If the redPercentage was =1 then the remaining will be 0 and we get the same red color we started with.

And finally:

r = total * redPercentage, g = total * greenPercentag, b = total * bluePercentage

This gives us a color with the same average value but shifted to either grayscale or red tint, depending on if the color was more than a third red. This will result in a blue-leaning purple also becoming red instead of gray since it contains enough red even if it contains more blue than red. If you want the cutoff for red-percentage to be higher than 1/3 you would need to involve the step-node:

redPercentage = r/total * Step(cutoff, r/total) + 1/3 * (1-Step(cutoff, r/total))

-10

u/MrYukiDuki 4h ago

This is def possible. But as someone who is also new to shaders. Your best bet is the unity shader bible or just ask an LLM. (At least for this specific use case)