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

How to Create a Foam Effect for Water

Discussion in 'Shaders' started by LostALife, Sep 12, 2021.

  1. LostALife

    LostALife

    Joined:
    Jul 19, 2020
    Posts:
    30
    I have a simple water shader that moves the vertices up and down to create waves. I want to add a foam effect to the top but im not sure how to do it. Im very new to shaders and would appreciate help.

    Currently this code checks the height of the vert and makes it white if above 0, black if bellow 0. The problem is its doing this with a static mesh so it is always half white half black. i want it so once the vertex passes a threshold it turns white. This is what it is currently doing.

    Screenshot 2021-09-12 210257.png

    I want it to change so the white line always stays horizontal and as the object goes up and down it changes colour vertically. Sorry if this is a bad explanation. I want something like this

    https://forum.unity.com/threads/using-local-height-to-determine-color-vertex-fragment-shader.505312/

    This is my code:
    Code (CSharp):
    1. Shader "Custom/Waves"
    2. {
    3.     Properties
    4.     {
    5.         _Color("Color", Color) = (0, 0, 1, 1)
    6.         _FoamColor("Foam Color", Color) = (1, 1, 1, 1)
    7.         _WaveHeight("Height", Range(0,2)) = 1.0
    8.         _Speed("Speed", Range(0,5)) = 100
    9.     }
    10.     SubShader
    11.     {
    12.         Tags { "RenderType"="Opaque" }
    13.         LOD 100
    14.  
    15.         Pass
    16.         {
    17.             CGPROGRAM
    18.             #pragma vertex vert
    19.             #pragma fragment frag
    20.  
    21.             #include "UnityCG.cginc"
    22.  
    23.             struct appdata
    24.             {
    25.                 float4 vertex : POSITION;
    26.                 float2 uv : TEXCOORD0;
    27.             };
    28.  
    29.             struct v2f
    30.             {
    31.                 float4 vertex : SV_POSITION;
    32.                 float height : TEXCOORD0;
    33.             };
    34.  
    35.             float _WaveHeight;
    36.             float _Speed;
    37.  
    38.             v2f vert (appdata v)
    39.             {
    40.                 v2f o;
    41.                 UNITY_INITIALIZE_OUTPUT(v2f, o);
    42.  
    43.                 float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
    44.  
    45.                 float displacement = (cos(worldPos.y) + cos(worldPos.x + (_Speed * _Time.y)));
    46.                 worldPos.y = worldPos.y + (displacement * _WaveHeight);
    47.  
    48.                 o.vertex = mul(UNITY_MATRIX_VP, worldPos);
    49.  
    50.                 o.height = v.vertex.y;
    51.  
    52.                 return o;
    53.             }
    54.  
    55.             float4 _Color;
    56.             float4 _FoamColor;
    57.  
    58.             fixed4 frag (v2f i) : COLOR
    59.             {
    60.                 if(i.height > -0.0){
    61.                     _Color = _FoamColor;
    62.                 }
    63.  
    64.                 return _Color;
    65.             }
    66.             ENDCG
    67.         }
    68.     }
    69. }
     
  2. SourOne

    SourOne

    Joined:
    May 15, 2018
    Posts:
    7
    Are you looking for something blended like in the second image? You'll probably want a gradient between the light and dark colors, and then sample the gradient at
    i.height /_WaveHeight
    . Alternatively you could do:
    Code (CSharp):
    1. float4 foamAtHeight = (i.height /_WaveHeight) * _FoamColor;
    2. _Color = (foamAtHeight + "Water Color") / 2;
    If you want to combine the foam color with a underlying water color
     
  3. LostALife

    LostALife

    Joined:
    Jul 19, 2020
    Posts:
    30
    That does the gradient which is nice but the problem is still the foam is always there. i only want foam on the highest parts of the water and not the lowest but this is always there. The idea would be to make the pixel white when above a threshold and then as the vertex goes back down it turns back to the original blue colour so there is only foam on the top of the waves.
    https://imgur.com/a/96BOCSP
     
  4. Shane_Michael

    Shane_Michael

    Joined:
    Jul 8, 2013
    Posts:
    157
    Then use "worldPos.y" instead of "v.vertex.y" for your height.
     
  5. LostALife

    LostALife

    Joined:
    Jul 19, 2020
    Posts:
    30
    Thanks that got it working. Is it possible to make it so the foam at the top does not go all the way to the bottom of the wave and is only at the very top and doesnt have the gradient?
    upload_2021-9-14_17-2-12.png
     
  6. SourOne

    SourOne

    Joined:
    May 15, 2018
    Posts:
    7
    Well there's two ways that I interpreted what you're looking for. If you want the whole seafoam to be pure white with no gradient anywhere on the wave then
    Code (CSharp):
    1. float4 foamAtHeight = (i.height /_WaveHeight) * _FoamColor;
    2. if(i.height > "Threshold Height");
    3. return float4 (1, 1, 1, 1);
    4. _Color = (foamAtHeight + "Water Color") / 2;
    Otherwise you'll want to find a function that ramps up smoothly between y = 0 and y = 1 from x = 0 to x = _WaveHeight. My favorite for this is the Witch of Agnesi: 1 / (x^2 + 1)
    To code it up with a control var, here's what I did:
    Code (CSharp):
    1. float steepness = 1; // You control your color change steepness here
    2. float inner = i.height - _WaveHeight;
    3. float denom = steepness * inner * inner + 1;
    4. float4 foamAtHeight = 1 / denom;
    5. _Color = (foamAtHeight + "Water Color") / 2;
    Of note with that function is that your lowest i.height will still have some foam contribution. The higher your steepness value the lower it'll be. There might be other functions that'll provide exactly what you're looking for but I think these are probably the simplest.
     
  7. LostALife

    LostALife

    Joined:
    Jul 19, 2020
    Posts:
    30
    Okay well i tried what you suggested but it just makes this effect which isnt really right. i dont understand the math here so its probably why its wrong. But i wasnt clear when i said i didnt want the gradient sorry, what i meant is i want to be able to reduce the gradient size so it doesnt reach the bottom of the wave which seems to be what you have suggested.
    upload_2021-9-15_10-46-5.png

    Here is a link to the new code: https://pastebin.com/iwHRU5L6
     
  8. SourOne

    SourOne

    Joined:
    May 15, 2018
    Posts:
    7
    So with the steepness variable, I'll call S you can control how quickly it ramps up as you get to the top of the wave. The function is in this graph, with a offset H that represents your _WaveHeight. The x axis represents the wave height, and the y height would be the percentage of foam. H can be any number, but for this it will just arbitrarily be 5.

    Screenshot (224).png

    As you increase your steepness variable, S, the amount of foam will increasingly concentrate towards the top of the wave height.

    Screenshot (225).png

    For a significantly large S value, the wave height where it begins to ramp up would be very close to _WaveHeight. In the picture below, the height where it will significantly begin to ramp up is 90% of the wave's height (4.5 / 5)

    Screenshot (226).png

    So I would recommend increasing the steepness variable until you reach a satisfactory amount. With respect to the bottom of the wave, this particular function will always have some small amount of foam at a wave height of zero. With a sufficiently high steepness value the amount will be very close to zero.

    Screenshot (227).png

    If you set _Steepness to a Range(1, 10000), you're likely to find the look you're looking for
     
    Last edited: Sep 15, 2021
    lilacsky824 likes this.
  9. Shane_Michael

    Shane_Michael

    Joined:
    Jul 8, 2013
    Posts:
    157
    I would just add _Scale and _Bias properties to your shader:

    Code (csharp):
    1.  
    2. fixed4 frag (v2f i) : COLOR
    3. {
    4.      float foamAmount = saturate(i.height * _Scale + _Bias);
    5.      return lerp(_Color, _FoamColor, foamAmount);
    6.  }
    7.  
    Start scale at 1 and bias at 0. Modify them in play mode to get the foam to appear wherever you want and as sharp or soft as you'd like. That gives you a clean 0 to 1 linear transition from water to foam, and you could then put the foamAmount variable through any function you'd like to modify the falloff.
     
  10. LostALife

    LostALife

    Joined:
    Jul 19, 2020
    Posts:
    30
    That worked exactly like i wanted it, thanks so much.

    Thanks for the suggestion @SourOne, Im afraid i dont completely understand what you were demonstrating as i havent worked with math like this in a long time and even then didnt use it much. Like i said i havent worked with shaders before either so im slowly learning how it all works from scratch which is why i chose Shane's solution, it was simplistic. Thanks for the help!
     
    SourOne likes this.