修改水
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
// Inspired by https://github.com/speps/GX-EncinoWaves
|
||||
|
||||
// First SIZE constant must match FFT_KERNEL_0_RESOLUTION in FFTCompute.cs
|
||||
#pragma kernel ComputeFFT SIZE=8 PASSES=3 CHANNEL=x TX=8 TY=1 FINAL=0
|
||||
#pragma kernel ComputeFFT SIZE=8 PASSES=3 CHANNEL=y TX=1 TY=8 FINAL=1
|
||||
#pragma kernel ComputeFFT SIZE=16 PASSES=4 CHANNEL=x TX=16 TY=1 FINAL=0
|
||||
#pragma kernel ComputeFFT SIZE=16 PASSES=4 CHANNEL=y TX=1 TY=16 FINAL=1
|
||||
#pragma kernel ComputeFFT SIZE=32 PASSES=5 CHANNEL=x TX=32 TY=1 FINAL=0
|
||||
#pragma kernel ComputeFFT SIZE=32 PASSES=5 CHANNEL=y TX=1 TY=32 FINAL=1
|
||||
#pragma kernel ComputeFFT SIZE=64 PASSES=6 CHANNEL=x TX=64 TY=1 FINAL=0
|
||||
#pragma kernel ComputeFFT SIZE=64 PASSES=6 CHANNEL=y TX=1 TY=64 FINAL=1
|
||||
#pragma kernel ComputeFFT SIZE=128 PASSES=7 CHANNEL=x TX=128 TY=1 FINAL=0
|
||||
#pragma kernel ComputeFFT SIZE=128 PASSES=7 CHANNEL=y TX=1 TY=128 FINAL=1
|
||||
#pragma kernel ComputeFFT SIZE=256 PASSES=8 CHANNEL=x TX=256 TY=1 FINAL=0
|
||||
#pragma kernel ComputeFFT SIZE=256 PASSES=8 CHANNEL=y TX=1 TY=256 FINAL=1
|
||||
#pragma kernel ComputeFFT SIZE=512 PASSES=9 CHANNEL=x TX=512 TY=1 FINAL=0
|
||||
#pragma kernel ComputeFFT SIZE=512 PASSES=9 CHANNEL=y TX=1 TY=512 FINAL=1
|
||||
|
||||
// Must match CASCADE_COUNT in FFTCompute.cs
|
||||
#define CASCADE_COUNT 16
|
||||
|
||||
Texture2D<float2> _Crest_InputButterfly;
|
||||
#if !FINAL
|
||||
RWTexture2DArray<float2> _Crest_Output1;
|
||||
RWTexture2DArray<float2> _Crest_Output2;
|
||||
RWTexture2DArray<float2> _Crest_Output3;
|
||||
#else
|
||||
Texture2DArray<float2> _Crest_InputH;
|
||||
Texture2DArray<float2> _Crest_InputX;
|
||||
Texture2DArray<float2> _Crest_InputZ;
|
||||
// Write zero to W to clear garbage.
|
||||
RWTexture2DArray<float4> _Crest_Output;
|
||||
#endif
|
||||
|
||||
groupshared float2 _Crest_IntermediatesH[SIZE];
|
||||
groupshared float2 _Crest_ScratchH[SIZE];
|
||||
groupshared float2 _Crest_IntermediatesX[SIZE];
|
||||
groupshared float2 _Crest_ScratchX[SIZE];
|
||||
groupshared float2 _Crest_IntermediatesZ[SIZE];
|
||||
groupshared float2 _Crest_ScratchZ[SIZE];
|
||||
|
||||
// This was required for Intel machines, but not required for Apple Silicon.
|
||||
#if defined(SHADER_API_METAL)
|
||||
// reversebits function appears to not make it across to Metal
|
||||
#if !defined(reversebits)
|
||||
uint reversebits( uint x )
|
||||
{
|
||||
x = ((x >> 1) & 0x55555555u) | ((x & 0x55555555u) << 1);
|
||||
x = ((x >> 2) & 0x33333333u) | ((x & 0x33333333u) << 2);
|
||||
x = ((x >> 4) & 0x0f0f0f0fu) | ((x & 0x0f0f0f0fu) << 4);
|
||||
x = ((x >> 8) & 0x00ff00ffu) | ((x & 0x00ff00ffu) << 8);
|
||||
x = ((x >> 16) & 0xffffu) | ((x & 0xffffu) << 16);
|
||||
return x;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void ButterflyPass(float2 butterfly, uint coord, uint passIndex, uint cascade)
|
||||
{
|
||||
uint indexA, indexB;
|
||||
|
||||
const uint offset = 1 << passIndex;
|
||||
if ((coord / offset) % 2 == 1)
|
||||
{
|
||||
indexA = coord - offset;
|
||||
indexB = coord;
|
||||
}
|
||||
else
|
||||
{
|
||||
indexA = coord;
|
||||
indexB = coord + offset;
|
||||
}
|
||||
|
||||
if (passIndex == 0)
|
||||
{
|
||||
indexA = reversebits(indexA) >> (32 - PASSES);
|
||||
indexB = reversebits(indexB) >> (32 - PASSES);
|
||||
}
|
||||
|
||||
const bool pingpong = (passIndex % 2) == 0;
|
||||
|
||||
float2 valueA_H, valueB_H;
|
||||
float2 valueA_X, valueB_X;
|
||||
float2 valueA_Z, valueB_Z;
|
||||
if (pingpong)
|
||||
{
|
||||
valueA_H = _Crest_IntermediatesH[indexA];
|
||||
valueB_H = _Crest_IntermediatesH[indexB];
|
||||
|
||||
valueA_X = _Crest_IntermediatesX[indexA];
|
||||
valueB_X = _Crest_IntermediatesX[indexB];
|
||||
|
||||
valueA_Z = _Crest_IntermediatesZ[indexA];
|
||||
valueB_Z = _Crest_IntermediatesZ[indexB];
|
||||
}
|
||||
else
|
||||
{
|
||||
valueA_H = _Crest_ScratchH[indexA];
|
||||
valueB_H = _Crest_ScratchH[indexB];
|
||||
|
||||
valueA_X = _Crest_ScratchX[indexA];
|
||||
valueB_X = _Crest_ScratchX[indexB];
|
||||
|
||||
valueA_Z = _Crest_ScratchZ[indexA];
|
||||
valueB_Z = _Crest_ScratchZ[indexB];
|
||||
}
|
||||
|
||||
const float2 weight = butterfly.xy;
|
||||
const float2 weightedValueH = weight * valueB_H.r + weight.gr * valueB_H.g * float2(-1.0, 1.0);
|
||||
const float2 weightedValueX = weight * valueB_X.r + weight.gr * valueB_X.g * float2(-1.0, 1.0);
|
||||
const float2 weightedValueZ = weight * valueB_Z.r + weight.gr * valueB_Z.g * float2(-1.0, 1.0);
|
||||
const float2 resultH = valueA_H + weightedValueH;
|
||||
const float2 resultX = valueA_X + weightedValueX;
|
||||
const float2 resultZ = valueA_Z + weightedValueZ;
|
||||
|
||||
if (pingpong)
|
||||
{
|
||||
_Crest_ScratchH[coord] = resultH;
|
||||
_Crest_ScratchX[coord] = resultX;
|
||||
_Crest_ScratchZ[coord] = resultZ;
|
||||
}
|
||||
else
|
||||
{
|
||||
_Crest_IntermediatesH[coord] = resultH;
|
||||
_Crest_IntermediatesX[coord] = resultX;
|
||||
_Crest_IntermediatesZ[coord] = resultZ;
|
||||
}
|
||||
}
|
||||
|
||||
float2 conj(float2 v)
|
||||
{
|
||||
return float2(v.x, -v.y);
|
||||
}
|
||||
|
||||
[numthreads(TX,TY,1)]
|
||||
void ComputeFFT(const uint3 id : SV_DispatchThreadID)
|
||||
{
|
||||
const uint coord = id.CHANNEL;
|
||||
#if !FINAL
|
||||
_Crest_IntermediatesH[coord] = conj(_Crest_Output1[id]);
|
||||
_Crest_IntermediatesX[coord] = conj(_Crest_Output2[id]);
|
||||
_Crest_IntermediatesZ[coord] = conj(_Crest_Output3[id]);
|
||||
#else
|
||||
_Crest_IntermediatesH[coord] = _Crest_InputH[id];
|
||||
_Crest_IntermediatesX[coord] = _Crest_InputX[id];
|
||||
_Crest_IntermediatesZ[coord] = _Crest_InputZ[id];
|
||||
#endif
|
||||
|
||||
[unroll(PASSES)]
|
||||
for (uint passIndex = 0; passIndex < PASSES; ++passIndex)
|
||||
{
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
ButterflyPass(_Crest_InputButterfly[uint2(coord, passIndex)].xy, coord, passIndex, id.z);
|
||||
}
|
||||
|
||||
GroupMemoryBarrierWithGroupSync();
|
||||
|
||||
const bool pingpong = (PASSES % 2) == 0;
|
||||
const float2 resultH = pingpong ? _Crest_IntermediatesH[coord] : _Crest_ScratchH[coord];
|
||||
const float2 resultX = pingpong ? _Crest_IntermediatesX[coord] : _Crest_ScratchX[coord];
|
||||
const float2 resultZ = pingpong ? _Crest_IntermediatesZ[coord] : _Crest_ScratchZ[coord];
|
||||
|
||||
#if !FINAL
|
||||
_Crest_Output1[id] = resultH;
|
||||
_Crest_Output2[id] = resultX;
|
||||
_Crest_Output3[id] = resultZ;
|
||||
#else
|
||||
const float sign = ((id.x + id.y) % 2) == 1 ? -1.0 : 1.0;
|
||||
const float3 res = float3(sign * resultX.x, sign * resultH.x, sign * resultZ.x);
|
||||
// Write zero to W to clear garbage.
|
||||
_Crest_Output[id] = float4(sign * resultX.x, sign * resultH.x, sign * resultZ.x, 1.0);
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5caa5dfb6d2c4632b41493dc2ba74d0
|
||||
ComputeShaderImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,263 @@
|
||||
// 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);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d493ec731bb6e43dfac22cc5921d31e3
|
||||
ComputeShaderImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user