CelticCraft/Plugins/VoxelFree/Shaders/Private/Erosion.usf

288 lines
No EOL
8.7 KiB
Text

// Copyright 2020 Phyronnaz
// From https://hal.inria.fr/file/index/docid/402079/filename/FastErosion_PG07.pdf
#include "/Engine/Public/Platform.ush"
#include "/Engine/Generated/GeneratedUniformBuffers.ush"
RWTexture2D<float> RainMap;
RWTexture2D<float> TerrainHeight;
RWTexture2D<float> WaterHeight;
RWTexture2D<float> Sediment;
RWTexture2D<float> Outflow;
RWTexture2D<float> Velocity;
RWTexture2D<float> WaterHeight1;
RWTexture2D<float> WaterHeight2;
RWTexture2D<float> Sediment1;
// Required for tilt angle
RWTexture2D<float> TerrainHeight1;
#if __INTELLISENSE__
struct FConstants
{
uint size;
float dt;
// Size of a pipe
float l;
// Gravity
float g;
// Sediment capacity
float Kc;
// Sediment dissolving
float Ks;
// Sediment deposition
float Kd;
// Rain strength
float Kr;
// Evaporation
float Ke;
};
FConstants VoxelErosionParameters;
#endif
/**
* Getter/Setters helpers as UAV must have a single component
*/
inline bool IsIn(int2 Pos) { return Pos.x >= 0 && Pos.y >= 0 && Pos.x < VoxelErosionParameters.size && Pos.y < VoxelErosionParameters.size; }
inline float GetLeft (int2 Pos) { return IsIn(Pos) ? Outflow[uint2(Pos.x * 4 + 0, Pos.y)] : 0; }
inline float GetRight (int2 Pos) { return IsIn(Pos) ? Outflow[uint2(Pos.x * 4 + 1, Pos.y)] : 0; }
inline float GetBottom(int2 Pos) { return IsIn(Pos) ? Outflow[uint2(Pos.x * 4 + 2, Pos.y)] : 0; }
inline float GetTop (int2 Pos) { return IsIn(Pos) ? Outflow[uint2(Pos.x * 4 + 3, Pos.y)] : 0; }
inline void SetLeft (int2 Pos, float Value) { Outflow[uint2(Pos.x * 4 + 0, Pos.y)] = Value; }
inline void SetRight (int2 Pos, float Value) { Outflow[uint2(Pos.x * 4 + 1, Pos.y)] = Value; }
inline void SetBottom(int2 Pos, float Value) { Outflow[uint2(Pos.x * 4 + 2, Pos.y)] = Value; }
inline void SetTop (int2 Pos, float Value) { Outflow[uint2(Pos.x * 4 + 3, Pos.y)] = Value; }
inline float2 GetVelocity(uint2 Pos)
{
return float2(
Velocity[uint2(Pos.x * 2 + 0, Pos.y)],
Velocity[uint2(Pos.x * 2 + 1, Pos.y)]);
}
inline void SetVelocity(uint2 Pos, float2 Value)
{
Velocity[uint2(Pos.x * 2 + 0, Pos.y)] = Value.x;
Velocity[uint2(Pos.x * 2 + 1, Pos.y)] = Value.y;
}
/**
* WaterIncrement
*/
[numthreads(NUM_THREADS_CS, NUM_THREADS_CS, 1)]
void WaterIncrement(uint3 ThreadId : SV_DispatchThreadID)
{
const uint2 Pos = ThreadId.xy;
WaterHeight1[Pos] = WaterHeight[Pos] + VoxelErosionParameters.dt * VoxelErosionParameters.Kr * RainMap[Pos];
}
/**
* FlowSimulation
*/
inline float HeightDifference(uint2 a, uint2 b)
{
return TerrainHeight[a] + WaterHeight1[a] - TerrainHeight[b] - WaterHeight1[b];
}
[numthreads(NUM_THREADS_CS, NUM_THREADS_CS, 1)]
void FlowSimulation(uint3 ThreadId : SV_DispatchThreadID)
{
const int2 Pos = ThreadId.xy;
const int2 Left = Pos + int2(-1, 0);
const int2 Right = Pos + int2(+1, 0);
const int2 Bottom = Pos + int2(0, -1);
const int2 Top = Pos + int2(0, +1);
float4 OutflowValue = float4(GetLeft(Pos), GetRight(Pos), GetBottom(Pos), GetTop(Pos));
// Flow update
{
// Compute the flow from height differences
OutflowValue += VoxelErosionParameters.dt * VoxelErosionParameters.g / VoxelErosionParameters.l *
float4(
HeightDifference(Pos, Left ),
HeightDifference(Pos, Right ),
HeightDifference(Pos, Bottom),
HeightDifference(Pos, Top ));
OutflowValue = max(OutflowValue, 0);
// Scale the flow down if needed
const float OutflowSum = OutflowValue.x + OutflowValue.y + OutflowValue.z + OutflowValue.w;
if (OutflowSum != 0)
{
const float K = WaterHeight1[Pos] * VoxelErosionParameters.l * VoxelErosionParameters.l / (VoxelErosionParameters.dt * OutflowSum);
OutflowValue *= min(K, 1);
}
SetLeft (Pos, OutflowValue.x);
SetRight (Pos, OutflowValue.y);
SetBottom(Pos, OutflowValue.z);
SetTop (Pos, OutflowValue.w);
}
// Update water height
{
const float DeltaVolume = VoxelErosionParameters.dt * (
GetRight(Left) +
GetLeft(Right) +
GetTop(Bottom) +
GetBottom(Top) +
-OutflowValue.x +
-OutflowValue.y +
-OutflowValue.z +
-OutflowValue.w);
WaterHeight2[Pos] = WaterHeight1[Pos] + DeltaVolume / (VoxelErosionParameters.l * VoxelErosionParameters.l);
}
// Boundaries
if(Pos.x == 0)
{
SetLeft(Pos, 0);
}
else if (Pos.x == VoxelErosionParameters.size - 1)
{
SetRight(Pos, 0);
}
if (Pos.y == 0)
{
SetBottom(Pos, 0);
}
else if (Pos.y == VoxelErosionParameters.size - 1)
{
SetTop(Pos, 0);
}
// Update velocity
{
float2 DeltaWaterA;
float2 DeltaWaterB;
DeltaWaterA.x = GetRight(Left) - GetLeft(Pos);
DeltaWaterB.x = GetRight(Pos) - GetLeft(Right);
DeltaWaterA.y = GetTop(Bottom) - GetBottom(Pos);
DeltaWaterB.y = GetTop(Pos) - GetBottom(Top);
float2 DeltaWater;
// Avoid spikes
if (abs(DeltaWaterA.x + DeltaWaterB.x) < 0.001)
{
DeltaWater.x = DeltaWaterA.x;
}
else
{
DeltaWater.x = (DeltaWaterA.x + DeltaWaterB.x) / 2;
}
if (abs(DeltaWaterA.y + DeltaWaterB.y) < 0.001)
{
DeltaWater.y = DeltaWaterA.y;
}
else
{
DeltaWater.y = (DeltaWaterA.y + DeltaWaterB.y) / 2;
}
const float MeanWater = (WaterHeight1[Pos] + WaterHeight2[Pos]) / 2;
const float Divisor = VoxelErosionParameters.l * MeanWater;
if (Divisor == 0)
{
SetVelocity(Pos, DeltaWater);
}
else
{
SetVelocity(Pos, DeltaWater / Divisor);
}
}
}
/**
* ErosionDeposition
*/
// Return sin(tilt angle)
inline float TiltAngle(int2 Pos)
{
// https://math.stackexchange.com/questions/1044044/local-tilt-angle-based-on-height-field
const int2 Left = Pos + int2(-1, 0);
const int2 Right = Pos + int2(+1, 0);
const int2 Bottom = Pos + int2(0, -1);
const int2 Top = Pos + int2(0, +1);
if (!IsIn(Left) ||
!IsIn(Right) ||
!IsIn(Bottom) ||
!IsIn(Top))
{
// We don't want the sediments to be stuck there
return 0.5;
}
const float DeltaXL = (TerrainHeight[Left ] - TerrainHeight[Pos]) / 2;
const float DeltaXR = (TerrainHeight[Right ] - TerrainHeight[Pos]) / 2;
const float DeltaYB = (TerrainHeight[Bottom] - TerrainHeight[Pos]) / 2;
const float DeltaYT = (TerrainHeight[Top ] - TerrainHeight[Pos]) / 2;
const float Sum = max(DeltaXL * DeltaXL, DeltaXR * DeltaXR) + max(DeltaYB * DeltaYB, DeltaYT * DeltaYT);
return sqrt(Sum / (1 + Sum));
}
[numthreads(NUM_THREADS_CS, NUM_THREADS_CS, 1)]
void ErosionDeposition(uint3 ThreadId : SV_DispatchThreadID)
{
const uint2 Pos = ThreadId.xy;
float C = VoxelErosionParameters.Kc * TiltAngle(Pos) * length(GetVelocity(Pos));
const float S = Sediment[Pos];
const float K = C > S ? VoxelErosionParameters.Ks : VoxelErosionParameters.Kd;
float Diff = K * (C - S);
if (Diff > 0)
{
Diff = min(Diff, TerrainHeight[Pos]);
}
else
{
Diff = -min(-Diff, Sediment[Pos]);
}
TerrainHeight1[Pos] = TerrainHeight[Pos] - Diff;
Sediment1[Pos] = Sediment[Pos] + Diff;
}
/**
* SedimentTransportation
*/
[numthreads(NUM_THREADS_CS, NUM_THREADS_CS, 1)]
void SedimentTransportation(uint3 ThreadId : SV_DispatchThreadID)
{
const uint2 Pos = ThreadId.xy;
const float2 SamplePos = Pos - GetVelocity(Pos) * VoxelErosionParameters.dt;
const uint2 Floor = floor(SamplePos);
const uint2 Ceil = ceil(SamplePos);
const float2 Frac = SamplePos - Floor;
Sediment[Pos] = lerp(
lerp(Sediment1[uint2(Floor.x, Floor.y)], Sediment1[uint2(Ceil.x, Floor.y)], Frac.x),
lerp(Sediment1[uint2(Floor.x, Ceil .y)], Sediment1[uint2(Ceil.x, Ceil .y)], Frac.x),
Frac.y);
}
/**
* Evaporation
*/
[numthreads(NUM_THREADS_CS, NUM_THREADS_CS, 1)]
void Evaporation(uint3 ThreadId : SV_DispatchThreadID)
{
const uint2 Pos = ThreadId.xy;
WaterHeight[Pos] = WaterHeight2[Pos] * (1 - VoxelErosionParameters.Ke * VoxelErosionParameters.dt);
// Also copy the height data
TerrainHeight[Pos] = TerrainHeight1[Pos];
}