[Tilemap-Renderer-Shader] Saturation per Tile based on Runtime-Value

Hello everyone!
I’m currently developing a tile based City-Builder for fun and want to make each Tile have a different
fertility and now I want to change the Tile-Colour corresponding to the Fertility-Value.
I never created a Shader before, so please be patient.
Currently I’m trying to change the Saturation of each Tile corresponding to a Texture that is created at
Runtime with Saturation Values as the alpha Value. (Because a float array would be to big for a shader I learned)
I got it to the point where it would change the Saturation of the colours, but I couldn’t get the current corresponding Tile from the current colour (Pixel) or any other way.
I hope it is possible to do this with the current Implementation of Tilemaps Renderer (Shader) in Unity.

Shader "TileMapShader" // Name changed
{
    Properties
    {
        [PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {}
        _Color("Tint", Color) = (1,1,1,1)
        [MaterialToggle] PixelSnap("Pixel snap", Float) = 0
        [HideInInspector] _RendererColor("RendererColor", Color) = (1,1,1,1)
        [HideInInspector] _Flip("Flip", Vector) = (1,1,1,1)
        [PerRendererData] _AlphaTex("External Alpha", 2D) = "white" {}
        [PerRendererData] _EnableExternalAlpha("Enable External Alpha", Float) = 0
        [PerRendererData] _Saturations("Saturations", 2D) = "white" {}
    }

    SubShader
    {
        Tags
        {
            "Queue" = "Transparent"
            "IgnoreProjector" = "True"
            "RenderType" = "Transparent"
            "PreviewType" = "Plane"
            "CanUseSpriteAtlas" = "True"
        }

        Cull Off
        Lighting Off
        ZWrite Off
        Blend One OneMinusSrcAlpha

        Pass
        {
            CGPROGRAM
            #pragma vertex SpriteVert
            #pragma fragment CustomSpriteFrag // Originally SpriteFrag
            #pragma multi_compile_instancing
            #pragma multi_compile_local _ PIXELSNAP_ON
            #pragma multi_compile _ ETC1_EXTERNAL_ALPHA
            #pragma target 2.0
            #include "UnitySprites.cginc"
            #include "UnityCG.cginc"
            // Custom properties
            float2 _startPosition;
            int _Width;
            int _Height;
            sampler2D _Saturations;
            void Unity_Saturation_float(float3 In, float Saturation, out float3 Out)
            {
                float luma = dot(In, float3(0.2126729, 0.7151522, 0.0721750));
                Out = luma.xxx + Saturation.xxx * (In - luma.xxx);
            }
            fixed4 CustomSpriteFrag(v2f IN) : SV_Target
            {
                fixed4 c = tex2D(_MainTex, IN.texcoord);
                c.rgb *= c.a;
                float2 worldXY = mul(unity_ObjectToWorld, IN.texcoord).xy;
                // Is needed because not the whole world is one Tilemap
                // because I have multiple islands instead of one big landmass
                float2 localXY = worldXY - _startPosition;
                float2 pos = float2(floor(worldXY.x), floor(worldXY.y));
                Unity_Saturation_float(c.rgb, tex2D(_Saturations, pos).a* tex2D(_Saturations, pos).a, c.rgb);

                return c;
            }
         
         
        ENDCG
        }
    }
    Fallback "Unlit/Color"
}

The easiest way to change color per tile is to not use a shader but instead use the TileData.color value. This is would give you a per-tile tint color via myTilemap.SetColor(cell, Color.red); Note: you would also need the correct TileFlags for SetColor to have any effect.

I tried that, but it does not work for me.

        editorTilemap.SetTileFlags(new Vector3Int(50, 50, 0), TileFlags.None);
        Color32 c = editorTilemap.GetColor(new Vector3Int(50, 50, 0));
        Color.RGBToHSV(c, out float H, out float S, out float V);
        // H = 0, S = 0, V = 1 - Doesn't matter if it has a Tile or not
        c = Color.HSVToRGB(H, S*0.1f, V); // so I can't lower Saturation
        editorTilemap.SetColor(new Vector3Int(50, 50, 0), c);

So is there a other way to lower Saturation?

What you posted isn’t going to work because by default the color at a particular cell with a tile is white. Try having art that’s either black & white or desaturated. Then give it a green tint, greener tint, super-green tint, etc.

Yea. That’s what I guessed. That’s why I’m trying to do it with a Shader. There I can change the Saturation just fine.
Just cant I get it to work per Tile, because I don’t know how to get the absolut World Position of the current
fixed4 CustomSpriteFrag(v2f IN) or any other way with a Shader.
That’s why I’m asking here if it is possible. I don’t want to have keep track for each different type of Tile what tint it is supposed to have.

You’re giving a raw position to tex2D instead of a UV. If your world is for example (x, y) = (0…200, 0…200) but the UV is expected to be from 0…1 then you need to convert the coordinates.

It might be more useful to see an example project which does something similar. The Robodash demo generates a TintMap (analogous to your Saturation map in its usage), then retrieves from a texture color info per tile.

Specifically here’s a shader using the TintMap, exacting a color from the texture. You can see they adjust the coordinates for a 256x256 world.

fixed4 tintmap = tex2D(_TintMap, (IN.worldPos.xy / 256) + .5);

That said, either way (tilemap.SetColor) vs (tex.SetPixel) you’re going to be managing the color / satuaration state. The tilemap color is already paid for whereas adjusting the saturation will be more costly, but maybe that cost doesn’t matter for your use case at all.

Also you double-sample the saturation and multiply it against itself? Not sure if that’s intentional.

Thank you very much!
I finally made it work with that Shader.
It’s probably bad, but if anyone looks for it.

Shader "Custom/NormalMappedSprite" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        [PerRendererData]_MainTex ("Albedo (RGB)", 2D) = "white" {}
        [HideInInspector] _RendererColor("RendererColor", Color) = (1,1,1,1)
        [HideInInspector] _Flip("Flip", Vector) = (1,1,1,1)
        [PerRendererData] _AlphaTex("External Alpha", 2D) = "white" {}
        [PerRendererData] _EnableExternalAlpha("Enable External Alpha", Float) = 0
        [PerRendererData] _Saturations("Saturations", 2D) = "white" {}

    }
    SubShader {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }
        LOD 200
        Cull Off
     
         

        CGPROGRAM
        #pragma multi_compile _ ETC1_EXTERNAL_ALPHA
        #pragma surface surf Custom alpha:fade vertex:vert nofog nolightmap noinstancing nodynlightmap
        #pragma target 3.0
        #include "UnityPBSLighting.cginc"

        inline half4 LightingCustom(SurfaceOutputStandard s, half3 lightDir, UnityGI gi)
        {
            float ditherPattern = s.Smoothness;
            int res = 4;

            gi.light.color.rgb *= 1.5;
            gi.light.color.rgb = clamp(gi.light.color.rgb,0, 2);
            float vall = gi.light.color.r + gi.light.color.g + gi.light.color.b;
            vall /= 3;

            float clampedLight = floor(vall * res) / res;
            float nextLight = ceil(vall * res) / res;
            float lerp = frac(vall * res);
            float stepper = step(ditherPattern, lerp);
            gi.light.color *= clampedLight * (1 - stepper) + nextLight * stepper;
            s.Smoothness = 0;
            s.Metallic = 0;
            half4 standard = LightingStandard(s, lightDir, gi);
            return standard;
        }
        inline void LightingCustom_GI(SurfaceOutputStandard s, UnityGIInput data, inout UnityGI gi)
        {
            LightingStandard_GI(s, data, gi);
        }

        sampler2D _MainTex;
     
        struct Input {
            float2 uv_MainTex;
            float4 screenPos;
            float3 worldPos;
            fixed4 color;
        };

        fixed4 _Color;
        fixed4 _RendererColor;
        sampler2D _Saturations;
        int _UnevenResolution;
        void vert(inout appdata_full v, out Input o)
        {
            UNITY_INITIALIZE_OUTPUT(Input, o);
            o.color = v.color * _Color * _RendererColor;
        }
        void Unity_Saturation_float(float3 In, float Saturation, out float3 Out)
        {
            float luma = dot(In, float3(0.2126729, 0.7151522, 0.0721750));
            Out = luma.xxx + Saturation.xxx * (In - luma.xxx);
        }

        void surf (Input IN, inout SurfaceOutputStandard o) {
         
            //nudge uv a little bit if the target resolution width is not an even number, fixes pixel perfectness bugs
            if (_UnevenResolution == 1) IN.uv_MainTex.xy += 1.0 / 1024.0;

            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * IN.color;
            c.rgb *= c.a;
            float2 pos = float2(floor(IN.worldPos.x), floor(IN.worldPos.y));
            Unity_Saturation_float(c.rgb, tex2D(_Saturations, pos /[SIZE]).a, c.rgb);
            o.Albedo = c.rgb;
            o.Alpha =  c.a;
            o.Metallic = 0;
        }

        ENDCG
    }
    FallBack "Diffuse"
}