Files
2026-03-05 00:14:42 +08:00

640 lines
18 KiB
HLSL

// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
// Guard against missing uniforms.
#ifdef SHADERPASS
#define m_Properties \
const float2 i_UndisplacedXZ, \
const float i_LodAlpha, \
const half i_WaterLevelOffset, \
const float2 i_WaterLevelDerivatives, \
const half2 i_Flow, \
const half3 i_ViewDirectionWS, \
const bool i_Facing, \
const half3 i_SceneColor, \
const float i_SceneDepthRaw, \
const float4 i_ScreenPosition, \
const float4 i_ScreenPositionRaw, \
const float3 i_PositionWS, \
const float3 i_PositionVS, \
const float2 i_StaticLightMapUV, \
out half3 o_Albedo, \
out half3 o_NormalWS, \
out half3 o_Specular, \
out half3 o_Emission, \
out half o_Smoothness, \
out half o_Occlusion, \
out half o_Alpha
// Guard against Shader Graph preview.
#ifndef SHADERGRAPH_PREVIEW
#define d_Crest_WaterSurface 1
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Surface/Shim.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Macros.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Globals.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/InputsDriven.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Cascade.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Surface/Geometry.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Utility/Depth.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Texture.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Flow.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Utility/Lighting.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Utility/Shadows.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Surface/Facing.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Surface/Normal.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Surface/Reflection.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Surface/Refraction.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Surface/Caustics.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Surface/VolumeLighting.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Surface/Fresnel.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Surface/Foam.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Surface/Alpha.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Surface/Fog.hlsl"
#if (CREST_PORTALS != 0)
#include "Packages/com.waveharmonic.crest.portals/Runtime/Shaders/Library/Portals.hlsl"
#endif
bool _Crest_DrawBoundaryXZ;
float4 _Crest_BoundaryXZ;
m_CrestNameSpace
static const TiledTexture _Crest_NormalMapTiledTexture =
TiledTexture::Make(_Crest_NormalMapTexture, sampler_Crest_NormalMapTexture, _Crest_NormalMapTexture_TexelSize, _Crest_NormalMapScale, _Crest_NormalMapScrollSpeed);
static const TiledTexture _Crest_FoamTiledTexture =
TiledTexture::Make(_Crest_FoamTexture, sampler_Crest_FoamTexture, _Crest_FoamTexture_TexelSize, _Crest_FoamScale, _Crest_FoamScrollSpeed);
static const TiledTexture _Crest_CausticsTiledTexture =
TiledTexture::Make(_Crest_CausticsTexture, sampler_Crest_CausticsTexture, _Crest_CausticsTexture_TexelSize, _Crest_CausticsTextureScale, _Crest_CausticsScrollSpeed);
static const TiledTexture _Crest_CausticsDistortionTiledTexture =
TiledTexture::Make(_Crest_CausticsDistortionTexture, sampler_Crest_CausticsDistortionTexture, _Crest_CausticsDistortionTexture_TexelSize, _Crest_CausticsDistortionScale, 1.0);
void Fragment(m_Properties)
{
o_Albedo = 0.0;
o_NormalWS = half3(0.0, 1.0, 0.0);
o_Specular = 0.0;
o_Emission = 0.0;
o_Smoothness = 0.7;
o_Occlusion = 1.0;
o_Alpha = 1.0;
const float2 positionSS = i_ScreenPosition.xy * _ScreenSize.xy;
// Editor only. There is no defined editor symbol.
if (_Crest_DrawBoundaryXZ)
{
const float2 p = abs(i_PositionWS.xz - _Crest_BoundaryXZ.xy);
const float2 s = _Crest_BoundaryXZ.zw * 0.5;
if ((p.x > s.x && p.x < s.x + 1.0 && p.y < s.y + 1.0) || (p.y > s.y && p.y < s.y + 1.0 && p.x < s.x + 1.0))
{
o_Emission = half3(1.0, 0.0, 1.0);
#if CREST_HDRP
o_Emission /= GetCurrentExposureMultiplier();
#endif
}
}
bool underwater = IsUnderWater(i_Facing, g_Crest_ForceUnderwater, positionSS);
// TODO: Should we use PosToSIs or check for overflow?
float slice0 = _Crest_LodIndex;
float slice1 = _Crest_LodIndex + 1;
#ifdef CREST_FLOW_ON
const Flow flow = Flow::Make(i_Flow, g_Crest_Time);
#endif
const Cascade cascade0 = Cascade::Make(slice0);
const Cascade cascade1 = Cascade::Make(slice1);
float sceneRawZ = i_SceneDepthRaw;
float negativeFog = _ProjectionParams.y;
#if (CREST_PORTALS != 0)
#ifndef CREST_SHADOWPASS
#if _ALPHATEST_ON
if (m_CrestPortal)
{
const float pixelRawZ = i_ScreenPositionRaw.z / i_ScreenPositionRaw.w;
if (Portal::EvaluateSurface(i_ScreenPosition.xy, pixelRawZ, i_PositionWS, underwater, sceneRawZ, negativeFog))
{
o_Alpha = 0.0;
return;
}
}
#endif
#endif
#endif
float sceneZ = Utility::CrestLinearEyeDepth(sceneRawZ);
float pixelZ = -i_PositionVS.z;
const bool isLastLod = _Crest_LodIndex == (uint)g_Crest_LodCount - 1;
const float weight0 = (1.0 - i_LodAlpha) * cascade0._Weight;
const float weight1 = (1.0 - weight0) * cascade1._Weight;
// Data that fades towards the edge.
half foam = 0.0; half _determinant = 0.0; half4 albedo = 0.0; half2 shadow = 0.0;
if (weight0 > m_CrestSampleLodThreshold)
{
Cascade::MakeAnimatedWaves(slice0).SampleNormals(i_UndisplacedXZ, weight0, o_NormalWS.xz, _determinant);
if (_Crest_FoamEnabled)
{
Cascade::MakeFoam(slice0).SampleFoam(i_UndisplacedXZ, weight0, foam);
}
if (_Crest_AlbedoEnabled)
{
Cascade::MakeAlbedo(slice0).SampleAlbedo(i_UndisplacedXZ, weight0, albedo);
}
if (_Crest_ShadowsEnabled)
{
Cascade::MakeShadow(slice0).SampleShadow(i_PositionWS.xz, weight0, shadow);
}
}
if (weight1 > m_CrestSampleLodThreshold)
{
Cascade::MakeAnimatedWaves(slice1).SampleNormals(i_UndisplacedXZ, weight1, o_NormalWS.xz, _determinant);
if (_Crest_FoamEnabled)
{
Cascade::MakeFoam(slice1).SampleFoam(i_UndisplacedXZ, weight1, foam);
}
if (_Crest_AlbedoEnabled)
{
Cascade::MakeAlbedo(slice1).SampleAlbedo(i_UndisplacedXZ, weight1, albedo);
}
if (_Crest_ShadowsEnabled)
{
Cascade::MakeShadow(slice1).SampleShadow(i_PositionWS.xz, weight1, shadow);
}
}
// Invert so shadows are black as we normally multiply this by lighting.
shadow = 1.0 - shadow;
// Data that displays to the edge.
// The default simulation value has been written to the border of the last slice.
half3 absorption = 0.0; half3 scattering = 0.0;
{
const float weight0 = (1.0 - (isLastLod ? 0.0 : i_LodAlpha)) * cascade0._Weight;
const float weight1 = (1.0 - weight0) * cascade1._Weight;
if (weight0 > m_CrestSampleLodThreshold)
{
if (g_Crest_SampleScatteringSimulation)
{
Cascade::MakeScattering(slice0).SampleScattering(i_UndisplacedXZ, weight0, scattering);
}
if (g_Crest_SampleAbsorptionSimulation)
{
Cascade::MakeAbsorption(slice0).SampleAbsorption(i_UndisplacedXZ, weight0, absorption);
}
}
if (weight1 > m_CrestSampleLodThreshold)
{
if (g_Crest_SampleScatteringSimulation)
{
Cascade::MakeScattering(slice1).SampleScattering(i_UndisplacedXZ, weight1, scattering);
}
if (g_Crest_SampleAbsorptionSimulation)
{
Cascade::MakeAbsorption(slice1).SampleAbsorption(i_UndisplacedXZ, weight1, absorption);
}
}
}
if (!g_Crest_SampleScatteringSimulation)
{
scattering = _Crest_Scattering.xyz;
}
if (!g_Crest_SampleAbsorptionSimulation)
{
absorption = _Crest_Absorption.xyz;
}
// Determinant needs to be one when no waves.
if (isLastLod)
{
_determinant += 1.0 - weight0;
}
// Normal.
{
if (_Crest_NormalMapEnabled)
{
o_NormalWS.xz += SampleNormalMaps
(
#ifdef CREST_FLOW_ON
flow,
#endif
_Crest_NormalMapTiledTexture,
_Crest_NormalMapStrength,
i_UndisplacedXZ,
i_LodAlpha,
cascade0
);
}
o_NormalWS = normalize(o_NormalWS);
WaterNormal
(
i_WaterLevelDerivatives,
i_ViewDirectionWS,
_Crest_MinimumReflectionDirectionY,
underwater,
o_NormalWS
);
o_NormalWS = normalize(o_NormalWS);
o_NormalWS.xz *= _Crest_NormalsStrengthOverall;
o_NormalWS.y = lerp(1.0, o_NormalWS.y, _Crest_NormalsStrengthOverall);
if (underwater)
{
// Flip when underwater.
o_NormalWS.xyz *= -1.0;
}
}
// Default for opaque render type.
float sceneDistance = 1000.0;
float3 scenePositionWS = 0.0;
const half3 ambientLight = AmbientLight();
float3 lightIntensity = 0.0;
half3 lightDirection = 0.0;
PrimaryLight
(
i_PositionWS,
lightIntensity,
lightDirection
);
half3 additionalLight = AdditionalLighting(i_PositionWS, i_ScreenPositionRaw, i_StaticLightMapUV);
#if d_Transparent
#ifndef d_SkipRefraction
bool caustics;
RefractedScene
(
_Crest_RefractionStrength,
o_NormalWS,
i_ScreenPosition.xy,
pixelZ,
i_SceneColor,
sceneZ,
sceneRawZ,
underwater,
o_Emission,
sceneDistance,
scenePositionWS,
caustics
);
#if CREST_BIRP
#if SHADERPASS == SHADERPASS_FORWARD_BASE
if (g_Crest_PrimaryLightHasCookie)
{
// If light has a cookie, it is zero for the ForwardBase pass. We need to split
// emission over ForwardBase and ForwardAdd. Ambient is done in the former, while
// direct (eg caustics) is done in ForwardAdd.
o_Emission = 0;
}
#endif
#endif
#endif // d_SkipRefraction
#endif // d_Transparent
float refractedSeaLevel = g_Crest_WaterCenter.y;
float3 refractedSurfacePosition = float3(0, refractedSeaLevel, 0);
if (!underwater && slice1 < g_Crest_LodCount)
{
// Sample larger slice to avoid the first slice.
float4 displacement = Cascade::MakeAnimatedWaves(slice1).Sample(scenePositionWS.xz);
refractedSeaLevel = g_Crest_WaterCenter.y + displacement.w;
refractedSurfacePosition = displacement.xyz;
refractedSurfacePosition.y += refractedSeaLevel;
}
// Out-scattering.
if (!underwater)
{
// Account for average extinction of light as it travels down through volume. Assume flat water as anything else would be expensive.
half3 extinction = absorption.xyz + scattering.xyz;
o_Emission *= exp(-extinction * max(0.0, refractedSeaLevel - scenePositionWS.y));
}
#if d_Transparent
#ifndef d_SkipRefraction
// Caustics
if (_Crest_CausticsEnabled && !underwater && caustics)
{
half lightOcclusion = PrimaryLightShadows(scenePositionWS, positionSS);
half blur = 0.0;
#ifdef CREST_FLOW_ON
blur = _Crest_CausticsMotionBlur;
#endif
o_Emission *= Caustics
(
#ifdef CREST_FLOW_ON
flow,
#endif
scenePositionWS,
refractedSurfacePosition.y,
lightIntensity,
lightDirection,
lightOcclusion,
sceneDistance,
_Crest_CausticsTiledTexture,
_Crest_CausticsTextureAverage,
_Crest_CausticsStrength,
_Crest_CausticsFocalDepth,
_Crest_CausticsDepthOfField,
_Crest_CausticsDistortionTiledTexture,
_Crest_CausticsDistortionStrength,
blur,
underwater
);
}
#endif // d_SkipRefraction
#endif // d_Transparent
half3 sss = 0.0;
if (_Crest_SSSEnabled)
{
sss = PinchSSS
(
_determinant,
_Crest_SSSPinchMinimum,
_Crest_SSSPinchMaximum,
_Crest_SSSPinchFalloff,
_Crest_SSSIntensity,
lightDirection,
_Crest_SSSDirectionalFalloff,
i_ViewDirectionWS
);
}
// Volume Lighting
const half3 extinction = VolumeExtinction(absorption, scattering);
const half3 volumeOpacity = VolumeOpacity(extinction, sceneDistance);
const half3 volumeLight = VolumeLighting
(
extinction,
scattering,
_Crest_Anisotropy,
shadow.x,
i_ViewDirectionWS,
ambientLight,
lightDirection,
lightIntensity,
additionalLight,
_Crest_AmbientTerm,
_Crest_DirectTerm,
sss,
_Crest_ShadowsAffectsAmbientFactor
);
// Fresnel
float reflected = 0.0;
float transmitted = 0.0;
{
ApplyFresnel
(
i_ViewDirectionWS,
o_NormalWS,
underwater,
1.0, // air
_Crest_RefractiveIndexOfWater,
_Crest_TotalInternalReflectionIntensity,
transmitted,
reflected
);
if (underwater)
{
o_Emission *= transmitted;
o_Emission += volumeLight * reflected;
}
else
{
o_Emission *= 1.0 - volumeOpacity;
o_Emission += volumeLight * volumeOpacity;
o_Emission *= transmitted;
}
}
// Specular
{
o_Specular = _Crest_Specular * reflected * shadow.y;
}
// Smoothness
{
// Vary smoothness by distance.
o_Smoothness = lerp(_Crest_Smoothness, _Crest_SmoothnessFar, pow(saturate(pixelZ / _Crest_SmoothnessFarDistance), _Crest_SmoothnessFalloff));
}
// Occlusion
{
o_Occlusion = underwater ? _Crest_OcclusionUnderwater : _Crest_Occlusion;
}
// Planar Reflections
if (_Crest_PlanarReflectionsEnabled)
{
half4 reflection = PlanarReflection
(
_Crest_ReflectionTexture,
sampler_Crest_ReflectionTexture,
_Crest_PlanarReflectionsIntensity,
o_Smoothness,
_Crest_PlanarReflectionsRoughness,
o_NormalWS,
_Crest_PlanarReflectionsDistortion,
i_ViewDirectionWS,
i_ScreenPosition.xy,
underwater
);
half alpha = reflection.a;
o_Emission = lerp(o_Emission, reflection.rgb, alpha * reflected * o_Occlusion);
// Override reflections with planar reflections.
// Results are darker than Unity's.
o_Occlusion *= 1.0 - alpha;
}
// Foam
if (_Crest_FoamEnabled)
{
half albedo = MultiScaleFoamAlbedo
(
#ifdef CREST_FLOW_ON
flow,
#endif
_Crest_FoamTiledTexture,
_Crest_FoamFeather,
foam,
cascade0,
cascade1,
i_LodAlpha,
i_UndisplacedXZ
);
half2 normal = MultiScaleFoamNormal
(
#ifdef CREST_FLOW_ON
flow,
#endif
_Crest_FoamTiledTexture,
_Crest_FoamFeather,
_Crest_FoamNormalStrength,
foam,
albedo,
cascade0,
cascade1,
i_LodAlpha,
i_UndisplacedXZ,
pixelZ
);
half3 intensity = _Crest_FoamIntensityAlbedo;
ApplyFoamToSurface
(
albedo,
normal,
intensity,
_Crest_Occlusion,
_Crest_FoamSmoothness,
_Crest_Specular,
underwater,
o_Albedo,
o_NormalWS,
o_Emission,
o_Occlusion,
o_Smoothness,
o_Specular
);
// We will use this for shadow casting.
foam = albedo;
}
// Albedo
if (_Crest_AlbedoEnabled)
{
const float foamMask = _Crest_AlbedoIgnoreFoam ? (1.0 - saturate(foam)) : 1.0;
o_Albedo = lerp(o_Albedo, albedo.rgb, albedo.a * foamMask);
o_Emission *= 1.0 - albedo.a * foamMask;
}
// Alpha
{
#ifndef CREST_SHADOWPASS
#if d_Transparent
// Feather at intersection. Cannot be used for shadows since depth is not available.
o_Alpha = saturate((sceneZ - pixelZ) / 0.2);
#endif
#endif
// This keyword works for all RPs despite BIRP having prefixes in serialised data.
#if _ALPHATEST_ON
#if CREST_SHADOWPASS
o_Alpha = max(foam, albedo.a) - _Crest_ShadowCasterThreshold;
#endif
// Add 0.5 bias for LOD blending and texel resolution correction. This will help to
// tighten and smooth clipped edges.
o_Alpha -= ClipSurface(i_PositionWS.xz) > 0.5 ? 2.0 : 0.0;
#endif // _ALPHATEST_ON
// Specular in HDRP is still affected outside the 0-1 alpha range.
o_Alpha = min(o_Alpha, 1.0);
}
SetUpFog
(
underwater,
i_PositionWS,
1.0, // N/A: multiplier for fog nodes
sceneDistance - negativeFog,
i_ViewDirectionWS,
positionSS
);
}
m_CrestNameSpaceEnd
#endif // SHADERGRAPH_PREVIEW
void Fragment_float(m_Properties)
{
#if SHADERGRAPH_PREVIEW
o_Albedo = 0.0;
o_NormalWS = half3(0.0, 1.0, 0.0);
o_Specular = 0.0;
o_Emission = 0.0;
o_Smoothness = 0.7;
o_Occlusion = 1.0;
o_Alpha = 1.0;
#else // SHADERGRAPH_PREVIEW
m_Crest::Fragment
(
i_UndisplacedXZ,
i_LodAlpha,
i_WaterLevelOffset,
i_WaterLevelDerivatives,
i_Flow,
i_ViewDirectionWS,
i_Facing,
i_SceneColor,
i_SceneDepthRaw,
i_ScreenPosition,
i_ScreenPositionRaw,
i_PositionWS,
i_PositionVS,
i_StaticLightMapUV,
o_Albedo,
o_NormalWS,
o_Specular,
o_Emission,
o_Smoothness,
o_Occlusion,
o_Alpha
);
#endif // SHADERGRAPH_PREVIEW
}
#undef m_Properties
#endif // SHADERPASS