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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Intersect Box With Wave Plane

Discussion in 'Shaders' started by jeshjesh, Feb 12, 2020.

  1. jeshjesh

    jeshjesh

    Joined:
    Jul 7, 2019
    Posts:
    14
    Hi all; I am relatively new to shaders, but understand them at a basic level. I have had success stitching together shaders in the past, but I would like a little bit of guidance for an effect I am trying to do

    wave+box.gif
    Here is a scene I have set up, with a plane on top using a vertex shader that projects sin waves down the plane with respect to time. Underneath is a simple scaled cube, with a transparency-enabled standard shader.

    Basically, I would like to use these two things to create a simple water solid. My initial idea is to write an effect that disables the box from being rendered for any pixel above the surface of the wave plane atop it. Right now, the box and plane are two separate objects, but I could foresee a version of this that applies the wave shader to only the top face of a box, with the other five faces of the box being flat, aside from the top edges of the sides deforming to meet the wave on top.

    Is this a fairly easy effect to achieve? What would be the best direction I can go in? The goal of the effect is to provide an interesting cross-section of a river or ocean; think "moses parts the sea", or like a glass wall to a lazy river, or a fishtank.

    Thank you for your help and advice! I am eager to learn and try some of your suggestions.
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,256
    This. Though with how extreme you have some of your waves it might be hard to do without some artifacts on the side unless you have just as many divisions down the whole sid as you do across the edges vs doing any kind of triangle fans. But generally speaking the most efficient and easiest solution is this one.

    You could also calculate the same sine waves as the top plane, but just in the fragment shader and use that to clip or fade out the edge. You’d need to be very aware of the top plane’s actual starting height and wave offset range so they line up. You’ll also need to have the sides go slightly higher than the top plane for the cutoff / fade since it’s impossible to have it perfectly match with this technique, both because of the plane being a grid and the sides doing it per pixel (though you could calculate the sine waves on a grid and interpolate to mimic that) and because numerical limitations of floating point math mean they will always be a few holes even if it’s “perfect” mathematical.

    Also remember you’d need to render in 3 passes. Interior (back faces of the box), top waves, then outside of the box. You may get rendering artifacts with your water no matter what you do unless you’re careful with the triangle order.
     
  3. jeshjesh

    jeshjesh

    Joined:
    Jul 7, 2019
    Posts:
    14
    Found a solution!
    FISHTANK WAVES.gif
    I modified my wave shader to scale the effect based on a black and white input texture, then faded the outlines to black.

    roundsquare2.png
    With 2 black pixels on the edge, the edges are seamless. And because the normals on the edges are still set as though they were being actually transformed, the lighting gives an illusion of waves there even though it's flat.
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,256
    Here's an example of the single mesh approach. It's a single 64x64x1 unit box with 128x128 divisions. The shader is checking if the starting y for the vertex is >0.5 before it modifies the position, and checking the normal is facing up before modifying that.
    upload_2020-2-13_0-25-16.png

    The sine waves are very bubbly, the normal isn't attempting to be all that accurate, and the numbers are all hard coded, but it works well enough to prove the concept.
    Code (csharp):
    1. Shader "Custom/WaterBox"
    2. {
    3.     Properties
    4.     {
    5.         _Color ("Color", Color) = (1,1,1,1)
    6.         _Color2 ("Color 2", Color) = (1,1,1,1)
    7.         _Glossiness ("Smoothness", Range(0,1)) = 0.5
    8.     }
    9.     SubShader
    10.     {
    11.         Tags { "Queue"="Transparent" "RenderType"="Transparent" }
    12.         LOD 200
    13.  
    14.         CGINCLUDE
    15.         struct Input
    16.         {
    17.             float h;
    18.         };
    19.  
    20.         half _Glossiness;
    21.         fixed4 _Color, _Color2;
    22.  
    23.         void waves(inout appdata_full v, inout Input IN)
    24.         {
    25.             IN.h = 0.0;
    26.  
    27.             float s0x, c0x, s1x, c1x;
    28.             float s0y, c0y, s1y, c1y;
    29.             sincos(v.vertex.x * 0.6 + _Time.y + v.vertex.z * 0.1, s0x, c0x);
    30.             sincos(v.vertex.z * 0.5 + _Time.y * 2.0 + v.vertex.x * 0.1, s0y, c0y);
    31.             sincos(v.vertex.x * 0.3 + _Time.y - v.vertex.z * 0.1, s1x, c1x);
    32.             sincos(v.vertex.z * 0.21 + _Time.y * 2.0 - v.vertex.x * 0.1, s1y, c1y);
    33.  
    34.             float offset = (s0x + s0y) * 0.15 + (s1x + s1y) * 1.0 + 1.5;
    35.             if (v.vertex.y > 0.5)
    36.             {
    37.                 v.vertex.y += offset;
    38.                 IN.h = 0.0;
    39.                 if (v.normal.y > 0.7)
    40.                 {
    41.                     v.normal = normalize(v.normal + float3(c0y * 0.15 + c1y * 0.3, 0.0, c0x * 0.15 + c1x * 0.3));
    42.                 }
    43.             }
    44.             else
    45.             {
    46.                 IN.h = offset + 1.0;
    47.             }
    48.         }
    49.  
    50.         void surf (Input IN, inout SurfaceOutputStandard o)
    51.         {
    52.             fixed4 color = lerp(_Color2, _Color, smoothstep(1.5, 0.0, pow(IN.h, 0.5)));
    53.             o.Albedo = color.rgb;
    54.             o.Metallic = 0;
    55.             o.Smoothness = _Glossiness;
    56.             o.Alpha = color.a;
    57.         }
    58.         ENDCG
    59.  
    60.         Cull Front
    61.         CGPROGRAM
    62.         #pragma surface surf Standard alpha:premul vertex:vert
    63.         #pragma target 3.0
    64.  
    65.         void vert(inout appdata_full v, out Input IN)
    66.         {
    67.            waves(v, IN);
    68.            v.normal = -v.normal;
    69.         }
    70.         ENDCG
    71.  
    72.         Cull Back
    73.         CGPROGRAM
    74.         #pragma surface surf Standard alpha:premul vertex:vert
    75.         #pragma target 3.0
    76.  
    77.         void vert(inout appdata_full v, out Input IN)
    78.         {
    79.            waves(v, IN);
    80.         }
    81.         ENDCG
    82.     }
    83. }
     
  5. jeshjesh

    jeshjesh

    Joined:
    Jul 7, 2019
    Posts:
    14
    I tried your shader, but maybe I did something wrong? The result is just a box that occasionally shifts color from Color1 to Color2 for a brief moment. I went into blender and made a 64x64x1 box mesh to use it on. Still, I think I can probably use some of the techniques you used, like only applying the shader to the top half of a box. Yours definitely looks a lot more connected and convincing than my previous solution.
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,256
    Here's the mesh I used too.
     

    Attached Files:

  7. jeshjesh

    jeshjesh

    Joined:
    Jul 7, 2019
    Posts:
    14
    That is very amazing haha. Can you help me understand this part?
    Code (CSharp):
    1. float offset = (s0x + s0y) * 0.15 + (s1x + s1y) * 1.0 + 1.5;
    also, are s0x, c0x, s0y, c0y for one wave? and s1x, c1x, s1y, c1y are for another wave propagating down the mesh? or are they for two different things?
     
  8. jeshjesh

    jeshjesh

    Joined:
    Jul 7, 2019
    Posts:
    14
    I made a version with a bunch of sliders on the properties so I could play with them and roughly figure out what they control. Would you agree with these property names?

    Code (CSharp):
    1. Shader "Custom/WaveBox"
    2. {
    3.     Properties
    4.     {
    5.         _Color("Top Color", Color) = (1,1,1,1)
    6.         _Color2("Fill Color", Color) = (1,1,1,1)
    7.         _Glossiness("Smoothness", Range(0,1)) = 0.5
    8.  
    9.         [Header(Global props)]
    10.         _CutoffY("Cutoff y value", Range(0,1)) = 0.5
    11.         _WaveSpeedX("Wave Speed X", Range(0, 10)) = 1.0
    12.         _WaveSpeedY("Wave Speed Y", Range(0, 10)) = 2.0
    13.         _BoxHeight("Base Height", Range(0,10)) = 1.5
    14.  
    15.         [Header(sincos Scalars)]
    16.         _PerpindicularScalar("Perpendicular Scalar", Range(0,1)) = 0.1
    17.         _0xScalar("Wave1 X Period", Range(0,1)) = 0.6
    18.         _0yScalar("Wave1 Y Period", Range(0,1)) = 0.5
    19.         _1xScalar("Wave2 X Period", Range(0,1)) = 0.3
    20.         _1yScalar("Wave2 Y Period", Range(0,1)) = 0.21
    21.  
    22.         [Header(Offset Scalars)]
    23.         _Wave1Amp("Wave 1 Amplitude", Range(0,2)) = 0.15
    24.         _Wave2Amp("Wave 2 Amplitude", Range(0,2)) = 1.0
    25.  
    26.         [Header(Normal Scalars)]
    27.         _Wave1NormalScalar("Wave 1 Normal Scalar", Range(0,1)) = 0.15
    28.         _Wave2NormalScalar("Wave 2 Normal Scalar", Range(0,1)) = 0.3
    29.     }
    30.         SubShader
    31.     {
    32.         Tags { "Queue" = "Transparent" "RenderType" = "Transparent" }
    33.         LOD 200
    34.  
    35.         CGINCLUDE
    36.         struct Input
    37.         {
    38.             float h;
    39.         };
    40.  
    41.         half _Glossiness;
    42.         fixed4 _Color, _Color2;
    43.         float _CutoffY;
    44.         float _PerpindicularScalar;
    45.         float _WaveSpeedX, _WaveSpeedY;
    46.         float _0xScalar, _0yScalar, _1xScalar, _1yScalar;
    47.         float _Wave1Amp, _Wave2Amp, _BoxHeight;
    48.         float _Wave1NormalScalar, _Wave2NormalScalar;
    49.  
    50.         void waves(inout appdata_full v, inout Input IN)
    51.         {
    52.             IN.h = 0.0;
    53.  
    54.             float s0x, c0x, s1x, c1x;
    55.             float s0y, c0y, s1y, c1y;
    56.             sincos(v.vertex.x * _0xScalar + _Time.y * _WaveSpeedX + v.vertex.z * _PerpindicularScalar, s0x, c0x);
    57.             sincos(v.vertex.z * _0yScalar + _Time.y * _WaveSpeedY + v.vertex.x * _PerpindicularScalar, s0y, c0y);
    58.             sincos(v.vertex.x * _1xScalar + _Time.y * _WaveSpeedX - v.vertex.z * _PerpindicularScalar, s1x, c1x);
    59.             sincos(v.vertex.z * _1yScalar + _Time.y * _WaveSpeedY - v.vertex.x * _PerpindicularScalar, s1y, c1y);
    60.  
    61.             float offset = _BoxHeight + (s0x + s0y) * _Wave1Amp + (s1x + s1y) * _Wave2Amp;
    62.             if (v.vertex.y > _CutoffY)
    63.             {
    64.                 v.vertex.y += offset;
    65.                 IN.h = 0.0;
    66.                 if (v.normal.y > 0.7)
    67.                 {
    68.                     v.normal = normalize(v.normal + float3(
    69.                         c0y * _Wave1NormalScalar + c1y * _Wave2NormalScalar,
    70.                         0.0,
    71.                         c0x * _Wave1NormalScalar + c1x * _Wave2NormalScalar));
    72.                 }
    73.             }
    74.             else
    75.             {
    76.                 IN.h = offset + 1.0;
    77.             }
    78.         }
    79.  
    80.         void surf(Input IN, inout SurfaceOutputStandard o)
    81.         {
    82.             fixed4 color = lerp(_Color2, _Color, smoothstep(1.5, 0.0, pow(IN.h, 0.5)));
    83.             o.Albedo = color.rgb;
    84.             o.Metallic = 0;
    85.             o.Smoothness = _Glossiness;
    86.             o.Alpha = color.a;
    87.         }
    88.         ENDCG
    89.  
    90.         Cull Front
    91.         CGPROGRAM
    92.         #pragma surface surf Standard alpha:premul vertex:vert
    93.         #pragma target 3.0
    94.  
    95.         void vert(inout appdata_full v, out Input IN)
    96.         {
    97.            waves(v, IN);
    98.            v.normal = -v.normal;
    99.         }
    100.         ENDCG
    101.  
    102.         Cull Back
    103.         CGPROGRAM
    104.         #pragma surface surf Standard alpha:premul vertex:vert
    105.         #pragma target 3.0
    106.  
    107.         void vert(inout appdata_full v, out Input IN)
    108.         {
    109.            waves(v, IN);
    110.         }
    111.         ENDCG
    112.     }
    113. }
    I may play with this more later and try to get the gerstner waves working on the top of the box. I think I understand this shader well enough to change the way the offset is being created and do that. The original tutorial I followed to make the cloth-y waves in the OP is here: https://catlikecoding.com/unity/tutorials/flow/waves/
     
  9. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,256
    Gerstner waves look much better than my hack. The problem with them is they move the vertices more than just up and down, which means the technique above won’t work with it at all. Actually none of the options I listed will really work when using Gerstner waves.

    Your breakdown of my terrible wave function is pretty spot on though.

    There are ways to sharpen the sine waves in ways that better mimic the look of Gerstner waves. Either by playing with the sample position or applying a power to the sine (
    0.5 - pow(sin(x) * 0.5 + 0.5, 0.67)
    looks a little better for example), but the normals won’t work out quite as nicely... not that the ones I have are all that great either.
     
  10. jeshjesh

    jeshjesh

    Joined:
    Jul 7, 2019
    Posts:
    14
    After playing with the various values and sliders with the above, it feels like it will be generally versatile enough for what I'm trying to do with it. Thank you for sharing and helping me learn. I may see if some hybrid method works though