Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Resolved World Space UV mismatch after horizontal displacement (object space is fine)

Discussion in 'Shaders' started by dogmachris, Mar 7, 2023.

  1. dogmachris

    dogmachris

    Joined:
    Sep 15, 2014
    Posts:
    1,373
    Okay, so here's the issue it actually sounds quite simple, but apparently it's not (or maybe it is and I'm just too stupid to see it :D)

    I have a simple shader that displaces the vertices of a mesh horizontally according to a simple logic:


    Now with object UVs I don't usually have to worry about the texture mapping all that much, it'll match the stretched and crunched mesh perfectly.

    However when I do the same, using world space mapped UVs for the mesh, I have to make sure, the UV coordinates displace like the vertices do. In order to do that, I use the same displacement vector that I use for the vertices to displace the World position of the UVs.

    For small displacements it seems fine, but upon a closer look it becomes evident that there's a slight mismatch.

    Here you can see the difference between the mesh with object space UVs and with displaced world space UVs (both in a displaced state):


    Now as you can see, the two produce similar results, but not quite the same. While it doesn't seem like much, the mismatch becomes more evident with more displacement, leading to some unacceptable artifacts in my usecase.

    Here are the shader codes:

    Displacement on mesh with object space UVs:
    Code (CSharp):
    1. Shader "Disp_OS_UVs"
    2. {
    3.     Properties
    4.     {
    5.         _Emissive("Emissive", 2D) = "white" {}
    6.         _Displacement("Displacement", Float) = 0
    7.         [HideInInspector] _texcoord("", 2D) = "white" {}
    8.         [HideInInspector] __dirty("", Int) = 1
    9.     }
    10.     SubShader
    11.     {
    12.         Tags
    13.         {
    14.             "RenderType" = "Opaque"
    15.             "Queue" = "Geometry+0"
    16.             "IsEmissive" = "true"
    17.         }
    18.  
    19.         Cull Back
    20.  
    21.         CGPROGRAM
    22.         #pragma target 3.0
    23.         #pragma surface surf Standard keepalpha addshadow fullforwardshadows vertex:vertexDataFunc
    24.  
    25.         struct Input
    26.         {
    27.             float2 uv_texcoord;
    28.         };
    29.  
    30.         uniform float _Displacement;
    31.         uniform sampler2D _Emissive;
    32.         uniform float4 _Emissive_ST;
    33.  
    34.         void vertexDataFunc(inout appdata_full v, out Input o)
    35.         {
    36.             UNITY_INITIALIZE_OUTPUT(Input, o);
    37.  
    38.             float2 texcoord_centered = (v.texcoord.xy - float2(0.5,0.5)) * float2(2,2);
    39.             float3 displacement = float3(((1.0 - abs(texcoord_centered.x)) * texcoord_centered.x) , 0.0 , (texcoord_centered.y * (1.0 - abs(texcoord_centered.y))));
    40.             displacement *= _Displacement;
    41.  
    42.             float4 world_pos = mul(unity_ObjectToWorld,float4(v.vertex.xyz, 1.0));
    43.             v.vertex.xyz = world_pos.xyz + displacement;
    44.             v.vertex.w = 1;
    45.         }
    46.  
    47.         void surf(Input i , inout SurfaceOutputStandard o)
    48.         {
    49.             float2 uv_Emissive = i.uv_texcoord * _Emissive_ST.xy + _Emissive_ST.zw;
    50.             o.Emission = tex2D(_Emissive, uv_Emissive).rgb;
    51.             o.Alpha = 1;
    52.         }
    53.  
    54.     ENDCG
    55.     }
    56. }

    Displacement on mesh with world space UVs:

    Code (CSharp):
    1. Shader "Disp_WS_UVs"
    2. {
    3.     Properties
    4.     {
    5.         _Emissive("Emissive", 2D) = "white" {}
    6.         _Displacement("Displacement", Float) = 0
    7.         [HideInInspector] _texcoord("", 2D) = "white" {}
    8.         [HideInInspector] __dirty("", Int) = 1
    9.     }
    10.     SubShader
    11.     {
    12.         Tags
    13.         {
    14.             "RenderType" = "Opaque"
    15.             "Queue" = "Geometry+0"
    16.             "IsEmissive" = "true"
    17.         }
    18.         Cull Back
    19.  
    20.         CGPROGRAM
    21.         #pragma target 3.0
    22.         #pragma surface surf Standard keepalpha addshadow fullforwardshadows vertex:vertexDataFunc
    23.  
    24.         struct Input
    25.         {
    26.             float3 worldPos;
    27.             float2 uv_texcoord;
    28.         };
    29.  
    30.         uniform float _Displacement;
    31.         uniform sampler2D _Emissive;
    32.  
    33.         void vertexDataFunc(inout appdata_full v, out Input o)
    34.         {
    35.             UNITY_INITIALIZE_OUTPUT(Input, o);
    36.  
    37.             float2 uvOffset = (v.texcoord.xy - float2(0.5, 0.5)) * float2(2, 2);
    38.             float3 displacement = float3(
    39.                 (1.0 - abs(uvOffset.x)) * uvOffset.x,
    40.                 0.0,
    41.                 uvOffset.y * (1.0 - abs(uvOffset.y))
    42.             ) * _Displacement;
    43.  
    44.             float4 worldPosition = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1.0));
    45.             v.vertex.xyz = (worldPosition + float4(displacement, 0.0)).xyz;
    46.         }
    47.  
    48.         void surf(Input i, inout SurfaceOutputStandard o)
    49.         {
    50.             float2 uvOffset = (i.uv_texcoord - float2(0.5, 0.5)) * float2(2, 2);
    51.             float3 displacement = float3(
    52.                 (1.0 - abs(uvOffset.x)) * uvOffset.x,
    53.                 0.0,
    54.                 uvOffset.y * (1.0 - abs(uvOffset.y))
    55.             ) * _Displacement;
    56.  
    57.             float3 worldPosition = i.worldPos - displacement;
    58.             float2 uvCoords = (worldPosition.xz / float2(2, 2)) + float2(0.5, 0.5);
    59.             o.Emission = tex2D(_Emissive, uvCoords).rgb;
    60.             o.Alpha = 1;
    61.         }
    62.         ENDCG
    63.     }
    64. }
    To reproduce the behaviour, just simply yank out a plane, place it at 0,0,0 and apply a material to it with one of the shaders above. Set the tiling to 5 to get a better picture, increase the displacement to something like 3 and then switch between the Disp_WS_UVs and Disp_OS_UVs shaders, to reproduce the issue.

    Now if anyone knows, how to match world space mapped UVs more precisely to a displaced mesh, without getting that mismatch, please let me know, any help is greatly appreciated. ( @bgolus :rolleyes::rolleyes::rolleyes: ).
     
    Last edited: Mar 8, 2023
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,238
    When you're displacing the vertices and using the mesh UVs, the UVs are only being directly distorted at each vertex. Those distorted UVs are then linearly interpolated across each triangle.

    When you're displacing the world space UVs, you're displacing them continuously across the entire surface.

    If you want them to match, you need to know the triangle structure of the mesh you're displacing, and then manually linearly interpolate the offsets the same way. This is complicated to get right, error prone, and generally a bad idea.

    The alternative is always do the displacement in the vertex shader. More specifically, always calculate the displacement offset in the vertex shader and pass that value to the fragment surf function via a custom variable in the Input struct.
     
    dogmachris likes this.
  3. dogmachris

    dogmachris

    Joined:
    Sep 15, 2014
    Posts:
    1,373
    So basically instead of calculating displacement again in the fragment shader, all I need to do is just simply reusing the displacement vector that I calculate in vertexDataFunc?
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,238
    Yep.
     
  5. dogmachris

    dogmachris

    Joined:
    Sep 15, 2014
    Posts:
    1,373
    Works flawlessly, many thanks.
     
    Last edited: Mar 12, 2023