Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

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

Discussion in '2D' started by ChiefInspectorEvenLonger, Oct 23, 2020.

  1. ChiefInspectorEvenLonger

    ChiefInspectorEvenLonger

    Joined:
    Jul 18, 2013
    Posts:
    5
    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.

    Code (csharp):
    1.  
    2. Shader "TileMapShader" // Name changed
    3. {
    4.     Properties
    5.     {
    6.         [PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {}
    7.         _Color("Tint", Color) = (1,1,1,1)
    8.         [MaterialToggle] PixelSnap("Pixel snap", Float) = 0
    9.         [HideInInspector] _RendererColor("RendererColor", Color) = (1,1,1,1)
    10.         [HideInInspector] _Flip("Flip", Vector) = (1,1,1,1)
    11.         [PerRendererData] _AlphaTex("External Alpha", 2D) = "white" {}
    12.         [PerRendererData] _EnableExternalAlpha("Enable External Alpha", Float) = 0
    13.         [PerRendererData] _Saturations("Saturations", 2D) = "white" {}
    14.     }
    15.  
    16.     SubShader
    17.     {
    18.         Tags
    19.         {
    20.             "Queue" = "Transparent"
    21.             "IgnoreProjector" = "True"
    22.             "RenderType" = "Transparent"
    23.             "PreviewType" = "Plane"
    24.             "CanUseSpriteAtlas" = "True"
    25.         }
    26.  
    27.         Cull Off
    28.         Lighting Off
    29.         ZWrite Off
    30.         Blend One OneMinusSrcAlpha
    31.  
    32.         Pass
    33.         {
    34.             CGPROGRAM
    35.             #pragma vertex SpriteVert
    36.             #pragma fragment CustomSpriteFrag // Originally SpriteFrag
    37.             #pragma multi_compile_instancing
    38.             #pragma multi_compile_local _ PIXELSNAP_ON
    39.             #pragma multi_compile _ ETC1_EXTERNAL_ALPHA
    40.             #pragma target 2.0
    41.             #include "UnitySprites.cginc"
    42.             #include "UnityCG.cginc"
    43.             // Custom properties
    44.             float2 _startPosition;
    45.             int _Width;
    46.             int _Height;
    47.             sampler2D _Saturations;
    48.             void Unity_Saturation_float(float3 In, float Saturation, out float3 Out)
    49.             {
    50.                 float luma = dot(In, float3(0.2126729, 0.7151522, 0.0721750));
    51.                 Out = luma.xxx + Saturation.xxx * (In - luma.xxx);
    52.             }
    53.             fixed4 CustomSpriteFrag(v2f IN) : SV_Target
    54.             {
    55.                 fixed4 c = tex2D(_MainTex, IN.texcoord);
    56.                 c.rgb *= c.a;
    57.                 float2 worldXY = mul(unity_ObjectToWorld, IN.texcoord).xy;
    58.                 // Is needed because not the whole world is one Tilemap
    59.                 // because I have multiple islands instead of one big landmass
    60.                 float2 localXY = worldXY - _startPosition;
    61.                 float2 pos = float2(floor(worldXY.x), floor(worldXY.y));
    62.                 Unity_Saturation_float(c.rgb, tex2D(_Saturations, pos).a* tex2D(_Saturations, pos).a, c.rgb);
    63.  
    64.                 return c;
    65.             }
    66.          
    67.          
    68.         ENDCG
    69.         }
    70.     }
    71.     Fallback "Unlit/Color"
    72. }
    73.  
     
    Last edited: Oct 23, 2020
  2. Lo-renzo

    Lo-renzo

    Joined:
    Apr 8, 2018
    Posts:
    1,503
    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.
     
  3. ChiefInspectorEvenLonger

    ChiefInspectorEvenLonger

    Joined:
    Jul 18, 2013
    Posts:
    5
    I tried that, but it does not work for me.

    Code (CSharp):
    1.  
    2.         editorTilemap.SetTileFlags(new Vector3Int(50, 50, 0), TileFlags.None);
    3.         Color32 c = editorTilemap.GetColor(new Vector3Int(50, 50, 0));
    4.         Color.RGBToHSV(c, out float H, out float S, out float V);
    5.         // H = 0, S = 0, V = 1 - Doesn't matter if it has a Tile or not
    6.         c = Color.HSVToRGB(H, S*0.1f, V); // so I can't lower Saturation
    7.         editorTilemap.SetColor(new Vector3Int(50, 50, 0), c);
    8.  
    So is there a other way to lower Saturation?
     
  4. Lo-renzo

    Lo-renzo

    Joined:
    Apr 8, 2018
    Posts:
    1,503
    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.
     
  5. ChiefInspectorEvenLonger

    ChiefInspectorEvenLonger

    Joined:
    Jul 18, 2013
    Posts:
    5
    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.
     
  6. Lo-renzo

    Lo-renzo

    Joined:
    Apr 8, 2018
    Posts:
    1,503
    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.

    https://github.com/Unity-Technologies/2d-gamedemo-robodash

    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.
    https://github.com/Unity-Technologi...ster/Assets/Shaders/NormalMappedSprite.shader
    Code (CSharp):
    1. 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.
     
    Last edited: Oct 25, 2020
  7. ChiefInspectorEvenLonger

    ChiefInspectorEvenLonger

    Joined:
    Jul 18, 2013
    Posts:
    5
    Thank you very much!
    I finally made it work with that Shader.
    It's probably bad, but if anyone looks for it.

    Code (CSharp):
    1. Shader "Custom/NormalMappedSprite" {
    2.     Properties {
    3.         _Color ("Color", Color) = (1,1,1,1)
    4.         [PerRendererData]_MainTex ("Albedo (RGB)", 2D) = "white" {}
    5.         [HideInInspector] _RendererColor("RendererColor", Color) = (1,1,1,1)
    6.         [HideInInspector] _Flip("Flip", Vector) = (1,1,1,1)
    7.         [PerRendererData] _AlphaTex("External Alpha", 2D) = "white" {}
    8.         [PerRendererData] _EnableExternalAlpha("Enable External Alpha", Float) = 0
    9.         [PerRendererData] _Saturations("Saturations", 2D) = "white" {}
    10.  
    11.     }
    12.     SubShader {
    13.         Tags { "RenderType"="Transparent" "Queue"="Transparent" }
    14.         LOD 200
    15.         Cull Off
    16.      
    17.          
    18.  
    19.         CGPROGRAM
    20.         #pragma multi_compile _ ETC1_EXTERNAL_ALPHA
    21.         #pragma surface surf Custom alpha:fade vertex:vert nofog nolightmap noinstancing nodynlightmap
    22.         #pragma target 3.0
    23.         #include "UnityPBSLighting.cginc"
    24.  
    25.         inline half4 LightingCustom(SurfaceOutputStandard s, half3 lightDir, UnityGI gi)
    26.         {
    27.             float ditherPattern = s.Smoothness;
    28.             int res = 4;
    29.  
    30.             gi.light.color.rgb *= 1.5;
    31.             gi.light.color.rgb = clamp(gi.light.color.rgb,0, 2);
    32.             float vall = gi.light.color.r + gi.light.color.g + gi.light.color.b;
    33.             vall /= 3;
    34.  
    35.             float clampedLight = floor(vall * res) / res;
    36.             float nextLight = ceil(vall * res) / res;
    37.             float lerp = frac(vall * res);
    38.             float stepper = step(ditherPattern, lerp);
    39.             gi.light.color *= clampedLight * (1 - stepper) + nextLight * stepper;
    40.             s.Smoothness = 0;
    41.             s.Metallic = 0;
    42.             half4 standard = LightingStandard(s, lightDir, gi);
    43.             return standard;
    44.         }
    45.         inline void LightingCustom_GI(SurfaceOutputStandard s, UnityGIInput data, inout UnityGI gi)
    46.         {
    47.             LightingStandard_GI(s, data, gi);
    48.         }
    49.  
    50.         sampler2D _MainTex;
    51.      
    52.         struct Input {
    53.             float2 uv_MainTex;
    54.             float4 screenPos;
    55.             float3 worldPos;
    56.             fixed4 color;
    57.         };
    58.  
    59.         fixed4 _Color;
    60.         fixed4 _RendererColor;
    61.         sampler2D _Saturations;
    62.         int _UnevenResolution;
    63.         void vert(inout appdata_full v, out Input o)
    64.         {
    65.             UNITY_INITIALIZE_OUTPUT(Input, o);
    66.             o.color = v.color * _Color * _RendererColor;
    67.         }
    68.         void Unity_Saturation_float(float3 In, float Saturation, out float3 Out)
    69.         {
    70.             float luma = dot(In, float3(0.2126729, 0.7151522, 0.0721750));
    71.             Out = luma.xxx + Saturation.xxx * (In - luma.xxx);
    72.         }
    73.  
    74.         void surf (Input IN, inout SurfaceOutputStandard o) {
    75.          
    76.             //nudge uv a little bit if the target resolution width is not an even number, fixes pixel perfectness bugs
    77.             if (_UnevenResolution == 1) IN.uv_MainTex.xy += 1.0 / 1024.0;
    78.  
    79.             fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * IN.color;
    80.             c.rgb *= c.a;
    81.             float2 pos = float2(floor(IN.worldPos.x), floor(IN.worldPos.y));
    82.             Unity_Saturation_float(c.rgb, tex2D(_Saturations, pos /[SIZE]).a, c.rgb);
    83.             o.Albedo = c.rgb;
    84.             o.Alpha =  c.a;
    85.             o.Metallic = 0;
    86.         }
    87.  
    88.         ENDCG
    89.     }
    90.     FallBack "Diffuse"
    91. }
     
    Last edited: Nov 24, 2020