Search Unity

Terrain replacement shader, is this a good idea?

Discussion in 'Shaders' started by steego, Mar 28, 2012.

  1. steego

    steego

    Joined:
    Jul 15, 2010
    Posts:
    969
    I was looking at some terrain replacement shaders, and all of them were passing in extra textures through script, only overriding the Lightmap-FirstPass but not the Lightmap-AddPass shader. This results in being restricted to only four textures, or visual discrepancies on any subsequent textures used.

    So I had this idea I decided to try out, since the shader can read four splat textures, what if I just put all the extra maps in the terrain editor? My shader using this technique uses a diffuse map and a normalmap, as in the image below:

    $terrain-textures.png

    The normalmaps shows up as grayscale in the terrain editor, but it looks like it works anyway. I also could not find any documentation on the decal:add statement that is used in the AddPass shader, but from my experiments it looks like it does take normals into account.

    As each splatmap has four channels, and the shader is passed four textures, this technique could also be modified so that the shader uses four different maps (or three, but that would be a bit silly as you would need an empty texture for "alignment" purposes).

    The downside to doing it like this is of course that you are wasting channels in your splatmap. My shader with normals only uses the red and blue channels of the splatmap, and a shader with four maps would only use one channel (though other channels could be used to paint effects, like painting specularity directly on the terrain).

    You could alleviate this a bit by using the technique of passing maps to the shader from script for the FirstPass shader, and then only use the other maps in the AddPass shader. This way you would use all the channels of the splatmap for the first four textures, and only have losses for the rest. You could also probably do something like parallax mapping on the first four textures, and then have normalmaps on the rest.

    I'm kind of new to shader programming, so I'm wondering if anyone has any comments on this. Is it a good idea? It seems to me if it was, someone would have though of it before, so I'm thinking it might not be?

    The shaders are below, if you want to try it out set up the textures as in the image above, every other with diffuse/normal, and make sure the tile size is the same for each pair. You also might need to restart Unity after creating the shaders to make sure it overrides the builtin shaders.

    Code (csharp):
    1.  
    2. Shader "Hidden/TerrainEngine/Splatmap/Lightmap-FirstPass"
    3. {
    4.     Properties
    5.     {
    6.         _Control ("Control (RGBA)", 2D) = "red" {}
    7.         _Splat3 ("Layer 3 (A)", 2D) = "white" {}
    8.         _Splat2 ("Layer 2 (B)", 2D) = "white" {}
    9.         _Splat1 ("Layer 1 (G)", 2D) = "white" {}
    10.         _Splat0 ("Layer 0 (R)", 2D) = "white" {}
    11.         // used in fallback on old cards
    12.         _MainTex ("BaseMap (RGB)", 2D) = "white" {}
    13.         _Color ("Main Color", Color) = (1,1,1,1)
    14.     }
    15.    
    16.     SubShader
    17.     {
    18.         Tags
    19.         {
    20.             "SplatCount" = "4"
    21.             "Queue" = "Geometry-100"
    22.             "RenderType" = "Opaque"
    23.         }
    24.        
    25.         CGPROGRAM
    26.         #pragma surface surf Lambert vertex:vert
    27.  
    28.         struct Input
    29.         {
    30.             float2 uv_Control : TEXCOORD0;
    31.             float2 uv_Splat0 : TEXCOORD1;
    32.             float2 uv_Splat1 : TEXCOORD2;
    33.             float2 uv_Splat2 : TEXCOORD3;
    34.             float2 uv_Splat3 : TEXCOORD4;
    35.         };
    36.  
    37.         void vert (inout appdata_full v)
    38.         {
    39.             float3 T1 = float3(1, 0, 1);
    40.             float3 Bi = cross(T1, v.normal);
    41.             float3 T = normalize(cross(v.normal, Bi));
    42.            
    43.             v.tangent.xyz = T.xyz;
    44.            
    45.             if (dot(cross(v.normal, T),Bi) < 0)
    46.                 v.tangent.w = -1.0f;
    47.             else
    48.                 v.tangent.w = 1.0f;                
    49.         }
    50.        
    51.        
    52.         sampler2D _Control;
    53.         sampler2D _Splat0,_Splat1,_Splat2,_Splat3;
    54.        
    55.         void surf (Input IN, inout SurfaceOutput o)
    56.         {
    57.             fixed4 splat_control = tex2D (_Control, IN.uv_Control);
    58.             fixed3 col;
    59.             fixed3 nor;
    60.            
    61.             col = splat_control.r * tex2D (_Splat0, IN.uv_Splat0).rgb;
    62.             nor = splat_control.r * UnpackNormal (tex2D (_Splat1, IN.uv_Splat1));
    63.            
    64.             col += splat_control.b * tex2D (_Splat2, IN.uv_Splat2).rgb;
    65.             nor += splat_control.b * UnpackNormal (tex2D (_Splat3, IN.uv_Splat3));
    66.            
    67.             o.Albedo = col;
    68.             o.Normal = normalize(nor);
    69.             o.Alpha = 0.0;
    70.         }
    71.         ENDCG  
    72.     }
    73.    
    74.     // Fallback to Diffuse
    75.     Fallback "Diffuse"
    76. }
    77.  
    Code (csharp):
    1.  
    2. Shader "Hidden/TerrainEngine/Splatmap/Lightmap-AddPass"
    3. {
    4.     Properties
    5.     {
    6.         _Control ("Control (RGBA)", 2D) = "black" {}
    7.         _Splat3 ("Layer 3 (A)", 2D) = "white" {}
    8.         _Splat2 ("Layer 2 (B)", 2D) = "white" {}
    9.         _Splat1 ("Layer 1 (G)", 2D) = "white" {}
    10.         _Splat0 ("Layer 0 (R)", 2D) = "white" {}
    11.     }
    12.    
    13.     SubShader
    14.     {
    15.         Tags
    16.         {
    17.             "SplatCount" = "4"
    18.             "Queue" = "Geometry-99"
    19.             "IgnoreProjector"="True"
    20.             "RenderType" = "Opaque"
    21.         }
    22.    
    23.         CGPROGRAM
    24.         #pragma surface surf Lambert vertex:vert decal:add
    25.        
    26.         struct Input
    27.         {
    28.             float2 uv_Control : TEXCOORD0;
    29.             float2 uv_Splat0 : TEXCOORD1;
    30.             float2 uv_Splat1 : TEXCOORD2;
    31.             float2 uv_Splat2 : TEXCOORD3;
    32.             float2 uv_Splat3 : TEXCOORD4;
    33.         };
    34.  
    35.         void vert (inout appdata_full v)
    36.         {
    37.             float3 T1 = float3(1, 0, 1);
    38.             float3 Bi = cross(T1, v.normal);
    39.             float3 T = normalize(cross(v.normal, Bi));
    40.            
    41.             v.tangent.xyz = T.xyz;
    42.            
    43.             if (dot(cross(v.normal, T),Bi) < 0)
    44.                 v.tangent.w = -1.0f;
    45.             else
    46.                 v.tangent.w = 1.0f;                
    47.         }
    48.        
    49.        
    50.         sampler2D _Control;
    51.         sampler2D _Splat0,_Splat1,_Splat2,_Splat3;
    52.        
    53.         void surf (Input IN, inout SurfaceOutput o)
    54.         {
    55.             fixed4 splat_control = tex2D (_Control, IN.uv_Control);
    56.             fixed3 col;
    57.             fixed3 nor;
    58.            
    59.             col = splat_control.r * tex2D (_Splat0, IN.uv_Splat0).rgb;
    60.             nor = splat_control.r * UnpackNormal (tex2D (_Splat1, IN.uv_Splat1));
    61.            
    62.             col += splat_control.b * tex2D (_Splat2, IN.uv_Splat2).rgb;
    63.             nor += splat_control.b * UnpackNormal (tex2D (_Splat3, IN.uv_Splat3));
    64.            
    65.             o.Albedo = col;
    66.             o.Normal = normalize(nor);
    67.             o.Alpha = 0.0;
    68.         }
    69.         ENDCG  
    70.     }
    71.    
    72.     Fallback off
    73. }
    74.  
     
  2. SevenBits

    SevenBits

    Joined:
    Dec 26, 2011
    Posts:
    1,953
    Are these from the ats package?
     
  3. steego

    steego

    Joined:
    Jul 15, 2010
    Posts:
    969
    No, the ats package only overrides the FirstPass shader and sets the normal maps by script, so it is limited to 4 textures.

    As I said above, a combination would be more ideal, setting the first four by script and then overriding the AddPass like I have done, but I haven't tried this yet. This is more a proof of concept.
     
  4. IrrSoft

    IrrSoft

    Joined:
    Jul 6, 2012
    Posts:
    1,526
    Thank you very much. I was looking for a way to modify the built in terrain shaders, and your solution to add bumpmapping was very good! I based my own shader on yours, and with this method it runs even in older machines without droping the framerate too much.

    Thanks for sharing this solution.