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

Creating and utilizing a tile-based shader such as Terrain Tile Editor?

Discussion in 'Shaders' started by Crayz, Jul 25, 2015.

  1. Crayz

    Crayz

    Joined:
    Mar 17, 2014
    Posts:
    193
    I'm trying to create a tile editor that functions similar to this in the asset store:
    https://www.assetstore.unity3d.com/en/#!/content/3201

    My grid mesh is procedurally generated, individual tiles in the mesh share vertices with its neighbors. What I'm trying to do is texture the base layer of the mesh with a base texture and apply vertex based editing to paint a new texture atop the base layer. The default photo for the asset linked above gives a good idea of what I'm trying to achieve.

    I'm completely new to shaders though making an effort to learn, and I've been trying to figure this out for quite some time now. Probably about a week and no luck yet =\ If somebody could give me a rundown of what I need to do, please eli5 as my brain is about fried by now. Code samples would be great if its not asking for too much.

    This is the closest I've come to achieving this, using a fragment shader and setting the uv coordinates on the uv/uv2 layers of the mesh.

    Screenshot:
    http://puu.sh/jc3al/f912a18c7e.jpg

    Shader:
    Code (csharp):
    1. Shader "Custom/SimpleShader" {
    2.    Properties {
    3.      _MainTex ( "Main Texture", 2D ) = "white" {}
    4.      _SecondTex ( "Second Texture", 2D ) = "white" {}
    5.      _Color ( "Tint Color", Color ) = ( 1.0, 1.0, 1.0, 1.0 )
    6.    }
    7.    SubShader {
    8.      Tags { "Queue" = "Transparent" }
    9.      ZWrite Off
    10.  
    11.      Blend SrcAlpha OneMinusSrcAlpha
    12.  
    13.      Pass
    14.      {
    15.      CGPROGRAM
    16.        
    17.        #pragma exclude_renderers ps3 xbox360 flash
    18.        #pragma vertex vert
    19.        #pragma fragment frag
    20.  
    21.        #include "UnityCG.cginc"
    22.  
    23.        uniform sampler2D _MainTex;
    24.        uniform float4 _MainTex_ST;
    25.        uniform sampler2D _SecondTex;
    26.        uniform float4 _SecondTex_ST;
    27.        uniform fixed4 _Color;
    28.  
    29.        struct vertexInput
    30.        {
    31.          float4 vertex : POSITION;
    32.          float4 texcoord : TEXCOORD0;
    33.          float4 texcoord1 : TEXCOORD1;
    34.        };
    35.      
    36.        struct fragmentInput
    37.        {
    38.          float4 pos : SV_POSITION;
    39.          half2 uv : TEXCOORD0;
    40.          half2 uv2 : TEXCOORD1;
    41.          fixed4 color : COLOR0;
    42.        };
    43.  
    44.        fragmentInput vert( vertexInput i )
    45.        {
    46.          fragmentInput o;
    47.  
    48.          o.pos = mul( UNITY_MATRIX_MVP, i.vertex );
    49.          o.uv = TRANSFORM_TEX( i.texcoord, _MainTex );
    50.          o.uv2 = TRANSFORM_TEX( i.texcoord1, _SecondTex );
    51.          o.color = _Color;
    52.  
    53.          return o;
    54.        }
    55.  
    56.        half4 frag( fragmentInput i ) : COLOR
    57.        {
    58.          if(i.uv2.x > 0)
    59.          {
    60.            return tex2D(_SecondTex, i.uv);
    61.          }
    62.          else
    63.          {
    64.            return tex2D(_MainTex, i.uv) * _Color;
    65.          }
    66.        }
    67.  
    68.      ENDCG
    69.      }
    70.    }
    71.    FallBack "Diffuse"
    72. }
    73.  
     
  2. brownboot67

    brownboot67

    Joined:
    Jan 5, 2013
    Posts:
    375
    ifs are generally not desirable for performance as it causes the shader to render all possible combinations then evaluate which one to draw. You can easily avoid them here. Also you aren't outputting vertex color, you're overwriting it with _Color property, is that intended? And your uv2 isn't being used anywhere.

    This will lerp between texture 1 and 2 based on vertex color red.
    Code (csharp):
    1.  
    2. struct vertexInput
    3. {
    4.     float4 vertex : POSITION;
    5.     float4 texcoord : TEXCOORD0;
    6.     float4 texcoord1 : TEXCOORD1;
    7.      
    8.     //need vertex color for mask
    9.     float4 color : COLOR;
    10. };
    11.  
    12. struct fragmentInput
    13. {
    14.     float4 pos : SV_POSITION;
    15.     half2 uv : TEXCOORD0;
    16.     half2 uv2 : TEXCOORD1;
    17.     fixed4 color : COLOR0;
    18. };
    19.  
    20. fragmentInput vert( vertexInput i )
    21. {
    22.     fragmentInput o;
    23.  
    24.     o.pos = mul( UNITY_MATRIX_MVP, i.vertex );
    25.     o.uv = TRANSFORM_TEX( i.texcoord, _MainTex );
    26.     o.uv2 = TRANSFORM_TEX( i.texcoord1, _SecondTex );
    27.      
    28.     o.color = i.color;//vertex color
    29.  
    30.     return o;
    31. }
    32.  
    33.  
    34. half4 frag( fragmentInput i ): COLOR
    35. {
    36.     float4 baseTexture = tex2D(_MainTex, i.uv) * _Color;
    37.    
    38.     float4 secondTexture = tex2D(_MainTex,i.uv2);
    39.    
    40.     //use vertex color red to lerp between base and second
    41.     float4 outColor = lerp(baseTexture,secondTexture,i.color.r);
    42.    
    43.     return outColor;
    44. }
    45.  
    46.  
    This won't do what you want though. You need to offset each mesh's UVs to match the appropriate tile you want to draw on each layer... This is tricky because you want to get the mask for "Grass Horizontal Top" or "Grass Vertical Left", etc.

    To do this you'll have to figure out what shape the layer needs by marching around its neighbors. Then bake the offset values into vertex color and use that to offset the UV's in the shader. The easy way to do this is with a non-square texture (fine on PC, generally bad on mobile). Put all your tiles in a big long strip. Let's assume you want 32 tiles like WC3. You have 0-31 ids. 1/32 = 0.03125. If you want tile id 5 you flood that tile's vertex color red with (5 * 0.03125). Then in the shader you get UV's like so;

    Code (csharp):
    1.  
    2. float2 GrassUVs = TRANSFORM_TEX( i.texcoord, _MainTex );
    3. GrassUVs.x += i.color.r; //all the grass will be offset to to the right texture on our strip
    4.  
     
  3. Crayz

    Crayz

    Joined:
    Mar 17, 2014
    Posts:
    193

    Thanks for the reply. This makes sense, though how would you go about setting the UV values when the grid mesh is initially generated? Is it possible to achieve this without texture distortion and with shared vertices?
     
  4. brownboot67

    brownboot67

    Joined:
    Jan 5, 2013
    Posts:
    375
    if you have a chunk of 64x64 tiles... UV the source tile into the 0,0 corner with the 1,1 corner of the mesh at 1/64,1/64 in UV space. Offset each tile of the chunk 1/64th in X and/or Y as needed.

    You could make all the tiles with the offset UV's and just instantiate the correct one in each location then combine the tiles to form your chunk.

    You probably don't need to combine the verts, if you need to set color or whatever its easy to march around.