Search Unity

2D Water Flow Shader

Discussion in 'Shaders' started by UnetDev, Nov 28, 2019.

  1. UnetDev

    UnetDev

    Joined:
    Aug 28, 2017
    Posts:
    54
    Hey. I came across this post: https://gamedev.stackexchange.com/q...-render-2d-top-down-tiled-directed-water-flow

    And the shader was presented in the answer. I tried to reproduce it, but did not get the effect that its author received.


    My try:
    Code (CSharp):
    1. Shader "2DWater"
    2. {
    3.     Properties
    4.     {
    5.         _MainTexture("MainTexture", 2D) = "white" {}
    6.         _Flow ("Flow", 2D) = "white" {}
    7.         _Wave ("Wave", 2D) = "white" {}
    8.     }
    9.     SubShader
    10.     {
    11.         Tags { "RenderType"="Opaque" }
    12.         LOD 100
    13.  
    14.         Pass
    15.         {
    16.             CGPROGRAM
    17.             #pragma vertex vert
    18.             #pragma fragment Frag
    19.  
    20.             #include "UnityCG.cginc"
    21.  
    22.             // Colour texture / atlas for my tileset.
    23.             sampler2D _MainTexture;
    24.             // Flowmap texture.
    25.             sampler2D _Flow;
    26.             // Wave surface texture.
    27.             sampler2D _Wave;
    28.  
    29.             // Tiling of the wave pattern texture.
    30.             float _WaveDensity = 0.5;
    31.             // Scrolling speed for the wave flow.
    32.             float _WaveSpeed  = 5.0;
    33.  
    34.             // Scaling from my world size of 8x8 tiles
    35.             // to the 0...1
    36.             float2 inverseFlowmapSize = (float2)(1.0f/8.0f);
    37.          
    38.             struct v2f
    39.             {
    40.     // Projected position of tile vertex.
    41.     float4 vertex   : SV_POSITION;
    42.     // Tint colour (not used in this effect, but handy to have.
    43.     fixed4 color    : COLOR;
    44.     // UV coordinates of the tile in the tile atlas.
    45.     float2 texcoord : TEXCOORD0;
    46.     // Worldspace coordinates, used to look up into the flow map.
    47.     float2 flowPos  : TEXCOORD1;
    48.     };
    49.  
    50.         v2f vert(appdata_full IN)
    51. {
    52.     v2f OUT;
    53.  
    54.     // Save xy world position into flow UV channel.
    55.     OUT.flowPos = mul(unity_ObjectToWorld, IN.vertex).xy;
    56.  
    57.     // Conventional projection & pass-throughs...
    58.     OUT.vertex = UnityObjectToClipPos(IN.vertex);
    59.     OUT.texcoord = IN.texcoord;
    60.     OUT.color = IN.color;
    61.  
    62.     return OUT;
    63. }
    64.  
    65. float2 WaveAmount(float2 uv, float2 sampleSite) {
    66.     // Sample from the flow map texture without any mipmapping/filtering.
    67.     // Convert to a vector in the -1...1 range.
    68.     float2 flowVector = tex2Dgrad(_Flow, sampleSite * inverseFlowmapSize, 0, 0).xy
    69.                         * 2.0f - 1.0f;
    70.     // Optionally, you can skip this step, and actually encode
    71.     // a flow speed into the flow map texture too.
    72.     // I just enforce a 1.0 length for consistency without getting fussy.
    73.     flowVector = normalize(flowVector);
    74.  
    75.     // I displace the UVs a little for each sample, so that adjacent
    76.     // tiles flowing the same direction don't repeat exactly.
    77.     float2 waveUV = uv * _WaveDensity + sin((3.3f * sampleSite.xy + sampleSite.yx) * 1.0f);
    78.  
    79.     // Subtract the flow direction scaled by time
    80.     // to make the wave pattern scroll this way.
    81.     waveUV -= flowVector * _Time * _WaveSpeed;
    82.  
    83.     // I use tex2DGrad here to avoid mipping down
    84.     // undesireably near tile boundaries.
    85.     float wave = tex2Dgrad(_Wave, waveUV,
    86.                            ddx(uv) * _WaveDensity, ddy(uv) * _WaveDensity);
    87.  
    88.     // Calculate the squared distance of this flowmap pixel center
    89.     // from our drawn position, and use it to fade the flow
    90.     // influence smoothly toward 0 as we get further away.
    91.     float2 offset = uv - sampleSite;
    92.     float fade = 1.0 - saturate(dot(offset, offset));
    93.  
    94.     return float2(wave * fade, fade);
    95. }
    96.  
    97. fixed4 Frag(v2f IN) : COLOR
    98. {
    99.     // Sample the tilemap texture.
    100.     fixed4 c = tex2D(_MainTexture, IN.texcoord);
    101.  
    102.     // In my case, I just select the water areas based on
    103.     // how blue they are. A more robust method would be
    104.     // to encode this into an alpha mask or similar.
    105.     float waveBlend = saturate(3.0f * (c.b - 0.4f));
    106.  
    107.     // Skip the water effect if we're not in water.
    108.     if(waveBlend == 0.0f)
    109.         return c * IN.color;
    110.  
    111.     float2 flowUV = IN.flowPos;
    112.     // Clamp to the bottom-left flowmap pixel
    113.     // that influences this location.
    114.     float2 bottomLeft = floor(flowUV);
    115.  
    116.     // Sum up the wave contributions from the four
    117.     // closest flow map pixels.  
    118.     float2 wave = WaveAmount(flowUV, bottomLeft);
    119.     wave += WaveAmount(flowUV, bottomLeft + float2(1, 0));
    120.     wave += WaveAmount(flowUV, bottomLeft + float2(1, 1));
    121.     wave += WaveAmount(flowUV, bottomLeft + float2(0, 1));
    122.  
    123.     // We store total influence in the y channel,
    124.     // so we can divide it out for a weighted average.
    125.     wave.x /= wave.y;
    126.  
    127.     // Here I tint the "low" parts a darker blue.
    128.     c = lerp(c, c*c + float4(0, 0, 0.05, 0), waveBlend * 0.5f * saturate(1.2f - 4.0f * wave.x));
    129.  
    130.     // Then brighten the peaks.
    131.     c += waveBlend * saturate((wave.x - 0.4f) * 20.0f) * 0.1f;
    132.  
    133.     // And finally return the tinted colour.
    134.     return c * IN.color;
    135. }
    136.             ENDCG
    137.         }
    138.     }
    139. }
    upload_2019-11-28_13-13-8.png
    Could you tell me what my mistake is?
     
    yuliyF likes this.
  2. UnetDev

    UnetDev

    Joined:
    Aug 28, 2017
    Posts:
    54
  3. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    It would help if you would describe what IS working and what is NOT working.

    I see an animated image for the effect you want, but your example you're using different input images, and your image isn't animated. So ... without mindreading, I ahve no idea what you're saying is wrong?
     
  4. UnetDev

    UnetDev

    Joined:
    Aug 28, 2017
    Posts:
    54
    This shader is not mine. As far as I understand, his task is to make the static image of water to move, but for some reason this does not work as it should. I also understand that the direction of the water flow is set using the Flow texture of 8x8 pixels (field float2 inverseFlowmapSize = (float2) (1.0f / 8.0f); also points to this),
    but the movement still doesn’t happen.
     
  5. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    No movement at all?

    Are you testing in game or in editor? By default, the editor won't animate shaders
     
  6. UnetDev

    UnetDev

    Joined:
    Aug 28, 2017
    Posts:
    54
    Yes, I run the scene for the test.
     
  7. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    Try deleting lines 108 and 109.

    But, really ... until you start describing exactly what's happening vs what you expect, there's not much to help with.
     
  8. UnetDev

    UnetDev

    Joined:
    Aug 28, 2017
    Posts:
    54
    If I could describe what is happening in it line by line, I think I would not ask the forum for help))
     
  9. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    I'm not asking you to explain the entire shader, I'm asking you to describe what you expected to see on screen vs what you actually see. This is the first step in fixing a problem - until you do that, you haven't started.

    Also, debugging simple shaders like this one is very easy, you just need to do small edits and see what happens. Unlike most shaders, this is very simple code, 100% of it can be learned from scratch in a couple of hours or less. I would strongly recommend you do some basic tutorials in scripting, so that you feel confident editing the shader line by line to see what happens. This will save you time right now, and save you lots of time in future.

    (You don't need to understand every line of code, but you need to know the basics of how lines of code are executed one after the other, and roughly what they do.)
     
  10. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    9,448
    well the issue is that it doesn't do anything, water doesn't scroll.

    it has _Time there so something should animate, but it also uses worldspace XY coordinates,
    so could be problem with the mesh location, scale of your mesh, or how the UV maps should be in the mesh?
     
  11. UnetDev

    UnetDev

    Joined:
    Aug 28, 2017
    Posts:
    54
    I am at the stage of studying shaders. I read the Cg tutorial and rummaged through the documentation for ShaderLab. But I still don’t understand some points. For example:

    wave += WaveAmount(flowUV, bottomLeft + float2(1, 0));
    wouldn't bottomLeft always be 0.0? That is, the lower left corner of the square?
    Why is the addition operation bottomLeft + float2 (1, 0) performed in the parameters, if you could just specify float2 (1, 0) and have the same effect?


    float2 flowVector = tex2Dgrad(_Flow, sampleSite * inverseFlowmapSize, 0, 0).xy
    * 2.0f - 1.0f;
    this thing I can’t understand at all
     
  12. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    bottomLeft is calculated here:

    Code (CSharp):
    1. // Clamp to the bottom-left flowmap pixel
    2.     // that influences this location.
    3.     float2 bottomLeft = floor(flowUV);
    UV's don't have to go 0...1, they can go 2 ... 3 ... 4 ... etc. So, one common approach in shaders is to UV-map across an area with - say - UV 0...10, and then you can use floor(UV) to get a repeating 0..1, 0..1, 0..1 ... but also get the full 0...1, ...2, ...3, ...4 at the same time (for different parts of your shader).
     
  13. UnetDev

    UnetDev

    Joined:
    Aug 28, 2017
    Posts:
    54
    as far as I understand, the movement should occur on the _Wave texture (the last in the inspector window on the screenshot) using tex2Dgrad(_Wave, waveUV, ddx (uv) * _WaveDensity, ddy (uv) * _WaveDensity);
    where waveUV coordinates that are obtained using flowVector * _Time * _WaveSpeed;
    but the problem is that I don’t quite understand the line
    float2 flowVector = tex2Dgrad (_Flow, sampleSite * inverseFlowmapSize, 0, 0) .xy
    * 2.0f - 1.0f;
    to imagine further calculations
     
  14. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    My approach is usually to delete big parts of the shader, make a simpler version, and build it up in stages. That way, eventually you find the part that isn't doing what you expected it to do, and you can fix it.

    e.g. on line 122, you could immediately return the value of wave - that will show you if the function that calculates "wave" is even working at all.

    Something like:

    Code (CSharp):
    1. float4 result = float4(0,0,0,1); // black, fully opaque
    2. result.rg = wave; // assign the wave to the R and G channels in result
    3. return result;
     
  15. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    ...Or at Line 115, do the same with flowUV, and then with bottomLeft: try returning each of them, see if they look like they have the values you'd expect if they were correct.
     
  16. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    As per the comment on the line above, the author is using it to disable mipmapping (vs using a normal tex2D).

    I'm guessing: so that the shader samples the blocky, sharp-edged flowmap, rather than automatically blurring it by using mipmaps.

    It would be worth returning this value immediately (see above), and then compare that to returning:

    tex2D( _Flow, sampleSite * inverseFlowmapSize)

    ...you should be able to clearly see the difference/purpose that way
     
  17. UnetDev

    UnetDev

    Joined:
    Aug 28, 2017
    Posts:
    54
    I read theoretical material about mipmap, and that tex2D can take a lower quality texture, since the object can be far from the camera.

    sampleSite * inverseFlowmapSize
    I don’t understand this calculation, since it seems to me that every time
    WaveAmount(flowUV, bottomLeft);
    will have bottomLeft as 0,0
     
  18. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
  19. UnetDev

    UnetDev

    Joined:
    Aug 28, 2017
    Posts:
    54
    I tried to do so, and was very surprised because tex2D(_Flow, bottomLeft + float2 (1.0)) returns
    upload_2019-12-2_1-59-35.png

    but I figured it would be only
    upload_2019-12-2_1-59-43.png
     
    Last edited: Dec 2, 2019
  20. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    Try just returning bottomLeft itself - so you can see what actual values it contains (if you return tex2D( ..., bottomLeft), then you're obscuring the value by mixing it with your texture, so you still don't know what value it has).
     
  21. UnetDev

    UnetDev

    Joined:
    Aug 28, 2017
    Posts:
    54
    Thank you for the method by which I can now output values to an object, and then just use the eyedropper to see what happens on each pixel, this is very cool!

    So, I understand more now what flowUV is. The values that take real world coordinates inside, and if you move the object, then a palette from black to red (along the x axis) and from black to green (along the y axis) will be visible, provided that the swizzle will be blackColor.rg = flowUV.xy;

    bollomLeft there will always be 0.0 in WaveAmount(flowUV, bottomLeft); since the nearest integer value for all values from 0 to 1 will be 0.
    Therefore, the expression tex2Dgrad(_Flow, bottomLeft * inverseFlowmapSize, 0, 0).xy will take the lower left pixel in the _Flow texture, whose values are r == ~0.77, and g == ~0.95 (I indicate approximately, since I do not remember the exact values). there are also additional actions * 2.0f - 1.0f; which turn ~ 0.77 and ~ 0.95 into ~ 0.55 and ~ 0.90 respectively
    and the result is always the same for all situations:
    Code (CSharp):
    1.     WaveAmount(flowUV,  bottomLeft);
    2.     or
    3.     WaveAmount(flowUV, bottomLeft + float2(1, 0));
    4.     or
    5.     WaveAmount(flowUV, bottomLeft + float2(1, 1));
    6.     or
    7.     WaveAmount(flowUV, bottomLeft + float2(0, 1));
    always returns the same values ~ 0.55 and ~ 0.90


    I understand the normalization stage, I see no reason to write something about it

    But then there is some kind of calculation
    float2 waveUV = uv * _WaveDensity + sin((3.3f * sampleSite.xy + sampleSite.yx) * 1.0f);
    that I don’t understand and which displays this:
    upload_2019-12-2_8-20-46.png
    then I did all 3 offsets that were:
    Code (CSharp):
    1.  
    2.     WaveAmount(flowUV, bottomLeft + float2(1, 0));
    3.     or
    4.     WaveAmount(flowUV, bottomLeft + float2(1, 1));
    5.     or
    6.     WaveAmount(flowUV, bottomLeft + float2(0, 1));
    and in all these situations there was a shift in the corresponding directions (for example, with + float2(1, 1), the lower left black square turned yellow)


    and here is the line that caused me a lot of questions:
    waveUV -= flowVector * _Time * _WaveSpeed;
    as far as I understand, it should make the colors in the grid above change with time, but this does not happen
    BUT, if I delete * _WaveSpeed part, then the grid starts to gradually change its colors in some places to black

    and at that moment my brains began to boil and I ceased to understand why this was happening ...
     
  22. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    Looking purely at this line ... we can see it is taking the UV, and subtracting a value from it (which means it subtracts from U and V simultaneously).

    We know - by definition - that has the effect that anything using that UV will seem to "flow" up and to the right (because it's sampling a bit lower and to the left on each timestep ... more and more over time).

    NOW ... if that is "gradually chang[ing] ... to black", that means it's eventually sampling a UV location that returns 0, i.e. the color black.

    ...which suggests that it's sampling off the edge of the texture, and off the edge of the texture, it's returning 0.

    SO .... check your texture import settings! What clamp mode are you using? Maybe you need to set your texture to clamp-mode = repeat?
     
  23. UnetDev

    UnetDev

    Joined:
    Aug 28, 2017
    Posts:
    54
    Yes you are right! I thought that the default texture in the "repeat" mode, but it is not.
    This is what happened when I set the _Wave texture to "repeat" mode:


    And two questions remain:
    1) Why does it work without "_WaveSpeed"?
    2) Why is the texture not moving?
     
    a436t4ataf likes this.
  24. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    Reading the code, _WaveSpeed appears to be a fixed value: the number 5.

    i.e. it merely increases/decreases the overall speed. If you remove it, that's the same as having a _WaveSpeed of 1 (because you were multiplying by _WaveSpeed).

    I don't know what else is wrong now, but ... keep debugging :)
     
  25. UnetDev

    UnetDev

    Joined:
    Aug 28, 2017
    Posts:
    54
    I also thought that this is just an acceleration of movement, but this is not so, since it simply does not work with this value.
    On the example of the gif, which I threw off at the beginning, it is clear that the texture is moving, but I don’t understand which part of the code moves this texture, it doesn’t move for me.
     
  26. UnetDev

    UnetDev

    Joined:
    Aug 28, 2017
    Posts:
    54
    Maybe someone else can help me in solving this problem please?

    The final result that I managed to get is still not like what was on the GIF animation :(

     
  27. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    A couple of things the post you're working off of is assuming, and some bugs.

    The flow texture is supposed to be an 8x8 texture with sRGB turned off. The example image they're showing is just that 8x8 texture zoomed in a bunch, you'd need to scale that down to an 8x8 for it to work properly. You could use a texture of any size if you change from using the hardcoded
    inverseFlowmapSize
    to adding
    float4 _Flow_TexelSize;
    to your shader and using
    _Flow_TexelSize.xy
    in place of it.

    They're assuming each "tile" is 1x1 in world space. If your tile scale is different, it won't work.

    The
    _Time
    in line line
    waveUV -= flowVector * _Time * _WaveSpeed;
    should be
    _Time.x
    or
    _Time.y
    , otherwise you'll get the waves panning in different speeds on the x and y. Also, you can get away with not having the
    _WaveSpeed
    on that line because what's actually causing things to move is that time variable itself.
    _WaveSpeed
    is just a multiplier on it. If you don't have
    _WaveSpeed
    as a material property, and aren't setting it via script, it might default to 0.0 which means nothing moves.
     
  28. UnetDev

    UnetDev

    Joined:
    Aug 28, 2017
    Posts:
    54
    I am very glad that you were able to answer this post! Thanks you.
    As for the 8x8 flow texture, in my comments #4 I noticed this point, but I really did not know that sRGB should be turned off.
    Regarding the variable _WaveSpeed, in the end I brought it to the inspector to speed up or slow down the movement.

    I replaced the rigid snapping to the size of the 8x8 texture, as you said, replacing the variable
    inverseFlowmapSize
    on
    float4 _Flow_TexelSize
    .

    The scale of my tile is 1 for
    x
    and 1 for
    y
    , but the scale still does not affect it.
    In the comments of the shader, it was indicated that it only affects the blue color, that is, I can use any texture that has a blue color as water.
    And if I understood everything correctly, then the shader works in world coordinates, and in fact it doesn’t matter at what point in space my tiles are, it replaces it everywhere.

    The result is still the same, the movement occurs, but there is no flow effect. Perhaps it should not be in the shader, and this is done with the help of any additional manipulations?


    Here are shader code:
    Code (CSharp):
    1. Shader "2DWater"
    2. {
    3.     Properties
    4.     {
    5.         _MainTexture("MainTexture", 2D) = "white" {}
    6.         _Flow ("Flow", 2D) = "white" {}
    7.         _Wave ("Wave", 2D) = "white" {}
    8.     }
    9.     SubShader
    10.     {
    11.         Pass
    12.         {
    13.             CGPROGRAM
    14.             #pragma target 3.0
    15.             #pragma vertex vert
    16.             #pragma fragment Frag
    17.  
    18.             #include "UnityCG.cginc"
    19.  
    20.             // Colour texture / atlas for my tileset.
    21.             sampler2D _MainTexture;
    22.             // Flowmap texture.
    23.             sampler2D _Flow;
    24.             // Wave surface texture.
    25.             sampler2D _Wave;
    26.  
    27.             float2 flowVector1 = float2(1, 1);
    28.  
    29.             // Tiling of the wave pattern texture.
    30.             float _WaveDensity = 0.1;
    31.             // Scrolling speed for the wave flow.
    32.             float _WaveSpeed = 5.0;
    33.  
    34.             float4 _Flow_TexelSize;
    35.          
    36.             struct v2f
    37.             {
    38.     // Projected position of tile vertex.
    39.     float4 vertex   : SV_POSITION;
    40.     // Tint colour (not used in this effect, but handy to have.
    41.     fixed4 color    : COLOR;
    42.     // UV coordinates of the tile in the tile atlas.
    43.     float2 texcoord : TEXCOORD0;
    44.     // Worldspace coordinates, used to look up into the flow map.
    45.     float2 flowPos  : TEXCOORD1;
    46.     };
    47.  
    48.         v2f vert(appdata_full IN)
    49. {
    50.     v2f OUT;
    51.  
    52.     // Save xy world position into flow UV channel.
    53.     OUT.flowPos = mul(unity_ObjectToWorld, IN.vertex).xy;
    54.  
    55.     // Conventional projection & pass-throughs...
    56.     OUT.vertex = UnityObjectToClipPos(IN.vertex);
    57.     OUT.texcoord = IN.texcoord;
    58.     OUT.color = IN.color;
    59.  
    60.     return OUT;
    61. }
    62.  
    63. float2 WaveAmount(float2 uv, float2 sampleSite)
    64. {
    65.     // Sample from the flow map texture without any mipmapping/filtering.
    66.     // Convert to a vector in the -1...1 range.
    67.     float2 flowVector = tex2Dgrad(_Flow, sampleSite * _Flow_TexelSize, 0, 0).xy
    68.                         * 2.0f - 1.0f;
    69.     // Optionally, you can skip this step, and actually encode
    70.     // a flow speed into the flow map texture too.
    71.     // I just enforce a 1.0 length for consistency without getting fussy.
    72.     flowVector = normalize(flowVector);
    73.  
    74.     // I displace the UVs a little for each sample, so that adjacent
    75.     // tiles flowing the same direction don't repeat exactly.
    76.     float2 waveUV = uv * _WaveDensity + sin((3.3f * sampleSite.xy + sampleSite.yx) * 1.0f);
    77.  
    78.     // Subtract the flow direction scaled by time
    79.     // to make the wave pattern scroll this way.
    80.     waveUV -= flowVector * _Time * 0.2f;
    81.  
    82.     // I use tex2DGrad here to avoid mipping down
    83.     // undesireably near tile boundaries.
    84.     float wave = tex2Dgrad(_Wave, waveUV,
    85.                            ddx(uv) * _WaveDensity, ddy(uv) * _WaveDensity);
    86.  
    87.     // Calculate the squared distance of this flowmap pixel center
    88.     // from our drawn position, and use it to fade the flow
    89.     // influence smoothly toward 0 as we get further away.
    90.     float2 offset = uv - sampleSite;
    91.     float fade = 1.0 - saturate(dot(offset, offset));
    92.  
    93.     return float2(wave * fade, fade);
    94. }
    95.  
    96. fixed4 Frag(v2f IN) : COLOR
    97. {
    98.     // Sample the tilemap texture.
    99.     fixed4 c = tex2D(_MainTexture, IN.texcoord);
    100.  
    101.     // In my case, I just select the water areas based on
    102.     // how blue they are. A more robust method would be
    103.     // to encode this into an alpha mask or similar.
    104.     float waveBlend = saturate(3.0f * (c.b - 0.4f));
    105.  
    106.     // Skip the water effect if we're not in water.
    107.     if(waveBlend == 0.0f)
    108.         return c * IN.color;
    109.  
    110.     float2 flowUV = IN.flowPos;
    111.     // Clamp to the bottom-left flowmap pixel
    112.     // that influences this location.
    113.     float2 bottomLeft = floor(flowUV);
    114.  
    115.     // Sum up the wave contributions from the four
    116.     // closest flow map pixels.  
    117.     float2 wave = WaveAmount(flowUV, bottomLeft);
    118.     wave += WaveAmount(flowUV, bottomLeft + float2(1, 0));
    119.     wave += WaveAmount(flowUV, bottomLeft + float2(1, 1));
    120.     wave += WaveAmount(flowUV, bottomLeft + float2(0, 1));
    121.  
    122.     // We store total influence in the y channel,
    123.     // so we can divide it out for a weighted average.
    124.     wave.x /= wave.y;
    125.  
    126.     // Here I tint the "low" parts a darker blue.
    127.     c = lerp(c, c*c + float4(0, 0, 0.05, 0), waveBlend * 0.5f * saturate(1.2f - 4.0f * wave.x));
    128.  
    129.     // Then brighten the peaks.
    130.     c += waveBlend * saturate((wave.x - 0.4f) * 20.0f) * 0.1f;
    131.  
    132.     // And finally return the tinted colour.
    133.     return c * IN.color;
    134. }
    135.             ENDCG
    136.         }
    137.     }
    138. }
     
    Last edited: Jan 15, 2020