// 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 RainMap; RWTexture2D TerrainHeight; RWTexture2D WaterHeight; RWTexture2D Sediment; RWTexture2D Outflow; RWTexture2D Velocity; RWTexture2D WaterHeight1; RWTexture2D WaterHeight2; RWTexture2D Sediment1; // Required for tilt angle RWTexture2D 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]; }