97ac0f71f5
https://assetstore.unity.com/packages/tools/generative-ai/synaptic-ai-pro-natural-language-control-for-unity-336030
1691 lines
78 KiB
GLSL
1691 lines
78 KiB
GLSL
Shader "Synaptic/WaterPro"
|
|
{
|
|
Properties
|
|
{
|
|
[Header(Color and Depth)]
|
|
_ShallowColor ("Shallow Color", Color) = (0.3, 0.85, 0.92, 0.4)
|
|
_DeepColor ("Deep Color", Color) = (0.02, 0.08, 0.25, 0.95)
|
|
_HorizonColor ("Horizon Color", Color) = (0.6, 0.85, 1.0, 1.0)
|
|
_DepthMaxDistance ("Depth Distance", Float) = 10.0
|
|
_DepthStrength ("Depth Strength", Range(0, 1)) = 0.8
|
|
|
|
[Header(Transparency and Absorption)]
|
|
_AbsorptionColor ("Absorption Color", Color) = (0.2, 0.5, 0.4, 1)
|
|
_AbsorptionStrength ("Absorption Strength", Range(0, 2)) = 0.5
|
|
_ClarityDistance ("Clarity Distance", Float) = 5.0
|
|
_UnderwaterFogDensity ("Underwater Fog Density", Range(0, 1)) = 0.15
|
|
_TransparencyDepth ("Transparency Depth", Float) = 3.0
|
|
|
|
[Header(Ocean Waves)]
|
|
_OceanScale ("Ocean Scale", Float) = 1.0
|
|
_WaveSpeed ("Wave Speed", Float) = 1.0
|
|
_WaveHeight ("Wave Height", Float) = 1.0
|
|
_WindDirection ("Wind Direction", Vector) = (1, 0, 0.3, 0)
|
|
_Choppiness ("Choppiness", Range(0, 2)) = 1.0
|
|
|
|
[Header(Large Swells)]
|
|
_WaveA ("Wave A (dir.xy, steepness, wavelength)", Vector) = (0.85, 0.5, 0.12, 60)
|
|
_WaveB ("Wave B (dir.xy, steepness, wavelength)", Vector) = (0.5, 0.85, 0.10, 40)
|
|
|
|
[Header(Medium Waves)]
|
|
_WaveC ("Wave C (dir.xy, steepness, wavelength)", Vector) = (0.95, 0.3, 0.08, 20)
|
|
_WaveD ("Wave D (dir.xy, steepness, wavelength)", Vector) = (0.35, 0.93, 0.07, 14)
|
|
_WaveE ("Wave E (dir.xy, steepness, wavelength)", Vector) = (0.7, 0.7, 0.06, 10)
|
|
|
|
[Header(Small Detail)]
|
|
_WaveF ("Wave F (dir.xy, steepness, wavelength)", Vector) = (0.88, -0.45, 0.05, 6)
|
|
_WaveG ("Wave G (dir.xy, steepness, wavelength)", Vector) = (-0.55, 0.82, 0.04, 4)
|
|
_WaveH ("Wave H (dir.xy, steepness, wavelength)", Vector) = (0.4, -0.85, 0.03, 2.5)
|
|
|
|
[Header(Extra Small Waves)]
|
|
_WaveI ("Wave I (dir.xy, steepness, wavelength)", Vector) = (0.92, 0.38, 0.025, 1.8)
|
|
_WaveJ ("Wave J (dir.xy, steepness, wavelength)", Vector) = (-0.42, 0.9, 0.02, 1.2)
|
|
_WaveK ("Wave K (dir.xy, steepness, wavelength)", Vector) = (0.65, -0.75, 0.018, 0.8)
|
|
_WaveL ("Wave L (dir.xy, steepness, wavelength)", Vector) = (-0.78, -0.62, 0.015, 0.5)
|
|
|
|
[Header(Micro Detail)]
|
|
_MicroWaveScale ("Micro Wave Scale", Float) = 40.0
|
|
_MicroWaveStrength ("Micro Wave Strength", Range(0, 0.5)) = 0.15
|
|
|
|
[Header(Normal Maps)]
|
|
_NormalMap1 ("Normal Map 1", 2D) = "bump" {}
|
|
_NormalMap2 ("Normal Map 2", 2D) = "bump" {}
|
|
_BumpMap ("Normal Map (Built-in)", 2D) = "bump" {}
|
|
_NormalStrength ("Normal Strength", Range(0, 2)) = 1.0
|
|
_NormalScale1 ("Normal Scale 1", Float) = 0.08
|
|
_NormalScale2 ("Normal Scale 2", Float) = 0.25
|
|
_NormalSpeed1 ("Normal Speed 1", Vector) = (0.03, 0.02, 0, 0)
|
|
_NormalSpeed2 ("Normal Speed 2", Vector) = (-0.025, 0.03, 0, 0)
|
|
|
|
[Header(Surface Ripples)]
|
|
_RippleScale1 ("Ripple Scale 1 (Fine)", Float) = 0.8
|
|
_RippleScale2 ("Ripple Scale 2 (Micro)", Float) = 2.0
|
|
_RippleScale3 ("Ripple Scale 3 (Ultra)", Float) = 4.0
|
|
_RippleStrength ("Ripple Strength", Range(0, 1)) = 0.1
|
|
_RippleSpeed ("Ripple Speed", Float) = 0.8
|
|
|
|
[Header(Flow Map)]
|
|
_FlowMap ("Flow Map", 2D) = "grey" {}
|
|
_FlowStrength ("Flow Strength", Range(0, 1)) = 0.3
|
|
_FlowSpeed ("Flow Speed", Float) = 0.4
|
|
|
|
[Header(Reflection)]
|
|
[Toggle(_SSR_ON)] _SSREnabled ("Enable SSR", Float) = 1
|
|
_ReflectionStrength ("Reflection Strength", Range(0, 1)) = 0.5
|
|
_SSRSteps ("SSR Steps", Range(8, 64)) = 32
|
|
_SSRStepSize ("SSR Step Size", Range(0.01, 0.5)) = 0.1
|
|
_SSRThickness ("SSR Thickness", Range(0.01, 1)) = 0.2
|
|
_CubeMap ("Cubemap Fallback", CUBE) = "" {}
|
|
|
|
[Header(Refraction)]
|
|
_RefractionStrength ("Refraction Strength", Range(0, 0.5)) = 0.15
|
|
_ChromaticAberration ("Chromatic Aberration", Range(0, 0.1)) = 0.02
|
|
|
|
[Header(Fresnel)]
|
|
_FresnelPower ("Fresnel Power", Range(1, 10)) = 4.0
|
|
_FresnelBias ("Fresnel Bias", Range(0, 1)) = 0.02
|
|
|
|
[Header(Subsurface Scattering)]
|
|
[Toggle(_SSS_ON)] _SSSEnabled ("Enable SSS", Float) = 1
|
|
_SSSColor ("SSS Color", Color) = (0.2, 0.9, 0.6, 1)
|
|
_SSSStrength ("SSS Strength", Range(0, 2)) = 0.8
|
|
_SSSDistortion ("SSS Distortion", Range(0, 1)) = 0.5
|
|
_SSSPower ("SSS Power", Range(1, 16)) = 4
|
|
|
|
[Header(Foam)]
|
|
_FoamTexture ("Foam Texture", 2D) = "gray" {}
|
|
_FoamColor ("Foam Color", Color) = (1, 1, 1, 1)
|
|
_FoamDistance ("Shore Foam Distance", Float) = 1.5
|
|
_FoamNoiseScale ("Foam Noise Scale", Float) = 8
|
|
_FoamSpeed ("Foam Speed", Float) = 0.3
|
|
_FoamSharpness ("Foam Sharpness", Range(0, 10)) = 1.5
|
|
[Toggle(_WAVE_FOAM_ON)] _WaveFoamEnabled ("Wave Crest Foam", Float) = 0
|
|
_WaveFoamThreshold ("Wave Foam Threshold", Range(0, 1)) = 0.75
|
|
_WaveFoamSoftness ("Wave Foam Softness", Range(0.01, 1)) = 0.15
|
|
|
|
[Header(Caustics)]
|
|
[Toggle(_CAUSTICS_ON)] _CausticsEnabled ("Enable Caustics", Float) = 1
|
|
_CausticsTexture ("Caustics Texture", 2D) = "white" {}
|
|
_CausticsStrength ("Caustics Strength", Range(0, 10)) = 3.5
|
|
_CausticsScale ("Caustics Scale", Float) = 0.25
|
|
_CausticsSpeed ("Caustics Speed", Float) = 0.6
|
|
_CausticsDepth ("Caustics Max Depth", Float) = 8
|
|
_CausticsDistortion ("Caustics Distortion", Range(0, 1)) = 0.4
|
|
_CausticsSplit ("Caustics RGB Split", Range(0, 0.1)) = 0.025
|
|
|
|
[Header(Underwater Light Rays)]
|
|
_UnderwaterRaysStrength ("Light Rays Strength", Range(0, 3)) = 1.0
|
|
_UnderwaterRaysScale ("Light Rays Scale", Float) = 0.15
|
|
_UnderwaterRaysSpeed ("Light Rays Speed", Float) = 0.3
|
|
_UnderwaterRaysFalloff ("Light Rays Depth Falloff", Float) = 15.0
|
|
|
|
[Header(Specular)]
|
|
_SpecularColor ("Specular Color", Color) = (1, 1, 1, 1)
|
|
_Smoothness ("Smoothness", Range(0, 1)) = 0.92
|
|
_SpecularIntensity ("Specular Intensity", Range(0, 10)) = 1.5
|
|
|
|
[Header(Sun Reflection)]
|
|
_SunDiskSize ("Sun Disk Size", Range(0.001, 0.1)) = 0.015
|
|
_SunDiskIntensity ("Sun Disk Intensity", Range(0, 50)) = 10.0
|
|
_SunDiskSharpness ("Sun Disk Sharpness", Range(1, 100)) = 20.0
|
|
|
|
[Header(Anisotropic Highlights)]
|
|
_AnisotropyStrength ("Anisotropy Strength", Range(0, 1)) = 0.6
|
|
_AnisotropyDirection ("Anisotropy Direction", Range(0, 1)) = 0.0
|
|
|
|
[Header(Micro Facet Glitter)]
|
|
_MicroFacetScale ("Micro Facet Scale", Float) = 150
|
|
_MicroFacetIntensity ("Micro Facet Intensity", Range(0, 5)) = 0.5
|
|
_MicroFacetThreshold ("Micro Facet Threshold", Range(0.9, 1.0)) = 0.96
|
|
|
|
[Header(Tessellation)]
|
|
[Toggle(_TESSELLATION_ON)] _TessEnabled ("Enable Tessellation", Float) = 1
|
|
_TessellationFactor ("Tessellation Factor", Range(1, 64)) = 16
|
|
_TessellationMinDist ("Tessellation Min Dist", Float) = 2
|
|
_TessellationMaxDist ("Tessellation Max Dist", Float) = 80
|
|
}
|
|
|
|
// ==================== URP SubShader ====================
|
|
SubShader
|
|
{
|
|
PackageRequirements { "com.unity.render-pipelines.universal" }
|
|
Tags
|
|
{
|
|
"RenderType" = "Transparent"
|
|
"Queue" = "Transparent"
|
|
"RenderPipeline" = "UniversalPipeline"
|
|
}
|
|
|
|
Pass
|
|
{
|
|
Name "WaterProURP"
|
|
Tags { "LightMode" = "UniversalForward" }
|
|
|
|
Blend SrcAlpha OneMinusSrcAlpha
|
|
ZWrite Off
|
|
Cull Back
|
|
|
|
HLSLPROGRAM
|
|
#pragma target 4.6
|
|
#pragma vertex vert
|
|
#pragma fragment frag
|
|
#pragma hull hull
|
|
#pragma domain domain
|
|
|
|
#pragma multi_compile_fog
|
|
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE
|
|
#pragma multi_compile_fragment _ _SHADOWS_SOFT
|
|
#pragma multi_compile _ _ADDITIONAL_LIGHTS
|
|
|
|
#pragma shader_feature_local _SSR_ON
|
|
#pragma shader_feature_local _SSS_ON
|
|
#pragma shader_feature_local _CAUSTICS_ON
|
|
#pragma shader_feature_local _WAVE_FOAM_ON
|
|
#pragma shader_feature_local _TESSELLATION_ON
|
|
|
|
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareOpaqueTexture.hlsl"
|
|
|
|
TEXTURE2D(_NormalMap1);
|
|
TEXTURE2D(_NormalMap2);
|
|
TEXTURE2D(_FlowMap);
|
|
TEXTURE2D(_FoamTexture);
|
|
TEXTURE2D(_CausticsTexture);
|
|
TEXTURECUBE(_CubeMap);
|
|
|
|
SAMPLER(sampler_NormalMap1);
|
|
SAMPLER(sampler_NormalMap2);
|
|
SAMPLER(sampler_FlowMap);
|
|
SAMPLER(sampler_FoamTexture);
|
|
SAMPLER(sampler_CausticsTexture);
|
|
SAMPLER(sampler_CubeMap);
|
|
|
|
CBUFFER_START(UnityPerMaterial)
|
|
float4 _ShallowColor;
|
|
float4 _DeepColor;
|
|
float4 _HorizonColor;
|
|
float _DepthMaxDistance;
|
|
float _DepthStrength;
|
|
float4 _AbsorptionColor;
|
|
float _AbsorptionStrength;
|
|
float _ClarityDistance;
|
|
float _UnderwaterFogDensity;
|
|
float _TransparencyDepth;
|
|
float _OceanScale;
|
|
float _WaveSpeed;
|
|
float _WaveHeight;
|
|
float4 _WindDirection;
|
|
float _Choppiness;
|
|
float4 _WaveA;
|
|
float4 _WaveB;
|
|
float4 _WaveC;
|
|
float4 _WaveD;
|
|
float4 _WaveE;
|
|
float4 _WaveF;
|
|
float4 _WaveG;
|
|
float4 _WaveH;
|
|
float4 _WaveI;
|
|
float4 _WaveJ;
|
|
float4 _WaveK;
|
|
float4 _WaveL;
|
|
float _MicroWaveScale;
|
|
float _MicroWaveStrength;
|
|
float4 _NormalMap1_ST;
|
|
float4 _NormalMap2_ST;
|
|
float _NormalStrength;
|
|
float _NormalScale1;
|
|
float _NormalScale2;
|
|
float4 _NormalSpeed1;
|
|
float4 _NormalSpeed2;
|
|
float _RippleScale1;
|
|
float _RippleScale2;
|
|
float _RippleScale3;
|
|
float _RippleStrength;
|
|
float _RippleSpeed;
|
|
float4 _FlowMap_ST;
|
|
float _FlowStrength;
|
|
float _FlowSpeed;
|
|
float _ReflectionStrength;
|
|
float _SSRSteps;
|
|
float _SSRStepSize;
|
|
float _SSRThickness;
|
|
float _RefractionStrength;
|
|
float _ChromaticAberration;
|
|
float _FresnelPower;
|
|
float _FresnelBias;
|
|
float4 _SSSColor;
|
|
float _SSSStrength;
|
|
float _SSSDistortion;
|
|
float _SSSPower;
|
|
float4 _FoamTexture_ST;
|
|
float4 _FoamColor;
|
|
float _FoamDistance;
|
|
float _FoamNoiseScale;
|
|
float _FoamSpeed;
|
|
float _FoamSharpness;
|
|
float _WaveFoamThreshold;
|
|
float4 _CausticsTexture_ST;
|
|
float _CausticsStrength;
|
|
float _CausticsScale;
|
|
float _CausticsSpeed;
|
|
float _CausticsDepth;
|
|
float _CausticsDistortion;
|
|
float _CausticsSplit;
|
|
float _UnderwaterRaysStrength;
|
|
float _UnderwaterRaysScale;
|
|
float _UnderwaterRaysSpeed;
|
|
float _UnderwaterRaysFalloff;
|
|
float4 _SpecularColor;
|
|
float _Smoothness;
|
|
float _SpecularIntensity;
|
|
float _SunDiskSize;
|
|
float _SunDiskIntensity;
|
|
float _SunDiskSharpness;
|
|
float _AnisotropyStrength;
|
|
float _AnisotropyDirection;
|
|
float _MicroFacetScale;
|
|
float _MicroFacetIntensity;
|
|
float _MicroFacetThreshold;
|
|
float _WaveFoamSoftness;
|
|
float _TessellationFactor;
|
|
float _TessellationMinDist;
|
|
float _TessellationMaxDist;
|
|
CBUFFER_END
|
|
|
|
struct Attributes
|
|
{
|
|
float4 positionOS : POSITION;
|
|
float3 normalOS : NORMAL;
|
|
float4 tangentOS : TANGENT;
|
|
float2 uv : TEXCOORD0;
|
|
};
|
|
|
|
struct TessellationControlPoint
|
|
{
|
|
float4 positionOS : INTERNALTESSPOS;
|
|
float3 normalOS : NORMAL;
|
|
float4 tangentOS : TANGENT;
|
|
float2 uv : TEXCOORD0;
|
|
};
|
|
|
|
struct TessellationFactors
|
|
{
|
|
float edge[3] : SV_TessFactor;
|
|
float inside : SV_InsideTessFactor;
|
|
};
|
|
|
|
struct Varyings
|
|
{
|
|
float4 positionCS : SV_POSITION;
|
|
float2 uv : TEXCOORD0;
|
|
float3 worldPos : TEXCOORD1;
|
|
float3 worldNormal : TEXCOORD2;
|
|
float3 worldTangent : TEXCOORD3;
|
|
float3 worldBitangent : TEXCOORD4;
|
|
float4 screenPos : TEXCOORD5;
|
|
float3 viewDir : TEXCOORD6;
|
|
float fogFactor : TEXCOORD7;
|
|
float waveHeight : TEXCOORD8;
|
|
};
|
|
|
|
float3 GerstnerWave(float4 wave, float3 p, inout float3 tangent, inout float3 binormal, float time)
|
|
{
|
|
float steepness = wave.z;
|
|
float wavelength = wave.w;
|
|
float k = 2.0 * PI / wavelength;
|
|
float c = sqrt(9.8 / k);
|
|
float2 d = normalize(wave.xy);
|
|
float f = k * (dot(d, p.xz) - c * time);
|
|
float a = steepness / k;
|
|
|
|
tangent += float3(
|
|
-d.x * d.x * steepness * sin(f),
|
|
d.x * steepness * cos(f),
|
|
-d.x * d.y * steepness * sin(f)
|
|
);
|
|
binormal += float3(
|
|
-d.x * d.y * steepness * sin(f),
|
|
d.y * steepness * cos(f),
|
|
-d.y * d.y * steepness * sin(f)
|
|
);
|
|
|
|
return float3(d.x * a * cos(f), a * sin(f), d.y * a * cos(f));
|
|
}
|
|
|
|
// Simple noise for micro detail
|
|
float SimpleNoise(float2 uv)
|
|
{
|
|
return frac(sin(dot(uv, float2(12.9898, 78.233))) * 43758.5453);
|
|
}
|
|
|
|
float SmoothNoise(float2 uv)
|
|
{
|
|
float2 i = floor(uv);
|
|
float2 f = frac(uv);
|
|
f = f * f * (3.0 - 2.0 * f);
|
|
|
|
float a = SimpleNoise(i);
|
|
float b = SimpleNoise(i + float2(1, 0));
|
|
float c = SimpleNoise(i + float2(0, 1));
|
|
float d = SimpleNoise(i + float2(1, 1));
|
|
|
|
return lerp(lerp(a, b, f.x), lerp(c, d, f.x), f.y);
|
|
}
|
|
|
|
float FBMNoise(float2 uv, float t)
|
|
{
|
|
float value = 0;
|
|
float amp = 0.5;
|
|
float2 animUV = uv;
|
|
|
|
[unroll(4)]
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
animUV += t * 0.1 * float(i + 1);
|
|
value += amp * SmoothNoise(animUV);
|
|
animUV *= 2.0;
|
|
amp *= 0.5;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
// ========== PROCEDURAL SURFACE RIPPLES (constant micro-movement) ==========
|
|
// Creates endless small ripples that make water surface feel alive
|
|
// Uses multiple overlapping sine waves at different scales - NO textures needed
|
|
// Hash function for pseudo-random noise (no texture needed)
|
|
float hash21(float2 p)
|
|
{
|
|
p = frac(p * float2(234.34, 435.345));
|
|
p += dot(p, p + 34.23);
|
|
return frac(p.x * p.y);
|
|
}
|
|
|
|
float2 hash22(float2 p)
|
|
{
|
|
float3 p3 = frac(float3(p.xyx) * float3(0.1031, 0.1030, 0.0973));
|
|
p3 += dot(p3, p3.yzx + 33.33);
|
|
return frac((p3.xx + p3.yz) * p3.zy);
|
|
}
|
|
|
|
// Smooth noise interpolation
|
|
float valueNoise(float2 p)
|
|
{
|
|
float2 i = floor(p);
|
|
float2 f = frac(p);
|
|
f = f * f * (3.0 - 2.0 * f); // Smoothstep
|
|
|
|
float a = hash21(i);
|
|
float b = hash21(i + float2(1, 0));
|
|
float c = hash21(i + float2(0, 1));
|
|
float d = hash21(i + float2(1, 1));
|
|
|
|
return lerp(lerp(a, b, f.x), lerp(c, d, f.x), f.y);
|
|
}
|
|
|
|
// Rotate UV to break grid alignment
|
|
float2 RotateUV(float2 uv, float angle)
|
|
{
|
|
float s = sin(angle);
|
|
float c = cos(angle);
|
|
return float2(uv.x * c - uv.y * s, uv.x * s + uv.y * c);
|
|
}
|
|
|
|
float3 CalculateSurfaceRipples(float2 worldXZ, float time)
|
|
{
|
|
float rippleTime = time * _RippleSpeed;
|
|
|
|
// Multiple rotated UV layers to completely break grid patterns
|
|
float2 uv1 = RotateUV(worldXZ * _RippleScale1, 0.0);
|
|
float2 uv2 = RotateUV(worldXZ * _RippleScale1, 1.047); // 60 degrees
|
|
float2 uv3 = RotateUV(worldXZ * _RippleScale1, 2.094); // 120 degrees
|
|
|
|
float2 uv4 = RotateUV(worldXZ * _RippleScale2, 0.524); // 30 degrees
|
|
float2 uv5 = RotateUV(worldXZ * _RippleScale2, 1.571); // 90 degrees
|
|
float2 uv6 = RotateUV(worldXZ * _RippleScale2, 2.618); // 150 degrees
|
|
|
|
float2 uv7 = RotateUV(worldXZ * _RippleScale3, 0.785); // 45 degrees
|
|
float2 uv8 = RotateUV(worldXZ * _RippleScale3, 2.356); // 135 degrees
|
|
|
|
// Noise for additional randomization
|
|
float n1 = valueNoise(worldXZ * 0.3 + rippleTime * 0.05);
|
|
float n2 = valueNoise(worldXZ * 0.7 - rippleTime * 0.07);
|
|
|
|
// Layer 1: Three 60-degree rotated waves (no grid alignment possible)
|
|
float w1 = sin(uv1.x * 4.0 + rippleTime * 1.1 + n1 * 3.0);
|
|
float w2 = sin(uv2.x * 4.0 + rippleTime * 0.9 + n1 * 2.5);
|
|
float w3 = sin(uv3.x * 4.0 + rippleTime * 1.3 + n2 * 2.0);
|
|
|
|
// Layer 2: Three more rotated waves at different angles
|
|
float w4 = sin(uv4.x * 6.0 + rippleTime * 1.5 + n2 * 4.0);
|
|
float w5 = sin(uv5.x * 6.0 - rippleTime * 1.2 + n1 * 3.5);
|
|
float w6 = sin(uv6.x * 6.0 + rippleTime * 1.7 + n2 * 3.0);
|
|
|
|
// Layer 3: Fine detail
|
|
float w7 = sin(uv7.x * 10.0 + rippleTime * 2.0 + n1 * 5.0);
|
|
float w8 = sin(uv8.x * 10.0 - rippleTime * 1.8 + n2 * 4.5);
|
|
|
|
// Combine - each direction contributes to both dx and dz due to rotation
|
|
float layer1 = (w1 + w2 + w3) / 3.0;
|
|
float layer2 = (w4 + w5 + w6) / 3.0;
|
|
float layer3 = (w7 + w8) / 2.0;
|
|
|
|
// Direct noise contribution
|
|
float directNoise = (valueNoise(worldXZ * _RippleScale2 + rippleTime) - 0.5) * 2.0;
|
|
|
|
// Calculate gradient from rotated waves
|
|
float dx = layer1 * 0.5 * cos(0.0) + layer1 * 0.5 * cos(1.047) + layer1 * 0.5 * cos(2.094)
|
|
+ layer2 * 0.35 * cos(0.524) + layer2 * 0.35 * cos(1.571)
|
|
+ layer3 * 0.2 * cos(0.785)
|
|
+ directNoise * 0.2;
|
|
|
|
float dz = layer1 * 0.5 * sin(0.0) + layer1 * 0.5 * sin(1.047) + layer1 * 0.5 * sin(2.094)
|
|
+ layer2 * 0.35 * sin(0.524) + layer2 * 0.35 * sin(1.571)
|
|
+ layer3 * 0.2 * sin(0.785)
|
|
+ directNoise * 0.15;
|
|
|
|
float3 rippleNormal = float3(0, 1, 0);
|
|
rippleNormal.x = dx * _RippleStrength * 0.12;
|
|
rippleNormal.z = dz * _RippleStrength * 0.12;
|
|
return normalize(rippleNormal);
|
|
}
|
|
|
|
// ========== PHYSICS-BASED WATER SPECULAR (Genshin-style) ==========
|
|
|
|
// GGX/Trowbridge-Reitz Normal Distribution Function
|
|
float GGX_D(float NdotH, float roughness)
|
|
{
|
|
float a2 = roughness * roughness;
|
|
float d = NdotH * NdotH * (a2 - 1.0) + 1.0;
|
|
return a2 / (PI * d * d + 0.0001);
|
|
}
|
|
|
|
// Schlick's Fresnel approximation
|
|
float3 FresnelSchlick(float cosTheta, float3 F0)
|
|
{
|
|
return F0 + (1.0 - F0) * pow(saturate(1.0 - cosTheta), 5.0);
|
|
}
|
|
|
|
// Smith's Geometry function for GGX
|
|
float Smith_G1(float NdotV, float roughness)
|
|
{
|
|
float k = roughness * roughness * 0.5;
|
|
return NdotV / (NdotV * (1.0 - k) + k + 0.0001);
|
|
}
|
|
|
|
float Smith_G(float NdotV, float NdotL, float roughness)
|
|
{
|
|
return Smith_G1(NdotV, roughness) * Smith_G1(NdotL, roughness);
|
|
}
|
|
|
|
// Anisotropic GGX for wave-direction stretched highlights (like Genshin)
|
|
float AnisotropicGGX(float NdotH, float TdotH, float BdotH, float roughnessT, float roughnessB)
|
|
{
|
|
float d = (TdotH * TdotH) / (roughnessT * roughnessT + 0.0001)
|
|
+ (BdotH * BdotH) / (roughnessB * roughnessB + 0.0001)
|
|
+ NdotH * NdotH;
|
|
return 1.0 / (PI * roughnessT * roughnessB * d * d + 0.0001);
|
|
}
|
|
|
|
// Sun disk reflection - visible sun reflection on wave peaks
|
|
float SunDiskReflection(float3 reflectDir, float3 lightDir, float diskSize, float sharpness)
|
|
{
|
|
float cosAngle = dot(reflectDir, lightDir);
|
|
// Create sharp disk with soft edge
|
|
float disk = saturate((cosAngle - (1.0 - diskSize)) / diskSize);
|
|
return pow(disk, sharpness);
|
|
}
|
|
|
|
// Micro-facet glitter - based on actual wave normal perturbation (not noise pattern)
|
|
// Returns intensity only - caller multiplies by light color
|
|
float MicroFacetGlitter(float3 normal, float3 viewDir, float3 lightDir, float waveHeight)
|
|
{
|
|
// Use wave height derivative as micro-normal perturbation source
|
|
// Higher waves = more micro-facet variation
|
|
float waveFactor = saturate(abs(waveHeight) * 3.0);
|
|
|
|
// Calculate specular based on perturbed normal
|
|
float3 H = normalize(lightDir + viewDir);
|
|
float NdotH = saturate(dot(normal, H));
|
|
|
|
// Glitter appears where reflection is nearly perfect
|
|
// Wave peaks get more glitter due to steeper micro-facets
|
|
float glitter = smoothstep(_MicroFacetThreshold, 1.0, NdotH);
|
|
glitter *= (1.0 + waveFactor * 0.5); // Boost on wave peaks
|
|
|
|
return glitter * _MicroFacetIntensity;
|
|
}
|
|
|
|
// Complete wave-based specular calculation (Genshin-style)
|
|
float3 CalculateWaterSpecular(
|
|
float3 worldNormal,
|
|
float3 viewDir,
|
|
float3 lightDir,
|
|
float3 lightColor,
|
|
float3 tangent,
|
|
float3 bitangent,
|
|
float2 worldXZ,
|
|
float waveHeight,
|
|
float time)
|
|
{
|
|
float3 H = normalize(lightDir + viewDir);
|
|
|
|
float NdotH = saturate(dot(worldNormal, H));
|
|
float NdotV = saturate(dot(worldNormal, viewDir));
|
|
float NdotL = saturate(dot(worldNormal, lightDir));
|
|
float VdotH = saturate(dot(viewDir, H));
|
|
float TdotH = dot(tangent, H);
|
|
float BdotH = dot(bitangent, H);
|
|
|
|
float roughness = 1.0 - _Smoothness;
|
|
roughness = max(roughness, 0.08); // Minimum roughness for wider spread
|
|
|
|
// === 1. Base GGX Specular ===
|
|
float D = GGX_D(NdotH, roughness);
|
|
float G = Smith_G(NdotV, NdotL, roughness);
|
|
float3 F0 = float3(0.02, 0.02, 0.02); // Water IOR ~1.33
|
|
float3 F = FresnelSchlick(VdotH, F0);
|
|
|
|
float3 specBRDF = (D * G * F) / (4.0 * NdotV * NdotL + 0.0001);
|
|
float3 baseSpecular = specBRDF * NdotL * _SpecularIntensity;
|
|
|
|
// === 2. Anisotropic Highlights (wave-direction stretch) ===
|
|
// Stretch highlights perpendicular to wave direction (tangent)
|
|
// High aniso = long horizontal streaks like real water
|
|
float anisoT = roughness * (1.0 + _AnisotropyStrength * 3.0); // Much wider in tangent direction
|
|
float anisoB = roughness * max(0.05, 1.0 - _AnisotropyStrength * 0.8); // Tighter in binormal
|
|
float anisoD = AnisotropicGGX(NdotH, TdotH, BdotH, anisoT, anisoB);
|
|
// Anisotropic should be the PRIMARY specular for water
|
|
float3 anisoSpecular = anisoD * G * F * NdotL * _SpecularIntensity;
|
|
|
|
// === 3. Sun Disk Reflection (uses light color) ===
|
|
float3 reflectDir = reflect(-viewDir, worldNormal);
|
|
float sunDisk = SunDiskReflection(reflectDir, lightDir, _SunDiskSize, _SunDiskSharpness);
|
|
// Boost sun disk on wave peaks
|
|
float peakBoost = saturate(waveHeight * 2.0 + 0.3);
|
|
float3 sunReflection = sunDisk * _SunDiskIntensity * peakBoost * lightColor;
|
|
|
|
// === 4. Micro-facet Glitter (wave-based, not noise) ===
|
|
float glitterIntensity = MicroFacetGlitter(worldNormal, viewDir, lightDir, waveHeight);
|
|
float3 glitter = glitterIntensity * lightColor;
|
|
|
|
// === Combine all specular components ===
|
|
// Blend between isotropic and anisotropic based on strength
|
|
float3 blendedSpecular = lerp(baseSpecular, anisoSpecular, _AnisotropyStrength);
|
|
float3 totalSpecular = blendedSpecular * lightColor + sunReflection + glitter;
|
|
|
|
return totalSpecular * _SpecularColor.rgb;
|
|
}
|
|
|
|
float3 ApplyWaves(float3 positionOS, inout float3 normal, out float waveHeight)
|
|
{
|
|
float3 tangent = float3(1, 0, 0);
|
|
float3 binormal = float3(0, 0, 1);
|
|
float time = _Time.y * _WaveSpeed;
|
|
|
|
// Scale position by ocean scale
|
|
float3 scaledPos = positionOS * _OceanScale;
|
|
|
|
// Apply wind influence to wave directions
|
|
float2 windDir = normalize(_WindDirection.xy);
|
|
|
|
float3 offset = float3(0, 0, 0);
|
|
|
|
// Large swells (long wavelength, slow)
|
|
offset += GerstnerWave(_WaveA, scaledPos, tangent, binormal, time * 0.8);
|
|
offset += GerstnerWave(_WaveB, scaledPos, tangent, binormal, time * 0.9);
|
|
|
|
// Medium waves
|
|
offset += GerstnerWave(_WaveC, scaledPos, tangent, binormal, time);
|
|
offset += GerstnerWave(_WaveD, scaledPos, tangent, binormal, time * 1.1);
|
|
offset += GerstnerWave(_WaveE, scaledPos, tangent, binormal, time * 1.2);
|
|
|
|
// Small detail waves (short wavelength, fast)
|
|
offset += GerstnerWave(_WaveF, scaledPos, tangent, binormal, time * 1.4);
|
|
offset += GerstnerWave(_WaveG, scaledPos, tangent, binormal, time * 1.6);
|
|
offset += GerstnerWave(_WaveH, scaledPos, tangent, binormal, time * 1.8);
|
|
|
|
// Extra small waves (very short wavelength, for surface texture)
|
|
offset += GerstnerWave(_WaveI, scaledPos, tangent, binormal, time * 2.0);
|
|
offset += GerstnerWave(_WaveJ, scaledPos, tangent, binormal, time * 2.2);
|
|
offset += GerstnerWave(_WaveK, scaledPos, tangent, binormal, time * 2.5);
|
|
offset += GerstnerWave(_WaveL, scaledPos, tangent, binormal, time * 2.8);
|
|
|
|
// Apply wave height multiplier
|
|
offset.y *= _WaveHeight;
|
|
|
|
// Apply choppiness to horizontal displacement
|
|
offset.xz *= _Choppiness;
|
|
|
|
// Add micro waves for surface detail (higher frequency noise)
|
|
float2 microUV = scaledPos.xz * _MicroWaveScale;
|
|
float microNoise = FBMNoise(microUV, time * 2.0);
|
|
float microNoise2 = FBMNoise(microUV * 1.7 + 50.0, time * 2.5);
|
|
float microNoise3 = FBMNoise(microUV * 2.5 + 100.0, time * 3.0);
|
|
offset.y += (microNoise - 0.5) * _MicroWaveStrength;
|
|
offset.y += (microNoise2 - 0.5) * _MicroWaveStrength * 0.5;
|
|
offset.y += (microNoise3 - 0.5) * _MicroWaveStrength * 0.25;
|
|
|
|
normal = normalize(cross(binormal, tangent));
|
|
waveHeight = offset.y;
|
|
return positionOS + offset;
|
|
}
|
|
|
|
float3 FlowUVW(float2 uv, float2 flowVector, float time, float phaseOffset)
|
|
{
|
|
float progress = frac(time + phaseOffset);
|
|
float3 uvw;
|
|
uvw.xy = uv - flowVector * progress;
|
|
uvw.z = 1 - abs(1 - 2 * progress);
|
|
return uvw;
|
|
}
|
|
|
|
TessellationControlPoint vert(Attributes IN)
|
|
{
|
|
TessellationControlPoint OUT;
|
|
OUT.positionOS = IN.positionOS;
|
|
OUT.normalOS = IN.normalOS;
|
|
OUT.tangentOS = IN.tangentOS;
|
|
OUT.uv = IN.uv;
|
|
return OUT;
|
|
}
|
|
|
|
float CalcDistanceTessFactor(float3 positionWS, float minDist, float maxDist, float tess)
|
|
{
|
|
float dist = distance(positionWS, _WorldSpaceCameraPos);
|
|
float f = clamp(1.0 - (dist - minDist) / (maxDist - minDist), 0.01, 1.0);
|
|
return f * tess;
|
|
}
|
|
|
|
TessellationFactors patchConstantFunction(InputPatch<TessellationControlPoint, 3> patch)
|
|
{
|
|
TessellationFactors f;
|
|
#if defined(_TESSELLATION_ON)
|
|
float3 p0 = TransformObjectToWorld(patch[0].positionOS.xyz);
|
|
float3 p1 = TransformObjectToWorld(patch[1].positionOS.xyz);
|
|
float3 p2 = TransformObjectToWorld(patch[2].positionOS.xyz);
|
|
f.edge[0] = CalcDistanceTessFactor(0.5 * (p1 + p2), _TessellationMinDist, _TessellationMaxDist, _TessellationFactor);
|
|
f.edge[1] = CalcDistanceTessFactor(0.5 * (p0 + p2), _TessellationMinDist, _TessellationMaxDist, _TessellationFactor);
|
|
f.edge[2] = CalcDistanceTessFactor(0.5 * (p0 + p1), _TessellationMinDist, _TessellationMaxDist, _TessellationFactor);
|
|
f.inside = (f.edge[0] + f.edge[1] + f.edge[2]) / 3.0;
|
|
#else
|
|
f.edge[0] = f.edge[1] = f.edge[2] = 1;
|
|
f.inside = 1;
|
|
#endif
|
|
return f;
|
|
}
|
|
|
|
[domain("tri")]
|
|
[outputcontrolpoints(3)]
|
|
[outputtopology("triangle_cw")]
|
|
[partitioning("fractional_odd")]
|
|
[patchconstantfunc("patchConstantFunction")]
|
|
TessellationControlPoint hull(InputPatch<TessellationControlPoint, 3> patch, uint id : SV_OutputControlPointID)
|
|
{
|
|
return patch[id];
|
|
}
|
|
|
|
[domain("tri")]
|
|
Varyings domain(TessellationFactors factors, OutputPatch<TessellationControlPoint, 3> patch, float3 barycentricCoordinates : SV_DomainLocation)
|
|
{
|
|
Attributes data;
|
|
#define INTERPOLATE(fieldName) data.fieldName = \
|
|
patch[0].fieldName * barycentricCoordinates.x + \
|
|
patch[1].fieldName * barycentricCoordinates.y + \
|
|
patch[2].fieldName * barycentricCoordinates.z;
|
|
INTERPOLATE(positionOS)
|
|
INTERPOLATE(normalOS)
|
|
INTERPOLATE(tangentOS)
|
|
INTERPOLATE(uv)
|
|
#undef INTERPOLATE
|
|
|
|
Varyings OUT;
|
|
float3 waveNormal;
|
|
float waveHeight;
|
|
float3 posOS = ApplyWaves(data.positionOS.xyz, waveNormal, waveHeight);
|
|
|
|
OUT.worldPos = TransformObjectToWorld(posOS);
|
|
OUT.positionCS = TransformWorldToHClip(OUT.worldPos);
|
|
OUT.uv = data.uv;
|
|
OUT.screenPos = ComputeScreenPos(OUT.positionCS);
|
|
OUT.worldNormal = TransformObjectToWorldNormal(waveNormal);
|
|
OUT.worldTangent = TransformObjectToWorldDir(data.tangentOS.xyz);
|
|
OUT.worldBitangent = cross(OUT.worldNormal, OUT.worldTangent) * data.tangentOS.w;
|
|
OUT.viewDir = GetWorldSpaceViewDir(OUT.worldPos);
|
|
OUT.fogFactor = ComputeFogFactor(OUT.positionCS.z);
|
|
OUT.waveHeight = waveHeight;
|
|
return OUT;
|
|
}
|
|
|
|
float3 ScreenSpaceReflection(float3 worldPos, float3 reflectDir, float2 screenUV)
|
|
{
|
|
#if defined(_SSR_ON)
|
|
float3 startPos = worldPos;
|
|
float3 stepDir = reflectDir * _SSRStepSize;
|
|
int maxSteps = min((int)_SSRSteps, 32); // Limit to 32 for GPU compatibility
|
|
[unroll(32)]
|
|
for (int i = 0; i < 32; i++)
|
|
{
|
|
if (i >= maxSteps) break;
|
|
startPos += stepDir;
|
|
float4 projPos = TransformWorldToHClip(startPos);
|
|
float2 sampleUV = projPos.xy / projPos.w * 0.5 + 0.5;
|
|
#if UNITY_UV_STARTS_AT_TOP
|
|
sampleUV.y = 1 - sampleUV.y;
|
|
#endif
|
|
if (sampleUV.x < 0 || sampleUV.x > 1 || sampleUV.y < 0 || sampleUV.y > 1) break;
|
|
float sceneDepth = LinearEyeDepth(SampleSceneDepth(sampleUV), _ZBufferParams);
|
|
float rayDepth = LinearEyeDepth(projPos.z, _ZBufferParams);
|
|
if (rayDepth > sceneDepth && rayDepth < sceneDepth + _SSRThickness)
|
|
return SampleSceneColor(sampleUV);
|
|
}
|
|
#endif
|
|
return SAMPLE_TEXTURECUBE(_CubeMap, sampler_CubeMap, reflectDir).rgb;
|
|
}
|
|
|
|
float3 SubsurfaceScattering(float3 viewDir, float3 lightDir, float3 normal, float3 lightColor)
|
|
{
|
|
#if defined(_SSS_ON)
|
|
float3 H = normalize(lightDir + normal * _SSSDistortion);
|
|
float VdotH = pow(saturate(dot(viewDir, -H)), _SSSPower);
|
|
return _SSSColor.rgb * VdotH * _SSSStrength * lightColor;
|
|
#else
|
|
return float3(0, 0, 0);
|
|
#endif
|
|
}
|
|
|
|
// Beer-Lambert absorption for realistic underwater color
|
|
float3 ApplyAbsorption(float3 backgroundColor, float depth, float3 viewDir, float3 lightDir)
|
|
{
|
|
// Exponential absorption based on depth
|
|
float absorptionFactor = exp(-depth * _AbsorptionStrength / _ClarityDistance);
|
|
|
|
// Color shift - water absorbs red first, then green
|
|
float3 absorptionTint = lerp(_AbsorptionColor.rgb, float3(1,1,1), absorptionFactor);
|
|
|
|
// Apply underwater fog
|
|
float fogFactor = 1.0 - exp(-depth * _UnderwaterFogDensity);
|
|
float3 underwaterFog = _DeepColor.rgb * 0.5;
|
|
|
|
// Combine absorption and fog
|
|
float3 result = backgroundColor * absorptionTint;
|
|
result = lerp(result, underwaterFog, fogFactor);
|
|
|
|
return result;
|
|
}
|
|
|
|
float3 SampleCaustics(float2 worldXZ, float depth, float3 worldNormal)
|
|
{
|
|
#if defined(_CAUSTICS_ON)
|
|
float time = _Time.y * _CausticsSpeed;
|
|
|
|
// Distort caustics UV based on wave normal for shimmer effect
|
|
float2 normalDistort = worldNormal.xz * _CausticsDistortion;
|
|
|
|
// Layer 1 - main caustics
|
|
float2 uv1 = worldXZ * _CausticsScale + normalDistort + float2(time * 0.4, time * 0.25);
|
|
// Layer 2 - offset and scaled differently
|
|
float2 uv2 = worldXZ * _CausticsScale * 1.4 - normalDistort * 0.7 - float2(time * 0.3, time * 0.35);
|
|
// Layer 3 - fine detail
|
|
float2 uv3 = worldXZ * _CausticsScale * 2.1 + normalDistort * 0.3 + float2(time * 0.15, -time * 0.2);
|
|
|
|
// Sample with RGB split for chromatic effect (like real caustics)
|
|
float2 rgbOffset = float2(_CausticsSplit, _CausticsSplit * 0.5);
|
|
|
|
float c1r = SAMPLE_TEXTURE2D(_CausticsTexture, sampler_CausticsTexture, uv1 + rgbOffset).r;
|
|
float c1g = SAMPLE_TEXTURE2D(_CausticsTexture, sampler_CausticsTexture, uv1).r;
|
|
float c1b = SAMPLE_TEXTURE2D(_CausticsTexture, sampler_CausticsTexture, uv1 - rgbOffset).r;
|
|
|
|
float c2r = SAMPLE_TEXTURE2D(_CausticsTexture, sampler_CausticsTexture, uv2 + rgbOffset * 0.7).r;
|
|
float c2g = SAMPLE_TEXTURE2D(_CausticsTexture, sampler_CausticsTexture, uv2).r;
|
|
float c2b = SAMPLE_TEXTURE2D(_CausticsTexture, sampler_CausticsTexture, uv2 - rgbOffset * 0.7).r;
|
|
|
|
float c3 = SAMPLE_TEXTURE2D(_CausticsTexture, sampler_CausticsTexture, uv3).r;
|
|
|
|
// Combine layers with min for sharp caustic lines
|
|
float3 caustics;
|
|
caustics.r = min(c1r, c2r) + c3 * 0.3;
|
|
caustics.g = min(c1g, c2g) + c3 * 0.3;
|
|
caustics.b = min(c1b, c2b) + c3 * 0.3;
|
|
|
|
// Enhance contrast
|
|
caustics = pow(caustics, 1.8) * 2.5;
|
|
|
|
// Depth fade - stronger in shallow water, disappears in deep
|
|
float shallowBoost = saturate(1.0 - depth / (_CausticsDepth * 0.3));
|
|
float depthFade = saturate(1.0 - depth / _CausticsDepth);
|
|
depthFade = depthFade * depthFade; // Quadratic falloff
|
|
depthFade *= (1.0 + shallowBoost * 2.0); // Extra bright in shallows
|
|
|
|
return caustics * depthFade * _CausticsStrength;
|
|
#else
|
|
return float3(0, 0, 0);
|
|
#endif
|
|
}
|
|
|
|
// Underwater light rays (God Rays) - visible when looking up from underwater
|
|
float3 CalculateUnderwaterRays(float2 worldXZ, float depth, float3 viewDir, float3 lightDir, float3 lightColor)
|
|
{
|
|
float time = _Time.y * _UnderwaterRaysSpeed;
|
|
|
|
// Rays are more visible when looking toward light source
|
|
float viewToLight = saturate(dot(viewDir, lightDir) * 0.5 + 0.5);
|
|
|
|
// Create volumetric ray pattern using layered noise
|
|
float2 rayUV1 = worldXZ * _UnderwaterRaysScale + float2(time * 0.1, 0);
|
|
float2 rayUV2 = worldXZ * _UnderwaterRaysScale * 1.7 + float2(time * 0.15, time * 0.05);
|
|
float2 rayUV3 = worldXZ * _UnderwaterRaysScale * 0.5 + float2(-time * 0.08, time * 0.02);
|
|
|
|
// Directional rays (streaks going down from surface)
|
|
float ray1 = sin(rayUV1.x * 8.0 + rayUV1.y * 2.0 + time) * 0.5 + 0.5;
|
|
float ray2 = sin(rayUV2.x * 12.0 + rayUV2.y * 3.0 - time * 0.7) * 0.5 + 0.5;
|
|
float ray3 = sin(rayUV3.x * 5.0 + rayUV3.y * 1.5 + time * 1.3) * 0.5 + 0.5;
|
|
|
|
// Combine with contrast
|
|
float rays = ray1 * ray2 + ray3 * 0.3;
|
|
rays = pow(saturate(rays), 2.0);
|
|
|
|
// Fade based on depth - stronger near surface, fades in deep water
|
|
float depthFade = exp(-depth / _UnderwaterRaysFalloff);
|
|
|
|
// Boost when looking up toward light
|
|
float lookUpBoost = pow(viewToLight, 2.0);
|
|
|
|
// Apply light color and strength
|
|
float3 rayColor = lightColor * rays * depthFade * lookUpBoost * _UnderwaterRaysStrength;
|
|
|
|
return rayColor;
|
|
}
|
|
|
|
float4 frag(Varyings IN) : SV_Target
|
|
{
|
|
float time = _Time.y;
|
|
float2 screenUV = IN.screenPos.xy / IN.screenPos.w;
|
|
float3 viewDir = normalize(IN.viewDir);
|
|
|
|
float2 flowVector = (SAMPLE_TEXTURE2D(_FlowMap, sampler_FlowMap, IN.worldPos.xz * _FlowMap_ST.xy).rg * 2 - 1) * _FlowStrength;
|
|
float flowTime = time * _FlowSpeed;
|
|
float3 uvwA = FlowUVW(IN.worldPos.xz, flowVector, flowTime, 0);
|
|
float3 uvwB = FlowUVW(IN.worldPos.xz, flowVector, flowTime, 0.5);
|
|
|
|
float2 normalUV1A = uvwA.xy * _NormalScale1 + time * _NormalSpeed1.xy;
|
|
float2 normalUV1B = uvwB.xy * _NormalScale1 + time * _NormalSpeed1.xy;
|
|
float2 normalUV2A = uvwA.xy * _NormalScale2 + time * _NormalSpeed2.xy;
|
|
float2 normalUV2B = uvwB.xy * _NormalScale2 + time * _NormalSpeed2.xy;
|
|
|
|
float3 normal1A = UnpackNormalScale(SAMPLE_TEXTURE2D(_NormalMap1, sampler_NormalMap1, normalUV1A), _NormalStrength);
|
|
float3 normal1B = UnpackNormalScale(SAMPLE_TEXTURE2D(_NormalMap1, sampler_NormalMap1, normalUV1B), _NormalStrength);
|
|
float3 normal2A = UnpackNormalScale(SAMPLE_TEXTURE2D(_NormalMap2, sampler_NormalMap2, normalUV2A), _NormalStrength * 0.5);
|
|
float3 normal2B = UnpackNormalScale(SAMPLE_TEXTURE2D(_NormalMap2, sampler_NormalMap2, normalUV2B), _NormalStrength * 0.5);
|
|
|
|
float3 normalTS1 = normal1A * uvwA.z + normal1B * uvwB.z;
|
|
float3 normalTS2 = normal2A * uvwA.z + normal2B * uvwB.z;
|
|
float3 normalTS = normalize(normalTS1 + normalTS2);
|
|
|
|
float3x3 TBN = float3x3(IN.worldTangent, IN.worldBitangent, IN.worldNormal);
|
|
float3 worldNormal = normalize(mul(normalTS, TBN));
|
|
|
|
// Add procedural surface ripples (constant micro-movement)
|
|
float3 rippleNormal = CalculateSurfaceRipples(IN.worldPos.xz, time);
|
|
worldNormal = normalize(worldNormal + float3(rippleNormal.x, 0, rippleNormal.z));
|
|
|
|
float depth = SampleSceneDepth(screenUV);
|
|
float linearDepth = LinearEyeDepth(depth, _ZBufferParams);
|
|
float surfaceDepth = LinearEyeDepth(IN.positionCS.z, _ZBufferParams);
|
|
float depthDifference = linearDepth - surfaceDepth;
|
|
float depthFactor = saturate(depthDifference / _DepthMaxDistance);
|
|
|
|
float4 waterColor = lerp(_ShallowColor, _DeepColor, pow(depthFactor, _DepthStrength));
|
|
float horizonFactor = pow(1 - saturate(dot(viewDir, float3(0, 1, 0))), 3);
|
|
waterColor = lerp(waterColor, _HorizonColor, horizonFactor * 0.5);
|
|
|
|
// Refraction with chromatic aberration
|
|
// Note: Requires Opaque Texture enabled in URP settings
|
|
float2 refractionOffset = worldNormal.xz * _RefractionStrength * saturate(depthDifference);
|
|
float3 refractionR = SampleSceneColor(screenUV + refractionOffset * (1 + _ChromaticAberration));
|
|
float3 refractionG = SampleSceneColor(screenUV + refractionOffset);
|
|
float3 refractionB = SampleSceneColor(screenUV + refractionOffset * (1 - _ChromaticAberration));
|
|
float3 sampledColor = float3(refractionR.r, refractionG.g, refractionB.b);
|
|
|
|
// Check if Opaque Texture is available (invalid samples are often very dark or saturated)
|
|
float sampledValid = step(0.01, dot(sampledColor, float3(0.299, 0.587, 0.114)));
|
|
|
|
// If sample looks invalid, use water color directly
|
|
// Otherwise blend with water color to maintain water appearance
|
|
float3 refractionColor = lerp(waterColor.rgb, lerp(waterColor.rgb, sampledColor, 0.5), sampledValid);
|
|
|
|
// Apply Beer-Lambert absorption for transparency
|
|
Light mainLightForAbsorption = GetMainLight();
|
|
refractionColor = ApplyAbsorption(refractionColor, depthDifference, viewDir, mainLightForAbsorption.direction);
|
|
|
|
// Enhanced caustics with wave distortion
|
|
float3 caustics = SampleCaustics(IN.worldPos.xz, depthDifference, worldNormal);
|
|
refractionColor += caustics * saturate(1.0 - depthFactor * 0.5); // Caustics visible through clear water
|
|
|
|
// Underwater light rays (God Rays) - adds volumetric light effect
|
|
float3 underwaterRays = CalculateUnderwaterRays(IN.worldPos.xz, depthDifference, viewDir, mainLightForAbsorption.direction, mainLightForAbsorption.color);
|
|
refractionColor += underwaterRays;
|
|
|
|
float fresnel = _FresnelBias + (1 - _FresnelBias) * pow(1 - saturate(dot(viewDir, worldNormal)), _FresnelPower);
|
|
float3 reflectDir = reflect(-viewDir, worldNormal);
|
|
float3 reflectionColor = ScreenSpaceReflection(IN.worldPos, reflectDir, screenUV);
|
|
|
|
Light mainLight = GetMainLight(TransformWorldToShadowCoord(IN.worldPos));
|
|
float3 lightDir = mainLight.direction;
|
|
float3 rawLightColor = mainLight.color * mainLight.shadowAttenuation;
|
|
|
|
// Strongly normalize light color to prevent over-saturation
|
|
// Water should maintain its color regardless of light color
|
|
float lightLuminance = dot(rawLightColor, float3(0.299, 0.587, 0.114));
|
|
float3 normalizedLightColor = rawLightColor / max(lightLuminance, 0.001);
|
|
// Only 30% of light hue affects water, rest is white
|
|
float3 lightColor = lerp(float3(1,1,1), normalizedLightColor, 0.3) * min(lightLuminance, 1.2);
|
|
|
|
float3 sss = SubsurfaceScattering(viewDir, lightDir, worldNormal, lightColor);
|
|
|
|
// === PHYSICS-BASED WATER SPECULAR (Genshin-style) ===
|
|
// Uses wave normals directly for realistic light reflection
|
|
float3 specularColor = CalculateWaterSpecular(
|
|
worldNormal,
|
|
viewDir,
|
|
lightDir,
|
|
lightColor,
|
|
IN.worldTangent,
|
|
IN.worldBitangent,
|
|
IN.worldPos.xz,
|
|
IN.waveHeight,
|
|
time
|
|
);
|
|
|
|
// Shore foam - multi-layer for natural look
|
|
float shoreFoam = 0;
|
|
if (depthDifference < _FoamDistance)
|
|
{
|
|
float foamTime = time * _FoamSpeed;
|
|
// Multiple layers at different scales
|
|
float2 foamUV1 = IN.worldPos.xz * _FoamNoiseScale + foamTime * float2(0.1, 0.05);
|
|
float2 foamUV2 = IN.worldPos.xz * _FoamNoiseScale * 2.3 + foamTime * float2(-0.08, 0.12);
|
|
float2 foamUV3 = IN.worldPos.xz * _FoamNoiseScale * 0.5 + foamTime * float2(0.03, -0.06);
|
|
|
|
float foamNoise1 = SAMPLE_TEXTURE2D(_FoamTexture, sampler_FoamTexture, foamUV1).r;
|
|
float foamNoise2 = SAMPLE_TEXTURE2D(_FoamTexture, sampler_FoamTexture, foamUV2).r;
|
|
float foamNoise3 = SAMPLE_TEXTURE2D(_FoamTexture, sampler_FoamTexture, foamUV3).r;
|
|
|
|
// Blend noise layers
|
|
float foamNoise = foamNoise1 * 0.5 + foamNoise2 * 0.3 + foamNoise3 * 0.2;
|
|
|
|
float foamFactor = 1 - saturate(depthDifference / _FoamDistance);
|
|
foamFactor = pow(foamFactor, 0.7); // Softer falloff
|
|
shoreFoam = saturate((foamNoise - (1 - foamFactor)) * _FoamSharpness);
|
|
}
|
|
|
|
// Wave crest foam - follows wave motion naturally
|
|
float waveFoam = 0;
|
|
#if defined(_WAVE_FOAM_ON)
|
|
float waveHeightNorm = saturate(IN.waveHeight * 2 + 0.5);
|
|
|
|
// Soft threshold with smooth transition
|
|
float foamMask = smoothstep(_WaveFoamThreshold - _WaveFoamSoftness, _WaveFoamThreshold + _WaveFoamSoftness, waveHeightNorm);
|
|
|
|
if (foamMask > 0.001)
|
|
{
|
|
float foamTime = time * _FoamSpeed * 0.5;
|
|
|
|
// UV that follows wave motion for more natural appearance
|
|
float2 waveMotion = float2(sin(time * 0.3), cos(time * 0.2)) * 0.5;
|
|
float2 foamUV1 = IN.worldPos.xz * _FoamNoiseScale * 0.3 + waveMotion + foamTime * float2(0.1, 0.08);
|
|
float2 foamUV2 = IN.worldPos.xz * _FoamNoiseScale * 0.7 - waveMotion * 0.5 + foamTime * float2(-0.05, 0.1);
|
|
float2 foamUV3 = IN.worldPos.xz * _FoamNoiseScale * 1.2 + foamTime * float2(0.08, -0.03);
|
|
|
|
float foamNoise1 = SAMPLE_TEXTURE2D(_FoamTexture, sampler_FoamTexture, foamUV1).r;
|
|
float foamNoise2 = SAMPLE_TEXTURE2D(_FoamTexture, sampler_FoamTexture, foamUV2).r;
|
|
float foamNoise3 = SAMPLE_TEXTURE2D(_FoamTexture, sampler_FoamTexture, foamUV3).r;
|
|
|
|
// Complex noise blend for organic look
|
|
float foamNoise = foamNoise1 * foamNoise2 * 2.0 + foamNoise3 * 0.3;
|
|
foamNoise = saturate(foamNoise);
|
|
|
|
waveFoam = foamNoise * foamMask;
|
|
}
|
|
#endif
|
|
|
|
float totalFoam = saturate(shoreFoam + waveFoam * 0.8);
|
|
|
|
// Enhanced transparency blending
|
|
// Shallow water shows more of the refracted background (transparency)
|
|
// Deep water shows more of the water color
|
|
float transparencyFactor = saturate(depthDifference / _TransparencyDepth);
|
|
transparencyFactor = pow(transparencyFactor, 0.6); // Softer transition
|
|
|
|
// Blend between clear (refracted) and tinted water based on depth
|
|
float3 finalColor = lerp(refractionColor, waterColor.rgb, transparencyFactor * waterColor.a);
|
|
|
|
// Apply reflection based on fresnel (view angle)
|
|
finalColor = lerp(finalColor, reflectionColor, fresnel * _ReflectionStrength);
|
|
|
|
// Add subsurface scattering (light through water) - reduced
|
|
finalColor += sss * 0.5;
|
|
|
|
// Add specular highlights - these should be bright but localized
|
|
finalColor += specularColor;
|
|
|
|
// Apply foam on top
|
|
finalColor = lerp(finalColor, _FoamColor.rgb, totalFoam);
|
|
|
|
// Unity fog
|
|
finalColor = MixFog(finalColor, IN.fogFactor);
|
|
|
|
// Alpha: more transparent in shallow, opaque in deep, plus foam
|
|
float alpha = saturate(transparencyFactor * 0.7 + fresnel * 0.3 + totalFoam);
|
|
alpha = max(alpha, 0.1); // Minimum visibility
|
|
|
|
return float4(finalColor, alpha);
|
|
}
|
|
ENDHLSL
|
|
}
|
|
}
|
|
|
|
// ==================== Built-in Pipeline SubShader ====================
|
|
SubShader
|
|
{
|
|
Tags
|
|
{
|
|
"RenderType" = "Transparent"
|
|
"Queue" = "Transparent"
|
|
}
|
|
|
|
GrabPass { "_GrabTexture" }
|
|
|
|
Pass
|
|
{
|
|
Name "WaterProBuiltIn"
|
|
Tags { "LightMode" = "ForwardBase" }
|
|
|
|
Blend SrcAlpha OneMinusSrcAlpha
|
|
ZWrite Off
|
|
Cull Back
|
|
|
|
CGPROGRAM
|
|
#pragma target 3.0
|
|
#pragma vertex vert
|
|
#pragma fragment frag
|
|
#pragma multi_compile_fog
|
|
|
|
#pragma shader_feature_local _SSS_ON
|
|
#pragma shader_feature_local _WAVE_FOAM_ON
|
|
|
|
#include "UnityCG.cginc"
|
|
#include "Lighting.cginc"
|
|
|
|
sampler2D _BumpMap;
|
|
sampler2D _NormalMap1;
|
|
sampler2D _NormalMap2;
|
|
sampler2D _FlowMap;
|
|
sampler2D _FoamTexture;
|
|
sampler2D _GrabTexture;
|
|
sampler2D _CameraDepthTexture;
|
|
samplerCUBE _CubeMap;
|
|
|
|
float4 _ShallowColor;
|
|
float4 _DeepColor;
|
|
float4 _HorizonColor;
|
|
float _DepthMaxDistance;
|
|
float _DepthStrength;
|
|
|
|
float4 _AbsorptionColor;
|
|
float _AbsorptionStrength;
|
|
float _ClarityDistance;
|
|
float _UnderwaterFogDensity;
|
|
float _TransparencyDepth;
|
|
|
|
float _OceanScale;
|
|
float _WaveSpeed;
|
|
float _WaveHeight;
|
|
float4 _WindDirection;
|
|
float _Choppiness;
|
|
float4 _WaveA;
|
|
float4 _WaveB;
|
|
float4 _WaveC;
|
|
float4 _WaveD;
|
|
float4 _WaveE;
|
|
float4 _WaveF;
|
|
float4 _WaveG;
|
|
float4 _WaveH;
|
|
float4 _WaveI;
|
|
float4 _WaveJ;
|
|
float4 _WaveK;
|
|
float4 _WaveL;
|
|
float _MicroWaveScale;
|
|
float _MicroWaveStrength;
|
|
|
|
float _NormalStrength;
|
|
float _NormalScale1;
|
|
float _NormalScale2;
|
|
float4 _NormalSpeed1;
|
|
float4 _NormalSpeed2;
|
|
float _RippleScale1;
|
|
float _RippleScale2;
|
|
float _RippleScale3;
|
|
float _RippleStrength;
|
|
float _RippleSpeed;
|
|
|
|
float4 _FlowMap_ST;
|
|
float _FlowStrength;
|
|
float _FlowSpeed;
|
|
|
|
float _ReflectionStrength;
|
|
float _RefractionStrength;
|
|
float _FresnelPower;
|
|
float _FresnelBias;
|
|
|
|
float4 _SSSColor;
|
|
float _SSSStrength;
|
|
float _SSSDistortion;
|
|
float _SSSPower;
|
|
|
|
float4 _FoamColor;
|
|
float _FoamDistance;
|
|
float _FoamNoiseScale;
|
|
float _FoamSpeed;
|
|
float _FoamSharpness;
|
|
float _WaveFoamThreshold;
|
|
|
|
sampler2D _CausticsTexture;
|
|
float _CausticsStrength;
|
|
float _CausticsScale;
|
|
float _CausticsSpeed;
|
|
float _CausticsDepth;
|
|
float _CausticsDistortion;
|
|
float _CausticsSplit;
|
|
float _UnderwaterRaysStrength;
|
|
float _UnderwaterRaysScale;
|
|
float _UnderwaterRaysSpeed;
|
|
float _UnderwaterRaysFalloff;
|
|
|
|
float4 _SpecularColor;
|
|
float _Smoothness;
|
|
float _SpecularIntensity;
|
|
float _SunDiskSize;
|
|
float _SunDiskIntensity;
|
|
float _SunDiskSharpness;
|
|
float _AnisotropyStrength;
|
|
float _AnisotropyDirection;
|
|
float _MicroFacetScale;
|
|
float _MicroFacetIntensity;
|
|
float _MicroFacetThreshold;
|
|
float _WaveFoamSoftness;
|
|
|
|
// Simple noise for sparkles
|
|
float SimpleNoiseBuiltin(float2 uv)
|
|
{
|
|
return frac(sin(dot(uv, float2(12.9898, 78.233))) * 43758.5453);
|
|
}
|
|
|
|
float SmoothNoiseBuiltin(float2 uv)
|
|
{
|
|
float2 i = floor(uv);
|
|
float2 f = frac(uv);
|
|
f = f * f * (3.0 - 2.0 * f);
|
|
float a = SimpleNoiseBuiltin(i);
|
|
float b = SimpleNoiseBuiltin(i + float2(1, 0));
|
|
float c = SimpleNoiseBuiltin(i + float2(0, 1));
|
|
float d = SimpleNoiseBuiltin(i + float2(1, 1));
|
|
return lerp(lerp(a, b, f.x), lerp(c, d, f.x), f.y);
|
|
}
|
|
|
|
// GGX Normal Distribution
|
|
float GGX_D_Builtin(float NdotH, float roughness)
|
|
{
|
|
float a2 = roughness * roughness;
|
|
float d = NdotH * NdotH * (a2 - 1.0) + 1.0;
|
|
return a2 / (UNITY_PI * d * d + 0.0001);
|
|
}
|
|
|
|
// Schlick Fresnel
|
|
float3 FresnelSchlickBuiltin(float cosTheta, float3 F0)
|
|
{
|
|
return F0 + (1.0 - F0) * pow(saturate(1.0 - cosTheta), 5.0);
|
|
}
|
|
|
|
// Smith Geometry
|
|
float SmithG_Builtin(float NdotV, float NdotL, float roughness)
|
|
{
|
|
float k = roughness * roughness * 0.5;
|
|
float g1 = NdotV / (NdotV * (1.0 - k) + k + 0.0001);
|
|
float g2 = NdotL / (NdotL * (1.0 - k) + k + 0.0001);
|
|
return g1 * g2;
|
|
}
|
|
|
|
// Sun disk reflection
|
|
float SunDiskBuiltin(float3 reflectDir, float3 lightDir)
|
|
{
|
|
float cosAngle = dot(reflectDir, lightDir);
|
|
float disk = saturate((cosAngle - (1.0 - _SunDiskSize)) / _SunDiskSize);
|
|
return pow(disk, _SunDiskSharpness);
|
|
}
|
|
|
|
// Rotate UV (Built-in)
|
|
float2 RotateUVBuiltin(float2 uv, float angle)
|
|
{
|
|
float s = sin(angle);
|
|
float c = cos(angle);
|
|
return float2(uv.x * c - uv.y * s, uv.x * s + uv.y * c);
|
|
}
|
|
|
|
// Procedural surface ripples (Built-in) with rotated waves to prevent moire
|
|
float3 CalculateSurfaceRipplesBuiltin(float2 worldXZ, float time)
|
|
{
|
|
float rippleTime = time * _RippleSpeed;
|
|
|
|
// Multiple rotated UV layers
|
|
float2 uv1 = RotateUVBuiltin(worldXZ * _RippleScale1, 0.0);
|
|
float2 uv2 = RotateUVBuiltin(worldXZ * _RippleScale1, 1.047);
|
|
float2 uv3 = RotateUVBuiltin(worldXZ * _RippleScale1, 2.094);
|
|
|
|
float2 uv4 = RotateUVBuiltin(worldXZ * _RippleScale2, 0.524);
|
|
float2 uv5 = RotateUVBuiltin(worldXZ * _RippleScale2, 1.571);
|
|
float2 uv6 = RotateUVBuiltin(worldXZ * _RippleScale2, 2.618);
|
|
|
|
float2 uv7 = RotateUVBuiltin(worldXZ * _RippleScale3, 0.785);
|
|
float2 uv8 = RotateUVBuiltin(worldXZ * _RippleScale3, 2.356);
|
|
|
|
float n1 = SmoothNoiseBuiltin(worldXZ * 0.3 + rippleTime * 0.05);
|
|
float n2 = SmoothNoiseBuiltin(worldXZ * 0.7 - rippleTime * 0.07);
|
|
|
|
float w1 = sin(uv1.x * 4.0 + rippleTime * 1.1 + n1 * 3.0);
|
|
float w2 = sin(uv2.x * 4.0 + rippleTime * 0.9 + n1 * 2.5);
|
|
float w3 = sin(uv3.x * 4.0 + rippleTime * 1.3 + n2 * 2.0);
|
|
|
|
float w4 = sin(uv4.x * 6.0 + rippleTime * 1.5 + n2 * 4.0);
|
|
float w5 = sin(uv5.x * 6.0 - rippleTime * 1.2 + n1 * 3.5);
|
|
float w6 = sin(uv6.x * 6.0 + rippleTime * 1.7 + n2 * 3.0);
|
|
|
|
float w7 = sin(uv7.x * 10.0 + rippleTime * 2.0 + n1 * 5.0);
|
|
float w8 = sin(uv8.x * 10.0 - rippleTime * 1.8 + n2 * 4.5);
|
|
|
|
float layer1 = (w1 + w2 + w3) / 3.0;
|
|
float layer2 = (w4 + w5 + w6) / 3.0;
|
|
float layer3 = (w7 + w8) / 2.0;
|
|
|
|
float directNoise = (SmoothNoiseBuiltin(worldXZ * _RippleScale2 + rippleTime) - 0.5) * 2.0;
|
|
|
|
float dx = layer1 * 0.5 * cos(0.0) + layer1 * 0.5 * cos(1.047) + layer1 * 0.5 * cos(2.094)
|
|
+ layer2 * 0.35 * cos(0.524) + layer2 * 0.35 * cos(1.571)
|
|
+ layer3 * 0.2 * cos(0.785) + directNoise * 0.2;
|
|
|
|
float dz = layer1 * 0.5 * sin(0.0) + layer1 * 0.5 * sin(1.047) + layer1 * 0.5 * sin(2.094)
|
|
+ layer2 * 0.35 * sin(0.524) + layer2 * 0.35 * sin(1.571)
|
|
+ layer3 * 0.2 * sin(0.785) + directNoise * 0.15;
|
|
|
|
float3 rippleNormal = float3(0, 1, 0);
|
|
rippleNormal.x = dx * _RippleStrength * 0.12;
|
|
rippleNormal.z = dz * _RippleStrength * 0.12;
|
|
return normalize(rippleNormal);
|
|
}
|
|
|
|
// Micro-facet glitter (wave-based, returns intensity only)
|
|
float MicroFacetGlitterBuiltin(float3 normal, float3 viewDir, float3 lightDir, float waveHeight)
|
|
{
|
|
float waveFactor = saturate(abs(waveHeight) * 3.0);
|
|
float3 H = normalize(lightDir + viewDir);
|
|
float NdotH = saturate(dot(normal, H));
|
|
float glitter = smoothstep(_MicroFacetThreshold, 1.0, NdotH);
|
|
glitter *= (1.0 + waveFactor * 0.5);
|
|
return glitter * _MicroFacetIntensity;
|
|
}
|
|
|
|
// Physics-based water specular (Built-in version)
|
|
// Anisotropic GGX for Built-in
|
|
float AnisotropicGGXBuiltin(float NdotH, float TdotH, float BdotH, float roughnessT, float roughnessB)
|
|
{
|
|
float d = (TdotH * TdotH) / (roughnessT * roughnessT + 0.0001)
|
|
+ (BdotH * BdotH) / (roughnessB * roughnessB + 0.0001)
|
|
+ NdotH * NdotH;
|
|
return 1.0 / (UNITY_PI * roughnessT * roughnessB * d * d + 0.0001);
|
|
}
|
|
|
|
float3 CalculateWaterSpecularBuiltin(float3 worldNormal, float3 viewDir, float3 lightDir, float3 lightColor, float3 tangent, float3 bitangent, float2 worldXZ, float waveHeight, float time)
|
|
{
|
|
float3 H = normalize(lightDir + viewDir);
|
|
float NdotH = saturate(dot(worldNormal, H));
|
|
float NdotV = saturate(dot(worldNormal, viewDir));
|
|
float NdotL = saturate(dot(worldNormal, lightDir));
|
|
float VdotH = saturate(dot(viewDir, H));
|
|
float TdotH = dot(tangent, H);
|
|
float BdotH = dot(bitangent, H);
|
|
|
|
float roughness = max(1.0 - _Smoothness, 0.08);
|
|
|
|
// GGX Specular
|
|
float D = GGX_D_Builtin(NdotH, roughness);
|
|
float G = SmithG_Builtin(NdotV, NdotL, roughness);
|
|
float3 F0 = float3(0.02, 0.02, 0.02);
|
|
float3 F = FresnelSchlickBuiltin(VdotH, F0);
|
|
float3 specBRDF = (D * G * F) / (4.0 * NdotV * NdotL + 0.0001);
|
|
float3 baseSpec = specBRDF * NdotL * _SpecularIntensity;
|
|
|
|
// Anisotropic highlights - stretch along wave direction
|
|
float anisoT = roughness * (1.0 + _AnisotropyStrength * 3.0);
|
|
float anisoB = roughness * max(0.05, 1.0 - _AnisotropyStrength * 0.8);
|
|
float anisoD = AnisotropicGGXBuiltin(NdotH, TdotH, BdotH, anisoT, anisoB);
|
|
float3 anisoSpec = anisoD * G * F * NdotL * _SpecularIntensity;
|
|
|
|
// Blend isotropic and anisotropic
|
|
float3 blendedSpec = lerp(baseSpec, anisoSpec, _AnisotropyStrength);
|
|
|
|
// Sun disk (with light color)
|
|
float3 reflectDir = reflect(-viewDir, worldNormal);
|
|
float sunDisk = SunDiskBuiltin(reflectDir, lightDir);
|
|
float peakBoost = saturate(waveHeight * 2.0 + 0.3);
|
|
float3 sunReflection = sunDisk * _SunDiskIntensity * peakBoost * lightColor;
|
|
|
|
// Micro-facet glitter (with light color)
|
|
float glitterIntensity = MicroFacetGlitterBuiltin(worldNormal, viewDir, lightDir, waveHeight);
|
|
float3 glitter = glitterIntensity * lightColor;
|
|
|
|
return (blendedSpec * lightColor + sunReflection + glitter) * _SpecularColor.rgb;
|
|
}
|
|
|
|
// Underwater light rays (God Rays) - Built-in version
|
|
float3 CalculateUnderwaterRaysBuiltin(float2 worldXZ, float depth, float3 viewDir, float3 lightDir, float3 lightColor)
|
|
{
|
|
float time = _Time.y * _UnderwaterRaysSpeed;
|
|
float viewToLight = saturate(dot(viewDir, lightDir) * 0.5 + 0.5);
|
|
|
|
float2 rayUV1 = worldXZ * _UnderwaterRaysScale + float2(time * 0.1, 0);
|
|
float2 rayUV2 = worldXZ * _UnderwaterRaysScale * 1.7 + float2(time * 0.15, time * 0.05);
|
|
float2 rayUV3 = worldXZ * _UnderwaterRaysScale * 0.5 + float2(-time * 0.08, time * 0.02);
|
|
|
|
float ray1 = sin(rayUV1.x * 8.0 + rayUV1.y * 2.0 + time) * 0.5 + 0.5;
|
|
float ray2 = sin(rayUV2.x * 12.0 + rayUV2.y * 3.0 - time * 0.7) * 0.5 + 0.5;
|
|
float ray3 = sin(rayUV3.x * 5.0 + rayUV3.y * 1.5 + time * 1.3) * 0.5 + 0.5;
|
|
|
|
float rays = ray1 * ray2 + ray3 * 0.3;
|
|
rays = pow(saturate(rays), 2.0);
|
|
|
|
float depthFade = exp(-depth / _UnderwaterRaysFalloff);
|
|
float lookUpBoost = pow(viewToLight, 2.0);
|
|
|
|
return lightColor * rays * depthFade * lookUpBoost * _UnderwaterRaysStrength;
|
|
}
|
|
|
|
struct appdata
|
|
{
|
|
float4 vertex : POSITION;
|
|
float3 normal : NORMAL;
|
|
float4 tangent : TANGENT;
|
|
float2 uv : TEXCOORD0;
|
|
};
|
|
|
|
struct v2f
|
|
{
|
|
float4 pos : SV_POSITION;
|
|
float2 uv : TEXCOORD0;
|
|
float3 worldPos : TEXCOORD1;
|
|
float3 worldNormal : TEXCOORD2;
|
|
float3 worldTangent : TEXCOORD3;
|
|
float3 worldBitangent : TEXCOORD4;
|
|
float4 screenPos : TEXCOORD5;
|
|
float3 viewDir : TEXCOORD6;
|
|
float waveHeight : TEXCOORD7;
|
|
UNITY_FOG_COORDS(8)
|
|
};
|
|
|
|
float3 GerstnerWave(float4 wave, float3 p, inout float3 tangent, inout float3 binormal, float time)
|
|
{
|
|
float steepness = wave.z;
|
|
float wavelength = wave.w;
|
|
float k = 2.0 * UNITY_PI / wavelength;
|
|
float c = sqrt(9.8 / k);
|
|
float2 d = normalize(wave.xy);
|
|
float f = k * (dot(d, p.xz) - c * time);
|
|
float a = steepness / k;
|
|
|
|
tangent += float3(-d.x * d.x * steepness * sin(f), d.x * steepness * cos(f), -d.x * d.y * steepness * sin(f));
|
|
binormal += float3(-d.x * d.y * steepness * sin(f), d.y * steepness * cos(f), -d.y * d.y * steepness * sin(f));
|
|
|
|
return float3(d.x * a * cos(f), a * sin(f), d.y * a * cos(f));
|
|
}
|
|
|
|
float3 ApplyWaves(float3 positionOS, inout float3 normal, out float waveHeight)
|
|
{
|
|
float3 tangent = float3(1, 0, 0);
|
|
float3 binormal = float3(0, 0, 1);
|
|
float time = _Time.y * _WaveSpeed;
|
|
float3 scaledPos = positionOS * _OceanScale;
|
|
|
|
float3 offset = float3(0, 0, 0);
|
|
offset += GerstnerWave(_WaveA, scaledPos, tangent, binormal, time * 0.8);
|
|
offset += GerstnerWave(_WaveB, scaledPos, tangent, binormal, time * 0.9);
|
|
offset += GerstnerWave(_WaveC, scaledPos, tangent, binormal, time);
|
|
offset += GerstnerWave(_WaveD, scaledPos, tangent, binormal, time * 1.1);
|
|
offset += GerstnerWave(_WaveE, scaledPos, tangent, binormal, time * 1.2);
|
|
offset += GerstnerWave(_WaveF, scaledPos, tangent, binormal, time * 1.4);
|
|
offset += GerstnerWave(_WaveG, scaledPos, tangent, binormal, time * 1.6);
|
|
offset += GerstnerWave(_WaveH, scaledPos, tangent, binormal, time * 1.8);
|
|
offset += GerstnerWave(_WaveI, scaledPos, tangent, binormal, time * 2.0);
|
|
offset += GerstnerWave(_WaveJ, scaledPos, tangent, binormal, time * 2.2);
|
|
offset += GerstnerWave(_WaveK, scaledPos, tangent, binormal, time * 2.5);
|
|
offset += GerstnerWave(_WaveL, scaledPos, tangent, binormal, time * 2.8);
|
|
|
|
offset.y *= _WaveHeight;
|
|
offset.xz *= _Choppiness;
|
|
|
|
normal = normalize(cross(binormal, tangent));
|
|
waveHeight = offset.y;
|
|
return positionOS + offset;
|
|
}
|
|
|
|
float3 FlowUVW(float2 uv, float2 flowVector, float time, float phaseOffset)
|
|
{
|
|
float progress = frac(time + phaseOffset);
|
|
float3 uvw;
|
|
uvw.xy = uv - flowVector * progress;
|
|
uvw.z = 1 - abs(1 - 2 * progress);
|
|
return uvw;
|
|
}
|
|
|
|
v2f vert(appdata v)
|
|
{
|
|
v2f o;
|
|
|
|
float3 waveNormal;
|
|
float waveHeight;
|
|
float3 posOS = ApplyWaves(v.vertex.xyz, waveNormal, waveHeight);
|
|
|
|
o.worldPos = mul(unity_ObjectToWorld, float4(posOS, 1)).xyz;
|
|
o.pos = UnityWorldToClipPos(o.worldPos);
|
|
o.uv = v.uv;
|
|
o.screenPos = ComputeGrabScreenPos(o.pos);
|
|
o.worldNormal = UnityObjectToWorldNormal(waveNormal);
|
|
o.worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
|
|
o.worldBitangent = cross(o.worldNormal, o.worldTangent) * v.tangent.w;
|
|
o.viewDir = normalize(_WorldSpaceCameraPos - o.worldPos);
|
|
o.waveHeight = waveHeight;
|
|
UNITY_TRANSFER_FOG(o, o.pos);
|
|
return o;
|
|
}
|
|
|
|
float4 frag(v2f i) : SV_Target
|
|
{
|
|
float time = _Time.y;
|
|
float2 screenUV = i.screenPos.xy / i.screenPos.w;
|
|
float3 viewDir = normalize(i.viewDir);
|
|
|
|
// Flow
|
|
float2 flowVector = (tex2D(_FlowMap, i.worldPos.xz * _FlowMap_ST.xy).rg * 2 - 1) * _FlowStrength;
|
|
float flowTime = time * _FlowSpeed;
|
|
float3 uvwA = FlowUVW(i.worldPos.xz, flowVector, flowTime, 0);
|
|
float3 uvwB = FlowUVW(i.worldPos.xz, flowVector, flowTime, 0.5);
|
|
|
|
// Normal maps
|
|
float2 normalUV1A = uvwA.xy * _NormalScale1 + time * _NormalSpeed1.xy;
|
|
float2 normalUV1B = uvwB.xy * _NormalScale1 + time * _NormalSpeed1.xy;
|
|
float2 normalUV2A = uvwA.xy * _NormalScale2 + time * _NormalSpeed2.xy;
|
|
float2 normalUV2B = uvwB.xy * _NormalScale2 + time * _NormalSpeed2.xy;
|
|
|
|
float3 normal1A = UnpackScaleNormal(tex2D(_NormalMap1, normalUV1A), _NormalStrength);
|
|
float3 normal1B = UnpackScaleNormal(tex2D(_NormalMap1, normalUV1B), _NormalStrength);
|
|
float3 normal2A = UnpackScaleNormal(tex2D(_NormalMap2, normalUV2A), _NormalStrength * 0.5);
|
|
float3 normal2B = UnpackScaleNormal(tex2D(_NormalMap2, normalUV2B), _NormalStrength * 0.5);
|
|
|
|
float3 normalTS1 = normal1A * uvwA.z + normal1B * uvwB.z;
|
|
float3 normalTS2 = normal2A * uvwA.z + normal2B * uvwB.z;
|
|
float3 normalTS = normalize(normalTS1 + normalTS2);
|
|
|
|
float3x3 TBN = float3x3(i.worldTangent, i.worldBitangent, i.worldNormal);
|
|
float3 worldNormal = normalize(mul(normalTS, TBN));
|
|
|
|
// Add procedural surface ripples
|
|
float3 rippleNormal = CalculateSurfaceRipplesBuiltin(i.worldPos.xz, time);
|
|
worldNormal = normalize(worldNormal + float3(rippleNormal.x, 0, rippleNormal.z));
|
|
|
|
// Depth
|
|
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, screenUV);
|
|
float linearDepth = LinearEyeDepth(depth);
|
|
float surfaceDepth = i.screenPos.w;
|
|
float depthDifference = linearDepth - surfaceDepth;
|
|
float depthFactor = saturate(depthDifference / _DepthMaxDistance);
|
|
|
|
// Water color
|
|
float4 waterColor = lerp(_ShallowColor, _DeepColor, pow(depthFactor, _DepthStrength));
|
|
float horizonFactor = pow(1 - saturate(dot(viewDir, float3(0, 1, 0))), 3);
|
|
waterColor = lerp(waterColor, _HorizonColor, horizonFactor * 0.5);
|
|
|
|
// Refraction with depth-based scaling
|
|
float2 refractionOffset = worldNormal.xz * _RefractionStrength * saturate(depthDifference);
|
|
float3 refractionColor = tex2D(_GrabTexture, screenUV + refractionOffset).rgb;
|
|
|
|
// Beer-Lambert absorption
|
|
float absorptionFactor = exp(-depthDifference * _AbsorptionStrength / _ClarityDistance);
|
|
float3 absorptionTint = lerp(_AbsorptionColor.rgb, float3(1,1,1), absorptionFactor);
|
|
float fogFactor = 1.0 - exp(-depthDifference * _UnderwaterFogDensity);
|
|
refractionColor = refractionColor * absorptionTint;
|
|
refractionColor = lerp(refractionColor, _DeepColor.rgb * 0.5, fogFactor);
|
|
|
|
// Caustics
|
|
float causticsTime = _Time.y * _CausticsSpeed;
|
|
float2 causticsDistort = worldNormal.xz * _CausticsDistortion;
|
|
float2 causticsUV1 = i.worldPos.xz * _CausticsScale + causticsDistort + causticsTime * float2(0.4, 0.25);
|
|
float2 causticsUV2 = i.worldPos.xz * _CausticsScale * 1.4 - causticsDistort * 0.7 - causticsTime * float2(0.3, 0.35);
|
|
float c1 = tex2D(_CausticsTexture, causticsUV1).r;
|
|
float c2 = tex2D(_CausticsTexture, causticsUV2).r;
|
|
float causticsIntensity = min(c1, c2);
|
|
causticsIntensity = pow(causticsIntensity, 1.8) * 2.5;
|
|
float causticsDepthFade = saturate(1.0 - depthDifference / _CausticsDepth);
|
|
causticsDepthFade *= causticsDepthFade;
|
|
float3 caustics = causticsIntensity * causticsDepthFade * _CausticsStrength;
|
|
refractionColor += caustics;
|
|
|
|
// Underwater light rays (God Rays)
|
|
float3 lightDirForRays = normalize(_WorldSpaceLightPos0.xyz);
|
|
float3 lightColorForRays = _LightColor0.rgb;
|
|
float3 underwaterRays = CalculateUnderwaterRaysBuiltin(i.worldPos.xz, depthDifference, viewDir, lightDirForRays, lightColorForRays);
|
|
refractionColor += underwaterRays;
|
|
|
|
// Fresnel
|
|
float fresnel = _FresnelBias + (1 - _FresnelBias) * pow(1 - saturate(dot(viewDir, worldNormal)), _FresnelPower);
|
|
|
|
// Reflection (cubemap)
|
|
float3 reflectDir = reflect(-viewDir, worldNormal);
|
|
float3 reflectionColor = texCUBE(_CubeMap, reflectDir).rgb;
|
|
|
|
// Lighting
|
|
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
|
|
float3 rawLightColor = _LightColor0.rgb;
|
|
|
|
// Strongly normalize light color to prevent over-saturation
|
|
float lightLuminance = dot(rawLightColor, float3(0.299, 0.587, 0.114));
|
|
float3 normalizedLightColor = rawLightColor / max(lightLuminance, 0.001);
|
|
float3 lightColor = lerp(float3(1,1,1), normalizedLightColor, 0.3) * min(lightLuminance, 1.2);
|
|
|
|
// SSS
|
|
float3 sss = float3(0, 0, 0);
|
|
#if defined(_SSS_ON)
|
|
float3 H = normalize(lightDir + worldNormal * _SSSDistortion);
|
|
float VdotH = pow(saturate(dot(viewDir, -H)), _SSSPower);
|
|
sss = _SSSColor.rgb * VdotH * _SSSStrength * lightColor;
|
|
#endif
|
|
|
|
// === PHYSICS-BASED WATER SPECULAR (Genshin-style) ===
|
|
float3 specularColor = CalculateWaterSpecularBuiltin(
|
|
worldNormal,
|
|
viewDir,
|
|
lightDir,
|
|
lightColor,
|
|
i.worldTangent,
|
|
i.worldBitangent,
|
|
i.worldPos.xz,
|
|
i.waveHeight,
|
|
time
|
|
);
|
|
|
|
// Shore foam - multi-layer
|
|
float shoreFoam = 0;
|
|
if (depthDifference < _FoamDistance)
|
|
{
|
|
float foamTime = time * _FoamSpeed;
|
|
float2 foamUV1 = i.worldPos.xz * _FoamNoiseScale + foamTime * float2(0.1, 0.05);
|
|
float2 foamUV2 = i.worldPos.xz * _FoamNoiseScale * 2.3 + foamTime * float2(-0.08, 0.12);
|
|
float foamNoise1 = tex2D(_FoamTexture, foamUV1).r;
|
|
float foamNoise2 = tex2D(_FoamTexture, foamUV2).r;
|
|
float foamNoise = foamNoise1 * 0.6 + foamNoise2 * 0.4;
|
|
float foamFactor = 1 - saturate(depthDifference / _FoamDistance);
|
|
foamFactor = pow(foamFactor, 0.7);
|
|
shoreFoam = saturate((foamNoise - (1 - foamFactor)) * _FoamSharpness);
|
|
}
|
|
|
|
// Wave crest foam
|
|
float waveFoam = 0;
|
|
#if defined(_WAVE_FOAM_ON)
|
|
float waveHeightNorm = saturate(i.waveHeight * 2 + 0.5);
|
|
float foamMask = smoothstep(_WaveFoamThreshold - _WaveFoamSoftness, _WaveFoamThreshold + _WaveFoamSoftness, waveHeightNorm);
|
|
if (foamMask > 0.001)
|
|
{
|
|
float foamTime = time * _FoamSpeed * 0.5;
|
|
float2 waveMotion = float2(sin(time * 0.3), cos(time * 0.2)) * 0.5;
|
|
float2 foamUV1 = i.worldPos.xz * _FoamNoiseScale * 0.3 + waveMotion + foamTime * float2(0.1, 0.08);
|
|
float2 foamUV2 = i.worldPos.xz * _FoamNoiseScale * 0.7 - waveMotion * 0.5;
|
|
float foamNoise1 = tex2D(_FoamTexture, foamUV1).r;
|
|
float foamNoise2 = tex2D(_FoamTexture, foamUV2).r;
|
|
float foamNoise = foamNoise1 * foamNoise2 * 2.0;
|
|
waveFoam = saturate(foamNoise) * foamMask;
|
|
}
|
|
#endif
|
|
|
|
float totalFoam = saturate(shoreFoam + waveFoam * 0.8);
|
|
|
|
// Enhanced transparency blending
|
|
float transparencyFactor = saturate(depthDifference / _TransparencyDepth);
|
|
transparencyFactor = pow(transparencyFactor, 0.6);
|
|
|
|
// Blend between clear (refracted) and tinted water based on depth
|
|
float3 finalColor = lerp(refractionColor, waterColor.rgb, transparencyFactor * waterColor.a);
|
|
|
|
finalColor = lerp(finalColor, reflectionColor, fresnel * _ReflectionStrength);
|
|
finalColor += sss * 0.5;
|
|
finalColor += specularColor;
|
|
finalColor = lerp(finalColor, _FoamColor.rgb, totalFoam);
|
|
|
|
UNITY_APPLY_FOG(i.fogCoord, finalColor);
|
|
|
|
// Alpha: more transparent in shallow, opaque in deep
|
|
float alpha = saturate(transparencyFactor * 0.7 + fresnel * 0.3 + totalFoam);
|
|
alpha = max(alpha, 0.1);
|
|
return float4(finalColor, alpha);
|
|
}
|
|
ENDCG
|
|
}
|
|
}
|
|
|
|
// ==================== HDRP ====================
|
|
// NOTE: For HDRP, use Unity's official Water System instead.
|
|
// It provides FFT-based waves, built-in buoyancy, and proper HDRP integration.
|
|
// See: https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@14.0/manual/WaterSystem.html
|
|
//
|
|
// This shader supports URP and Built-in pipelines only.
|
|
|
|
FallBack "Synaptic/Water"
|
|
}
|