Search Unity

Get uv coordinates based on a vertex world position

Discussion in 'Shaders' started by jister, Aug 9, 2017.

  1. jister

    jister

    Joined:
    Oct 9, 2009
    Posts:
    1,749
    hey, so I wrote a shader that makes my characters cloths wet when he goes into the water.
    I calculate how deep he was submerged and then add that to his transform y position.
    now my problem is that when he starts walk/running the vertices start moving up and down so it looks like the waterline moves across his cloths. I tried so many things to fix it i can't even remember all of them.
    my last hope is to try and set the waterline in the uv's instead of vertex based. but i can't seem to figure out how.
    here's the part of the shader that does the "making wet"
    Code (CSharp):
    1. float dieDiff = cos(IN.worldPos.y) + (_WaterLevel - IN.worldPos.y) / _GradientScale;
    2.          
    3.             if (dieDiff < 0)
    4.             {
    5.                 dieDiff = 0;
    6.             }
    7.             else if (dieDiff > 1)
    8.             {
    9.                 dieDiff = 1;
    10.             }
    11.  
    12.             float dieFrac = _WaterColor.rgb * dieDiff + sg.a * (1-dieDiff);
    13.          
    14.             o.Metallic = sg.rgb  + (dieFrac* _Metallic) + IN.rainMask.a * _RainMultiplier;// - sm.rgb * _SkinTint;
    15.             o.Smoothness = sg.a  + (dieFrac* _Glossiness) +IN.rainMask.a * _RainMultiplier;// - sm.rgb * _SkinTint;
    _WaterLevel gets set by this piece of code
    Code (CSharp):
    1.  public void MakePlayerWet()
    2.     {
    3.         if (WaterBroker.IsInWater(m_Player.position))
    4.         {
    5.             m_IsWet = true;
    6.             m_UnderWaterDeepest = m_UnderWaterLevel;
    7.             if (WaterBroker.PreciseUnderWaterDepth(m_Player.position) > m_UnderWaterLevel)
    8.             {
    9.                 m_UnderWaterDeepest = WaterBroker.PreciseUnderWaterDepth(m_Player.position);
    10.                 m_UnderWaterLevel = m_UnderWaterDeepest;
    11.             }
    12.             m_Lerp = 0.0f;
    13.         }
    14.         if (m_IsWet)
    15.         {
    16.             m_WaterLevel = (m_Player.position.y + m_UnderWaterDeepest);
    17.             if (!WaterBroker.IsInWater(m_Player.position))
    18.                 StartDrying();
    19.             if (m_currentMaterials.Count == 0)
    20.                 GetMaterials();
    21.             foreach (Material m in m_currentMaterials)
    22.                 m.SetFloat("_WaterLevel", m_Player.position.y + m_UnderWaterDeepest);
    23.         }
    24.     }
    25.  
    26.     private void StartDrying()
    27.     {
    28.         m_Lerp = Mathf.Clamp01(m_Lerp + Time.deltaTime*m_DrySpeed);
    29.         m_WaterLevel = Mathf.Lerp(m_WaterLevel, m_Player.position.y, m_Lerp);
    30.         m_UnderWaterLevel = m_WaterLevel- m_Player.position.y;
    31.         if (m_Lerp >= 1.0f)
    32.         {
    33.             m_IsWet = false;
    34.             m_Lerp = 0.0f;
    35.             m_UnderWaterLevel = 0.0f;
    36.         }
    37.     }
    and here is a small vid showing my problem:
     
    Last edited: Aug 9, 2017
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    You can't just use world / object space on skinned meshes since the vertex positions change. You'll want to use the pre-skinned position which by default you won't have access to in Unity. The easiest solution is to add an extra UV for the model that's just from head to toe. Then hand-wavily remap that to it's in-game position with some manual value tweaking. Most games that do this kind of effect add a fairly large falloff region to account for things like splashing hiding the fact the line isn't perfect. Uncharted games for example track fairly well for the legs up to just above the hips, and anything above that is basically just makes the entire character wet and doesn't bother trying to be accurate.

    If you want to be super accurate you could use render textures to output the mesh's world position and use that to draw into a second "wetness" render texture. Only works if your mesh is uniquely UVed (no overlaping, not out of 0.0 to 1.0 ranges), but would let you get perfect alignment regardless of the animation. You could have an animation where the model dunks it's head into the water and then walks away and it'd only have the head wet.

    Keijiro's Skinner is an example of how to do this to get mesh positions into a texture.
    https://github.com/keijiro/Skinner

    The short version is you use the mesh's UVs as the vertex positions so you render to the render texture the "unwrapped" model. Then you could use that texture to blit into your wetness texture with a shader that does your water height test and a BlendOp Max to only overwrite the pixels that are less-wet.

    You would then use another blit every frame that would slowly dry out the wetness texture.
     
  3. jister

    jister

    Joined:
    Oct 9, 2009
    Posts:
    1,749
    Hey @bgolus thanks for helping out again! I'll try the 2nd UV head to toe first and see if it gives a satisfying result.
     
  4. jister

    jister

    Joined:
    Oct 9, 2009
    Posts:
    1,749
    works like a charm!! thanks again @bgolus.
    will post the result later.
     
  5. jister

    jister

    Joined:
    Oct 9, 2009
    Posts:
    1,749
    The result :)

    even though Ethan doesn't have a second Head to Toe UV (Can't show the character i did it with for non-disclosure reasons) You can still see it working on his pants.
    The only thing i had to do was multiply the coordinates by the players height for obvious reasons (the UV running from 0 to 1)