Files
3d-bxqz/Packages/com.waveharmonic.crest.shallow-water/Runtime/Shaders/ShallowWaterSimulation.compute
zhangjiajia 81ffaaeca6 'push'
2026-05-06 16:56:59 +08:00

1117 lines
35 KiB
Plaintext

// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
#pragma kernel CrestInitialize
#pragma kernel CrestInitializeGroundHeight
#pragma kernel CrestAdvect
#pragma kernel CrestUpdateHeight
#pragma kernel CrestHeightOvershootReduction
#pragma kernel CrestUpdateVelocity
#pragma kernel CrestBlurHeight
#pragma kernel CrestBlur
#pragma kernel CrestMaskEdge
#pragma kernel CrestExpandEdge
#pragma kernel CrestFinalizeHeight
#pragma kernel CrestInjectShape
#pragma kernel CrestInjectLevel
#pragma kernel CrestInjectFoam
#pragma kernel CrestInjectFlow
#pragma kernel CrestBakeLevel
#pragma kernel CrestBakeFoam
#pragma kernel CrestBakeFlow
#include "HLSLSupport.cginc"
#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/Helpers.hlsl"
#include "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Cascade.hlsl"
#define FLT_MIN 1.175494351e-38
SamplerState linear_clamp_sampler;
Texture2D<float> _Crest_LevelSource;
Texture2D<float> _Crest_HeightSource;
RWTexture2D<float> _Crest_HeightTarget;
Texture2D<float> _Crest_VelocityXSource;
RWTexture2D<float> _Crest_VelocityXTarget;
Texture2D<float> _Crest_VelocityYSource;
RWTexture2D<float> _Crest_VelocityYTarget;
Texture2D<float> _Crest_GroundHeightSource;
RWTexture2D<float> _Crest_GroundHeightTarget;
Texture2D<float> _Crest_MaskSource;
RWTexture2D<float> _Crest_MaskTarget;
// Injector Targets.
RWTexture2DArray<float> _Crest_FoamTarget;
RWTexture2DArray<float2> _Crest_FlowTarget;
RWTexture2DArray<float4> _Crest_ShapeTarget;
RWTexture2DArray<float> _Crest_LevelTarget;
// Bake Targets.
RWTexture2D<float> _Crest_FoamBakeTarget;
RWTexture2D<float2> _Crest_FlowBakeTarget;
RWTexture2D<float> _Crest_LevelBakeTarget;
// Depth Probe.
Texture2D<float2> _Crest_DepthProbe;
CBUFFER_START(CrestBuffer)
float _Crest_SimDeltaTime;
float _Crest_SimulationDeltaTime;
float _Crest_DomainWidth;
float3 _Crest_DomainOrigin;
uint _Crest_Resolution;
float _Crest_DrainWaterAtBoundaries;
float _Crest_Evaporation;
float _Crest_Friction;
float _Crest_MaximumVelocity;
float _Crest_AddAdditionalWater;
float _Crest_TexelSize;
bool _Crest_MacCormackAdvection;
bool _Crest_MacCormackAdvectionForHeight;
bool _Crest_UpwindHeight;
bool _Crest_DepthLimiter;
bool _Crest_InjectLevel;
float _Crest_OvershootReductionStrength;
float _Crest_ShallowMinimumDepth;
float _Crest_ShallowMaximumDepth;
float _Crest_BlendPushUpStrength;
float _Crest_InjectionStrength;
float _Crest_InjectionMaximum;
float _Crest_Weight;
bool _Crest_SampleFromDepthProbe;
bool _Crest_SampleFromLevelInputs;
float _Crest_DepthProbeHeightOffset;
float _Crest_DepthProbeResolution;
float _Crest_MarginWidth;
CBUFFER_END
m_CrestNameSpace
float2 IdToWorldXZ_H(uint2 id)
{
// H grid is centered
return ((id + 0.5) / _Crest_Resolution - 0.5) * _Crest_DomainWidth + _Crest_DomainOrigin.xz;
}
float2 IdToWorldXZ_GroundHeight(uint2 id)
{
// H and GroundHeight are aligned
return IdToWorldXZ_H(id);
}
float2 IdToWorldXZ_Vx(uint2 id)
{
// Vx grid is offset right half a texel
return ((id + float2(1.0, 0.5)) / _Crest_Resolution - 0.5) * _Crest_DomainWidth + _Crest_DomainOrigin.xz;
}
float2 IdToWorldXZ_Vy(uint2 id)
{
// Vy grid is offset forward half a texel
return ((id + float2(0.5, 1.0)) / _Crest_Resolution - 0.5) * _Crest_DomainWidth + _Crest_DomainOrigin.xz;
}
float2 WorldXZToUv_H(float2 worldXZ)
{
// H grid is centered
return (worldXZ - _Crest_DomainOrigin.xz) / _Crest_DomainWidth + 0.5;
}
float2 WorldXZToUv_GroundHeight(float2 worldXZ)
{
// Aligned to H
return WorldXZToUv_H(worldXZ);
}
float2 WorldXZToUv_Vx(float2 worldXZ)
{
// Vx grid is offset right half a texel
return (worldXZ - _Crest_DomainOrigin.xz) / _Crest_DomainWidth + 0.5 - float2(0.5 / _Crest_Resolution, 0.0);
}
float2 WorldXZToUv_Vy(float2 worldXZ)
{
// Vy grid is offset forward half a texel
return (worldXZ - _Crest_DomainOrigin.xz) / _Crest_DomainWidth + 0.5 - float2(0.0, 0.5 / _Crest_Resolution);
}
float SampleWaterLevel(float2 worldXZ)
{
return _Crest_LevelSource.SampleLevel(linear_clamp_sampler, WorldXZToUv_H(worldXZ), 0.0).x;
}
float SampleH(float2 worldXZ)
{
return _Crest_HeightSource.SampleLevel(linear_clamp_sampler, WorldXZToUv_H(worldXZ), 0.0).x;
}
float SampleGroundHeight(float2 worldXZ)
{
return _Crest_GroundHeightSource.SampleLevel(linear_clamp_sampler, WorldXZToUv_GroundHeight(worldXZ), 0.0).x;
}
float SampleVx0(float2 worldXZ)
{
return _Crest_VelocityXSource.SampleLevel(linear_clamp_sampler, WorldXZToUv_Vx(worldXZ), 0.0).x;
}
float SampleVy0(float2 worldXZ)
{
return _Crest_VelocityYSource.SampleLevel(linear_clamp_sampler, WorldXZToUv_Vy(worldXZ), 0.0).x;
}
//
// Initialize
//
void Initialize(const uint3 i_ID)
{
const uint2 id = i_ID.xy;
const float2 worldXZ = IdToWorldXZ_H(id);
uint slice0, slice1; float alpha;
PosToSliceIndices(worldXZ, 0.0, g_Crest_LodCount - 1.0, g_Crest_WaterScale, slice0, slice1, alpha);
float h = g_Crest_WaterCenter.y;
const float weight0 = (1.0 - alpha) * g_Crest_CascadeData[slice0].y;
const float weight1 = (1.0 - weight0) * g_Crest_CascadeData[slice1].y;
if (_Crest_InjectLevel)
{
if (_Crest_SampleFromLevelInputs)
{
h += SampleWaterLevel(worldXZ);
}
else
{
h +=
weight0 * Cascade::MakeLevel(slice0).SampleLevel(worldXZ) +
weight1 * Cascade::MakeLevel(slice1).SampleLevel(worldXZ);
}
}
h -= _Crest_DomainOrigin.y;
h = max(0.0, h - SampleGroundHeight(worldXZ));
h += _Crest_AddAdditionalWater;
_Crest_HeightTarget[id] = h;
_Crest_VelocityXTarget[id] = 0.0;
_Crest_VelocityYTarget[id] = 0.0;
}
// Called first.
void InitializeGroundHeight(const uint3 i_ID)
{
const uint2 id = i_ID.xy;
const float2 worldXZ = IdToWorldXZ_GroundHeight(id);
uint slice0, slice1; float alpha;
PosToSliceIndices(worldXZ, 0.0, g_Crest_LodCount - 1.0, g_Crest_WaterScale, slice0, slice1, alpha);
const float weight0 = (1.0 - alpha) * g_Crest_CascadeData[slice0].y;
const float weight1 = (1.0 - weight0) * g_Crest_CascadeData[slice1].y;
float g;
if (_Crest_SampleFromDepthProbe)
{
// Sample from the Depth Probe directly otherwise we bake in LOD losses.
g = _Crest_DepthProbe.SampleLevel(linear_clamp_sampler, id.xy / (float)_Crest_Resolution, 0.0).x;
g += _Crest_DepthProbeHeightOffset;
}
else
{
g = weight0 * Cascade::MakeDepth(slice0).SampleSceneHeight(worldXZ) +
weight1 * Cascade::MakeDepth(slice1).SampleSceneHeight(worldXZ);
}
// LOD losses for height should be acceptable providing height is flat (which it should be).
// Bake water level into ground by offsetting ground with water level.
if (!_Crest_InjectLevel)
{
g -=
weight0 * Cascade::MakeLevel(slice0).SampleLevel(worldXZ) +
weight1 * Cascade::MakeLevel(slice1).SampleLevel(worldXZ);
}
// Mask is directly based off depth at the moment.
float depth = g_Crest_WaterCenter.y - g;
_Crest_MaskTarget[id] = 1.0 - saturate((depth - _Crest_ShallowMinimumDepth) / _Crest_ShallowMaximumDepth);
g = max(0.0, g - _Crest_DomainOrigin.y);
_Crest_GroundHeightTarget[id] = g;
}
//
// Advect
//
void Advect(const uint3 id)
{
// Vx
if (id.z == 0)
{
const float2 worldXZ = IdToWorldXZ_Vx(id.xy);
// Advect forwards to predict advected position
const float2 worldXZAdvected_prediction = worldXZ - _Crest_SimulationDeltaTime * float2(SampleVx0(worldXZ), SampleVy0(worldXZ));
float2 worldXZAdvected = worldXZAdvected_prediction;
const float vxSemiLagrangian = SampleVx0(worldXZAdvected);
float vxAdvected = vxSemiLagrangian;
if (_Crest_MacCormackAdvection)
{
// Update prediction by advecting backwards to obtain a starting position and using the difference as an error term
// Advect forwards to predict original position
const float2 worldXZ_prediction = worldXZAdvected_prediction + _Crest_SimulationDeltaTime * float2(vxSemiLagrangian, SampleVy0(worldXZAdvected_prediction));
// Remove half of prediction error to yield average of both
worldXZAdvected -= (worldXZ_prediction - worldXZ) / 2.0;
float vxAdvectedNew = SampleVx0(worldXZAdvected);
const float4 vxsUsedForFirstSLStep = _Crest_VelocityXSource.Gather(linear_clamp_sampler, WorldXZToUv_Vx(worldXZ));
const float vxMin = min(min(vxsUsedForFirstSLStep[0], vxsUsedForFirstSLStep[1]), min(vxsUsedForFirstSLStep[2], vxsUsedForFirstSLStep[3]));
const float vxMax = max(max(vxsUsedForFirstSLStep[0], vxsUsedForFirstSLStep[1]), max(vxsUsedForFirstSLStep[2], vxsUsedForFirstSLStep[3]));
if (vxAdvectedNew >= vxMin && vxAdvectedNew <= vxMax)
{
vxAdvected = vxAdvectedNew;
}
}
// Abs to silence 3571 which for some reason cannot disable with pragma even
// HLSLSupport can do exactly that.
const float h_vx = SampleH(worldXZAdvected);
const float friction = _Crest_Friction * _Crest_SimulationDeltaTime / max(0.001f, pow(abs(h_vx), 1.333333f));
vxAdvected -= abs(vxAdvected) * vxAdvected * friction;
vxAdvected = clamp(vxAdvected, -_Crest_MaximumVelocity, _Crest_MaximumVelocity);
_Crest_VelocityXTarget[id.xy] = vxAdvected;
}
// Vy
if (id.z == 1)
{
const float2 worldXZ = IdToWorldXZ_Vy(id.xy);
// Advect forwards to predict advected position
const float2 worldXZAdvected_prediction = worldXZ - _Crest_SimulationDeltaTime * float2(SampleVx0(worldXZ), SampleVy0(worldXZ));
float2 worldXZAdvected = worldXZAdvected_prediction;
const float vySemiLagrangian = SampleVy0(worldXZAdvected);
float vyAdvected = vySemiLagrangian;
if (_Crest_MacCormackAdvection)
{
// Update prediction by advecting backwards to obtain a starting position and using the difference as an error term
// Advect forwards to predict original position
const float2 worldXZ_prediction = worldXZAdvected_prediction + _Crest_SimulationDeltaTime * float2(SampleVx0(worldXZAdvected_prediction), vySemiLagrangian);
// Remove half of prediction error to yield average of both
worldXZAdvected -= (worldXZ_prediction - worldXZ) / 2.0;
float vyAdvectedNew = SampleVy0(worldXZAdvected);
const float4 vysUsedForFirstSLStep = _Crest_VelocityYSource.Gather(linear_clamp_sampler, WorldXZToUv_Vy(worldXZ));
const float vyMin = min(min(vysUsedForFirstSLStep[0], vysUsedForFirstSLStep[1]), min(vysUsedForFirstSLStep[2], vysUsedForFirstSLStep[3]));
const float vyMax = max(max(vysUsedForFirstSLStep[0], vysUsedForFirstSLStep[1]), max(vysUsedForFirstSLStep[2], vysUsedForFirstSLStep[3]));
if (vyAdvectedNew >= vyMin && vyAdvectedNew <= vyMax)
{
vyAdvected = vyAdvectedNew;
}
}
// Abs to silence 3571 which for some reason cannot disable with pragma even
// HLSLSupport can do exactly that.
const float h_vy = SampleH(worldXZAdvected);
const float friction = _Crest_Friction * _Crest_SimulationDeltaTime / max(0.001f, pow(abs(h_vy), 1.333333f));
vyAdvected -= abs(vyAdvected) * vyAdvected * friction;
vyAdvected = clamp(vyAdvected, -_Crest_MaximumVelocity, _Crest_MaximumVelocity);
_Crest_VelocityYTarget[id.xy] = vyAdvected;
}
// H
if (id.z == 2)
{
const float2 worldXZ = IdToWorldXZ_H(id.xy);
// Advect forwards to predict advected position
const float2 worldXZAdvected_prediction = worldXZ - _Crest_SimulationDeltaTime * float2(SampleVx0(worldXZ), SampleVy0(worldXZ));
float2 worldXZAdvected = worldXZAdvected_prediction;
if (_Crest_MacCormackAdvectionForHeight)
{
// Update prediction by advecting backwards to obtain a starting position and using the difference as an error term
// Advect forwards to predict original position
const float2 worldXZ_prediction = worldXZAdvected_prediction + _Crest_SimulationDeltaTime * float2(SampleVx0(worldXZAdvected_prediction), SampleVy0(worldXZAdvected_prediction));
// Remove half of prediction error to yield average of both
worldXZAdvected -= (worldXZ_prediction - worldXZ) / 2.0;
}
_Crest_HeightTarget[id.xy] = SampleH(worldXZAdvected);
}
}
//
// Update Height
//
float HeightAdjustment(uint2 id)
{
const float gravity = 9.81f;
float hn = _Crest_HeightSource[id + uint2(0, 1)];
float hs = _Crest_HeightSource[id - uint2(0, 1)];
float he = _Crest_HeightSource[id + uint2(1, 0)];
float hw = _Crest_HeightSource[id - uint2(1, 0)];
float h_avg = 0.25 * (hn + hs + he + hw);
float beta = 2.0;
float minThickness = beta * _Crest_TexelSize / (gravity * _Crest_SimulationDeltaTime);
return max(0.0, h_avg - minThickness);
}
void UpdateHeight(const uint3 i_ID)
{
const int2 id = i_ID.xy;
// Fix height at border to prevent instability.
if (i_ID.x == 0 || i_ID.y == 0 || i_ID.x == _Crest_Resolution - 1 || i_ID.y == _Crest_Resolution - 1)
{
_Crest_HeightTarget[id] = _Crest_HeightSource[id];
return;
}
float h = _Crest_HeightSource[id];
const uint x = id.x;
const uint y = id.y;
const float2 worldXZ = IdToWorldXZ_H(id);
float pump = 0.0;
if (h > 0.01)
{
// Drain water out at boundary of domain.
{
const float2 offset = abs(id / float(_Crest_Resolution) - 0.5);
pump += _Crest_DrainWaterAtBoundaries * smoothstep(0.4, 0.5, max(offset.x, offset.y));
}
// Blend waves into simulation. Pumps water in.
{
uint slice0, slice1; float alpha;
PosToSliceIndices(worldXZ, 0.0, g_Crest_LodCount - 1.0, g_Crest_WaterScale, slice0, slice1, alpha);
const float weight0 = (1.0 - alpha) * g_Crest_CascadeData[slice0].y;
const float weight1 = (1.0 - weight0) * g_Crest_CascadeData[slice1].y;
float waterY = g_Crest_WaterCenter.y;
// If injecting to height then ignore waves.
if (_Crest_InjectLevel)
{
if (_Crest_SampleFromLevelInputs)
{
waterY += SampleWaterLevel(worldXZ);
}
else
{
waterY +=
weight0 * Cascade::MakeLevel(slice0).SampleLevel(worldXZ) +
weight1 * Cascade::MakeLevel(slice1).SampleLevel(worldXZ);
}
}
else
{
waterY +=
weight0 * Cascade::MakeAnimatedWaves(slice0).SampleAnimatedWaves(worldXZ).y +
weight1 * Cascade::MakeAnimatedWaves(slice1).SampleAnimatedWaves(worldXZ).y;
}
float simulationY = h + _Crest_DomainOrigin.y + _Crest_GroundHeightSource[id];
if (waterY > simulationY)
{
// This is tricksy. Don't apply forces from waves in deep, but also don't apply in very shallow.
// The latter will mean that the SWS is held at sea level instead of being allowed to drop below.
// I tried asserting disp.y > somevalue but that led to a whole different set of issues. Therefore
// settle for a y=4x(1-x) weight that chooses the mid point of the mask blend. Seems to work.
float maskWeight = 1.0;
if (!_Crest_InjectLevel)
{
maskWeight = _Crest_MaskSource[id];
maskWeight *= 4.0 * (1.0 - maskWeight);
}
pump += _Crest_BlendPushUpStrength * maskWeight * (waterY - simulationY);
}
}
}
// Update height based on total flow in minus total flow out.
// Prevents wave reflections at domain boundary.
float drain = 30.0;
float vxp = (x == uint(_Crest_Resolution - 1)) ? drain : _Crest_VelocityXSource[id];
float vyp = (y == uint(_Crest_Resolution - 1)) ? drain : _Crest_VelocityYSource[id];
float vxm = (x == 0) ? -drain : _Crest_VelocityXSource[uint2(x - 1, y)];
float vym = (y == 0) ? -drain : _Crest_VelocityYSource[uint2(x, y - 1)];
// FIXME: Causes jitters which triggers foam.
float hvel;
if (_Crest_UpwindHeight)
{
// Get forward or backward diffs respectively.
float h = _Crest_HeightSource[id];
float2 hx = vxp <= 0.0
? float2(h, _Crest_HeightSource[id + uint2(1, 0)])
: float2(_Crest_HeightSource[id - uint2(1, 0)], h);
float2 hy = vyp <= 0.0
? float2(h, _Crest_HeightSource[id + uint2(0, 1)])
: float2(_Crest_HeightSource[id - uint2(0, 1)], h);
if (_Crest_DepthLimiter)
{
const float h_adj = HeightAdjustment(id);
hx -= h_adj;
hy -= h_adj;
}
hvel = -(vxp * hx[1] - vxm * hx[0] + vyp * hy[1] - vym * hy[0]) / _Crest_TexelSize;
}
else
{
const float dx = (vxp - vxm) / _Crest_TexelSize;
const float dy = (vyp - vym) / _Crest_TexelSize;
const float divergence = dx + dy;
if (_Crest_DepthLimiter)
{
hvel = -(h - HeightAdjustment(id)) * divergence;
}
else
{
hvel = -h * divergence;
}
}
h += _Crest_SimulationDeltaTime * hvel + pump - _Crest_Evaporation;
h = max(h, 0.0);
// // Dry - force to zero.
// if (h < 0.001) h = 0.0;
_Crest_HeightTarget[id] = h;
}
//
// Height Overshoot Reduction
//
void HeightOvershootReduction(const uint3 i_ID)
{
const uint2 id = i_ID.xy;
float h = _Crest_HeightSource[id];
if (any(id == 0) || any(id == _Crest_Resolution - 1))
{
// Don't do this at boundary
_Crest_HeightTarget[id] = h;
return;
}
float y = _Crest_HeightSource[id] + _Crest_GroundHeightSource[id];
float hn = _Crest_HeightSource[id + uint2(1, 0)];
float hs = _Crest_HeightSource[id - uint2(1, 0)];
float he = _Crest_HeightSource[id + uint2(1, 0)];
float hw = _Crest_HeightSource[id - uint2(1, 0)];
float yn = hn + _Crest_GroundHeightSource[id + uint2(1, 0)];
float ys = hs + _Crest_GroundHeightSource[id - uint2(1, 0)];
float ye = he + _Crest_GroundHeightSource[id + uint2(1, 0)];
float yw = hw + _Crest_GroundHeightSource[id - uint2(1, 0)];
float lambdaEdge = 2.0 * _Crest_TexelSize;
// X axis
if ((y - yw) > lambdaEdge && y > ye)
{
h += _Crest_OvershootReductionStrength * (max(0.0, 0.5 * (h + he)) - h);
}
if ((y - ye) > lambdaEdge && y > yw)
{
h += _Crest_OvershootReductionStrength * (max(0.0, 0.5 * (h + hw)) - h);
}
// Z axis
if ((y - ys) > lambdaEdge && y > yn)
{
h += _Crest_OvershootReductionStrength * (max(0.0, 0.5 * (h + hn)) - h);
}
if ((y - yn) > lambdaEdge && y > ys)
{
h += _Crest_OvershootReductionStrength * (max(0.0, 0.5 * (h + hs)) - h);
}
_Crest_HeightTarget[id] = h;
}
//
// Update Velocity
//
void UpdateVelocity(const uint3 i_ID)
{
const int2 id = i_ID.xy;
// Clear velocity at border to prevent water leaking.
if (i_ID.x == 0 || i_ID.y == 0 || i_ID.x == _Crest_Resolution - 1 || i_ID.y == _Crest_Resolution - 1)
{
_Crest_VelocityXTarget[id] = 0;
_Crest_VelocityYTarget[id] = 0;
return;
}
// Height before vel
const float2 worldXZ_H0 = IdToWorldXZ_H(id);
const float g0 = _Crest_GroundHeightSource[id];
const float h0 = _Crest_HeightSource[id];
const uint x = id.x;
const uint y = id.y;
const uint xp = min(x + 1, _Crest_Resolution - 1);
const uint yp = min(y + 1, _Crest_Resolution - 1);
const float gravity = 9.81f;
// Vx
{
float vx = _Crest_VelocityXTarget[id];
const float2 worldXZ_H1 = IdToWorldXZ_H(uint2(xp, y));
const float g1 = _Crest_GroundHeightSource[uint2(xp, y)];
const float h1 = _Crest_HeightSource[uint2(xp, y)];
const float hdiff = (h1 + g1) - (h0 + g0);
//if (fabs(hdiff) > maxDiff) hdiff = sign(hdiff) * maxDiff;
float accel = -gravity * hdiff / _Crest_TexelSize;
vx += _Crest_SimulationDeltaTime * accel;
// Kill velocity if totally dry
if (h0 < 0.001 && h1 < 0.001)
{
vx = 0.0;
}
else if (accel < 0 && h1 < 0.001)
{
// Don't produce acceleration from water flowing in from a dry cell
vx = 0.0;
}
else if (accel > 0 && h0 < 0.001)
{
// Don't produce acceleration from water flowing in from a dry cell
vx = 0.0;
}
_Crest_VelocityXTarget[id] = vx;
if (h0 < 0.001 && h1 < 0.001) _Crest_VelocityXTarget[id] = 0.0;
}
// Vy
{
float vy = _Crest_VelocityYTarget[id];
const float2 worldXZ_H1 = IdToWorldXZ_H(uint2(x, yp));
const float g1 = _Crest_GroundHeightSource[uint2(x, yp)];
const float h1 = _Crest_HeightSource[uint2(x, yp)];
const float hdiff = (h1 + g1) - (h0 + g0);
//if (fabs(hdiff) > maxDiff) hdiff = sign(hdiff) * maxDiff;
float accel = -gravity * hdiff / _Crest_TexelSize;
vy += _Crest_SimulationDeltaTime * accel;
// Kill velocity if totally dry
if (h0 < 0.001 && h1 < 0.001)
{
vy = 0.0;
}
else if (accel < 0 && h1 < 0.001)
{
// Don't produce acceleration from water flowing in from a dry cell
vy = 0.0;
}
else if (accel > 0 && h0 < 0.001)
{
// Don't produce acceleration from water flowing in from a dry cell
vy = 0.0;
}
_Crest_VelocityYTarget[id] = vy;
}
}
//
// Blur
//
void Blur(const uint3 i_ID)
{
const int2 id = i_ID.xy;
const int resolution = _Crest_Resolution;
float result = 0.0;
const int radius = 1;
float sampleCount = 0.0;
for (int y = -radius; y <= radius; y++)
{
for (int x = -radius; x <= radius; x++)
{
int2 idx = id + int2(x, y);
// Skip pixel if outside. Clamping coordinates produced bad values at edge.
if (any(idx < 0) || any(idx >= resolution)) continue;
result += _Crest_MaskSource[idx];
sampleCount += 1.0;
}
}
_Crest_MaskTarget[id] = result / sampleCount;
}
void BlurHeight(const uint3 i_ID)
{
const int2 id = i_ID.xy;
float result = 0.0;
float twt = 0.0;
const int rad = 1;
float hcenter = _Crest_HeightSource[id];
if (hcenter < 0.001)
{
_Crest_HeightTarget[id] = 0.0;
return;
}
for (int y = -rad; y <= rad; y++)
{
for (int x = -rad; x <= rad; x++)
{
int2 idx;
// Need to cast otherwise load will wrap around at edges.
idx.x = clamp(id.x + x, 0, (int)_Crest_Resolution - 1);
idx.y = clamp(id.y + y, 0, (int)_Crest_Resolution - 1);
float h = _Crest_HeightSource[idx];
if (h >= 0.001)
{
result += h + _Crest_GroundHeightSource[idx];
twt += 1.0;
}
}
}
if (twt > 0.0)
{
float blurredY = result / twt;
_Crest_HeightTarget[id] = max(blurredY - _Crest_GroundHeightSource[id], 0.0);
}
else
{
_Crest_HeightTarget[id] = hcenter;
}
}
void AddAboveGroundHeight(const uint2 i_Coordinates, const float i_Ground, inout float io_Height, inout float io_Samples)
{
float h = _Crest_HeightSource[i_Coordinates];
if (h > FLT_MIN)
{
// Make sure we do not push water through ground.
if (h < i_Ground)
{
io_Height += h;
io_Samples += 1.0;
}
}
}
void MaskOut(const uint2 i_Coordinates, const float i_HeightBelowGround, inout bool i_Mask)
{
const float h = _Crest_HeightSource[i_Coordinates];
if (h > 0.0)
{
const float g = _Crest_GroundHeightSource[i_Coordinates];
i_Mask = i_Mask || (i_HeightBelowGround < (h + g));
}
}
void MaskEdge(const uint3 i_ID)
{
const uint2 id = i_ID.xy;
float h = _Crest_HeightSource[id];
// Mask above ground height so we do not process it in expand edge.
bool mask = h > 0.0;
// Add ground height to water height to get world height of surface.
h += _Crest_GroundHeightSource[id];
if (!mask)
{
const int3 dd = int3(1, -1, 0);
MaskOut(id + dd.xx, h, mask);
MaskOut(id + dd.xy, h, mask);
MaskOut(id + dd.xz, h, mask);
MaskOut(id + dd.yx, h, mask);
MaskOut(id + dd.yy, h, mask);
MaskOut(id + dd.yz, h, mask);
MaskOut(id + dd.zx, h, mask);
MaskOut(id + dd.zy, h, mask);
}
if (mask)
{
// Move to world space.
h += _Crest_DomainOrigin.y;
// Make relative to sea level.
h -= g_Crest_WaterCenter.y;
}
else
{
h = FLT_MIN;
}
_Crest_HeightTarget[id] = h;
}
void ExpandEdge(const uint3 i_ID)
{
const uint2 id = i_ID.xy;
const int3 dd = int3(1, -1, 0);
float h = _Crest_HeightSource[id];
if (h > FLT_MIN)
{
_Crest_HeightTarget[id] = h;
return;
}
const float g = _Crest_GroundHeightSource[id] + _Crest_DomainOrigin.y - g_Crest_WaterCenter.y;
// Add margin inside geometry to prevent rise or drops at intersections.
float samples = 0.0;
h = 0.0;
AddAboveGroundHeight(id + dd.xx, g, h, samples);
AddAboveGroundHeight(id + dd.xy, g, h, samples);
AddAboveGroundHeight(id + dd.xz, g, h, samples);
AddAboveGroundHeight(id + dd.yx, g, h, samples);
AddAboveGroundHeight(id + dd.yy, g, h, samples);
AddAboveGroundHeight(id + dd.yz, g, h, samples);
AddAboveGroundHeight(id + dd.zx, g, h, samples);
AddAboveGroundHeight(id + dd.zy, g, h, samples);
// Found a height.
if (samples > 0.0)
{
// Includes ground, sea level and origin.
h /= samples;
}
_Crest_HeightTarget[id] = h;
}
void FinalizeHeight(const uint3 i_ID)
{
const uint2 id = i_ID.xy;
float h = _Crest_HeightSource[id];
// Clip doesn't work if ground is below sea level - it will make the water pop back up to sea level/anim waves.
// Same for multiplying down alpha blend weight. This does something reasonable - yanks down water when almost dry..
h -= 0.1 * (1.0 - saturate(h / 0.02));
// Add ground height to water height to get world height of surface.
h += _Crest_GroundHeightSource[id];
// Move to world space.
h += _Crest_DomainOrigin.y;
// Make relative to sea level.
h -= g_Crest_WaterCenter.y;
_Crest_HeightTarget[id] = h;
}
//
// Injectors
//
float WaterHeight(float2 i_UV, float i_Current = 0.0)
{
float h = _Crest_HeightSource.SampleLevel(LODData_linear_clamp_sampler, i_UV, 0.0).x;
// Reject any samples below ground.
if (h < 0.001)
{
// Return the current height to not trigger the curve test.
h = i_Current;
}
else
{
// Add ground height to water height to get height of surface
h += _Crest_GroundHeightSource.SampleLevel(LODData_linear_clamp_sampler, i_UV, 0.0).x;
}
// Silences: shader warning use of potentially uninitialized variable.
return h;
}
float ComputeAlpha(const float2 i_UV)
{
// Domain mask.
const float2 offset = abs(i_UV - 0.5);
float alpha = smoothstep(0.5, 0.45, max(offset.x, offset.y));
if (alpha >= 0.0)
{
// Simulation mask.
alpha *= _Crest_MaskSource.SampleLevel(LODData_linear_clamp_sampler, i_UV, 0.0);
}
return alpha;
}
float ComputeAlphaFromMargin(const float2 i_Position)
{
float alpha = 1.0;
float2 position = abs(i_Position - _Crest_DomainOrigin.xz);
if (max(position.x, position.y) > (_Crest_DomainWidth.x * 0.5 - _Crest_MarginWidth)) alpha = 0.0;
return alpha;
}
void InjectShape(const uint3 i_ID)
{
const float2 position = Cascade::MakeAnimatedWaves(i_ID.z).IDToWorld(i_ID.xy);
const float2 uv = (position - _Crest_DomainOrigin.xz) / _Crest_DomainWidth + 0.5;
// Over scan to ensure signal continued off the edges which helps at low LODs.
if (any(uv != saturate(uv))) return;
float alpha = _Crest_Weight * ComputeAlpha(uv);
if (alpha <= 0.0) return;
float h = _Crest_HeightSource.SampleLevel(LODData_linear_clamp_sampler, uv, 0.0).x;
// Power up alpha to bring anim waves further in towards shore.
alpha = pow(alpha, 2.0);
float4 result = _Crest_ShapeTarget[i_ID];
// Alpha blend.
result.xyz *= 1.0 - alpha;
result.xyz += float3(0, h * alpha, 0);
_Crest_ShapeTarget[i_ID] = result;
}
void InjectLevel(const uint3 i_ID)
{
const float2 position = Cascade::MakeLevel(i_ID.z).IDToWorld(i_ID.xy);
const float2 uv = (position - _Crest_DomainOrigin.xz) / _Crest_DomainWidth + 0.5;
// Over scan to ensure signal continued off the edges which helps at low LODs.
if (any(uv != saturate(uv))) return;
float alpha = _Crest_Weight * ComputeAlphaFromMargin(position);
if (alpha <= 0.0) return;
float h = _Crest_HeightSource.SampleLevel(LODData_linear_clamp_sampler, uv, 0.0).x;
float result = _Crest_Weight * _Crest_LevelTarget[i_ID];
// Height is a source of water and should be the minimum height.
// The simulation will still ripple over the top which is acceptable.
result = max(result, h);
_Crest_LevelTarget[i_ID] = result;
}
void InjectFoam(const uint3 i_ID)
{
const float2 position = Cascade::MakeFoam(i_ID.z).IDToWorld(i_ID.xy);
const float2 uv = (position - _Crest_DomainOrigin.xz) / _Crest_DomainWidth + 0.5;
// Over scan to ensure signal continued off the edges which helps at low LODs.
if (any(uv != saturate(uv))) return;
float alpha = _Crest_Weight * (_Crest_InjectLevel ? ComputeAlphaFromMargin(position) : ComputeAlpha(uv));
if (alpha <= 0.0) return;
// Use approximation of max curvature as foam term. Seems to grab leading wave edge alright.
const float2 dx = float2(1.0 / _Crest_Resolution, 0.0);
const float h = WaterHeight(uv);
if (h <= 0.0) return;
const float h_xm = WaterHeight(uv - dx.xy, h);
const float h_xp = WaterHeight(uv + dx.xy, h);
const float h_zm = WaterHeight(uv - dx.yx, h);
const float h_zp = WaterHeight(uv + dx.yx, h);
const float curvature = max(abs(h_xp + h_xm - 2.0 * h), abs(h_zp + h_zm - 2.0 * h)) / (2.0 * dx.x * _Crest_DomainWidth);
float foam = 10.0 * curvature;
// Apply velocity to flow to prevent foam at edges of geometry.
const float vx = _Crest_VelocityXSource.SampleLevel(LODData_linear_clamp_sampler, uv, 0.0);
const float vy = _Crest_VelocityYSource.SampleLevel(LODData_linear_clamp_sampler, uv, 0.0);
foam *= length(float2(vx, vy));
foam *= alpha;
// Integrate.
foam *= _Crest_SimDeltaTime;
foam *= _Crest_InjectionStrength;
_Crest_FoamTarget[i_ID] += foam;
}
void InjectFlow(const uint3 i_ID)
{
const Cascade cascade = Cascade::MakeFlow(i_ID.z);
const float2 position = cascade.IDToWorld(i_ID.xy);
const float2 uv = (position - _Crest_DomainOrigin.xz) / _Crest_DomainWidth + 0.5;
// Over scan to ensure signal continued off the edges which helps at low LODs.
if (any(uv != saturate(uv))) return;
float alpha = _Crest_Weight * (_Crest_InjectLevel ? ComputeAlphaFromMargin(position) : ComputeAlpha(uv));
if (alpha <= 0.0) return;
float mip;
{
const float3 uv = cascade.IDToUV(i_ID.xy);
const float2 dd = float2(cascade._OneOverResolution * 8.0, 0);
const float2 dx = cascade.UVToWorld(uv + dd.xyy) - position;
const float2 dy = cascade.UVToWorld(uv + dd.yxy) - position;
// Calculate the rate of change in x and y directions.
const float2 rateOfChange = float2(length(dx.xy), length(dy.xy));
mip = log2(max(rateOfChange.x, rateOfChange.y));
}
// These should use mip maps. Without this bad pops ensue from aliasing x combine-pass-flow.
const float vx = _Crest_VelocityXSource.SampleLevel(LODData_linear_clamp_sampler, uv, mip);
const float vy = _Crest_VelocityYSource.SampleLevel(LODData_linear_clamp_sampler, uv, mip);
float2 result = _Crest_FlowTarget[i_ID];
// Alpha blend.
result *= 1.0 - alpha;
result += float2(vx, vy) * _Crest_InjectionStrength * alpha;
_Crest_FlowTarget[i_ID] = result;
}
//
// Baking
//
// Copy texture. Here because script code path simpler if same for all outputs
// rather than using CopyTexture.
void BakeLevel(const uint3 i_ID)
{
const uint2 id = i_ID.xy;
_Crest_LevelBakeTarget[id] = _Crest_HeightSource[id];
}
void BakeFoam(const uint3 i_ID)
{
const uint2 id = i_ID.xy;
const float2 uv = id / (float)_Crest_Resolution;
// Over scan to ensure signal continued off the edges which helps at low LODs.
if (any(uv != saturate(uv))) return;
float alpha = ComputeAlphaFromMargin(IdToWorldXZ_H(id));
if (alpha <= 0.0) return;
// Cannot load texture as curvature is not picked up. Must need interpolation.
const float2 dx = float2(1.0 / _Crest_Resolution, 0.0);
const float h = WaterHeight(uv);
if (h <= 0.0)
{
_Crest_FoamBakeTarget[id] = 0.0;
return;
}
const float h_xm = WaterHeight(uv - dx.xy, h);
const float h_xp = WaterHeight(uv + dx.xy, h);
const float h_zm = WaterHeight(uv - dx.yx, h);
const float h_zp = WaterHeight(uv + dx.yx, h);
const float curvature = max(abs(h_xp + h_xm - 2.0 * h), abs(h_zp + h_zm - 2.0 * h)) / (2.0 * dx.x * _Crest_DomainWidth);
float foam = 10.0 * curvature;
foam *= length(float2(_Crest_VelocityXSource[id], _Crest_VelocityYSource[id]));
foam *= _Crest_InjectionStrength;
_Crest_FoamBakeTarget[id] = foam;
}
void BakeFlow(const uint3 i_ID)
{
const uint2 id = i_ID.xy;
float alpha = ComputeAlphaFromMargin(IdToWorldXZ_H(id));
if (alpha <= 0.0) return;
_Crest_FlowBakeTarget[id.xy] = float2(_Crest_VelocityXSource[id], _Crest_VelocityYSource[id]) * _Crest_InjectionStrength;
}
m_CrestNameSpaceEnd
m_CrestKernelDefault(Initialize);
m_CrestKernelDefault(InitializeGroundHeight);
m_CrestKernelDefault(Advect);
m_CrestKernelDefault(UpdateHeight);
m_CrestKernelDefault(HeightOvershootReduction);
m_CrestKernelDefault(UpdateVelocity);
m_CrestKernelDefault(BlurHeight);
m_CrestKernelDefault(Blur);
m_CrestKernelDefault(MaskEdge);
m_CrestKernelDefault(ExpandEdge);
m_CrestKernelDefault(FinalizeHeight);
m_CrestInputKernelDefault(InjectShape);
m_CrestInputKernelDefault(InjectLevel);
m_CrestInputKernelDefault(InjectFoam);
m_CrestInputKernelDefault(InjectFlow);
m_CrestKernelDefault(BakeLevel);
m_CrestKernelDefault(BakeFoam);
m_CrestKernelDefault(BakeFlow);