Search Unity

How To Dynamically Tessellate A Mesh

Discussion in 'Shaders' started by Mr_Admirals, Dec 13, 2018.

  1. Mr_Admirals

    Mr_Admirals

    Joined:
    May 13, 2017
    Posts:
    86
    Hello,

    Work continues on my snow deformation shader, and right now I'm focusing on the next big task which is optimizing the mesh so It can use as few vertices as possible. To do this, I want to tessellate only the areas which are marked for deformation on my depth map. I know this is possible, as I've seen it done on another Unity developer's snow tessellation shader.



    To start, I wanted to see if at the very least I can manipulate Unity's existing tessellation following Cone Wars' technique, which involves setting a Y value in world coordinates that tessellates parts of meshes below this value. This doesn't quite work well with planes, as the technique only tessellates objects in increments of 1 world unit (for a reason I have yet to determine).

    The effect can be seen well when I rotate the plane.
    upload_2018-12-13_13-12-6.png

    So now I'm back at square one, unsure of how to best proceed. Let alone how to map the tessellation to the depth map.

    Thoughts, ideas, opinions?

    EDIT: I also know about the flatness test, however I have yet to see any implementation or sources on how to go about testing flatness.
     
    Last edited: Dec 13, 2018
    Zonlib and Lars-Steenhoff like this.
  2. Przemyslaw_Zaworski

    Przemyslaw_Zaworski

    Joined:
    Jun 9, 2017
    Posts:
    328
    Code (CSharp):
    1. // Author: Przemyslaw Zaworski
    2. // Modified version of original:
    3. // https://github.com/przemyslawzaworski/Unity3D-CG-programming/blob/master/terrain_shader_tessellation_diffuse.shader
    4. // Shader supports maximum four splat textures (Splat and Normal maps) with triangle tessellation.
    5. // Works seamlessly with built-in Terrain Component. Lambert light model (directional) with shadow casting and receiving.
    6.  
    7. Shader "Terrain Shader Tessellation Diffuse"
    8. {
    9.     Properties
    10.     {
    11.         [HideInInspector] _Control ("Control (RGBA)", 2D) = "red" {}
    12.         [HideInInspector] _Splat3 ("Layer 3 (A)", 2D) = "white" {}
    13.         [HideInInspector] _Splat2 ("Layer 2 (B)", 2D) = "white" {}
    14.         [HideInInspector] _Splat1 ("Layer 1 (G)", 2D) = "white" {}
    15.         [HideInInspector] _Splat0 ("Layer 0 (R)", 2D) = "white" {}
    16.         [HideInInspector] _Normal3 ("Normal 3 (A)", 2D) = "bump" {}
    17.         [HideInInspector] _Normal2 ("Normal 2 (B)", 2D) = "bump" {}
    18.         [HideInInspector] _Normal1 ("Normal 1 (G)", 2D) = "bump" {}
    19.         [HideInInspector] _Normal0 ("Normal 0 (R)", 2D) = "bump" {}      
    20.         _TessellationMap ("Tessellation Map", 2D) = "black" {}
    21.     }
    22.     SubShader
    23.     {
    24.         Tags {"RenderType" = "Opaque"}
    25.         Pass
    26.         {  
    27.             Tags {"LightMode" = "ForwardBase" "SplatCount" = "4" "Queue" = "Geometry-100"}
    28.             CGPROGRAM
    29.  
    30.             #pragma vertex vertex_shader
    31.             #pragma hull hull_shader
    32.             #pragma domain domain_shader
    33.             #pragma fragment pixel_shader
    34.             #pragma target 5.0
    35.  
    36.             #pragma multi_compile_fwdbase
    37.             #include "AutoLight.cginc"
    38.  
    39.             sampler2D _Control;
    40.             float4 _Control_ST;
    41.             sampler2D _Splat0;
    42.             sampler2D _Splat1;
    43.             sampler2D _Splat2;
    44.             sampler2D _Splat3;
    45.             float4 _Splat0_ST;
    46.             float4 _Splat1_ST;
    47.             float4 _Splat2_ST;
    48.             float4 _Splat3_ST;
    49.             float4 _LightColor0;
    50.             sampler2D _TessellationMap;
    51.          
    52.             sampler2D _Normal0, _Normal1, _Normal2, _Normal3;
    53.          
    54.             struct APPDATA
    55.             {
    56.                 float2 tc_Control: COLOR;
    57.                 float4 vertex : POSITION;
    58.                 float3 normal : NORMAL;
    59.                 float2 uv_Splat0 : TEXCOORD0;
    60.                 float2 uv_Splat1 : TEXCOORD1;
    61.                 float2 uv_Splat2 : TEXCOORD2;
    62.                 float2 uv_Splat3 : TEXCOORD3;
    63.             };
    64.      
    65.             struct VS_CONTROL_POINT_OUTPUT
    66.             {
    67.                 float4 position : SV_POSITION;
    68.                 float3 normal : NORMAL;
    69.                 float2 uv_Splat0 : TEXCOORD0;
    70.                 float2 uv_Splat1 : TEXCOORD1;
    71.                 float2 uv_Splat2 : TEXCOORD2;
    72.                 float2 uv_Splat3 : TEXCOORD3;
    73.                 float2 tc_Control : COLOR;
    74.                 float4 _ShadowCoord : TEXCOORD4;          
    75.             };
    76.  
    77.             struct HS_CONSTANT_DATA_OUTPUT
    78.             {
    79.                 float edge[3] : SV_TessFactor;
    80.                 float inside  : SV_InsideTessFactor;
    81.             };
    82.  
    83.             float4 ComputeScreenPos (float4 p)
    84.             {
    85.                 float4 o = p * 0.5;
    86.                 o.xy = float2(o.x, o.y*_ProjectionParams.x) + o.w;  
    87.                 o.zw = p.zw;
    88.                 return o;
    89.             }
    90.          
    91.             VS_CONTROL_POINT_OUTPUT vertex_shader (APPDATA i)
    92.             {
    93.                 VS_CONTROL_POINT_OUTPUT vs;
    94.                 vs.position = i.vertex;
    95.                 vs.normal = i.normal;
    96.                 vs.uv_Splat0 = i.uv_Splat0;
    97.                 vs.uv_Splat1 = i.uv_Splat1;
    98.                 vs.uv_Splat2 = i.uv_Splat2;
    99.                 vs.uv_Splat3 = i.uv_Splat3;
    100.                 vs.tc_Control = i.tc_Control;
    101.                 vs._ShadowCoord = ComputeScreenPos(vs.position);
    102.                 return vs;
    103.             }
    104.             HS_CONSTANT_DATA_OUTPUT constantsHS (InputPatch<VS_CONTROL_POINT_OUTPUT,3> V, uint PatchID : SV_PrimitiveID)
    105.             {
    106.                 HS_CONSTANT_DATA_OUTPUT output = (HS_CONSTANT_DATA_OUTPUT)0;
    107.                 // float2 uv = (V[0].uv_Splat0 + V[1].uv_Splat0 + V[2].uv_Splat0) / 3.0;
    108.                 float2 uv = V[0].uv_Splat0;
    109.                 float factor = tex2Dlod(_TessellationMap,float4(uv,0,0)).r ;
    110.                 output.edge[0] = output.edge[1] = output.edge[2]  = lerp(1.0,64.0,factor);
    111.                 output.inside = lerp(1.0,64.0,factor);
    112.                 return output;
    113.             }
    114.  
    115.             [domain("tri")]
    116.             [partitioning("integer")]
    117.             [outputtopology("triangle_cw")]
    118.             [patchconstantfunc("constantsHS")]
    119.             [outputcontrolpoints(3)]
    120.  
    121.             VS_CONTROL_POINT_OUTPUT hull_shader (InputPatch<VS_CONTROL_POINT_OUTPUT,3> V, uint ID : SV_OutputControlPointID)
    122.             {
    123.                 return V[ID];
    124.             }
    125.  
    126.             [domain("tri")]
    127.             VS_CONTROL_POINT_OUTPUT domain_shader (HS_CONSTANT_DATA_OUTPUT input, const OutputPatch<VS_CONTROL_POINT_OUTPUT,3> P, float3 K : SV_DomainLocation)
    128.             {
    129.                 APPDATA ds;
    130.                 ds.vertex = UnityObjectToClipPos(P[0].position*K.x + P[1].position*K.y + P[2].position*K.z);
    131.                 ds.normal = (P[0].normal*K.x + P[1].normal*K.y + P[2].normal*K.z);
    132.                 ds.uv_Splat0 = (P[0].uv_Splat0*K.x + P[1].uv_Splat0*K.y + P[2].uv_Splat0*K.z)*_Splat0_ST.xy+_Splat0_ST.zw;
    133.                 ds.uv_Splat1 = (P[0].uv_Splat1*K.x + P[1].uv_Splat1*K.y + P[2].uv_Splat1*K.z)*_Splat1_ST.xy+_Splat1_ST.zw;
    134.                 ds.uv_Splat2 = (P[0].uv_Splat2*K.x + P[1].uv_Splat2*K.y + P[2].uv_Splat2*K.z)*_Splat2_ST.xy+_Splat2_ST.zw;
    135.                 ds.uv_Splat3 = (P[0].uv_Splat3*K.x + P[1].uv_Splat3*K.y + P[2].uv_Splat3*K.z)*_Splat3_ST.xy+_Splat3_ST.zw;
    136.                 ds.tc_Control = (P[0].uv_Splat0*K.x + P[1].uv_Splat0*K.y + P[2].uv_Splat0*K.z)*_Control_ST.xy+_Control_ST.zw;
    137.                 return vertex_shader(ds);
    138.             }
    139.  
    140.             float4 pixel_shader (VS_CONTROL_POINT_OUTPUT ps) : SV_Target
    141.             {
    142.                 float3 normal = normalize(mul((float3x3)unity_ObjectToWorld,ps.normal));
    143.                 float4 tangent = float4 (cross(ps.normal, float3(0,0,1)),-1);
    144.                 tangent.xyz = normalize(mul((float3x3)unity_ObjectToWorld,tangent.xyz));
    145.                 float3 binormal = normalize(cross(normal,tangent)*tangent.w);
    146.                 float4 splat_control = tex2D (_Control, ps.tc_Control);
    147.                 float weight = dot(splat_control, half4(1,1,1,1));
    148.                 clip(weight == 0.0f ? -1 : 1);
    149.                 splat_control /= (weight + 1e-3f);
    150.                 float3 color  = splat_control.r * tex2D (_Splat0, ps.uv_Splat0).rgb;
    151.                 color += splat_control.g * tex2D (_Splat1, ps.uv_Splat1).rgb;
    152.                 color += splat_control.b * tex2D (_Splat2, ps.uv_Splat2).rgb;
    153.                 color += splat_control.a * tex2D (_Splat3, ps.uv_Splat3).rgb;
    154.                 float4 nrm = splat_control.r * tex2D(_Normal0, ps.uv_Splat0);
    155.                 nrm += splat_control.g * tex2D(_Normal1, ps.uv_Splat1);
    156.                 nrm += splat_control.b * tex2D(_Normal2, ps.uv_Splat2);
    157.                 nrm += splat_control.a * tex2D(_Normal3, ps.uv_Splat3);
    158.                 nrm.rgb = float3(2.0*nrm.ag-1.0, 0.0);
    159.                 nrm.b = sqrt(1.0 - dot(nrm.rgb,nrm.rgb));
    160.                 float3x3 tbn = float3x3(tangent.xyz,binormal,normal);          
    161.                 float attenuation = SHADOW_ATTENUATION(ps);
    162.                 float3 AmbientLight = UNITY_LIGHTMODEL_AMBIENT;
    163.                 float3 LightDirection = normalize(_WorldSpaceLightPos0.xyz);
    164.                 float3 LightColor = _LightColor0.xyz*attenuation;
    165.                 float3 NormalDirection = normalize(mul(nrm.rgb, tbn));
    166.                 float3 diffuse = max(dot(LightDirection, NormalDirection),0.0) * LightColor + AmbientLight;
    167.                 return float4(color*weight*diffuse,1.0);
    168.             }
    169.             ENDCG
    170.         }
    171.     }
    172.     FallBack "Diffuse"
    173. }
    You can make render texture where certain pixels will define tesselated area, and you can sample texture in patchconstantfunc , where tesselation factor for every patch is computed.
    In my example, I added default terrain component to scene. Then I changed terrain material. I use red channel of input texture to define tessellation factors (terrain component in default is low poly so you can see "blocky" sphere area.).

    upload_2018-12-14_12-17-13.png

    upload_2018-12-14_12-18-16.png

    upload_2018-12-14_12-19-14.png

    You can also visit :
    http://tankmechanic.degenerals.com/category/development-blog/
    especially read Development Blog #01, where I use render textures and (tessellation / vertex texture fetch) in terrain
    deformation system.

     
    Last edited: Dec 14, 2018
    Reanimate_L, bgolus and Mr_Admirals like this.
  3. Mr_Admirals

    Mr_Admirals

    Joined:
    May 13, 2017
    Posts:
    86
    =O Holy moly! I wasn't expecting such a comprehensive answer! Thank you so much!! I'll start looking at your sources and read through your post ASAP!

    Also, your game looks phenomenal!
     
    Last edited: Dec 14, 2018
  4. Mr_Admirals

    Mr_Admirals

    Joined:
    May 13, 2017
    Posts:
    86
    Okay, so after some on and off messing around with your code, I'm a bit stuck. I added displacement functionality to the code in the vertex shader along the given vertex's Y normal, but it's being displaced in some very odd ways.

    Here's my displacement map:
    upload_2018-12-19_10-44-58.png

    Here's what the mesh looks like before displacement:
    upload_2018-12-19_10-45-32.png

    Here's what it looks like after displacement:
    upload_2018-12-19_10-46-29.png

    If you'll note, despite the code only telling the vertices to be displaced in the Y direction, they're also being displaced in the X and Z direction. Not to mention how weirdly the vertices are reacting to the displacement map.

    Here's the altered vertex shader:
    Code (CSharp):
    1. VS_CONTROL_POINT_OUTPUT vertex_shader(APPDATA i)
    2.             {
    3.                 VS_CONTROL_POINT_OUTPUT vs;
    4.  
    5.                 float d = tex2Dlod(_TessellationMap, float4(1 - i.uv_Splat0.x, i.uv_Splat0.y, 0, 0)).r * _Displacement;
    6.                 i.vertex.y -= i.normal.y * d;
    7.                 i.vertex.y += i.normal.y * _Displacement;
    8.  
    9.                 vs.position = i.vertex;
    10.                 vs.normal = i.normal;
    11.                 vs.uv_Splat0 = i.uv_Splat0;
    12.                 vs.uv_Splat1 = i.uv_Splat1;
    13.                 vs.uv_Splat2 = i.uv_Splat2;
    14.                 vs.uv_Splat3 = i.uv_Splat3;
    15.                 vs.tc_Control = i.tc_Control;
    16.                 vs._ShadowCoord = ComputeScreenPos(vs.position);
    17.                 return vs;
    18.             }
     
  5. sewy

    sewy

    Joined:
    Oct 11, 2015
    Posts:
    150
    I've saw your post on reddit showing you found the solution for the cracks, can you share how you did it please? I am getting holes at the edges, where tesselation doesn't match - which make sence, but hot to avoid it? upload_2020-1-17_16-11-5.png

    @Mr_Admirals
     
    Last edited: Feb 12, 2020