264 lines
7.6 KiB
Plaintext
264 lines
7.6 KiB
Plaintext
// Crest Water System
|
|
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
|
|
|
// Inspired by https://github.com/speps/GX-EncinoWaves
|
|
|
|
#pragma kernel SpectrumInitalize
|
|
#pragma kernel SpectrumUpdate
|
|
|
|
#define INV2PI 0.15915494309f
|
|
#define PI4 0.33661977236f
|
|
#define INVPI2 0.63661977236f
|
|
#define HPI 1.57079632679f
|
|
#define PI 3.14159265358f
|
|
#define PI2 6.28318530717f
|
|
#define HSQRT2 0.70710678118f
|
|
|
|
// These must match corresponding constants in WaveSpectrum.cs
|
|
#define SPECTRUM_OCTAVE_COUNT 14.0
|
|
#define SPECTRUM_SMALLEST_WL_POW_2 -4.0
|
|
|
|
uint _Crest_Size;
|
|
float _Crest_WindSpeed;
|
|
float _Crest_Turbulence;
|
|
float _Crest_Gravity;
|
|
float _Crest_Period;
|
|
float _Crest_Alignment;
|
|
|
|
uint WangHash(uint seed)
|
|
{
|
|
seed = (seed ^ 61) ^ (seed >> 16);
|
|
seed *= 9;
|
|
seed = seed ^ (seed >> 4);
|
|
seed *= 0x27d4eb2d;
|
|
seed = seed ^ (seed >> 15);
|
|
return seed;
|
|
}
|
|
|
|
uint Rand(inout uint rngState)
|
|
{
|
|
rngState ^= (rngState << 13);
|
|
rngState ^= (rngState >> 17);
|
|
rngState ^= (rngState << 5);
|
|
return rngState;
|
|
}
|
|
|
|
float RandFloat(inout uint rngState)
|
|
{
|
|
return Rand(rngState) / 4294967296.0f;
|
|
}
|
|
|
|
float RandGauss(inout uint rngState)
|
|
{
|
|
float u1 = RandFloat(rngState);
|
|
float u2 = RandFloat(rngState);
|
|
if (u1 < 1e-6f)
|
|
u1 = 1e-6f;
|
|
return sqrt(-2.0f * log(u1)) * cos(PI2 * u2);
|
|
}
|
|
|
|
void DeepDispersion(float k, out float w, out float dwdk)
|
|
{
|
|
w = sqrt(abs(_Crest_Gravity * k));
|
|
|
|
// Allow FFT to loop in time
|
|
if( _Crest_Period > 0.0 )
|
|
{
|
|
float thisPeriod = PI2 / w;
|
|
float loops = _Crest_Period / thisPeriod;
|
|
// Make sure loops integral number of times
|
|
loops = ceil( loops );
|
|
// Work our way back to frequency
|
|
thisPeriod = _Crest_Period / loops;
|
|
w = PI2 / thisPeriod;
|
|
}
|
|
|
|
dwdk = _Crest_Gravity / (2.0f * w);
|
|
}
|
|
|
|
float AlphaBetaSpectrum(float A, float B, float g, float w, float wm)
|
|
{
|
|
return
|
|
(A * g * g / pow(w, 5.0f)) *
|
|
exp(-B * pow(wm / w, 4.0f));
|
|
}
|
|
|
|
float PiersonMoskowitzSpectrum(float w)
|
|
{
|
|
float wm = 0.87f * _Crest_Gravity / _Crest_WindSpeed;
|
|
return AlphaBetaSpectrum(8.1e-3f, 1.291f, _Crest_Gravity, w, wm);
|
|
}
|
|
|
|
float PiersonMoskowitzWindTerm( float w )
|
|
{
|
|
float wm = 0.87f * _Crest_Gravity / _Crest_WindSpeed;
|
|
return exp( -1.291 * pow( wm / w, 4.0f ) );
|
|
}
|
|
|
|
float PosCosSquaredDirectionalSpreading( float cosTheta )
|
|
{
|
|
// Aligned waves.
|
|
float alignment;
|
|
{
|
|
float minWeight = lerp(1.0, 0.1, _Crest_Alignment);
|
|
float wt = max(cosTheta, minWeight);
|
|
wt *= lerp(0.25, 1.5, _Crest_Alignment);
|
|
float power = lerp(1.0, 16.0, _Crest_Alignment);
|
|
|
|
// Needs 2 to match current settings.
|
|
alignment = wt * INVPI2 * pow(abs(cosTheta), power) * PI;
|
|
}
|
|
|
|
float turbulence;
|
|
{
|
|
if (cosTheta > 0.0)
|
|
{
|
|
turbulence = lerp(INVPI2 * (cosTheta * cosTheta), PI4, _Crest_Turbulence);
|
|
}
|
|
else
|
|
{
|
|
turbulence = PI4 * _Crest_Turbulence;
|
|
}
|
|
}
|
|
|
|
// Integrate alignment and turbulence.
|
|
return lerp(turbulence, alignment, _Crest_Alignment * (1.0 - _Crest_Turbulence));
|
|
}
|
|
|
|
RWTexture2DArray<float4> _Crest_ResultInit;
|
|
Texture2D<float> _Crest_SpectrumControls;
|
|
SamplerState linear_clamp_sampler;
|
|
float2 _Crest_WindDir;
|
|
|
|
[numthreads(8,8,1)]
|
|
void SpectrumInitalize(uint3 id : SV_DispatchThreadID)
|
|
{
|
|
const int2 center = _Crest_Size.xx / 2;
|
|
const int2 coord = id.xy - center;
|
|
|
|
if( coord.x == 0 && coord.y == 0 )
|
|
{
|
|
_Crest_ResultInit[id] = 0.0;
|
|
return;
|
|
}
|
|
|
|
uint depth;
|
|
{
|
|
uint width, height;
|
|
_Crest_ResultInit.GetDimensions( width, height, depth );
|
|
}
|
|
|
|
uint maxCoord = int( max( abs( coord.x ), abs( coord.y ) ) );
|
|
|
|
// Matches variable with same name in ShapeFFT.cs
|
|
uint WAVE_SAMPLE_FACTOR = 8;
|
|
|
|
// If not largest cascade which will get all wavelengths, then limit
|
|
// so we split range of frequencies with no overlaps.
|
|
// The check on maxCoord below looks pretty magic. It is optimised version of:
|
|
// uint samplesPerWave = _Crest_Size / WAVE_SAMPLE_FACTOR;
|
|
// Too low wavelength (maxCoord < _Crest_Size / (2 * samplesPerWave) ||
|
|
// Too high wavelength maxCoord >= _Crest_Size / samplesPerWave)
|
|
if ( id.z < (depth - 1) &&
|
|
// Too low wavelength
|
|
maxCoord < WAVE_SAMPLE_FACTOR / 2 ||
|
|
// Too high wavelength
|
|
maxCoord >= WAVE_SAMPLE_FACTOR
|
|
)
|
|
{
|
|
_Crest_ResultInit[id] = 0.0;
|
|
return;
|
|
}
|
|
|
|
const float worldSize = 0.5f * (1 << id.z);
|
|
|
|
// Find wave vector and number
|
|
const float2 k = PI2 * coord / worldSize;
|
|
const float kMag = length(k);
|
|
|
|
// Init seed. rngState was _RngState (file level variable), but GameCore platforms does not allow assigning to them.
|
|
// See issue #856 for more information: https://github.com/wave-harmonic/crest/issues/856
|
|
uint rngState = WangHash(id.z * _Crest_Size * _Crest_Size + id.y * _Crest_Size + id.x);
|
|
|
|
// Dispersion
|
|
float w; float dwdk;
|
|
DeepDispersion(kMag, w, dwdk);
|
|
|
|
// Spectrum - use power values from users spectrum, but borrow wind term from PM
|
|
const float wavelength = PI2 / kMag;
|
|
const float octaveIndex = log2( wavelength ) - SPECTRUM_SMALLEST_WL_POW_2;
|
|
const float2 spectrumUV = float2((octaveIndex + 0.5) / SPECTRUM_OCTAVE_COUNT, 0.5);
|
|
const float spectrum = _Crest_SpectrumControls.SampleLevel( linear_clamp_sampler, spectrumUV, 0.0 ) *
|
|
PiersonMoskowitzWindTerm( w );
|
|
|
|
float deltaSPos = spectrum;
|
|
float deltaSNeg = spectrum;
|
|
|
|
// Directional spreading
|
|
const float cosTheta = dot( k, _Crest_WindDir ) / kMag;
|
|
deltaSPos *= PosCosSquaredDirectionalSpreading( cosTheta );
|
|
deltaSNeg *= PosCosSquaredDirectionalSpreading( -cosTheta );
|
|
const float dK = PI2 / worldSize;
|
|
deltaSPos *= (dK * dK) * dwdk / kMag;
|
|
deltaSNeg *= (dK * dK) * dwdk / kMag;
|
|
|
|
// Amplitude
|
|
const float ampPos = RandGauss(rngState) * sqrt(abs(deltaSPos) * 2.0f);
|
|
const float ampNeg = RandGauss(rngState) * sqrt(abs(deltaSNeg) * 2.0f);
|
|
|
|
// Output
|
|
const float phasePos = RandFloat(rngState) * PI2;
|
|
const float phaseNeg = RandFloat(rngState) * PI2;
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
const float spiceyMultiplier = 1.5;
|
|
_Crest_ResultInit[id] = float4(ampPos * float2(cos(phasePos), -sin(phasePos)), ampNeg * float2(cos(phaseNeg), -sin(phaseNeg)))
|
|
* spiceyMultiplier;
|
|
}
|
|
|
|
float _Crest_Time;
|
|
float _Crest_Chop;
|
|
|
|
Texture2DArray<float4> _Crest_Init0;
|
|
RWTexture2DArray<float2> _Crest_ResultHeight;
|
|
RWTexture2DArray<float2> _Crest_ResultDisplaceX;
|
|
RWTexture2DArray<float2> _Crest_ResultDisplaceZ;
|
|
|
|
float2 cmul(float2 lhs, float2 rhs)
|
|
{
|
|
return float2(
|
|
lhs.x * rhs.x - lhs.y * rhs.y,
|
|
lhs.x * rhs.y + lhs.y * rhs.x
|
|
);
|
|
}
|
|
|
|
[numthreads(8, 8, 1)]
|
|
void SpectrumUpdate(uint3 id : SV_DispatchThreadID)
|
|
{
|
|
const int2 center = _Crest_Size.xx / 2;
|
|
const int2 coord = id.xy - center;
|
|
|
|
// Find wave vector and number
|
|
const float worldSize = 0.5 * (1 << id.z);
|
|
const float2 k = PI2 * coord / worldSize;
|
|
const float kMag = length(k);
|
|
|
|
// Dispersion
|
|
float w; float dwdk;
|
|
DeepDispersion(kMag, w, dwdk);
|
|
|
|
// Advance time
|
|
float sw; float cw;
|
|
sincos(w * _Crest_Time, sw, cw);
|
|
|
|
const float2 fwd = float2(cw, -sw);
|
|
const float2 bkwd = float2(cw, sw);
|
|
|
|
const float4 h0 = _Crest_Init0[id];
|
|
const float2 h = cmul(h0.xy, fwd) + cmul(h0.zw, bkwd);
|
|
|
|
_Crest_ResultHeight[id] = h;
|
|
_Crest_ResultDisplaceX[id] = _Crest_Chop * float2(-h.y * k.x, h.x * k.x) / (kMag + 0.00001f);
|
|
_Crest_ResultDisplaceZ[id] = _Crest_Chop * float2(-h.y * k.y, h.x * k.y) / (kMag + 0.00001f);
|
|
}
|