Files
UnrealEngine/Engine/Plugins/Experimental/Water/Shaders/Private/GerstnerWaveFunctions.ush
2025-05-18 13:04:45 +08:00

433 lines
14 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
GerstnerWaveFunctions.ush: Utility functions for Gerstner waves computation.
=============================================================================*/
#pragma once
#include "WaterDataFunctions.ush"
#define Gravity 980
#define SOLVE_NORMAL_Z 1
#define SteepnessThreshold 50
struct WaveOutput
{
float3 Normal;
float3 WPO;
};
struct WaveOutputs
{
WaveOutput LowFrequency;
WaveOutput MiddleFrequency;
WaveOutput HighFrequency;
WaveOutput AllWaves;
};
struct GerstnerWaveRenderer
{
float2 WaveParamRTSize;
int MaxWaves;
float2 WorldPos;
float Time;
};
FWaveParams GetWaveRTData(GerstnerWaveRenderer inWaveRenderer, Texture2D inWaveParamsRT, SamplerState inWaveParamsRTSampler, int inWaveIndex, int inWaterBodyIndex)
{
FWaveParams OutWaveParams;
float2 WaveUV;
float WaveUV_b;
inWaveIndex *= 2;
WaveUV.x = inWaveIndex / inWaveRenderer.WaveParamRTSize.x;
WaveUV.y = inWaterBodyIndex / inWaveRenderer.WaveParamRTSize.y;
WaveUV += 0.5 / inWaveRenderer.WaveParamRTSize;
WaveUV_b = WaveUV.x + (1 / inWaveRenderer.WaveParamRTSize.x);
OutWaveParams.Direction = inWaveParamsRT.SampleLevel(inWaveParamsRTSampler, WaveUV, 0).rg;
float3 TempParams = inWaveParamsRT.SampleLevel(inWaveParamsRTSampler, float2(WaveUV_b, WaveUV.y), 0).rgb;
OutWaveParams.Wavelength = TempParams.x;
OutWaveParams.Amplitude = TempParams.y;
OutWaveParams.Steepness = TempParams.z;
return OutWaveParams;
}
WaveOutput AddWaves(WaveOutput inWaveA, WaveOutput inWaveB)
{
inWaveA.Normal += inWaveB.Normal;
inWaveA.WPO += inWaveB.WPO;
return inWaveA;
}
float3 FinalizeNormal(float3 inNormal)
{
return normalize(float3(inNormal.xy, 1 - inNormal.z));
}
float3 PackNormalAndWPO(WaveOutput inWave)
{
float3 packedoutput = 0;
packedoutput = floor(inWave.WPO * 100);
packedoutput += ((inWave.Normal + 1.01) / 2.02);
return packedoutput;
}
float3 UnpackWaveNormal(float inPackedWave)
{
float3 outnormal = frac(inPackedWave);
outnormal *= 2.02;
outnormal -= 1.01;
return outnormal;
}
float3 UnpackWaveWPO(float inPackedWave)
{
float3 outWPO;
outWPO = floor(inPackedWave) / 100;
return outWPO;
}
float2 GetWavePositionAndDispersion(GerstnerWaveRenderer inWaveRenderer, FWaveParams inWaveParams)
{
float dispersion = 2 * PI / inWaveParams.Wavelength;
float2 wavevector = inWaveParams.Direction * dispersion;
float wavespeed = sqrt(dispersion * Gravity);
float wavetime = wavespeed * inWaveRenderer.Time;
float wavepos = dot(inWaveRenderer.WorldPos, wavevector) - wavetime;
return float2(wavepos, dispersion);
}
WaveOutput ComputeSingleWaveVectorized(float inWaveSin, float inWaveCos, float inDispersion, FWaveParams inWaveParams)
{
WaveOutput OutWave;
float wKA = inWaveParams.Amplitude * inDispersion;
float q = inWaveParams.Steepness / wKA;
OutWave.Normal.xy = inWaveSin * wKA * inWaveParams.Direction;
#if SOLVE_NORMAL_Z
OutWave.Normal.z = inWaveCos * wKA * q;
#else
OutWave.Normal.z = 0;
#endif
OutWave.WPO.xy = -q * inWaveSin * inWaveParams.Direction * inWaveParams.Amplitude;
OutWave.WPO.z = inWaveCos * inWaveParams.Amplitude;
return OutWave;
}
WaveOutput GetSingleGerstnerWave(GerstnerWaveRenderer inWaveRenderer, Texture2D inWaveParamsRT, SamplerState inWaveParamsRTSampler, int inWaveIndex, int inWaterBodyIndex)
{
WaveOutput OutWave;
FWaveParams CurrentWave;
CurrentWave = GetWaveRTData(inWaveRenderer, inWaveParamsRT, inWaveParamsRTSampler, inWaveIndex, inWaterBodyIndex);
float dispersion = 2 * PI / CurrentWave.Wavelength;
float2 wavevector = CurrentWave.Direction * dispersion;
float wavespeed = sqrt(dispersion * Gravity);
float wavetime = wavespeed * inWaveRenderer.Time;
float wavepos = dot(inWaveRenderer.WorldPos, wavevector) - wavetime;
float wavesin = sin(wavepos);
float wavecos = cos(wavepos);
float wKA = CurrentWave.Amplitude * dispersion;
float q = CurrentWave.Steepness / wKA;
OutWave.Normal.xy = wavesin * wKA * CurrentWave.Direction;
#if SOLVE_NORMAL_Z
OutWave.Normal.z = wavecos * CurrentWave.Steepness * saturate((CurrentWave.Amplitude * SteepnessThreshold) / CurrentWave.Wavelength);
//OutWave.Normal.z = wavecos * wKA * (q/MaxWaves);
#else
OutWave.Normal.z = 0;
#endif
OutWave.WPO.xy = -q * wavesin * CurrentWave.Direction * CurrentWave.Amplitude;
OutWave.WPO.z = wavecos * CurrentWave.Amplitude;
return OutWave;
}
WaveOutput GetAllGerstnerWaves(GerstnerWaveRenderer inWaveRenderer, Texture2D inWaveParamsRT, SamplerState inWaveParamsRTSampler, int inWaterBodyIndex)
{
WaveOutput OutWaves = (WaveOutput)0;
WaveOutput CurrentWave;
int MaxWaveRange = inWaveRenderer.MaxWaves - 1;
for (int i = 0; i <= MaxWaveRange; i++)
{
CurrentWave = GetSingleGerstnerWave(inWaveRenderer, inWaveParamsRT, inWaveParamsRTSampler, i, inWaterBodyIndex);
OutWaves = AddWaves(OutWaves, CurrentWave);
}
//The Normal B channel must be inverted after combining waves
OutWaves.Normal = FinalizeNormal(OutWaves.Normal);
return OutWaves;
}
WaveOutput GetAllGerstnerWavesVectorized(GerstnerWaveRenderer inWaveRenderer, int inWaterBodyIndex)
{
WaveOutput OutWaves = (WaveOutput)0;
WaveOutput CurrentWave;
int MaxWaveRange = (inWaveRenderer.MaxWaves / 4) - 1;
for (int i = 0; i <= MaxWaveRange; i++)
{
FWaveParams WaveA;
FWaveParams WaveB;
FWaveParams WaveC;
FWaveParams WaveD;
float2 WaveAPosAndDispersion = GetWavePositionAndDispersion(inWaveRenderer, WaveA);
float2 WaveBPosAndDispersion = GetWavePositionAndDispersion(inWaveRenderer, WaveB);
float2 WaveCPosAndDispersion = GetWavePositionAndDispersion(inWaveRenderer, WaveC);
float2 WaveDPosAndDispersion = GetWavePositionAndDispersion(inWaveRenderer, WaveD);
float4 WaveSines = 0;
float4 WaveCosines = 0;
WaveSines = sin(float4(WaveAPosAndDispersion.x, WaveBPosAndDispersion.x, WaveCPosAndDispersion.x, WaveDPosAndDispersion.x));
WaveCosines = cos(float4(WaveAPosAndDispersion.x, WaveBPosAndDispersion.x, WaveCPosAndDispersion.x, WaveDPosAndDispersion.x));
//sincos( float4( WaveAPosAndDispersion.x, WaveBPosAndDispersion.x, WaveCPosAndDispersion.x, WaveDPosAndDispersion.x), WaveSines, WaveCosines);
CurrentWave = ComputeSingleWaveVectorized(WaveSines.x, WaveCosines.x, WaveAPosAndDispersion.y, WaveA);
OutWaves = AddWaves(OutWaves, CurrentWave);
CurrentWave = ComputeSingleWaveVectorized(WaveSines.y, WaveCosines.y, WaveBPosAndDispersion.y, WaveB);
OutWaves = AddWaves(OutWaves, CurrentWave);
CurrentWave = ComputeSingleWaveVectorized(WaveSines.z, WaveCosines.z, WaveCPosAndDispersion.y, WaveC);
OutWaves = AddWaves(OutWaves, CurrentWave);
CurrentWave = ComputeSingleWaveVectorized(WaveSines.a, WaveCosines.a, WaveDPosAndDispersion.y, WaveD);
OutWaves = AddWaves(OutWaves, CurrentWave);
}
//The Normal B channel must be inverted after combining waves
OutWaves.Normal = FinalizeNormal(OutWaves.Normal);
return OutWaves;
}
WaveOutput GetRangeGerstnerWaves(GerstnerWaveRenderer inWaveRenderer, Texture2D inWaveParamsRT, SamplerState inWaveParamsRTSampler, int inMinWaveIndex, int inMaxWaveIndex, int inWaterBodyIndex)
{
WaveOutput OutWaves;
WaveOutput CurrentWave;
int MaxWaveRange = min(inMaxWaveIndex, inWaveRenderer.MaxWaves - 1);
for (int i = inMinWaveIndex; i <= MaxWaveRange; i++)
{
CurrentWave = GetSingleGerstnerWave(inWaveRenderer, inWaveParamsRT, inWaveParamsRTSampler, i, inWaterBodyIndex);
OutWaves = AddWaves(OutWaves, CurrentWave);
}
//The Normal B channel must be inverted after combining waves
OutWaves.Normal = FinalizeNormal(OutWaves.Normal);
return OutWaves;
}
// TODO[ jbard] placeholder : Remove ASAP
float ComputeWaveDepthAttenuationFactor(int InWaterBodyIndex, float InWaterDepth)
{
return 1.0f;
}
GerstnerWaveRenderer InitializeGerstnerWaveRenderer(float2 inWaveParamsRTSize, int inMaxwaves, float2 inWorldPos, float inTime)
{
GerstnerWaveRenderer OutWaveRenderer;
OutWaveRenderer.WaveParamRTSize = inWaveParamsRTSize;
OutWaveRenderer.MaxWaves = inMaxwaves;
OutWaveRenderer.WorldPos = inWorldPos;
OutWaveRenderer.Time = inTime;
return OutWaveRenderer;
}
WaveOutput AddWavesNew(WaveOutput inWaveA, WaveOutput inWaveB)
{
inWaveA.Normal += inWaveB.Normal;
inWaveA.WPO += inWaveB.WPO;
return inWaveA;
}
float3 FinalizeNormalNew(float3 inNormal)
{
return normalize(float3(inNormal.xy, 1.0f - inNormal.z));
}
FWaveParams GetWaveDataByWaveIndexNew(int InWaterBodyIndex, int InWaveIndex)
{
const FWaterBodyData WaterBodyData = GetWaterBodyData(InWaterBodyIndex);
return GetWaveDataNew(InWaveIndex, WaterBodyData);
}
float3 PackNormalAndWPONew(WaveOutput inWave)
{
float3 packedoutput = (float3)0;
packedoutput = floor(inWave.WPO * 100);
packedoutput += ((inWave.Normal + 1.01) / 2.02);
return packedoutput;
}
WaveOutput GetSingleGerstnerWaveNew(int InWaveIndex, FWaterBodyData InWaterBodyData, float InTime, FDFVector2 InWorldPos)
{
WaveOutput OutWave = (WaveOutput)0;
FWaveParams CurrentWave = GetWaveDataNew(InWaveIndex, InWaterBodyData);
float Dispersion = 2 * PI / CurrentWave.Wavelength;
float2 WaveVector = CurrentWave.Direction * Dispersion;
float WaveSpeed = sqrt(Dispersion * Gravity);
float WaveTime = WaveSpeed * InTime;
// Use the offset of the normalized tile as world position.
// Doing proper LWC math is both more expensive and leads to less smooth/precise results.
FLWCVector2 LWCWorldPos = DFToTileOffset_Hack(InWorldPos);
float2 WorldPos = LWCWorldPos.Offset;
float WavePos = dot(WorldPos, WaveVector) - WaveTime;
float WaveSin, WaveCos;
sincos(WavePos, WaveSin, WaveCos);
// Fix up tile borders by blending between them
const float2 TileBorderDist = (UE_LWC_RENDER_TILE_SIZE * 0.5f) - abs(WorldPos);
const float BlendZoneWidth = 400.0f; // Blend over a range of 4 meters on each side of the tile
if (any(TileBorderDist < BlendZoneWidth))
{
const float2 BlendWorldPos = TileBorderDist;
const float2 BlendAlpha2 = saturate(TileBorderDist / BlendZoneWidth);
const float BlendAlpha = BlendAlpha2.x * BlendAlpha2.y;
const float BlendWavePos = dot(BlendWorldPos, WaveVector) - WaveTime;
WaveSin = lerp(sin(BlendWavePos), WaveSin, BlendAlpha);
WaveCos = lerp(cos(BlendWavePos), WaveCos, BlendAlpha);
}
float wKA = CurrentWave.Amplitude * Dispersion;
float q = CurrentWave.Steepness / wKA;
OutWave.Normal.xy = WaveSin * wKA * CurrentWave.Direction;
#if SOLVE_NORMAL_Z
OutWave.Normal.z = WaveCos * CurrentWave.Steepness * saturate( (CurrentWave.Amplitude * SteepnessThreshold) / CurrentWave.Wavelength );
//OutWave.Normal.z = WaveCos * wKA * (q/MaxWaves);
#else
OutWave.Normal.z = 0;
#endif
OutWave.WPO.xy = -q * WaveSin * CurrentWave.Direction * CurrentWave.Amplitude;
OutWave.WPO.z = WaveCos * CurrentWave.Amplitude;
return OutWave;
}
WaveOutput GetAllGerstnerWavesNew(int InWaterBodyIndex, float InTime, FDFVector2 InWorldPos)
{
WaveOutput OutWaves = (WaveOutput)0;
WaveOutput CurrentWave = (WaveOutput)0;
const FWaterBodyData WaterBodyData = GetWaterBodyData(InWaterBodyIndex);
const int NumWaves = WaterBodyData.NumWaves;
for (int i = 0; i < NumWaves; i++)
{
CurrentWave = GetSingleGerstnerWaveNew(i, WaterBodyData, InTime, InWorldPos);
OutWaves = AddWavesNew(OutWaves, CurrentWave);
}
//The Normal B channel must be inverted after combining waves
OutWaves.Normal = FinalizeNormalNew(OutWaves.Normal);
return OutWaves;
}
WaveOutput GetAllGerstnerWavesNew(int InWaterBodyIndex, float InTime, FLWCVector2 InWorldPos) { return GetAllGerstnerWavesNew(InWaterBodyIndex, InTime, DFFromTileOffset(InWorldPos)); }
WaveOutput GetRangeGerstnerWavesNew(int InMinWaveIndex, int InMaxWaveIndex, int InWaterBodyIndex, float InTime, FDFVector2 InWorldPos)
{
WaveOutput OutWaves = (WaveOutput)0;
WaveOutput CurrentWave = (WaveOutput)0;
const FWaterBodyData WaterBodyData = GetWaterBodyData(InWaterBodyIndex);
const int NumWaves = WaterBodyData.NumWaves;
const int MaxWaveRange = min(InMaxWaveIndex + 1, NumWaves);
const int MinWaveRange = max(0, InMinWaveIndex);
for (int i = MinWaveRange; i < MaxWaveRange; i++)
{
CurrentWave = GetSingleGerstnerWaveNew(i, WaterBodyData, InTime, InWorldPos);
OutWaves = AddWavesNew(OutWaves, CurrentWave);
}
//The Normal B channel must be inverted after combining waves
OutWaves.Normal = FinalizeNormalNew(OutWaves.Normal);
return OutWaves;
}
WaveOutputs GetPerFrequencyGerstnerWavesNew(int LowFrequencyMaxIndex, int MiddleFrequencyMaxIndex, int InWaterBodyIndex, float InTime, FDFVector2 InWorldPos)
{
const int NumWaves = GetWaterBodyData(InWaterBodyIndex).NumWaves;
// Correct bad user input. Further input handling is done internally inside GetRangeGerstnerWavesNew to ensure the index ranges are valid for the number of waves in a given water body.
MiddleFrequencyMaxIndex = max(LowFrequencyMaxIndex + 1, MiddleFrequencyMaxIndex);
WaveOutputs Result = (WaveOutputs)0;
Result.LowFrequency = GetRangeGerstnerWavesNew(0, LowFrequencyMaxIndex, InWaterBodyIndex, InTime, InWorldPos);
Result.MiddleFrequency = GetRangeGerstnerWavesNew(LowFrequencyMaxIndex + 1, MiddleFrequencyMaxIndex, InWaterBodyIndex, InTime, InWorldPos);
Result.HighFrequency = GetRangeGerstnerWavesNew(MiddleFrequencyMaxIndex + 1, NumWaves, InWaterBodyIndex, InTime, InWorldPos);
Result.AllWaves = AddWavesNew(Result.LowFrequency, AddWavesNew(Result.HighFrequency, Result.MiddleFrequency));
return Result;
}
WaveOutputs GetPerFrequencyGerstnerWavesNew(int LowFrequencyMaxIndex, int MiddleFrequencyMaxIndex, int InWaterBodyIndex, float InTime, FLWCVector2 InWorldPos)
{
return GetPerFrequencyGerstnerWavesNew(LowFrequencyMaxIndex, MiddleFrequencyMaxIndex, InWaterBodyIndex, InTime, DFFromTileOffset(InWorldPos));
}
/** Returns the wave attenuation factor according to a given water body and a given water depth. Should match the CPU version (GetWaveAttenuationFactor) */
float ComputeWaveDepthAttenuationFactorNew(int InWaterBodyIndex, float InWaterDepth)
{
const FWaterBodyData WaterBodyData = GetWaterBodyData(InWaterBodyIndex);
// Completely erase waves when there are no waves at all (in case waves are computed on a water body with no waves, like a swimming pool) :
const float TotalErasureFactor = saturate((float)WaterBodyData.NumWaves);
const float StrengthCoefficient = exp(-max(InWaterDepth, 0.0f) / (WaterBodyData.TargetWaveMaskDepth / 2.0f));
return saturate(1.0f - StrengthCoefficient) * TotalErasureFactor;
}