Search Unity

Random Tiling Mask Shader

Discussion in 'Shaders' started by gerhatt, Nov 10, 2015.

  1. gerhatt

    gerhatt

    Joined:
    Nov 3, 2015
    Posts:
    14
    Hi all,

    I'm trying to recreate the Random Tiling Mask effect from Unreal engine (as described here) in a shader.

    I was able to manage to rotate the input texture and place the rotated textures on the mesh based on a grayscaled tile mask:
    upload_2015-11-10_22-6-48.png

    Now it looks like this:
    upload_2015-11-10_22-15-50.png

    I have two issues:
    • as you probably noticed there are some visual glitches (missing pixels?) at the texture intersections, the texture is seamless with green color on the edges
    • I was not able to find out how to rotate randomly the texture, now it's completely based on the mask colours, but for the Random Tiling effect it shoud be really random for each of the mesh face
    Could you please advise me how to fix those issues without changing the mesh?
    I was thinking to generate random colors for the mesh vertexes or rotate the mesh UVs randomly but then the shader could not be used on meshes that are not procedurally generated.
    I think it could be solved somehow with a vertex shader or I need a completly different approach but just couldn't figure out how...

    The shader code:
    Code (csharp):
    1. Shader "Custom/Test2" {
    2.     Properties {
    3.         _Tex1 ("Texture 1", 2D) = "white" {}
    4.         _Mask ("Mask", 2D) = "white" {}
    5.     }
    6.     SubShader {
    7.         Tags { "RenderType"="Opaque" }
    8.         LOD 200
    9.      
    10.         CGPROGRAM
    11.         #pragma surface surf Lambert
    12.  
    13.         sampler2D _Tex1, _Mask;
    14.        
    15.         struct Input {
    16.             float2 uv_Tex1;
    17.             float2 uv_Mask;
    18.         };
    19.  
    20.         void surf (Input IN, inout SurfaceOutput o) {
    21.          
    22.             half4 mask = tex2D(_Mask, IN.uv_Mask * 0.5);    
    23.             half4 outTex;
    24.  
    25.             float2 uvTexRot = IN.uv_Tex1 - 0.5;
    26.  
    27.             if ( mask.r <= 0.3 ) {
    28.                 //rotated 90 degrees
    29.                 float2x2 rotationMatrix = float2x2(  0,  1, -1,  0); // 90 degrees
    30.                 uvTexRot = mul (uvTexRot, rotationMatrix);
    31.                 uvTexRot.xy += 0.5;
    32.                 outTex = tex2D (_Tex1, uvTexRot);
    33.             } else if ( mask.r <= 0.6 ) {
    34.                 // rotated 180 degrees
    35.                 float2x2 rotationMatrix = float2x2( -1,  0,  0, -1); // 180 degrees
    36.                 uvTexRot = mul (uvTexRot, rotationMatrix);
    37.                 uvTexRot.xy += 0.5;
    38.                 outTex = tex2D (_Tex1, uvTexRot);
    39.             } else if ( mask.r <= 0.8 ) {
    40.                 // rotated 270 degrees
    41.                 float2x2 rotationMatrix = float2x2(  0, -1,  1,  0); // 270 degrees
    42.                 uvTexRot = mul (uvTexRot, rotationMatrix);
    43.                 uvTexRot.xy += 0.5;
    44.                 outTex = tex2D (_Tex1, uvTexRot);
    45.             } else {
    46.                 // rotated 0 degrees
    47.                 outTex = tex2D (_Tex1, IN.uv_Tex1);
    48.             }
    49.                      
    50.             o.Albedo = outTex.rgb;
    51.             o.Alpha = outTex.a;
    52.         }
    53.         ENDCG
    54.     }
    55.     FallBack "Diffuse"
    56. }
    I also attached the textures used.

    Thanks
     

    Attached Files:

  2. StevenGerrard

    StevenGerrard

    Joined:
    Jun 1, 2015
    Posts:
    97
    Interesting topic.
    => About random tiling effect, In fact there is some existing book talk about this. For example this one.
    => About visual glitches at the texture intersections problem, just some guess, maybe try to change texture's "Wrap Mode" ?
     
  3. gerhatt

    gerhatt

    Joined:
    Nov 3, 2015
    Posts:
    14
    So finally figured it out...
    Not using the Wang tiles (thats used for different purposes) but generating a random RGBA mask based on UV coordinates of the mesh and then using the original texture blended with the rotated (90, 180, 270 degrees) variations.

    The shader settings (shader code at the end of this post):
    upload_2015-11-28_7-4-28.png

    The generated mask on a basic plane:
    upload_2015-11-28_7-5-22.png

    Texture applied based on mask:
    upload_2015-11-28_7-5-46.png

    Texture with and without random masking in a terrain-like mesh:
    upload_2015-11-28_7-6-56.png

    Any texture can be used, but it has to be seamless horizontally and also vertically.

    The shader code with comments:
    Code (CSharp):
    1. Shader "Custom/Test3" {
    2.     Properties {
    3.         _Tex1 ("Texture", 2D) = "white" {}
    4.         [Toggle] _UseRandMask ("Use Random Mask", Int) = 0
    5.         [Toggle] _ShowMask ("Show Mask", Int) = 0
    6.     }
    7.     SubShader {
    8.         Tags { "RenderType"="Opaque" }
    9.         LOD 200
    10.      
    11.         CGPROGRAM
    12.         #pragma surface surf Lambert
    13.  
    14.         sampler2D _Tex1;
    15.           float _UseRandMask, _ShowMask;
    16.        
    17.         struct Input {
    18.             float2 uv_Tex1;
    19.             float3 worldPos;
    20.         };
    21.  
    22.         // generic pseudo-random function
    23.         float rand2 ( float2 coords ){
    24.             return frac(sin(dot(coords, float2(12.9898,78.233))) * 43758.5453);
    25.         }
    26.  
    27.         void surf (Input IN, inout SurfaceOutput o) {
    28.             half4 tex1 = tex2D (_Tex1, IN.uv_Tex1);
    29.                      
    30.             // generate random value based on UV mapping
    31.             float x = (round(rand2(floor(IN.uv_Tex1))*3))/1;
    32.             float4 mask;
    33.          
    34.             // transform the random value to RGBA mask
    35.             if ( x == 0 ) {
    36.                 mask = float4(1, 0, 0, 0);
    37.             } else if ( x == 1 ) {
    38.                 mask = float4(0, 1, 0, 0);
    39.             } else if ( x == 2 ) {
    40.                 mask = float4(0, 0, 1, 0);
    41.             } else {
    42.                 mask = float4(0, 0, 0, 1);
    43.             }
    44.          
    45.             if ( _ShowMask == 1 ) {
    46.                 // show the RGBA mask
    47.                 o.Albedo.rgb = mask.rgb;
    48.              
    49.             } else if ( _UseRandMask == 1 ) {
    50.                 // create rotated textures using the original texture data rotating its UVs:
    51.                 // tex1 - 0 degrees, the original texture
    52.                 // tex2 - rotated 90 degrees clockwise
    53.                 // tex3 - rotated 180 degrees clockwise
    54.                 // tex4 - rotated 270 degrees clockwise
    55.              
    56.                 // texture rotated 90 degrees
    57.                 float2 uvTexRot90 = IN.uv_Tex1 - 0.5;
    58.                 uvTexRot90 = mul(uvTexRot90, float2x2(  0,  1, -1,  0)); // rotate 90 degrees
    59.                 uvTexRot90.xy += 0.5;
    60.                 half4 tex2 = tex2D(_Tex1, uvTexRot90);
    61.  
    62.                 // texture rotated 180 degrees
    63.                 float2 uvTexRot180 = IN.uv_Tex1 - 0.5;
    64.                 uvTexRot180 = mul(uvTexRot180, float2x2( -1,  0,  0, -1)); // rotate 180 degrees
    65.                 uvTexRot180.xy += 0.5;
    66.                 half4 tex3 = tex2D(_Tex1, uvTexRot180);
    67.                                          
    68.                 // texture rotated 270 degrees
    69.                 float2 uvTexRot270 = IN.uv_Tex1 - 0.5;
    70.                 uvTexRot270 = mul(uvTexRot270, float2x2(  0, -1,  1,  0)); // rotate 270 degrees
    71.                 uvTexRot270.xy += 0.5;
    72.                 half4 tex4 = tex2D(_Tex1, uvTexRot270);
    73.  
    74.                 // mask the textures using the RGBA mask                                          
    75.                 o.Albedo = float3(0,0,0);
    76.                 o.Albedo = lerp(o.Albedo, tex1.rgb, mask.r);
    77.                 o.Albedo = lerp(o.Albedo, tex2.rgb, mask.g);
    78.                 o.Albedo = lerp(o.Albedo, tex3.rgb, mask.b);
    79.                 o.Albedo = lerp(o.Albedo, tex4.rgb, mask.a);
    80.              
    81.             } else {
    82.                 // _UseRandMask == 0
    83.                 // show the original texture without random mask
    84.                 o.Albedo = tex1.rgb;
    85.             }
    86.          
    87.             o.Alpha = 1;
    88.         }
    89.         ENDCG
    90.     }
    91.     FallBack "Diffuse"
    92. }
     
    magnetic_scho likes this.
  4. KeithKong

    KeithKong

    Joined:
    May 31, 2015
    Posts:
    73
    This really helped me so I wanted to contribute my improvements to the concept. Instead of creating 4 tex2D reads and lerping between them, I decided to create a formula to generate the proper matrix multiplication for the resulting tile rotation "r":

    Code (CSharp):
    1. half2 samp = IN.uv_Tex1;
    2. half r = (round(rand2(floor(samp))*3));
    3. half m1 = ((r-1)*(3-r))/min(r-3, -1);
    4. half m2 = (r*(2-r))/max(r,1);
    5. half m3 = (r*(r-2))/max(r,1);
    6. half m4 = ((3-r)*(r-1))/min(r-3, -1);
    7.  
    8. samp -= 0.5;
    9. samp = mul(samp, float2x2(m1, m2, m3, m4));
    10. samp.xy += 0.5;
    11.  
    12. o.Albedo = samp.rgb
    This eliminates the need for a mask (since you weren't actually blending anyways) and requires no if statements (which can be very costly).
     
    Last edited: Dec 2, 2015
  5. gerhatt

    gerhatt

    Joined:
    Nov 3, 2015
    Posts:
    14
    thanks a lot
    I just tried to use the same approach as described in the UDK documentation (using a random mask), but your solution is much better and produces the same output

    the updated shader code:
    Code (CSharp):
    1. Shader "Custom/RandomTilingMask" {
    2.     Properties {
    3.         _Tex1 ("Texture", 2D) = "white" {}
    4.         [Toggle] _UseRandMask ("Use Random Mask", Int) = 0
    5.     }
    6.     SubShader {
    7.         Tags { "RenderType"="Opaque" }
    8.         LOD 200
    9.    
    10.         CGPROGRAM
    11.         #pragma surface surf Lambert
    12.         sampler2D _Tex1;
    13.         float _UseRandMask;
    14.      
    15.         struct Input {
    16.             float2 uv_Tex1;
    17.             float3 worldPos;
    18.         };
    19.         // generic pseudo-random function
    20.         float rand2 ( float2 coords ){
    21.             return frac(sin(dot(coords, float2(12.9898,78.233))) * 43758.5453);
    22.         }
    23.         void surf (Input IN, inout SurfaceOutput o) {
    24.                            
    25.             if ( _UseRandMask == 1 ) {
    26.                 // calculate rotation matrix parameters from the original UV data
    27.                 half2 samp = IN.uv_Tex1;
    28.                 half r = (round(rand2(floor(samp))*3));
    29.                 half m1 = ((r-1)*(3-r))/min(r-3, -1);
    30.                 half m2 = (r*(2-r))/max(r,1);
    31.                 half m3 = (r*(r-2))/max(r,1);
    32.                 half m4 = ((3-r)*(r-1))/min(r-3, -1);
    33.                
    34.                 // rotate texture UVs based on the calculated rotation matrix parameters
    35.                 samp -= 0.5;
    36.                 samp = mul(samp, float2x2(m1, m2, m3, m4));
    37.                 samp.xy += 0.5;
    38.                
    39.                 // use input texture with calculated UVs
    40.                 half4 tex1 = tex2D (_Tex1, samp);
    41.                 o.Albedo = tex1.rgb;
    42.            
    43.             } else {
    44.                 // _UseRandMask == 0
    45.                 // show the original texture without UV rotation
    46.                 half4 tex1 = tex2D (_Tex1, IN.uv_Tex1);
    47.                 o.Albedo = tex1.rgb;
    48.             }
    49.        
    50.             o.Alpha = 1;
    51.         }
    52.         ENDCG
    53.     }
    54.     FallBack "Diffuse"
    55. }
     
    KeithKong likes this.
  6. KeithKong

    KeithKong

    Joined:
    May 31, 2015
    Posts:
    73
    Have you noticed some issues of the wrong color showing at the seems?

    Using this image:
    TileTester.jpg
    I would expect it to be seamless (minus some red circles connecting with black circles).

    However, I get yellow seem flicker (not even purple?!):
    Screen Shot 2015-12-01 at 10.00.51 PM.png

    I'm going to keep working on it, but any ideas?
     
  7. gerhatt

    gerhatt

    Joined:
    Nov 3, 2015
    Posts:
    14
    you are using JPEG (at least you posted JPEG here) which is a lossy compression format, you could try to set the filter mode and the compression format in the texture import settings of Unity but this will still not lead to 100% quality - I opened your image in a photo editing software and there are still blurry areas on the edges

    I would suggest to test it with a PNG image (which is lossless compression format)
     
  8. KeithKong

    KeithKong

    Joined:
    May 31, 2015
    Posts:
    73
    Yeah I was relying on the import settings. Tried with png source with the same result though. Unfortunately my addition is the source of the problem somehow. If I use the same image on your original solution it doesn't have the issue.

    There's gotta be a way to do it without all those reads though. I'm trying to add this into a script that is already blending 7 textures so doing this to each of them just isn't going to work. I shall keep searching!
     
  9. gerhatt

    gerhatt

    Joined:
    Nov 3, 2015
    Posts:
    14
    could you please send me the png version of the image?
     
  10. KeithKong

    KeithKong

    Joined:
    May 31, 2015
    Posts:
    73
    Currently exploring blending the edges with a second pixel read. Here's the png I've been using. It helps give a better idea what part of the image pixels are coming from.
    SubTileTester.png
     
  11. gerhatt

    gerhatt

    Joined:
    Nov 3, 2015
    Posts:
    14
    i think the issue is not with the lerp/mask but rather in calculating the rotation of the texture

    i made 3 versions of the shader (see them attached):
    • RandomTilingMask - this runs without issues with correct texture seams, it is using a calculated mask and 4 rotations of the texture which are blended together via lerp
    • Random TilingMask1 - much the same as the previous but it is using only 1 texture which is rotated based on the random calculated value, altough it still uses blending via lerp, this one has the issue with the incorrect texture seams
    • Random TilingMask2 - uses 1 texture which is rotated based on the random value and its used as the surface shader output without blending via lerp - produces the same effect as the previous
    Correct texture seams (RandomTilingMask):
    upload_2015-12-5_14-55-26.png

    Incorrect seams (RandomTilingMask1, RandomTilingMask2):
    upload_2015-12-5_14-55-47.png
     

    Attached Files:

  12. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    With the rotatedmask1 you are just lerping to the same texture over and over, as long as there's no holes in the mask it's identical to rotatedmask2.

    The issue you're running into is how video cards determine the texture mip map level. Basically it's looking at the distance between UVs in pixel space in 2x2 blocks of pixels. If you've got the UVs from two random orientations in the same block it now thinks the UV distance is really far apart and thus the mip level should be a very high mip (very low resolution). Thus you get those 2x2 pixel blocks of aliasing or weird colors.

    There are three ways to fix this. Your first shader is one of them, the four separate UVs are used for the texture mip calculation so they don't get the harsh disparities. The second option is to have geometry splits at the edges, which isn't really something you can do here.

    The third option is to calculate the mip level on your own with the un-randomized UV and use tex2Dlod instead of tex2D.

    edit: there's also an overload of tex2D that might work for you.

    tex2D(_Texture, randomUV, ddx(in.UV), ddy(in.UV));
     
    Last edited: Dec 5, 2015
  13. KeithKong

    KeithKong

    Joined:
    May 31, 2015
    Posts:
    73
    Thanks for pointing us in the right direction. I went with a compromise that blends out the resulting mip level distortion, but it sounds like I can get it working without any texture blending (much preferred).

    I'm currently side tracked working on another more pressing feature but I did find this thread which I feel should point anyone trying to solve this in the right direction.

    I will hopefully be back on this within the next few days and will post a working shader if gerhatt doesn't beat me to it.
     
  14. gerhatt

    gerhatt

    Joined:
    Nov 3, 2015
    Posts:
    14
    so here is the update version based on the suggestions above
    seems to me that its working fine
    i also replaced all the floats with half or int

    Code (CSharp):
    1. Shader "Custom/RandomTilingMask" {
    2.     Properties {
    3.         _Tex1 ("Texture", 2D) = "white" {}
    4.         [Toggle] _UseRandMask ("Use Random Mask", Int) = 0
    5.     }
    6.     SubShader {
    7.         Tags { "RenderType"="Opaque" }
    8.         LOD 200
    9.    
    10.         CGPROGRAM
    11.         #pragma surface surf Lambert
    12.  
    13.         sampler2D _Tex1;
    14.         int _UseRandMask;
    15.                      
    16.         struct Input {
    17.             half2 uv_Tex1;
    18.         };
    19.  
    20.         // generic pseudo-random function
    21.         half rand2 ( half2 coords ) {
    22.             return frac(sin(dot(coords, half2(12.9898,78.233))) * 43758.5453);
    23.         }
    24.  
    25.         void surf (Input IN, inout SurfaceOutput o) {
    26.      
    27.             // declare the rotation matrixes in array
    28.             const int2x2 rotMx[4] = {
    29.                 int2x2(1, 0, 0, 1),
    30.                 int2x2(0, 1, -1, 0),
    31.                 int2x2(-1, 0, 0, -1),
    32.                 int2x2(0, -1, 1, 0)
    33.             };    
    34.  
    35.             // calculate random values from 0 to 3 to select random rotation from the matrix array
    36.             int x = (round(rand2(floor(IN.uv_Tex1))*3)) * _UseRandMask; // switch random mask on/off
    37.  
    38.             // rotate texture UVs based on random element of the rotation matrix array
    39.             half2 uvTex1 = mul(IN.uv_Tex1 - 0.5, rotMx[x]);
    40.             uvTex1.xy += 0.5;
    41.          
    42.             // use input texture with calculated UVs
    43.             // and using texture derivates ddx and ddy to resolve mip mapping issues
    44.             half4 tex1 = tex2D(_Tex1, uvTex1, ddx(IN.uv_Tex1.xy), ddy(IN.uv_Tex1.xy));
    45.  
    46.             // send texture data to shader output
    47.             o.Albedo = tex1.rgb;
    48.             o.Alpha = tex1.a;
    49.         }
    50.         ENDCG
    51.     }
    52.     FallBack "Diffuse"
    53. }
     
  15. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    A few comments:

    int and float are the same size (32 bit) and speed, and some devices have a cost for converting between int and half which is what you're doing when you mul the UVs. You should be using half2x2 instead of int2x2 if you're going to use half precision UVs.

    [Toggle] does more than just make your int display as a toggle switch, it also enables and disables shader keywords. Instead of using _UseRandMask to multiply the rotation matrix index all of the time you can use it to bypass the calculation when it's disabled.

    Add #pragma shader_feature _USERANDMASK_ON under the #pragma surface surf Lambert line. Then do something like this:
    #ifdef _USERANDMASK_ON
    int x = rand2(IN.uv_Tex1) * 4; // random between 0-3
    half2 uvTex1 = mul(rotMx[x], IN.uv_Tex1); // apply rotation
    #else
    half2 uvTex1 = IN.uv_Tex1; // use unmodifed UVs
    #endif


    Now if you have it toggled on it'll do the math, and if not it'll just skip it. The #pragma shader_feature tells the shader to expect the keyword to change, but only compile the version in use (read up on shader_feature and multi_compile here: http://docs.unity3d.com/Manual/SL-MultipleProgramVariants.html ). You might have noticed a few other changes in there too.

    int x = rand2(IN.uv_Tex1) * 4;
    The frac() function in the rand2() means the return value will be between 0.0 and <1.0, it can never return 1.0 as frac would make that 0.0. This means rand2() * 3 will only ever get values between 0.0 - 2.999... which when converted to int will be floored to 0 - 2, hence * 4 will result in 0 - 3. The round() was getting you the full range of 0 - 3, but a result of 1 or 2 were twice as likely as 0 or 3. We could also change int x to unsigned char x, but let's skip that for now.

    Lastly I removed the - / + 0.5 and flipped the mul order. The mul order is what it should be with the matrix first, but it kind of doesn't matter if you get the results you want the other way. The - / + 0.5 is just unnecessary as you're rotating in cardinal directions. It would matter if you were animating the rotation or doing non 90 degree rotations and cared where you were rotating at, but here it's just wasted math.
     
  16. gerhatt

    gerhatt

    Joined:
    Nov 3, 2015
    Posts:
    14
    thanks for your comments :D
    here is the modified shader code:
    Code (CSharp):
    1. Shader "Custom/RandomTilingMask" {
    2.     Properties {
    3.         _Tex1 ("Texture", 2D) = "white" {}
    4.         [Toggle] _UseRandMask ("Use Random Mask", Int) = 0
    5.     }
    6.     SubShader {
    7.         Tags { "RenderType"="Opaque" }
    8.         LOD 200
    9.    
    10.         CGPROGRAM
    11.         #pragma surface surf Lambert
    12.         #pragma shader_feature _USERANDMASK_ON
    13.  
    14.         sampler2D _Tex1;
    15.         int _UseRandMask;
    16.                      
    17.         struct Input {
    18.             half2 uv_Tex1;
    19.         };
    20.  
    21.         // generic pseudo-random function
    22.         half rand2 ( half2 coords ) {
    23.             return frac(sin(dot(coords, half2(12.9898,78.233))) * 43758.5453);
    24.         }
    25.  
    26.         void surf (Input IN, inout SurfaceOutput o) {
    27.             // declare the rotation matrixes in array fo 0, 90, 180 and 270 degrees
    28.             const half2x2 rotMx[4] = { half2x2(1, 0, 0, 1), half2x2(0, 1, -1, 0),
    29.                                        half2x2(-1, 0, 0, -1), half2x2(0, -1, 1, 0) };      
    30.  
    31.             #ifdef _USERANDMASK_ON
    32.                 int x = rand2(floor(IN.uv_Tex1)) * 4; // random between 0-3
    33.                 half2 uvTex1 = mul(rotMx[x], IN.uv_Tex1); // apply random rotation based on rotation matrix
    34.             #else
    35.                 half2 uvTex1 = IN.uv_Tex1; // use unmodifed UVs if _UseRandMask is Off
    36.             #endif
    37.  
    38.             // use input texture with calculated UVs
    39.             // and using texture derivates ddx and ddy to resolve mip mapping issues
    40.             half4 tex1 = tex2D(_Tex1, uvTex1, ddx(IN.uv_Tex1.xy), ddy(IN.uv_Tex1.xy));
    41.  
    42.             // send texture data to shader output
    43.             o.Albedo = tex1.rgb;
    44.             o.Alpha = tex1.a;
    45.         }
    46.         ENDCG
    47.     }
    48.     FallBack "Diffuse"
    49. }
     
  17. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    Nice work!

    However, there are a few notes. The version in the 3rd post from the top (shader named "Test3") works very nicely. The updated version in the last post doesn't work so well and makes the terrain like in the figure below:
    Screen Shot 2016-04-13 at 01.35.16.png

    By the way, I noticed that this shader when applied on Unity terrain does not render at large distances (leaving black squares), while standard terrain shader covers terrain from any distances. So I guess at some point it needs to make it as standard terrain shader.
     
  18. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    Hey guys, so I managed to adapt the shader to Unity's terrain shader. I did it by changing TerrainSplatmapCommon.cginc file in built-in shader archive. It also needs to move TerrainSplatmapCommon.cginc to TerrainShaders/Splats folder, where are present terrain shaders. As a result it works nicely not just with Legacy Terrain Shaders, but also with a new Standard Terrain Shader. Different terrain splats seems to be working nicely this way (normal maps are also affected by random rotations). All other built-in terrain features, like fog also works nicely.
    Screen Shot 2016-04-26 at 23.47.37.png
    Here I include modified cginc file.
    P.S. If shaders won't recompile when copied cginc file, try to reimport. If still no success, you can always copy cginc content into shader scripts.
    P.P.S. Don't forget to create a material with terrain shader, which you are using when building, as otherwise Unity will use default shaders, which won't randomly rotate tiles :)
     

    Attached Files:

    nani_sahu and mitaywalle like this.
  19. toroomegg

    toroomegg

    Joined:
    Jan 29, 2017
    Posts:
    1
    Hello every one. Have any possible to convert about this article's shader from surface to HDRP/Lit? Thanks a lot.
     
  20. nani_sahu

    nani_sahu

    Joined:
    Sep 13, 2014
    Posts:
    1
    Adding Random Tiles in Unity Terrain shader :

    Standard Unity Process:
    Step 1: Create a custom shader and named to CustomTerrain and add following code

    Code (CSharp):
    1.  
    2. Shader "Custom/CustomTerrain"
    3.             {
    4.                 Properties{
    5.                     _Tex1("Texture", 2D) = "white" {}
    6.                     [Toggle] _UseRandMask("Use Random Mask", Int) = 0
    7.                     _RandMaskValue("Mask Value", Range(1.0, 3.5)) = 3.0
    8.                 }
    9.                 SubShader{
    10.                     Tags { "RenderType" = "Opaque" }
    11.                     LOD 200
    12.    
    13.                     CGPROGRAM
    14.                     #pragma surface surf Lambert
    15.                     sampler2D _Tex1;
    16.                     int _UseRandMask;
    17.                     float _RandMaskValue;
    18.    
    19.                     struct Input {
    20.                         float2 uv_Tex1;
    21.                         float3 worldPos;
    22.                     };
    23.                     // generic pseudo-random function
    24.                     float rand2(float2 coords) {
    25.                         return frac(sin(dot(coords, float2(12.9898,78.233))) * 43758.5453);
    26.                     }
    27.                     void surf(Input IN, inout SurfaceOutput o) {
    28.    
    29.                         if (_UseRandMask == 1) {
    30.                             // calculate rotation matrix parameters from the original UV data
    31.                             half2 samp = IN.uv_Tex1;
    32.                             half r = (round(rand2(floor(samp)) * _RandMaskValue));
    33.                             half m1 = ((r - 1) * (3 - r)) / min(r - 3, -1);
    34.                             half m2 = (r * (2 - r)) / max(r,1);
    35.                             half m3 = (r * (r - 2)) / max(r,1);
    36.                             half m4 = ((3 - r) * (r - 1)) / min(r - 3, -1);
    37.    
    38.                             // rotate texture UVs based on the calculated rotation matrix parameters
    39.                             samp -= 0.5;
    40.                             samp = mul(samp, float2x2(m1, m2, m3, m4));
    41.                             samp.xy += 0.5;
    42.    
    43.                             // use input texture with calculated UVs
    44.                             half4 tex1 = tex2D(_Tex1, samp);
    45.                             o.Albedo = tex1.rgb;
    46.    
    47.                         }
    48.                         else {
    49.                             // _UseRandMask == 0
    50.                             // show the original texture without UV rotation
    51.                             half4 tex1 = tex2D(_Tex1, IN.uv_Tex1);
    52.                             o.Albedo = tex1.rgb;
    53.                         }
    54.    
    55.                         o.Alpha = 1;
    56.                     }
    57.                     ENDCG
    58.                 }
    59.                 FallBack "Diffuse"
    60.             }
    61.  
    Step 2: create material and named to CustomTerrain
    Step 3: Change material shader to Custom/Terrain shader
    Step 4: Add this material in to Basic Terrain material

    Unity URP Process:
    Step 1: If not downloaded URP packages, Please download URP packages from Package Manager
    Step 2: Move the High Definiation RP from Library Package cache to your project packages directory. (Note: If it's in library package cache, if you modify the below code it will not save once project reloads.)
    Step 3: Open below two files from Packages/High Definition RP/Runtime/Material/TerrainLit and below code
    1. TerrainLit.shader
    Add below two line in Properties

    Code (CSharp):
    1.  
    2. +   [ToggleUI] _UseRandMask("Use Random Mask", Int) = 0
    3. +   _RandMaskValue("Mask Value", Range(1.0, 3.5)) = 3.0
    4.  
    Inspector Window:
    InspectorWindow.PNG

    2. Terrain_Splatmap.hlsl
    Add below code above of the TerrainSplatBlend method:

    Code (CSharp):
    1.  
    2. + int _UseRandMask;
    3. +  float _RandMaskValue;
    4.  
    5. +  float rand2 (float2 coords){
    6. +         return frac(sin(dot(coords, float2(12.9898,78.233))) * 43758.5453);
    7. +  }
    8.  
    9. +  float2 mixrotation(float2 uv_float2){
    10. +      if(_UseRandMask)
    11. +      {
    12. +          float2 samp = uv_float2;
    13. +          float r = (round(rand2(ceil(samp)) * _RandMaskValue));
    14. +          float m1 = ((r - 1) * (3 - r)) / min(r - 3, -1);
    15. +          float m2 = (r * (2 - r)) / max(r, 1);
    16. +          float m3 = (r * (r - 2)) / max(r, 1);
    17. +          float m4 = ((3 - r) * (r - 1)) / min(r-3, -1);
    18. +    
    19. +          samp -= 0.5;
    20. +          samp = mul(samp, float2x2(m1, m2, m3, m4));
    21. +          samp += 0.5;
    22. +    
    23. +          return samp;
    24. +      }
    25. +      else{
    26. +          return uv_float2;
    27. +      }
    28. +  }
    29.  
    Customize TerrainSplatBlend method:

    - float2 splatuv = splatBaseUV * _Splat##i##_ST.xy + _Splat##i##_ST.zw; \
    + float2 splatuv = mixrotation(splatBaseUV * _Splat##i##_ST.xy + _Splat##i##_ST.zw); \
    - float2 blendUV0 = (controlUV.xy * (_Control0_TexelSize.zw - 1.0f) + 0.5f) * _Control0_TexelSize.xy;
    + float2 blendUV0 = mixrotation((controlUV.xy * (_Control0_TexelSize.zw - 1.0f) + 0.5f) * _Control0_TexelSize.xy);
    - float2 blendUV1 = (controlUV.xy * (_Control1_TexelSize.zw - 1.0f) + 0.5f) * _Control1_TexelSize.xy;
    + float2 blendUV1 = mixrotation((controlUV.xy * (_Control1_TexelSize.zw - 1.0f) + 0.5f) * _Control1_TexelSize.xy);

    Step 4: Create material and named to Custom Terrain
    Step 5: Change material shader to HDRP/TerrainLit shader
    Step 6: Add this material in to Basic Terrain material

    Before Random in HDRP:
    BeforeRandom.PNG

    After Random in HDRP:
    AfterRandom.PNG
     
    Last edited: Jun 4, 2021