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

546 lines
19 KiB
HLSL

// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
#ifndef CREST_CASCADE_INCLUDED
#define CREST_CASCADE_INCLUDED
// Fix Unity macro leaks.
#undef _Weight
#ifdef SHADER_API_PSSL
#define m_ConstantReturn const
#else
#define m_ConstantReturn
#endif
#define m_SanitizeAbsorption(x) x
#define m_SanitizeAlbedo(x) x
#define m_SanitizeAnimatedWaves(x) x
#define m_SanitizeClip(x) x
// Infinity is unsafe, as it causes NaNs if multiplied by zero.
#define m_SanitizeDepth(x) max(x, -m_FloatMaximum)
#define m_SanitizeDynamicWaves(x) x
#define m_SanitizeFlow(x) x
#define m_SanitizeFoam(x) x
#define m_SanitizeLevel(x) x
#define m_SanitizeScattering(x) x
#define m_SanitizeShadow(x) x
#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"
#define m__MakeCascade(name, source) \
static Cascade Make##name##source(uint i_Index) \
{ \
float4 perType = g_Crest_SamplingParameters##name; \
float4 perSlice = g_Crest_SamplingParametersCascade##name##source[i_Index]; \
Cascade result; \
result._Texture = g_Crest_Cascade##name##source; \
result._SamplingParameters = g_Crest_SamplingParametersCascade##name##source; \
result._Index = i_Index; \
result._PositionSnapped = perSlice.xy; \
result._Texel = perSlice.z; \
result._Resolution = perType.y; \
result._OneOverResolution = perType.z; \
result._Count = perType.x; \
return result; \
} \
#define m_MakeCascadeCopy(name) \
static Cascade Make##name(uint i_Index, Cascade i_Cascade) \
{ \
float4 perType = g_Crest_SamplingParameters##name; \
float4 perSlice = i_Cascade._SamplingParameters[i_Index]; \
Cascade result; \
result._Texture = i_Cascade._Texture; \
result._SamplingParameters = i_Cascade._SamplingParameters; \
result._Index = i_Index; \
result._PositionSnapped = perSlice.xy; \
result._Texel = perSlice.z; \
result._Resolution = perType.y; \
result._OneOverResolution = perType.z; \
result._Count = perType.x; \
return result; \
}
#define m__MakeCascadeShared(source) \
static Cascade Make##source(uint i_Index) \
{ \
const float4 perAll = g_Crest_CascadeData##source[i_Index]; \
Cascade result; \
result._Index = i_Index; \
result._Scale = perAll.x; \
result._Weight = perAll.y; \
result._MaximumWavelength = perAll.z; \
return result; \
}
#define m_MakeCascade(name) m__MakeCascade(name,)
#define m_MakeCascadePrevious(name) m__MakeCascade(name, Source)
#define m_MakeCascadeShared m__MakeCascadeShared()
#define m_MakeCascadeSharedPrevious m__MakeCascadeShared(Source)
#define m_Sample(name, type, swizzle) \
type Sample##name(const float2 i_Position) m_ConstantReturn \
{ \
type result = Sample(i_Position)swizzle; \
result = m_Sanitize##name(result); \
return result; \
} \
type Sample##name(const float3 i_UV) m_ConstantReturn \
{ \
return Sample(i_UV)swizzle; \
} \
type Sample##name(const uint2 i_ID) m_ConstantReturn \
{ \
return Sample(i_ID)swizzle; \
} \
type Sample##name##Overflow(const float2 i_Position, const float i_Border) m_ConstantReturn \
{ \
type result = 0.0; \
const float3 uv = WorldToUV(i_Position); \
const half2 r = abs(uv.xy - 0.5); \
const half rMax = 0.5 - _OneOverResolution * i_Border; \
if (max(r.x, r.y) <= rMax) \
{ \
result = Sample##name(uv); \
} \
else if ((_Index + 1) < _Count) \
{ \
const Cascade cascade = Cascade::Make##name(_Index + 1, this); \
const float3 uv = cascade.WorldToUV(i_Position); \
const half2 r = abs(uv.xy - 0.5); \
const half rMax = 0.5 - cascade._OneOverResolution * i_Border; \
if (max(r.x, r.y) <= rMax) \
{ \
result = Sample##name(uv); \
} \
} \
return result; \
} \
type Sample##name##Overflow(const float3 i_UV, const float i_Border) m_ConstantReturn \
{ \
type result = 0.0; \
const half2 r = abs(i_UV.xy - 0.5); \
const half rMax = 0.5 - _OneOverResolution * i_Border; \
if (max(r.x, r.y) <= rMax) \
{ \
result = Sample##name(i_UV); \
} \
else if ((_Index + 1) < _Count) \
{ \
const Cascade cascade = Cascade::Make##name(_Index + 1, this); \
const float3 uv = cascade.WorldToUV(UVToWorld(i_UV)); \
const half2 r = abs(uv.xy - 0.5); \
const half rMax = 0.5 - cascade._OneOverResolution * i_Border; \
if (max(r.x, r.y) <= rMax) \
{ \
result = Sample##name(uv); \
} \
} \
return result; \
}
#define m_SampleWeighted(name, type) \
void Sample##name(const float2 i_Position, const float i_Weight, inout type io_##name) m_ConstantReturn \
{ \
io_##name += Sample##name(i_Position) * i_Weight; \
} \
void Sample##name(const float3 i_UV, const float i_Weight, inout type io_##name) m_ConstantReturn \
{ \
io_##name += Sample##name(i_UV) * i_Weight; \
} \
void Sample##name##Overflow(const float2 i_Position, const float i_Border, const float i_Weight, inout type io_##name) m_ConstantReturn \
{ \
io_##name += Sample##name##Overflow(i_Position, i_Border) * i_Weight; \
}
m_CrestNameSpace
struct Cascade
{
Texture2DArray _Texture;
float _Index;
float2 _PositionSnapped;
float _Resolution;
float _Count;
float _OneOverResolution;
float _Texel;
float _MaximumWavelength;
float _Scale;
float _Weight;
// For copy constructor.
float4 _SamplingParameters[MAX_LOD_COUNT];
m_MakeCascadeShared
m_MakeCascadeSharedPrevious
static Cascade Make(const uint i_Index, bool i_Previous)
{
const float4 perAll = i_Previous ? g_Crest_CascadeDataSource[i_Index] : g_Crest_CascadeData[i_Index];
Cascade result;
result._Index = i_Index;
result._Scale = perAll.x;
result._Weight = perAll.y;
result._MaximumWavelength = perAll.z;
return result;
}
m_MakeCascade(Absorption)
m_MakeCascadeCopy(Absorption)
m_MakeCascade(Albedo)
m_MakeCascadeCopy(Albedo)
m_MakeCascade(AnimatedWaves)
m_MakeCascadeCopy(AnimatedWaves)
m_MakeCascadePrevious(AnimatedWaves)
m_MakeCascade(Clip)
m_MakeCascadeCopy(Clip)
m_MakeCascade(Depth)
m_MakeCascadeCopy(Depth)
m_MakeCascade(DynamicWaves)
m_MakeCascadeCopy(DynamicWaves)
m_MakeCascadePrevious(DynamicWaves)
m_MakeCascade(Flow)
m_MakeCascadeCopy(Flow)
m_MakeCascade(Foam)
m_MakeCascadeCopy(Foam)
m_MakeCascadePrevious(Foam)
m_MakeCascade(Level)
m_MakeCascadeCopy(Level)
m_MakeCascade(Scattering)
m_MakeCascadeCopy(Scattering)
m_MakeCascade(Shadow)
m_MakeCascadeCopy(Shadow)
m_MakeCascadePrevious(Shadow)
// Convert compute shader id to uv texture coordinates
float3 IDToUV(const uint2 i_ID) m_ConstantReturn
{
return float3((i_ID + 0.5) / _Resolution, _Index);
}
float2 UVToWorld(const float3 i_UV) m_ConstantReturn
{
return _Texel * _Resolution * (i_UV.xy - 0.5) + _PositionSnapped;
}
float3 WorldToUV(const float2 i_Position) m_ConstantReturn
{
return float3((i_Position - _PositionSnapped) / (_Texel * _Resolution) + 0.5, _Index);
}
float2 IDToWorld(const uint2 i_ID) m_ConstantReturn
{
return UVToWorld(IDToUV(i_ID));
}
bool IsOutOfBounds(float2 uv, float offset) m_ConstantReturn
{
const half2 r = abs(uv - 0.5);
const half rMax = 0.5 - _OneOverResolution * offset;
return max(r.x, r.y) > rMax;
}
half4 Sample(const float3 i_UV) m_ConstantReturn
{
return _Texture.SampleLevel(LODData_linear_clamp_sampler, i_UV, 0.0);
}
half4 Sample(const Texture2DArray i_Texture, const float3 i_UV) m_ConstantReturn
{
return i_Texture.SampleLevel(LODData_linear_clamp_sampler, i_UV, 0.0);
}
half4 Sample(const float2 i_Position) m_ConstantReturn
{
return Sample(WorldToUV(i_Position));
}
half4 Sample(const Texture2DArray i_Texture, const float2 i_Position) m_ConstantReturn
{
return Sample(i_Texture, WorldToUV(i_Position));
}
half4 Sample(const uint2 i_ID) m_ConstantReturn
{
return Sample(IDToUV(i_ID));
}
half4 Sample(const Texture2DArray i_Texture, const uint2 i_ID) m_ConstantReturn
{
return Sample(i_Texture, IDToUV(i_ID));
}
float3 Internal_WrapToNextSlice(float3 i_uv, float i_overflowed) m_ConstantReturn
{
// Next slice is twice the size so half the coordinates to match position.
float overflow = 0.5 * i_overflowed;
i_uv = float3((i_uv.xy - overflow) * (1.0 - overflow) + overflow, i_uv.z + i_overflowed);
return i_uv;
}
// Wraps to next slice if coordinates outside of range.
float3 WrapToNextSlice(float3 i_uv) m_ConstantReturn
{
return Internal_WrapToNextSlice(i_uv, any(i_uv.xy > 1.0) || any(i_uv.xy < 0.0));
}
// Wraps to next slice if coordinates outside of range.
float3 WrapToNextSlice(float3 i_uv, float i_depth) m_ConstantReturn
{
return Internal_WrapToNextSlice(i_uv, any(i_uv.xy > 1.0) || any(i_uv.xy < 0.0) && i_uv.z + 1.0 < i_depth);
}
m_Sample(Absorption, half3, .xyz)
m_SampleWeighted(Absorption, half3)
m_Sample(Albedo, half4, )
m_SampleWeighted(Albedo, half4)
m_Sample(AnimatedWaves, half4, )
m_SampleWeighted(AnimatedWaves, float4) // Use float because parameter is position
m_Sample(Clip, half, .x)
m_SampleWeighted(Clip, half)
m_Sample(Depth, half2, .xy)
m_SampleWeighted(Depth, half2)
m_Sample(DynamicWaves, half2, .xy)
m_Sample(Flow, half2, .xy)
m_SampleWeighted(Flow, half2)
m_Sample(Foam, half, .x)
m_SampleWeighted(Foam, half)
m_Sample(Level, half, .x)
m_SampleWeighted(Level, half)
m_Sample(Scattering, half3, .xyz)
m_SampleWeighted(Scattering, half3)
m_Sample(Shadow, half2, .xy)
m_SampleWeighted(Shadow, half2)
float3 SampleDisplacement(const float2 i_Position) m_ConstantReturn
{
float4 position = SampleAnimatedWaves(i_Position);
position.y += position.w;
return position.xyz;
}
void SampleDisplacement(const float2 i_Position, const float i_Weight, inout float3 io_Position) m_ConstantReturn
{
io_Position += SampleDisplacement(i_Position).xyz * i_Weight;
}
half3 SampleWaveDisplacement(const float2 i_Position) m_ConstantReturn
{
return SampleAnimatedWaves(i_Position).xyz;
}
half3 SampleWaveDisplacement(const float3 i_UV) m_ConstantReturn
{
return SampleAnimatedWaves(i_UV).xyz;
}
half4 __SampleDisplacements(const float2 i_Position, out float3 o_DisplacementX, out float3 o_DisplacementZ) m_ConstantReturn
{
const float3 uv = WorldToUV(i_Position);
const half4 displacement = SampleAnimatedWaves(uv);
const float3 dd = float3(_OneOverResolution, 0.0, _Texel);
o_DisplacementX = dd.zyy + SampleWaveDisplacement(uv + dd.xyy);
o_DisplacementZ = dd.yyz + SampleWaveDisplacement(uv + dd.yxy);
return displacement;
}
void SampleDisplacement(const float2 i_Position, const float i_Weight, inout float3 io_Position, inout half2 io_Derivatives, inout half io_LevelOffset) m_ConstantReturn
{
float3 uv = WorldToUV(i_Position);
float4 position = SampleAnimatedWaves(uv);
io_LevelOffset += position.w * i_Weight;
io_Position += position.xyz * i_Weight;
// Derivatives
{
// Compute derivative of water level - needed to get base normal of water. Water
// normal, normal map etc is then added to base normal.
const float2 dd = float2(_OneOverResolution, 0.0);
const float xOffset = SampleAnimatedWaves(uv + dd.xyy).w;
const float zOffset = SampleAnimatedWaves(uv + dd.yxy).w;
// TODO: Is weight in correct position?
io_Derivatives.x += i_Weight * (xOffset - position.w) / _Texel;
io_Derivatives.y += i_Weight * (zOffset - position.w) / _Texel;
}
}
void SampleDisplacement(const float2 i_Position, const float i_Weight, inout float3 io_Position, inout half2 io_Derivatives) m_ConstantReturn
{
half offset = 0.0;
SampleDisplacement(i_Position, i_Weight, io_Position, io_Derivatives, offset);
io_Position.y += offset;
}
half3 SampleDisplacement(const float2 i_Position, out half o_Determinent) m_ConstantReturn
{
float3 xDisplacement; float3 zDisplacement;
half4 displacement = __SampleDisplacements(i_Position, xDisplacement, zDisplacement);
o_Determinent = __ComputeDisplacementDeterminant(displacement.xyz, xDisplacement, zDisplacement);
displacement.y += displacement.w;
return displacement.xyz;
}
half __ComputeDisplacementDeterminant(half3 i_Displacement, float3 i_DisplacementX, float3 i_DisplacementZ) m_ConstantReturn
{
const float2x2 jacobian = (float4(i_DisplacementX.xz, i_DisplacementZ.xz) - i_Displacement.xzxz) / _Texel;
// Determinant is < 1 for pinched, < 0 for overlap/inversion and > 1 for stretched.
return determinant(jacobian);
}
half2 __ComputeDisplacementNormals(half3 i_Displacement, float3 i_DisplacementX, float3 i_DisplacementZ) m_ConstantReturn
{
float3 xProduct = cross(i_DisplacementZ - i_Displacement, i_DisplacementX - i_Displacement);
// Situation could arise where cross returns 0, prob when arguments are two aligned vectors. This
// resulted in NaNs and flashing screen in HDRP. Force normal to point upwards as the only time
// it should point downwards is for underwater (handled elsewhere) or in surface inversions which
// should not happen for well tweaked waves, and look broken anyway.
xProduct.y = max(xProduct.y, 0.0001);
return normalize(xProduct).xz;
}
// TODO: Rename
void SampleNormals(const float2 i_Position, const float i_Weight, inout half2 io_Normal, inout half io_Determinant) m_ConstantReturn
{
float3 xDisplacement; float3 zDisplacement;
half3 displacement = __SampleDisplacements(i_Position, xDisplacement, zDisplacement).xyz;
io_Normal += __ComputeDisplacementNormals(displacement, xDisplacement, zDisplacement) * i_Weight;
io_Determinant += __ComputeDisplacementDeterminant(displacement, xDisplacement, zDisplacement) * i_Weight;
}
half SampleSceneHeight(const float2 i_Position) m_ConstantReturn
{
return SampleDepth(i_Position).x;
}
void SampleSceneHeight(const float2 i_Position, const float i_Weight, inout half io_Height) m_ConstantReturn
{
io_Height += SampleSceneHeight(i_Position) * i_Weight;
}
half SampleShorelineDistance(const float2 i_Position) m_ConstantReturn
{
return Sample(i_Position).y;
}
half SampleShorelineDistance(const float3 i_UV) m_ConstantReturn
{
return Sample(i_UV).y;
}
half SampleShorelineDistance(const uint2 i_ID) m_ConstantReturn
{
return Sample(i_ID).y;
}
void SampleShorelineDistance(const float2 i_Position, const float i_Weight, inout half io_Distance) m_ConstantReturn
{
io_Distance += SampleShorelineDistance(i_Position) * i_Weight;
}
half SampleSignedDepthFromSeaLevel(const float2 i_Position) m_ConstantReturn
{
return g_Crest_WaterCenter.y - SampleSceneHeight(i_Position);
}
half2 SampleSignedDepthFromSeaLevelAndDistance(const float2 i_Position) m_ConstantReturn
{
half2 value = SampleDepth(i_Position);
value.x = g_Crest_WaterCenter.y - value.x;
return value;
}
void SampleSignedDepthFromSeaLevel(const float2 i_Position, const float i_Weight, inout half io_Depth) m_ConstantReturn
{
io_Depth += (g_Crest_WaterCenter.y - SampleSceneHeight(i_Position)) * i_Weight;
}
// Perform iteration to invert the displacement vector field - find position that displaces to query position.
float2 SampleInvertedDisplacement(const float2 i_Position) m_ConstantReturn
{
float2 inverted = i_Position;
for (uint i = 0; i < 4; i++)
{
const float2 displacement = SampleAnimatedWaves(inverted).xz;
const float2 error = (inverted + displacement) - i_Position;
inverted -= error;
}
return inverted;
}
half3 SampleDisplacementFromUndisplaced(const float2 i_Position) m_ConstantReturn
{
return SampleDisplacement(SampleInvertedDisplacement(i_Position));
}
half3 SampleDynamicWavesDisplacement(const float2 i_Position, const float i_HorizontalDisplace, const float i_DisplaceClamp) m_ConstantReturn
{
const float3 uv = WorldToUV(i_Position);
return SampleDynamicWavesDisplacement(uv, i_HorizontalDisplace, i_DisplaceClamp);
}
half3 SampleDynamicWavesDisplacement(const float3 i_UV, const float i_HorizontalDisplace, const float i_DisplaceClamp) m_ConstantReturn
{
const float3 uv = i_UV;
half3 displacement = 0.0;
displacement.y = Sample(uv).x;
const float2 invRes = float2(_OneOverResolution, 0.0);
const half waveSimY_px = Sample(uv + invRes.xyy).x;
const half waveSimY_nx = Sample(uv - invRes.xyy).x;
const half waveSimY_pz = Sample(uv + invRes.yxy).x;
const half waveSimY_nz = Sample(uv - invRes.yxy).x;
// Compute displacement from gradient of water surface - discussed in issue #18 and then in issue #47.
// For gerstner waves, horizontal displacement is proportional to derivative of
// vertical displacement multiplied by the wavelength.
const float wavelength_mid = 2.0 * _Texel * 1.5;
const float wavevector = 2.0 * 3.14159 / wavelength_mid;
const float2 dydx = (float2(waveSimY_px, waveSimY_pz) - float2(waveSimY_nx, waveSimY_nz)) / (2.0 * _Texel);
displacement.xz = i_HorizontalDisplace * dydx / wavevector;
const float maxDisp = _Texel * i_DisplaceClamp;
displacement.xz = clamp(displacement.xz, -maxDisp, maxDisp);
return displacement;
}
};
float2 DataIDToInputUV
(
const float2 i_ID,
const Cascade i_Cascade,
const float2 i_Position,
const float2 i_Rotation,
const float2 i_Size
)
{
const float2 position = i_Cascade.IDToWorld(i_ID);
float2 uv = (position - i_Position) / i_Size;
// Clockwise transform rotation.
uv = uv.x * float2(i_Rotation.y, -i_Rotation.x) + uv.y * i_Rotation;
uv += 0.5;
return uv;
}
m_CrestNameSpaceEnd
#undef m__MakeCascade
#undef m_MakeCascade
#undef m_MakeCascadePrevious
#undef m_Sample
#undef m_SampleWeighted
#undef m_ComputeDepth
#endif // CREST_CASCADE_INCLUDED