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 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 patch, uint id : SV_OutputControlPointID) { return patch[id]; } [domain("tri")] Varyings domain(TessellationFactors factors, OutputPatch 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" }