Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Help with "Scrawk's" Ocean Shader!

Discussion in 'Shaders' started by Clazzid, Jun 15, 2014.

  1. Clazzid

    Clazzid

    Joined:
    Jun 15, 2014
    Posts:
    5
    Hello all, i am currently looking for a solution for a ocean shader, i think we all need this!

    The only partial solution i have found is from a project done by "Scrawk" at his blog
    http://scrawkblog.com/2013/06/04/ocean-with-brdf-lighting-in-unity/
    the lighting, wave generator and runtime mesh are awesome! the frames!,

    it still needs:
    semi transparent color range by depth
    relfections and refractions
    white caps and wave height detection for buoyancy would be nice

    so far i managed to get some nice underwater effects going on like caustics, light rays and fog

    Since Scrawk has stated that his code is for learning purposes and can be used by all, i think we all need to take his project further as a open source ocean/water shader.

    This is a challenging task given the complex lighting used, and my poor knowledge of shading.

    Scrawk if you see this i hope you help out,

    here is the shader

    Code (CSharp):
    1.  
    2. Shader "Ocean/OceanBRDF"
    3. {
    4.     SubShader
    5.     {
    6.      
    7.         Pass
    8.         {
    9.             Tags { "RenderType"="Opaque" }
    10.          
    11.             CGPROGRAM
    12.             #include "UnityCG.cginc"
    13.             #pragma target 3.0
    14.             #pragma vertex vert
    15.             #pragma fragment frag
    16.             #include "Atmosphere.cginc"
    17.             #pragma glsl
    18.  
    19.             uniform sampler2D _Map0, _Map1, _Map2, _SkyMap;
    20.             uniform sampler3D _Variance;
    21.             uniform float4 _GridSizes;
    22.             uniform float3 _SeaColor;
    23.             uniform float _MaxLod, _LodFadeDist;
    24.             uniform float2 _VarianceMax;
    25.          
    26.             struct v2f
    27.             {
    28.                 float4  pos : SV_POSITION;
    29.                 float3 worldPos : TEXCOORD;
    30.             };
    31.  
    32.             v2f vert(appdata_base v)
    33.             {
    34.                 float3 worldPos = mul(_Object2World, v.vertex).xyz;
    35.          
    36.                 float dist = clamp(distance(_WorldSpaceCameraPos.xyz, worldPos) / _LodFadeDist, 0.0, 1.0);
    37.                 float lod = _MaxLod * dist;
    38.                 //lod = 0.0;
    39.              
    40.                 float ht = 0.0;
    41.                 ht += tex2Dlod(_Map0, float4(worldPos.xz/_GridSizes.x, 0, lod)).x;
    42.                 ht += tex2Dlod(_Map0, float4(worldPos.xz/_GridSizes.y, 0, lod)).y;
    43.                 //ht += tex2Dlod(_Map0, float4(worldPos.xz/_GridSizes.z, 0, lod)).z;
    44.                 //ht += tex2Dlod(_Map0, float4(worldPos.xz/_GridSizes.w, 0, lod)).w;
    45.  
    46.                 v.vertex.y += ht;
    47.          
    48.                 v2f OUT;
    49.                 OUT.worldPos = mul(_Object2World, v.vertex).xyz;
    50.                 OUT.pos = mul(UNITY_MATRIX_MVP, v.vertex);
    51.                 return OUT;
    52.             }
    53.          
    54.             float meanFresnel(float cosThetaV, float sigmaV)
    55.             {
    56.                 return pow(1.0 - cosThetaV, 5.0 * exp(-2.69 * sigmaV)) / (1.0 + 22.7 * pow(sigmaV, 1.5));
    57.             }
    58.          
    59.             // V, N in world space
    60.             float MeanFresnel(float3 V, float3 N, float2 sigmaSq)
    61.             {
    62.                 float2 v = V.xz; // view direction in wind space
    63.                 float2 t = v * v / (1.0 - V.y * V.y); // cos^2 and sin^2 of view direction
    64.                 float sigmaV2 = dot(t, sigmaSq); // slope variance in view direction
    65.                 return meanFresnel(dot(V, N), sqrt(sigmaV2));
    66.             }
    67.          
    68.             // assumes x>0
    69.             float erfc(float x)
    70.             {
    71.                 return 2.0 * exp(-x * x) / (2.319 * x + sqrt(4.0 + 1.52 * x * x));
    72.             }
    73.          
    74.             float Lambda(float cosTheta, float sigmaSq)
    75.             {
    76.                 float v = cosTheta / sqrt((1.0 - cosTheta * cosTheta) * (2.0 * sigmaSq));
    77.                 return max(0.0, (exp(-v * v) - v * sqrt(M_PI) * erfc(v)) / (2.0 * v * sqrt(M_PI)));
    78.                 //return (exp(-v * v)) / (2.0 * v * sqrt(M_PI)); // approximate, faster formula
    79.             }
    80.  
    81.             // L, V, N, Tx, Ty in world space
    82.             float ReflectedSunRadiance(float3 L, float3 V, float3 N, float3 Tx, float3 Ty, float2 sigmaSq)
    83.             {
    84.                 float3 H = normalize(L + V);
    85.                 float zetax = dot(H, Tx) / dot(H, N);
    86.                 float zetay = dot(H, Ty) / dot(H, N);
    87.          
    88.                 float zL = dot(L, N); // cos of source zenith angle
    89.                 float zV = dot(V, N); // cos of receiver zenith angle
    90.                 float zH = dot(H, N); // cos of facet normal zenith angle
    91.                 float zH2 = zH * zH;
    92.          
    93.                 float p = exp(-0.5 * (zetax * zetax / sigmaSq.x + zetay * zetay / sigmaSq.y)) / (2.0 * M_PI * sqrt(sigmaSq.x * sigmaSq.y));
    94.          
    95.                 float tanV = atan2(dot(V, Ty), dot(V, Tx));
    96.                 float cosV2 = 1.0 / (1.0 + tanV * tanV);
    97.                 float sigmaV2 = sigmaSq.x * cosV2 + sigmaSq.y * (1.0 - cosV2);
    98.          
    99.                 float tanL = atan2(dot(L, Ty), dot(L, Tx));
    100.                 float cosL2 = 1.0 / (1.0 + tanL * tanL);
    101.                 float sigmaL2 = sigmaSq.x * cosL2 + sigmaSq.y * (1.0 - cosL2);
    102.          
    103.                 float fresnel = 0.02 + 0.98 * pow(1.0 - dot(V, H), 5.0);
    104.          
    105.                 zL = max(zL, 0.01);
    106.                 zV = max(zV, 0.01);
    107.          
    108.                 return fresnel * p / ((1.0 + Lambda(zL, sigmaL2) + Lambda(zV, sigmaV2)) * zV * zH2 * zH2 * 4.0);
    109.  
    110.             }
    111.          
    112.             // V, N, Tx, Ty in world space
    113.             float2 U(float2 zeta, float3 V, float3 N, float3 Tx, float3 Ty)
    114.             {
    115.                 float3 f = normalize(float3(-zeta, 1.0)); // tangent space
    116.                 float3 F = f.x * Tx + f.y * Ty + f.z * N; // world space
    117.                 float3 R = 2.0 * dot(F, V) * F - V;
    118.                 return R.xz / (1.0 + R.y);
    119.             }
    120.          
    121.             // V, N, Tx, Ty in world space;
    122.             float3 MeanSkyRadiance(float3 V, float3 N, float3 Tx, float3 Ty, float2 sigmaSq)
    123.             {
    124.                 float4 result;
    125.          
    126.                 const float eps = 0.001;
    127.                 float2 u0 = U(float2(0,0), V, N, Tx, Ty);
    128.                 float2 dux = 2.0 * (U(float2(eps, 0.0), V, N, Tx, Ty) - u0) / eps * sqrt(sigmaSq.x);
    129.                 float2 duy = 2.0 * (U(float2(0.0, eps), V, N, Tx, Ty) - u0) / eps * sqrt(sigmaSq.y);
    130.          
    131.                 result = tex2D(_SkyMap, u0 * (0.5 / 1.1) + 0.5, dux * (0.5 / 1.1), duy * (0.5 / 1.1));
    132.  
    133.                 //if texture2DLod and texture2DGrad are not defined, you can use this (no filtering):
    134.                 //result = tex2D(_SkyMap, u0 * (0.5 / 1.1) + 0.5);
    135.          
    136.                 return result.rgb;
    137.             }
    138.          
    139.             float4 frag(v2f IN) : COLOR
    140.             {
    141.  
    142.                 float2 uv = IN.worldPos.xz;
    143.              
    144.                 float2 slope = float2(0,0);
    145.                 slope += tex2D(_Map1, uv/_GridSizes.x).xy;
    146.                 slope += tex2D(_Map1, uv/_GridSizes.y).zw;
    147.                 slope += tex2D(_Map2, uv/_GridSizes.z).xy;
    148.                 slope += tex2D(_Map2, uv/_GridSizes.w).zw;
    149.          
    150.                 float3 V = normalize(_WorldSpaceCameraPos-IN.worldPos);
    151.  
    152.                 float3 N = normalize(float3(-slope.x, 1.0, -slope.y));
    153.  
    154.                 if (dot(V, N) < 0.0) {
    155.                    N = reflect(N, V); // reflects backfacing normals
    156.                 }
    157.              
    158.              
    159.                 float Jxx = ddx(uv.x);
    160.                 float Jxy = ddy(uv.x);
    161.                 float Jyx = ddx(uv.y);
    162.                 float Jyy = ddy(uv.y);
    163.                 float A = Jxx * Jxx + Jyx * Jyx;
    164.                 float B = Jxx * Jxy + Jyx * Jyy;
    165.                 float C = Jxy * Jxy + Jyy * Jyy;
    166.                 const float SCALE = 10.0;
    167.                 float ua = pow(A / SCALE, 0.25);
    168.                 float ub = 0.5 + 0.5 * B / sqrt(A * C);
    169.                 float uc = pow(C / SCALE, 0.25);
    170.                 float2 sigmaSq = tex3D(_Variance, float3(ua, ub, uc)).xy * _VarianceMax;
    171.          
    172.                 sigmaSq = max(sigmaSq, 2e-5);
    173.              
    174.                 float3 Ty = normalize(float3(0.0, N.z, -N.y));
    175.                 float3 Tx = cross(Ty, N);
    176.              
    177.                 float fresnel = 0.02 + 0.98 * MeanFresnel(V, N, sigmaSq);
    178.              
    179.                 float3 Lsun = SunRadiance(_WorldSpaceCameraPos);
    180.                 float3 Esky = SkyIrradiance(_WorldSpaceCameraPos);
    181.              
    182.                 float3 col = float3(0,0,0);
    183.              
    184.                 col += ReflectedSunRadiance(SUN_DIR, V, N, Tx, Ty, sigmaSq) * Lsun;
    185.              
    186.                 col += MeanSkyRadiance(V, N, Tx, Ty, sigmaSq) * fresnel;
    187.              
    188.                 float3 Lsea = _SeaColor * Esky / M_PI;
    189.                 col +=  Lsea * (1.0 - fresnel);
    190.  
    191.                 return float4(hdr(col),1.0);
    192.             }
    193.          
    194.             ENDCG
    195.  
    196.         }
    197.     }
    198. }
    Can someone please point this is the right direction?
    i have seen a depth calculation like:

    Code (CSharp):
    1.  
    2. void vert (inout appdata_full v, out Input o) {
    3.             o.worldPos = v.vertex.xyz;
    4.             o.position = mul(UNITY_MATRIX_MVP, v.vertex);
    5.             o.proj0 = ComputeScreenPos(o.position);
    6.             COMPUTE_EYEDEPTH(o.proj0.z);
    7.      
    8.             o.proj1 = o.proj0;
    9.             #if UNITY_UV_STARTS_AT_TOP
    10.             o.proj1.y = (o.position.w - o.position.y) * 0.5;
    11.             #endif
    12.          
    13. }
    14.  
    and then in the surface function

    Code (CSharp):
    1.  
    2. // Calculate the depth difference at the current pixel
    3.             half4 projTC = UNITY_PROJ_COORD(i.proj0);
    4.             half depth = tex2Dproj(_CameraDepthTexture, projTC).r;
    5.             depth = LinearEyeDepth(depth);
    6.             depth -= i.proj0.z;
    7.      
    8. // Calculate the depth ranges (X = Alpha, Y = Color Depth)
    9.             half3 ranges = saturate(_InvRanges.xyz * depth);
    10.             ranges.y = 1.0 - ranges.y;
    11.             ranges.y = lerp(ranges.y, ranges.y * ranges.y * ranges.y, 0.5);
    12.      
    13. // Calculate the color tint
    14.             half4 col;
    15.             col.rgb = lerp(_Color1.rgb, _Color0.rgb, ranges.y);
    16.             col.a = ranges.x;
    17.  
    i think main pass should be that, depth color as a range from light blue to dark blue, and blend the BRDF lighting over it


    for refraction my guess is something like the Glass Refraction from Standard Assets Pro
    but i am having trouble converting the wave normals to the bumpmap texture input.

    currently usign GrabPass to get transparency, idealy this base empty pass should have the color depth, thats prett much it, hope you guys chip in to develop this.

    Let me know if you need pointers and help on the underwater effects part.
    Clazzid
     
  2. Clazzid

    Clazzid

    Joined:
    Jun 15, 2014
    Posts:
    5
    managed to distort the grabpass adding in the wave normals, and tint it by depth using above function, and set that as main color, adding in the BRDF light over that, the code is a monster mess right now since im learning from decompiled surface shaders, but is working, half way there, now i need some foam on the edges to hide the horrible blend, ill post some images if anyone pops up on this
     
    Last edited: Jun 17, 2014
  3. metaleap

    metaleap

    Joined:
    Oct 3, 2012
    Posts:
    589
    Go right ahead, screenshots are I think always appreciated in this forum ;)
     
  4. WhoRainZone1

    WhoRainZone1

    Joined:
    Oct 6, 2013
    Posts:
    15
    I'm also interrested, any progress so far? Been fiddling with that water for a little now too, pretty impressive!
     
  5. kideternal

    kideternal

    Joined:
    Nov 20, 2014
    Posts:
    82
    I'm interested in this as well. Any chance you could post the work you've done thus far for us to tinker with? We may be able to clean it up a bit and take it further. (Shader techniques like this are a little over my head as well, but I'm learning, and am not the only one here.)

    I'm particularly interested in the "semi transparent color range by depth" part and how that relates to changing viewing-angles. (Looking straight down should be more transparent than at a 45-degree angle.)
     
  6. Zicandar

    Zicandar

    Joined:
    Feb 10, 2014
    Posts:
    388
    For the reflections you most likely want to use reflection probes (cubemap).
    For the depth as you seemed to have solved already you would generally use the built in depth buffer. (It would give depth from surface to bottom as if a ray was traced from the camera.
    For this I think the alpha should probably be reduced by the specular. (I think this is common?) As that would cause it to loose transparency when you get strong cubemap reflections.
    While this would be really awesome, the white caps generally only happen on water when waves are "sharp" and not nice and slow rolling. The most common white is when the water gets to shallow, and it rolls in over something. (This should be possible and not to hard to do?)

    I would assume it'd be possible to have the wave height calculations running for a few game objects on the CPU side for those points?
     
  7. Clazzid

    Clazzid

    Joined:
    Jun 15, 2014
    Posts:
    5
    Hey guys, ive been working on other things, but ill be back to this post in a few days, maybe we can take it further, i am currently using the skymap provided with usky plugin, which is awesome, but there is still a few formulas i need to figure out. ill be back within the week
     
    odival likes this.
  8. kideternal

    kideternal

    Joined:
    Nov 20, 2014
    Posts:
    82
    Hey Clazzid, have you had a chance to work on this more yet? If you want to just zip what you have up and post a link we can at least try to carry the ball forward a bit more...