Search Unity

Mobile water with "Depth Alpha" or "Depth Opacity" or "Darker stuff deeper"?

Discussion in 'Shaders' started by MD_Reptile, Aug 1, 2017.

  1. MD_Reptile

    MD_Reptile

    Joined:
    Jan 19, 2012
    Posts:
    2,664
    Hey guys. I have been spending a good amount of time and effort trying to find/create a solution for mobile devices to use water with "depth alpha" or "depth opacity", where objects that are placed under the water, are only visible nearest the surface of the water, and the deeper they get, the more and more "hidden under the water" the object becomes visually to the player.

    This might not sound like its that complicated, but in practice, I've yet to find a solid tutorial, code snippet, or open source shader that shows how to achieve this effect. I've seen things like the free open source ocean system that has had a few different authors working on it, as well as plenty of water examples that don't have any different rendering no matter how deep the object gets.

    An example of a (bad) workaround might be to layer 5 or so planes/quads on top of one another, each with slight transparency, so that the deeper the objects were, the less transparency there would be visible, like you see here:



    But that is obviously not the solution I'm looking for, it looks bad, and probably would even have rendering problems on some mobile devices... another less than perfect solution might be to manually change the alpha of objects the lower they get in the water... but this also is not an optimal solution and wouldn't work for objects not specifically decided to be adjusted at lower depths.

    So how in the world do you guys do this? I'm not asking for a asset store package to solve this problem (although I'd be interested to see/hear how they did it for those water assets available in the asset store) but I'm hoping somebody can actually shed some light on specifically what worked, whether you wrote a custom shader, or used shader forge, or did something else entirely!

    How did you make your mobile water "darker" at lower depths? Thanks ahead of time for any info and tips that might help me figure this out!

    EDIT: another thing I've heard of is "schlicks fresnel equation" - which I don't know anything about, but perhaps would help me?

    EDIT2: forgot to mention, the "pro water" is too performance heavy for my target devices, so even though it might have options to do this sort of stuff (not sure) I found it was just too slow even on default settings for mobile devices.
     
    Last edited: Aug 1, 2017
  2. Namey5

    Namey5

    Joined:
    Jul 5, 2013
    Posts:
    188
    Well, the way it's done is by using the depth buffer (_CameraDepthTexture) to find the distance of all objects in the scene to the camera (this automatically disregards the water itself) and comparing that to the distance of the water to the camera. This is what the pro water assets do, but they also factor in fresnel calculations, reflections, refraction and potentially displacement. A simple shader that would fade from nothing to a colour could look like this;

    Code (CSharp):
    1. Shader "Custom/Depth Fade" {
    2.     Properties {
    3.         _Color ("Color", Color) = (1,1,1,1)
    4.         _Depth ("Depth Fade", Float) = 1.0
    5.         _Fix ("Depth Distance", Float) = -0.09
    6.     }
    7.     SubShader {
    8.         Tags { "RenderType"="Transparent" "Queue"="Transparent" }
    9.         LOD 200
    10.        
    11.         ZWrite Off
    12.  
    13.         Pass
    14.         {
    15.             Blend SrcAlpha OneMinusSrcAlpha
    16.            
    17.             CGPROGRAM
    18.             #pragma vertex vert
    19.             #pragma fragment frag
    20.  
    21.             #include "UnityCG.cginc"
    22.  
    23.             sampler2D _CameraDepthTexture;
    24.  
    25.             half _Depth;
    26.             half _Fix;
    27.  
    28.             fixed4 _Color;
    29.  
    30.             struct v2f
    31.             {
    32.                 float4 pos : SV_POSITION;
    33.                 float4 uv : TEXCOORD0;
    34.                 //float3 worldNorm : TEXCOORD1;
    35.                 //float3 viewDir : TEXCOORD2;
    36.             };
    37.  
    38.             v2f vert (appdata_base v)
    39.             {
    40.                 v2f o;
    41.                 o.pos = UnityObjectToClipPos (v.vertex);
    42.                 o.uv = ComputeScreenPos (o.pos);
    43.                 //o.worldNorm = UnityObjectToWorldNormal (v.normal);
    44.                 //o.viewDir = WorldSpaceViewDir (v.vertex);
    45.                 return o;
    46.             }
    47.  
    48.             half4 frag (v2f i) : SV_Target
    49.             {
    50.                 float2 uv = i.uv.xy / i.uv.w;
    51.  
    52.                 //float rim = saturate (dot (normalize (i.viewDir), i.worldNorm));
    53.  
    54.                 half lin = LinearEyeDepth (tex2D (_CameraDepthTexture, uv).r);
    55.                 half dist = i.uv.w - _Fix;
    56.                 half depth = lin - dist;
    57.  
    58.                 //return lerp (half4 (1,1,1,0), _Color, saturate (depth * _Depth * rim));
    59.                 return lerp (half4 (1,1,1,0), _Color, saturate (depth * _Depth));
    60.             }
    61.             ENDCG
    62.         }
    63.     }
    64.     FallBack "Diffuse"
    65. }
    The stuff that's been commented out is a bit more intensive, but it counters the 'condensing' effect you get when looking at glancing angles to the surface of the water. Take note however; if you are using Forward rendering, you have to manually enable the depth texture per camera via scripting. If you don't, it won't be generated and you'll just get a blank object.
     
    MD_Reptile likes this.
  3. MD_Reptile

    MD_Reptile

    Joined:
    Jan 19, 2012
    Posts:
    2,664
    Ok, I've tried this shader, and created this little script to set depth mode on the main camera:

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. [ExecuteInEditMode]
    4. public class EnableDepthTexture : MonoBehaviour
    5. {
    6.     void Start()
    7.     {
    8.         Camera.main.depthTextureMode = DepthTextureMode.Depth;
    9.     }
    10. }
    11.  
    And this is the result:



    EDIT: I see this on the camera component (it has a notice at the bottom):


    Is this the expected result? Should I use a different setting with DepthTextureMode? I'll keep experimenting, but any more info is appreciated :)

    EDIT 2: Ok, had to adjust the alpha of the color of the plane, then it looks proper:


    Thank you very much good sir/ma'am!
     
    Last edited: Aug 1, 2017
  4. brownboot67

    brownboot67

    Joined:
    Jan 5, 2013
    Posts:
    375
    If your water is always at a given height, set that as a global variable and do the fade to your "deep water" color in the object, not in the water. Then you don't need the depth tex and your water shader just handles specularity.
     
    MD_Reptile likes this.
  5. MD_Reptile

    MD_Reptile

    Joined:
    Jan 19, 2012
    Posts:
    2,664
    Hmm, forgive me @brownboot67 but I don't know much about shaders and don't understand all the terminology and stuff.

    -Your saying that you can do it without the depth texture on the camera, if your water doesn't move (doesn't have waves or multiple heights in diff areas?) by setting the water height in the shader - is that correct?

    -How do you go about fading the object then, like would you need to do some checks again in the shader to find out the height of the object?

    -Would you need to assign values (like the "deep water" color/alpha values) on a per-object basis if you did it this way?

    Thanks for your advice!
     
    Last edited: Aug 1, 2017
  6. brownboot67

    brownboot67

    Joined:
    Jan 5, 2013
    Posts:
    375
    First question: you could have waves. It'd be problematic if you have like a lake at the top of a mountain, and an ocean at the bottom of the mountain.

    Over simplification incoming...

    Step 1: set your water height and deep water color as globals. (you might also want like "water opacity" as a global, whatever)
    Step 2: make a shader for your objects and declare your globals.
    Step 3: in your object shader fade to the deep water color based on vertex world position.

    Ta-da. No alpha, no depth tex. And because everything in "deep water" is the same color, nobody knows!

    That ought to get you there.
     
    JoeStrout, tangwilliam and MD_Reptile like this.
  7. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,493
    See also mario sunshine
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    See any game before 2005 with water?
     
  9. Namey5

    Namey5

    Joined:
    Jul 5, 2013
    Posts:
    188
    If you are going to go with the depth buffer based shader, I would recommend disabling shadow casting for the water object. If you don't want to do this manually per object, you can just remove the fallback at the end of the shader.
     
  10. aubergine

    aubergine

    Joined:
    Sep 12, 2009
    Posts:
    2,878
    If you still couldnt solve your issue, here is my pack in the assetstore with an alternative method.
    TOZ Water
    If you watch the video, around 10th minute, you will see depth painting.
     
    MD_Reptile likes this.
  11. MD_Reptile

    MD_Reptile

    Joined:
    Jan 19, 2012
    Posts:
    2,664
    Thanks for the info @brownboot67 - that makes sense I think, I might try working on something like that.

    @aubergine - your asset does seem to be pretty full featured!

    I'm curious if it would work in the situation I'm trying to use it for, which is to show "fish" and other underwater stuff, ever so slightly, from the surface of the water, if they are near it. Imagine your standing on a dock overlooking a pond or river.... and you can see fish that are close to the surface, but not the ones who are deep in the water. From what I could tell in the video on the asset store, it looks like you also have a depth texture approach, is that correct?

    The reason I ask, is in the part of the video where you show the "painting" on the edges of the water near the beach, it seems like your creating transparency for specific areas, rather than the entire water surface, so does that mean you would not see objects just under the surface, if they were not near the edges of the water area? If you could still see objects slightly beneath the surface, then what exactly is the purpose of painting the edges like that?
     
  12. Alan-Ward

    Alan-Ward

    Joined:
    Aug 26, 2012
    Posts:
    84
    Hi all,

    Thanks @Namey5 - this is exactly the effect I've been looking for - and using the shader code above I've got this effect going on (see below). It works really well in the editor but I'm having an issue with it on android- as you can see from the gifs. The device is an old Galaxy Note II, with GLES 2.0. The opaque objects all use the standard shader Mobile/Vertex Lit (direction lights only)...

    BuildyRoadLogs2.gif Editor

    BuildyRoadLogs2Android.gif Android

    Cheers,
    Al.
     
    MD_Reptile likes this.
  13. brownboot67

    brownboot67

    Joined:
    Jan 5, 2013
    Posts:
    375
    That's poor depth precision. Don't use a depth mask use the vertex position.
     
    JoeStrout and MD_Reptile like this.
  14. Alan-Ward

    Alan-Ward

    Joined:
    Aug 26, 2012
    Posts:
    84
    Thanks @brownboot67. I'm a newb at shader writing but after a load of research I've managed to get it working using your suggestion - with the results looking almost exactly like the depth buffer version... and working beautifully on mobile :)

    BRLogsNew.gif
     
    MD_Reptile likes this.
  15. MD_Reptile

    MD_Reptile

    Joined:
    Jan 19, 2012
    Posts:
    2,664
  16. Alan-Ward

    Alan-Ward

    Joined:
    Aug 26, 2012
    Posts:
    84
    Ok here's the shader. I've cobbled this together from various sources...

    It's a basic diffuse vertex lit vertex/fragment shader - similar to standard "mobile/diffuse" but doesn't account for shadows.

    I've made it a little more flexible now - you can set whether lighting is included in the fade or whether it's just flat colour, and also specify a colour tint that will modify fade :

    Properties
    _Maintex = your 2D texture map
    _colorToFade = the 'deep water' colour at maximum fade
    _colorModifier = a colour tint that's added to the texture during the fade, %50 Gray = no tint
    _useLightingInFade = positive values = true, negative = false
    _fadeDepth = the y units to fade across
    _fadeStart = the y world height to start the fade

    Add this shader to the objects that you want to fade, and enjoy...


    Code (CSharp):
    1. Shader "Custom/FadeToColour"
    2. {
    3.      Properties
    4.      {
    5.         _MainTex ("Texture", 2D) = "white" {}
    6.         _colorToFade("Fade to Color", Color) = (1,1,1,1)
    7.         _useLightingInFade("Use Lighting in Fade", Int) = 1
    8.         _colorModifier("Color Modifier", Color) = (0.5,0.5,0.5,0.5)
    9.         _fadeDepth("Fade Depth", Float) = 1.0
    10.         _fadeStart("Fade Start Y", Float) = 0.0
    11.      }
    12.  
    13.      SubShader
    14.      {
    15.         Tags {"Queue"="Geometry" "LightMode"="ForwardBase"}
    16.         LOD 200
    17.  
    18.            Pass
    19.         {
    20.             Blend SrcAlpha OneMinusSrcAlpha
    21.             CGPROGRAM
    22.  
    23.             #pragma vertex vert
    24.             #pragma fragment frag
    25.             #include "UnityCG.cginc"
    26.             #include "Lighting.cginc"
    27.  
    28.             sampler2D _MainTex;
    29.  
    30.             half4 _colorToFade;
    31.             half4 _colorModifier;
    32.             float _fadeDepth;
    33.             float _fadeStart;
    34.             fixed _useLightingInFade;
    35.  
    36.             struct v2f
    37.             {
    38.                 float4 position : POSITION;
    39.                 float2 uv : TEXCOORD0;
    40.                 float4 wPos : TEXCOORD1;
    41.                 float4 col : COLOR0;
    42.                 float3 normal : NORMAL;
    43.             };
    44.             v2f vert (v2f v)
    45.             {
    46.                 v2f o;
    47.                 // set Wpos to be vertex world postion for y checking
    48.                 o.wPos = mul (unity_ObjectToWorld, v.position);
    49.  
    50.                 o.position = UnityObjectToClipPos(v.position);
    51.                 o.uv = v.uv;
    52.                 o.normal = v.normal;
    53.          
    54.                 //lighting - basic vertex shading with one light, no shadows
    55.                 float4x4 modelMatrixInverse = unity_WorldToObject;
    56.                 float3 normalDirection = normalize(float3(mul(float4(v.normal, 0.0), modelMatrixInverse).xyz));
    57.                 float3 lightDirection = normalize(float3(_WorldSpaceLightPos0.xyz));
    58.                 float3 diffuseReflection = float3(_LightColor0.xyz) * max(0.0, dot(normalDirection, lightDirection));
    59.                 o.col = float4(diffuseReflection, 1.0) + UNITY_LIGHTMODEL_AMBIENT;
    60.  
    61.                 //return the v2f output structure
    62.                 return o;
    63.             }
    64.  
    65.             half4 frag (v2f i) : SV_Target
    66.             {
    67.                 //get the texture colour
    68.                 half4 texel = tex2D (_MainTex, i.uv);
    69.                 //get the world height
    70.                 half vertHeight = i.wPos.y;
    71.                 //fade if below fade height
    72.                 if (vertHeight < _fadeStart)
    73.                 {
    74.                     //work out how deep we are in the fadeDepth for use in lerp
    75.                     half vertDepth = abs(_fadeStart - vertHeight);
    76.                     //calculate the colour tint - 50% gray = no tint
    77.                     half4 tint = _colorModifier - (0.5,0.5,0.5,0.5);
    78.  
    79.                     if (_useLightingInFade > 0)
    80.                         //lerp between texture color and complete fade depending on depth - with lighting.
    81.                         return (lerp (_colorToFade, (texel * i.col ) + tint, saturate ((_fadeDepth-vertDepth)/_fadeDepth))) ;
    82.                     else
    83.                         //or ignore lighting - flat shade the Fade
    84.                         return (lerp (_colorToFade, texel + tint, saturate ((_fadeDepth-vertDepth)/_fadeDepth))) ;
    85.                 }
    86.                 else
    87.                 {
    88.                     //otherwise just return the texture colour affected by lighting
    89.                     return texel * i.col;
    90.                 }
    91.             }
    92.             ENDCG
    93.         }
    94.      }
    95.      Fallback "Diffuse"
    96. }
     
    Last edited: Feb 21, 2018
    sonofbryce and MD_Reptile like this.
  17. Alan-Ward

    Alan-Ward

    Joined:
    Aug 26, 2012
    Posts:
    84
    Hi all - just FYI - I edited the shader slightly so that the colour tint works to darken the colours too. 50% Gray is now set as the "no tint" colour. Hope this makes sense.
    Cheers,
    Al.