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

Transparent VertexLit Blended (-like) Shader (Unlit)

Discussion in 'Shaders' started by Hurri04, Jan 4, 2019.

  1. Hurri04

    Hurri04

    Joined:
    Nov 27, 2017
    Posts:
    59
    Hello,

    I'm working on a little 2D Tile-based game for which I'm implementing a custom Tile Editor and Tile Drawing System.

    A Tile is a quad made from 2 triangles like this:


    In order to be able to make the level less boring I've implemented a Color variable in the Tile class. This allows me to get 4 colors (1 for each vertex) by interpolating the colors of the Tiles surrounding the respecting vertex. This way I'll be able to add various highlights/shadows at specific positions in the level.

    My plan is to create e.g. 12x12 instances of the Mesh class which have their UV coordinates set according to the positions of Tile textures in a texture atlas. Using Graphics.DrawMesh or Graphics.DrawMeshInstanced I'll then be able to draw the visible part of the level by drawing the Mesh with the UVs which correspond to the necessary Tile texture. Further these methods have a Material parameter, allowing me to quickly switch out the Material on a per-Tile basis as needed (so that I really only need 12x12=144 Tiles at all times, even when using multiple texture atlases). Using the MaterialPropertyBlock.SetColor method I'd then set the 4 colors from above to be used in the shader.


    The tricky part with this is the shader, since I've not had much experience writing any. :(

    The shader I need should
    • use a Texture
    • use 4 Colors which can get set via script and which are used 100% at the respective vertex positions and interpolated inbetween
    • be unlit (I don't need or want to use any light since e.g. shadows will be handled by setting the color of certain Tiles to a darker shade)
    • support transparency (since some Tiles contain e.g. half blocks)
    Does anyone know if such a shader exists already?
    If not, could someone point me in the right direction on how to write one?
    (I realize that people probably aren't wild about writing full solutions for all problems posted here but if someone wants to write such a shader for me that would obviously also be appreciated :D)

    I've found the "Mobile/Particles/Alpha Blended" shader which seems to go somewhat in the right direction when using the Mesh.colors array (0 = red, 1 = green, 2 = blue, 3 = white):


    However this has a few problems:
    • Depending on the vertex color and the texture color, the former blots out the later (very red corner with no texture visible anymore)
    • This shader uses the Mesh.colors array instead of providing 4 custom color fields which can be set via script. This would require creating enough Mesh instances for all visible Tiles in the level which is more than 12x12 and would probably require an object pool to recycle the Meshes when the Camera moves :rolleyes:

    Cheers!
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Working as intended. Try multiplying your texture by a solid red color in Photoshop/Gimp. You'll see the texture effectively disappears there too. Just use less saturated colors and you'll be fine.

    https://docs.unity3d.com/ScriptReference/MeshRenderer-additionalVertexStreams.html
     
  3. Hurri04

    Hurri04

    Joined:
    Nov 27, 2017
    Posts:
    59
    Thanks for the reply!

    Yeah, figured as much.
    I guess I'll have a look at the big picture once I have a level built. If necessary I'll clamp the color that gets multiplied somehow.

    This requires a MeshRenderer component though which I don't have since I want to use Gaphics.DrawMesh instead of GameObjects in order to bypass the GO creation/management overhead.


    I've modified a default Unlit Shader a bit now with the help of way too many reference/turorial pages ( :rolleyes: ):
    Code (CSharp):
    1. Shader "Unlit/ColoredQuad"
    2. {
    3.     Properties
    4.     {
    5.         _Texture ("Texture", 2D) = "white" {}
    6.         _UVRect ("UV Rect", Vector) = (0,0,1,1)
    7.         _Color0 ("Color 0", Color) = (1,1,1,1)
    8.         _Color1 ("Color 1", Color) = (1,1,1,1)
    9.         _Color2 ("Color 2", Color) = (1,1,1,1)
    10.         _Color3 ("Color 3", Color) = (1,1,1,1)
    11.     }
    12.     SubShader
    13.     {
    14.         Tags
    15.         {
    16.             "Queue" = "Transparent"
    17.             "RenderType" = "Transparent"
    18.         }
    19.  
    20.         Zwrite Off
    21.         Blend SrcAlpha OneMinusSrcAlpha
    22.  
    23.         Pass
    24.         {
    25.             CGPROGRAM
    26.             #pragma vertex vert
    27.             #pragma fragment frag
    28.  
    29.             #include "UnityCG.cginc"
    30.  
    31.             struct appdata
    32.             {
    33.                 float4 vertex : POSITION;
    34.                 float2 uv : TEXCOORD0;
    35.             };
    36.  
    37.             struct v2f
    38.             {
    39.                 float4 vertex : SV_POSITION;
    40.                 float2 uv : TEXCOORD0;
    41.             };
    42.  
    43.             sampler2D _Texture;
    44.             float4 _Texture_ST;
    45.  
    46.             UNITY_INSTANCING_BUFFER_START(Props)
    47.             UNITY_DEFINE_INSTANCED_PROP(float4, _UVRect)
    48.             UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color0)
    49.             UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color1)
    50.             UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color2)
    51.             UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color3)
    52.             UNITY_INSTANCING_BUFFER_END(Props)
    53.  
    54.             v2f vert (appdata v)
    55.             {
    56.                 v2f o;
    57.                 o.vertex = UnityObjectToClipPos(v.vertex);
    58.                 o.uv = float2(
    59.                     lerp(_UVRect.x, _UVRect.x + _UVRect.z, v.uv.x),
    60.                     lerp(_UVRect.y, _UVRect.y + _UVRect.w, v.uv.y));
    61.                 return o;
    62.             }
    63.  
    64.             fixed4 frag (v2f i) : SV_Target
    65.             {
    66.                 fixed4 _color = lerp(lerp(_Color0, _Color1, i.uv.x), lerp(_Color2, _Color3, i.uv.x), i.uv.y);
    67.                 return tex2D(_Texture, i.uv) * UNITY_ACCESS_INSTANCED_PROP(Props, _color);
    68.             }
    69.             ENDCG
    70.         }
    71.     }
    72. }
    73.  

    Seems to work alright (although it also has the texture blotted out in the corners...):


    These are the properties in the inspector:


    By using the "Texture" property and the "UV Rect" property I put in I'll even be able to set these by script as well and use a grand total of 1 Mesh for all Tiles! :D

    I might remove the Properties though to only be able to set the respective variables via script if that's possible. Also this might allow me to set the 4 colors as an array instead 1 by 1.

    If possible I'd probably also like to replace the color interpolation in the frag method by setting the vertex colors directly in the vert method by index.

    Also adding GPUInstancing is giving me some problem: Adding
    Code (CSharp):
    1. #pragma multi_compile_instancing
    at line 28 results in an error:
    and the shader not compiling (pink Meshes).
     
    Last edited: Jan 4, 2019
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    _color is not an instanced property, that's just the name of the variable you created on the line before. _Color0, _Color1, _Color2, _Color3, and _UVRect are all instanced properties. To access those values you need to use that macro on each property individually. _UVRect is just the first spot where the shader is encountering the issue.

    For example:
    Code (csharp):
    1. float4 uvRect = UNITY_ACCESS_INSTANCED_PROP(Props, _UVRect);
    2. o.uv = float2(
    3.                     lerp(uvRect.x, uvRect.x + uvRect.z, v.uv.x),
    4.                     lerp(uvRect.y, uvRect.y + uvRect.w, v.uv.y));
    You need to do the same thing for all of those _Color# variables for instancing to work.
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Also, again, working exactly as it should. That texture * red = no visible texture across most of the bottom. Here's the result of using a red multiply layer in Photoshop.
    upload_2019-1-4_16-1-3.png

    Most of the stripe detail is in the blue and green color channels, which you're multiplying away. It's also why the blue color is completely crushing the top left. The grass is almost solid green with no blue, so grass * blue = black.
     
  6. Hurri04

    Hurri04

    Joined:
    Nov 27, 2017
    Posts:
    59
    Okay, that + changing it for the _Color# variables fixed the errors.

    I also read about "UNITY_SETUP_INSTANCE_ID" here (at the bottom under "Further notes", bullet point 5) and added it to my code according to the examples given above on that page:
    Code (CSharp):
    1. Shader "Unlit/ColoredQuad"
    2. {
    3.     Properties
    4.     {
    5.         _Texture ("Texture", 2D) = "white" {}
    6.         _UVRect ("UV Rect", Vector) = (0,0,1,1)
    7.         _Color0 ("Color 0", Color) = (1,1,1,1)
    8.         _Color1 ("Color 1", Color) = (1,1,1,1)
    9.         _Color2 ("Color 2", Color) = (1,1,1,1)
    10.         _Color3 ("Color 3", Color) = (1,1,1,1)
    11.     }
    12.     SubShader
    13.     {
    14.         Tags
    15.         {
    16.             "Queue" = "Transparent"
    17.             "RenderType" = "Transparent"
    18.         }
    19.  
    20.         Zwrite Off
    21.         Blend SrcAlpha OneMinusSrcAlpha
    22.  
    23.         Pass
    24.         {
    25.             CGPROGRAM
    26.             #pragma vertex vert
    27.             #pragma fragment frag
    28.             #pragma multi_compile_instancing
    29.             #include "UnityCG.cginc"
    30.  
    31.             struct appdata
    32.             {
    33.                 float4 vertex : POSITION;
    34.                 float2 uv : TEXCOORD0;
    35.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    36.             };
    37.  
    38.             struct v2f
    39.             {
    40.                 float4 vertex : SV_POSITION;
    41.                 float2 uv : TEXCOORD0;
    42.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    43.             };
    44.  
    45.             sampler2D _Texture;
    46.             float4 _Texture_ST;
    47.  
    48.             UNITY_INSTANCING_BUFFER_START(Props)
    49.             UNITY_DEFINE_INSTANCED_PROP(float4, _UVRect)
    50.             UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color0)
    51.             UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color1)
    52.             UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color2)
    53.             UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color3)
    54.             UNITY_INSTANCING_BUFFER_END(Props)
    55.  
    56.             v2f vert (appdata v)
    57.             {
    58.                 UNITY_SETUP_INSTANCE_ID(v);
    59.                 v2f o;
    60.                 UNITY_TRANSFER_INSTANCE_ID(v, o);
    61.                 o.vertex = UnityObjectToClipPos(v.vertex);
    62.                 float4 uvRect = UNITY_ACCESS_INSTANCED_PROP(Props, _UVRect);
    63.                 o.uv = float2(
    64.                         lerp(uvRect.x, uvRect.x + uvRect.z, v.uv.x),
    65.                         lerp(uvRect.y, uvRect.y + uvRect.w, v.uv.y));
    66.                 return o;
    67.             }
    68.  
    69.             fixed4 frag (v2f i) : SV_Target
    70.             {
    71.                 UNITY_SETUP_INSTANCE_ID(i);
    72.                 fixed4 _color = lerp(
    73.                     lerp(UNITY_ACCESS_INSTANCED_PROP(Props, _Color0), UNITY_ACCESS_INSTANCED_PROP(Props, _Color1), i.uv.x),
    74.                     lerp(UNITY_ACCESS_INSTANCED_PROP(Props, _Color2), UNITY_ACCESS_INSTANCED_PROP(Props, _Color3), i.uv.x),
    75.                     i.uv.y);
    76.                 return tex2D(_Texture, i.uv) * _color;
    77.             }
    78.             ENDCG
    79.         }
    80.     }
    81. }
    4 outer ones are on GOs for testing, center one is with Graphics.DrawMesh hence no orange border since it's not selectable:


    Now the only thing left might be changing the _color lerping from the frag method to setting the vertex colors directly in the vert method (as mentioned previous) since I'd image it'd be a bit cheaper to calculate this way, no?
    Is there a way to access vertices by index in the vert method?
     
    Last edited: Jan 5, 2019
  7. Hurri04

    Hurri04

    Joined:
    Nov 27, 2017
    Posts:
    59
    Yeah, makes sense.
    I guess I'll have to see how it looks once I have the code in place to build a level.
    In theory I already have all the data for a level since I'm porting an already existing (open sourced) game for fun and learning from C++ to Unity ;)
    But since the original project uses it's own compressed format to store maps of course, I added some code to print out the Tile data into the console in JSON format which will then allow me to import the map into my project :D
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    You'd be letting the hardware do the interpolation for you, sure. Whether or not that's faster ... maybe, maybe not. Probably about the same honestly. Also, it won't look exactly the same if you do it on the vertices. It might be fine, but on really extreme color changes like how you're testing you'll likely see a strong line down the center "crease" of the two triangles. Right now you're doing bilinear interpolation of the colors, but on the vertices they'll be using barycentric interpolation. It'd be easy enough to test it now by just moving the color lerps into the vertex shader and passing the color value in the v2f struct.

    You're also eventually going to need to pass two versions of the UVs from the vertex to the fragment, or at least once unmodified. You're modifying the UVs with your UVRect property, presumably so you can eventually use a texture atlas, but once you do the interpolated colors won't match the quad's corners since the UV is being scaled. In the v2f you can change the uv to a float4 then do something like this:

    o.uv.xy = // uv with uvRect applied
    o.uv.zw = v.uv.xy;


    Then the .zw will be the quad UV straight. I'd also look into forming your uvRect values like Unity does its texture scale offset *_ST property. That is calculating the values in C# so they can be applied as a single MAD, or a multiply and an add.

    Assuming your UV rect values are the bottom left and top right corners of the UV, do something like this:
    Code (csharp):
    1. // in c#
    2. Vector4 uvRect;
    3. // xy uv scale
    4. uvRect.x = rect.z - rect.x;
    5. uvRect.y = rect.w - rect.y;
    6. // xy uv offset
    7. uvRect.z = rect.x;
    8. uvRect.w = rect.y;
    9.  
    10. // in shader
    11. o.uv.xy = v.uv.xy * uvRect.xy + uvRect.zw; // one MAD instruction on GPUs
    That'll be way faster than the lerps.
     
  9. Hurri04

    Hurri04

    Joined:
    Nov 27, 2017
    Posts:
    59
    Right. I'll probably keep that part as is then since I noticed e.g. in the 2. picture I posted above (in the spoiler) that the red from the bottom left corner does not reach as far to the top right as it does now, since the triangle edge (hypotenuse) is in the way.

    Good catch! Zooming in on a tile in a texture atlas did indeed also scale the colored area, making the texture e.g. red when zooming into the bottom left corner.

    I had to swap "uvRect.xy" and "uvRect.zw" here though, giving me this:
    Code (CSharp):
    1. Shader "Unlit/ColoredQuad"
    2. {
    3.     Properties
    4.     {
    5.         _Texture ("Texture", 2D) = "white" {}
    6.     }
    7.     SubShader
    8.     {
    9.         Tags
    10.         {
    11.             "Queue" = "Transparent"
    12.             "RenderType" = "Transparent"
    13.         }
    14.  
    15.         Zwrite Off
    16.         Blend SrcAlpha OneMinusSrcAlpha
    17.  
    18.         Pass
    19.         {
    20.             CGPROGRAM
    21.             #pragma vertex vert
    22.             #pragma fragment frag
    23.             #pragma multi_compile_instancing
    24.             #include "UnityCG.cginc"
    25.  
    26.             struct appdata
    27.             {
    28.                 float4 vertex : POSITION;
    29.                 float2 uv : TEXCOORD0;
    30.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    31.             };
    32.  
    33.             struct v2f
    34.             {
    35.                 float4 vertex : SV_POSITION;
    36.                 float4 uv : TEXCOORD0;
    37.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    38.             };
    39.  
    40.             sampler2D _Texture;
    41.             float4 _Texture_ST;
    42.  
    43.             UNITY_INSTANCING_BUFFER_START(Props)
    44.             UNITY_DEFINE_INSTANCED_PROP(float4, _UVRect)
    45.             UNITY_DEFINE_INSTANCED_PROP(fixed4, _Colors[4])
    46.             UNITY_INSTANCING_BUFFER_END(Props)
    47.  
    48.             v2f vert(appdata v)
    49.             {
    50.                 UNITY_SETUP_INSTANCE_ID(v);
    51.                 v2f o;
    52.                 UNITY_TRANSFER_INSTANCE_ID(v, o);
    53.                 o.vertex = UnityObjectToClipPos(v.vertex);
    54.                 float4 uvRect = UNITY_ACCESS_INSTANCED_PROP(Props, _UVRect);
    55.                 o.uv.xy = v.uv.xy * uvRect.zw + uvRect.xy;
    56.                 o.uv.zw = v.uv.xy;
    57.                 return o;
    58.             }
    59.  
    60.             fixed4 frag(v2f i) : SV_Target
    61.             {
    62.                 UNITY_SETUP_INSTANCE_ID(i);
    63.                 fixed4 _color = lerp(
    64.                     lerp(UNITY_ACCESS_INSTANCED_PROP(Props, _Colors[0]), UNITY_ACCESS_INSTANCED_PROP(Props, _Colors[1]), i.uv.z),
    65.                     lerp(UNITY_ACCESS_INSTANCED_PROP(Props, _Colors[2]), UNITY_ACCESS_INSTANCED_PROP(Props, _Colors[3]), i.uv.z),
    66.                     i.uv.w);
    67.                 return tex2D(_Texture, i.uv.xy) * _color;
    68.             }
    69.             ENDCG
    70.         }
    71.     }
    72. }
    I also replaced the _Color# variables with the _Colors[4] array which I set in the C# script together with the _UVRect:
    Code (CSharp):
    1.         MaterialPropertyBlock block = new MaterialPropertyBlock();
    2.         block.SetVector("_UVRect", new Vector4(uv.x, uv.y, uv.width, uv.height));
    3.         block.SetVectorArray("_Colors", colors.Select(color => (Vector4)color).ToList());
    4.  
    5.         Mesh mesh = CreateTileMesh();
    6.         for(int y = 0; y < size.y; y++)
    7.         {
    8.             for(int x = 0; x < size.x; x++)
    9.             {
    10.                 Graphics.DrawMesh(mesh, new Vector3(x, y, 0f) * Tile.Scale, Quaternion.identity, mat, 0, null, 0, block);
    11.             }
    12.         }
    It's a bit annoying they never added a "SetColorArray" method to the MaterialPropertyBlock and I need to cast the colors List to a Vector4 List right now. Later I'll just move the cast to the method where I calculate the 4 colors from the 4 respectively surrounding Tiles though.

    But it works pretty well now while not using GUI instancing (using a texture atlas here):


    However, I discovered that enabling GUI Instancing gives this fun result (Epilepsy warning!):

    :eek:

    I guess I'll test if reverting the _Colors array to _Color# variables fixed this again...

    Also I've noticed that using the MaterialPropertyBlock.SetTexture method seems to have a bit of a performance impact (from ~45 fps to ~38 fps) when setting the Texture for every Tile though. Swapping out the Material in the Graphics.DrawMesh call keeps it at ~45 fps. Which is a bit unfortunate as it might in the future make modding a bit more complicated for the user who will then have to create a Material for each texture atlas he adds (instead of being able to just drop the texture in a folder/AssetPackage and have them applied automatically...)
     
  10. Hurri04

    Hurri04

    Joined:
    Nov 27, 2017
    Posts:
    59
    Yep, turns out it works when using individual _Color# variables instead of the array. No more flickering this way when using GPU Instancing. It also somehow has much better performance at ~92 fps now!
     
  11. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Yeah, you can't use arrays for instanced properies. I'm surprised the shader compiler even allowed that.

    Not necessarily a bad thing as it means they can customize the shader too. Otherwise you could just create the materials at runtime.
     
  12. Hurri04

    Hurri04

    Joined:
    Nov 27, 2017
    Posts:
    59
    I hadn't thought about that yet. I might see if I need/want this. If I do I'll just expose a field for it. Currently I am indeed creating a new Material at runtime (in the Awake method).

    I did some more testing and I think the Color array was causing issues with my first fps test as well;
    while the default is at ~92 fps now (with vsync on) with 1 Texture, using MaterialPropertyBlock.SetTexture on the same Material to replace the Texture for every Tile interestingly now increases the fps to ~120 when using more textures! Although this might need some further testing since I'm only drawing 1 layer when later I'll have up to 2 layers (background + foreground graphic for each Tile).
     
  13. Hurri04

    Hurri04

    Joined:
    Nov 27, 2017
    Posts:
    59
    Just to give an update, here's how the shader looks by now:
    Code (CSharp):
    1. Shader "Unlit/ColoredQuad"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex("Texture", 2D) = "white" {}
    6.         _Color0("Color Bottom Left", Color) = (1,1,1,1)
    7.         _Color1("Color Bottom Right", Color) = (1,1,1,1)
    8.         _Color2("Color Top Left", Color) = (1,1,1,1)
    9.         _Color3("Color Top Right", Color) = (1,1,1,1)
    10.     }
    11.     SubShader
    12.     {
    13.         Tags
    14.         {
    15.             "Queue" = "Transparent"
    16.             "RenderType" = "Transparent"
    17.             "PreviewType" = "Plane"
    18.         }
    19.  
    20.         Zwrite Off
    21.         Blend SrcAlpha OneMinusSrcAlpha
    22.  
    23.         Pass
    24.         {
    25.             CGPROGRAM
    26.             #pragma vertex vert
    27.             #pragma fragment frag
    28.             #pragma multi_compile_instancing
    29.             #include "UnityCG.cginc"
    30.  
    31.             struct appdata
    32.             {
    33.                 float4 vertex : POSITION;
    34.                 float2 uv : TEXCOORD0;
    35.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    36.             };
    37.  
    38.             struct v2f
    39.             {
    40.                 float4 vertex : SV_POSITION;
    41.                 float4 uv : TEXCOORD0;
    42.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    43.             };
    44.  
    45.             sampler2D _MainTex;
    46.  
    47.             UNITY_INSTANCING_BUFFER_START(Props)
    48.             UNITY_DEFINE_INSTANCED_PROP(float4, _MainTex_ST)
    49.             UNITY_DEFINE_INSTANCED_PROP(half4, _Color0)
    50.             UNITY_DEFINE_INSTANCED_PROP(half4, _Color1)
    51.             UNITY_DEFINE_INSTANCED_PROP(half4, _Color2)
    52.             UNITY_DEFINE_INSTANCED_PROP(half4, _Color3)
    53.             UNITY_INSTANCING_BUFFER_END(Props)
    54.  
    55.             v2f vert(appdata v)
    56.             {
    57.                 UNITY_SETUP_INSTANCE_ID(v);
    58.                 v2f o;
    59.                 UNITY_TRANSFER_INSTANCE_ID(v, o);
    60.                 o.vertex = UnityObjectToClipPos(v.vertex);
    61.                 float4 _uvRect = UNITY_ACCESS_INSTANCED_PROP(Props, _MainTex_ST);
    62.                 o.uv.xy = v.uv.xy * _uvRect.xy + _uvRect.zw;
    63.                 o.uv.zw = v.uv.xy;
    64.                 return o;
    65.             }
    66.  
    67.             fixed4 frag(v2f i) : SV_Target
    68.             {
    69.                 UNITY_SETUP_INSTANCE_ID(i);
    70.                 half4 _color = lerp(
    71.                     lerp(UNITY_ACCESS_INSTANCED_PROP(Props, _Color0), UNITY_ACCESS_INSTANCED_PROP(Props, _Color1), i.uv.z),
    72.                     lerp(UNITY_ACCESS_INSTANCED_PROP(Props, _Color2), UNITY_ACCESS_INSTANCED_PROP(Props, _Color3), i.uv.z),
    73.                     i.uv.w);
    74.                 return tex2D(_MainTex, i.uv.xy) * _color;
    75.             }
    76.             ENDCG
    77.         }
    78.     }
    79. }
    I changed "_uvRect" in the vert function to use "_MainTex_ST" (since it already exists) instead of a custom Vector. To have the "Tiling" and "Offset" fields (which are neat btw) in the Inspector for Materials with this Shader make more sense I just had to switch around some "xy" and "zw" values.

    I also added the Color Properties in the top to act as default values. Together with the "PreviewType" = "Plane" tag this allows having a nice flat preview of the Tileset Texture in the Material Inspector.

    And as a tiny optimization I changed the type of Color variables from float4 to half4.

    Screenshot (with a dummy texture hence the grid being a bit off):

    This is a tiny test level with 12*12 Tiles and 2 Layers on top of each other (not visible on non-transparent Tiles though). Since this is running with GPU Instancing and all of these Tiles are in a single Texture Atlas this level can be drawn using a single Batch!

    The colors being blended from each corner of a Tile also works nicely, it's just not visible here since I'll need to finish up a few things with my custom LevelEditor first in order to have the right tools to edit the colors easily. I might post another screenshot when it works! :)