升级6.4.升级水,升级天气

This commit is contained in:
2026-04-05 00:26:54 +08:00
parent 63bc9b5536
commit 5f7cbfb713
635 changed files with 34718 additions and 22567 deletions

View File

@@ -14,21 +14,32 @@ namespace WaveHarmonic.Crest
{
// Waves
[Tooltip("Whether to use the wind turbulence on this component rather than the global wind turbulence.\n\nGlobal wind turbulence comes from the Water Renderer component.")]
[Tooltip("Whether to apply the options shown when \"Show Advanced Controls\" is active.")]
[@Order(nameof(_EvaluateSpectrumAtRunTimeEveryFrame), Order.Placement.Below)]
[@DecoratedField]
[@GenerateAPI]
[@InlineToggle(order = -3), SerializeField]
[@SerializeField]
bool _ApplyAdvancedSpectrumControls;
[Tooltip("Whether to use the wind turbulence on this component rather than the global wind turbulence.\n\nGlobal wind turbulence comes from the Water Renderer component.")]
[@Order("Waves")]
[@InlineToggle]
[@GenerateAPI]
[@SerializeField]
bool _OverrideGlobalWindTurbulence;
[Tooltip("How turbulent/chaotic the waves are.")]
[@Predicated(nameof(_OverrideGlobalWindTurbulence), hide: true)]
[@Order("Waves")]
[@Show(nameof(_OverrideGlobalWindTurbulence))]
[@ShowComputedProperty(nameof(WindTurbulence))]
[@Range(0, 1, order = -4)]
[@Range(0, 1)]
[@GenerateAPI(Getter.Custom)]
[SerializeField]
float _WindTurbulence = 0.145f;
[Tooltip("How aligned the waves are with wind.")]
[@Range(0, 1, order = -5)]
[@Range(0, 1)]
[@Order("Waves")]
[@GenerateAPI]
[SerializeField]
float _WindAlignment;
@@ -37,21 +48,30 @@ namespace WaveHarmonic.Crest
// Generation
[Tooltip("FFT waves will loop with a period of this many seconds.")]
[@Order("Generation Settings")]
[@Range(4f, 128f, Range.Clamp.Minimum)]
[@GenerateAPI]
[SerializeField]
float _TimeLoopLength = Mathf.Infinity;
[Header("Culling")]
[@Heading("Culling")]
[Tooltip("Whether to override automatic culling based on heuristics.")]
[@GenerateAPI]
[@DecoratedField]
[SerializeField]
bool _OverrideCulling;
[Tooltip("Maximum amount the surface will be displaced vertically from sea level.\n\nIncrease this if gaps appear at bottom of screen.")]
[@GenerateAPI]
[@DecoratedField]
[SerializeField]
float _MaximumVerticalDisplacement = 10f;
[Tooltip("Maximum amount a point on the surface will be displaced horizontally by waves from its rest position.\n\nIncrease this if gaps appear at sides of screen.")]
[@GenerateAPI]
[@DecoratedField]
[SerializeField]
float _MaximumHorizontalDisplacement = 15f;
@@ -63,7 +83,7 @@ namespace WaveHarmonic.Crest
#endif
[Tooltip("Enable running this FFT with baked data.\n\nThis makes the FFT periodic (repeating in time).")]
[@Predicated(nameof(_Mode), inverted: true, nameof(LodInputMode.Global), hide: true)]
[@Show(nameof(_Mode), nameof(LodInputMode.Global))]
[@DecoratedField, SerializeField]
internal bool _EnableBakedCollision = false;
@@ -72,8 +92,8 @@ namespace WaveHarmonic.Crest
#endif
[Tooltip("Frames per second of baked data.\n\nLarger values may help the collision track the surface closely at the cost of more frames and increase baked data size.")]
[@Predicated(nameof(_EnableBakedCollision))]
[@Predicated(nameof(_Mode), inverted: true, nameof(LodInputMode.Global), hide: true)]
[@Enable(nameof(_EnableBakedCollision))]
[@Show(nameof(_Mode), nameof(LodInputMode.Global))]
[@DecoratedField, SerializeField]
internal int _TimeResolution = 4;
@@ -82,8 +102,8 @@ namespace WaveHarmonic.Crest
#endif
[Tooltip("Smallest wavelength required in collision.\n\nTo preview the effect of this, disable power sliders in spectrum for smaller values than this number. Smaller values require more resolution and increase baked data size.")]
[@Predicated(nameof(_EnableBakedCollision))]
[@Predicated(nameof(_Mode), inverted: true, nameof(LodInputMode.Global), hide: true)]
[@Enable(nameof(_EnableBakedCollision))]
[@Show(nameof(_Mode), nameof(LodInputMode.Global))]
[@DecoratedField, SerializeField]
internal float _SmallestWavelengthRequired = 2f;
@@ -92,8 +112,8 @@ namespace WaveHarmonic.Crest
#endif
[Tooltip("FFT waves will loop with a period of this many seconds.\n\nSmaller values decrease data size but can make waves visibly repetitive.")]
[@Predicated(nameof(_EnableBakedCollision))]
[@Predicated(nameof(_Mode), inverted: true, nameof(LodInputMode.Global), hide: true)]
[@Enable(nameof(_EnableBakedCollision))]
[@Show(nameof(_Mode), nameof(LodInputMode.Global))]
[@Range(4f, 128f)]
[SerializeField]
internal float _BakedTimeLoopLength = 32f;
@@ -104,8 +124,9 @@ namespace WaveHarmonic.Crest
#endif
_TimeLoopLength;
// WebGPU will crash above at 128.
private protected override int MinimumResolution => 16;
private protected override int MaximumResolution => int.MaxValue;
private protected override int MaximumResolution => Helpers.IsWebGPU ? 64 : int.MaxValue;
FFTCompute _FFTCompute;
@@ -119,7 +140,8 @@ namespace WaveHarmonic.Crest
WindDirRadForFFT,
WindTurbulence,
_WindAlignment,
gravity
gravity,
_ApplyAdvancedSpectrumControls
);
private protected override void OnUpdate(WaterRenderer water)
@@ -189,13 +211,28 @@ namespace WaveHarmonic.Crest
{
if (!Enabled) return;
// Apply weight or will cause popping due to scale change.
MaximumReportedHorizontalDisplacement = _MaximumHorizontalDisplacement * Weight;
MaximumReportedVerticalDisplacement = MaximumReportedWavesDisplacement = _MaximumVerticalDisplacement * Weight;
if (Mode == LodInputMode.Global)
if (_OverrideCulling)
{
water.ReportMaximumDisplacement(MaximumReportedHorizontalDisplacement, MaximumReportedVerticalDisplacement, MaximumReportedVerticalDisplacement);
// Apply weight or will cause popping due to scale change.
MaximumReportedHorizontalDisplacement = _MaximumHorizontalDisplacement * Weight;
MaximumReportedVerticalDisplacement = MaximumReportedWavesDisplacement = _MaximumVerticalDisplacement * Weight;
}
else
{
var powerLinear = 0f;
for (var i = 0; i < WaveSpectrum.k_NumberOfOctaves; i++)
{
powerLinear += _ActiveSpectrum._PowerLinearScales[i];
}
// Empirical multiplier (3-5), went with 5 to be safe.
// We may be missing some more multipliers from the compute shader.
// Look there if this proves insufficient.
var wind = Mathf.Clamp01(WindSpeedKPH / 150f);
var rms = Mathf.Sqrt(powerLinear) * 5f;
MaximumReportedHorizontalDisplacement = rms * _ActiveSpectrum._Chop * Weight * wind;
MaximumReportedVerticalDisplacement = MaximumReportedWavesDisplacement = rms * Weight * wind;
}
}
@@ -250,28 +287,18 @@ namespace WaveHarmonic.Crest
}
}
partial class ShapeFFT : ISerializationCallbackReceiver
partial class ShapeFFT
{
[SerializeField, HideInInspector]
#pragma warning disable 414
int _Version = 2;
#pragma warning restore 414
private protected override int Version => Mathf.Max(base.Version, 2);
void ISerializationCallbackReceiver.OnAfterDeserialize()
private protected override void OnMigrate()
{
_Version = MigrateV1(_Version);
base.OnMigrate();
if (_Version < 2)
{
_OverrideGlobalWindTurbulence = true;
}
_Version = MigrateV2(_Version);
}
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
// Empty.
}
}

View File

@@ -19,14 +19,18 @@ namespace WaveHarmonic.Crest
[@Space(10)]
// If disabled, there will be a difference due to Random.value.
[Tooltip("Use a swell spectrum as the default.\n\nUses a swell spectrum as default (when none is assigned), and disabled reverse waves.")]
[@Order("Waves")]
[@DecoratedField]
[@GenerateAPI]
[@DecoratedField(order = -3), SerializeField]
[@SerializeField]
bool _Swell = true;
[Tooltip("The weight of the opposing, second pair of Gerstner waves.\n\nEach Gerstner wave is actually a pair of waves travelling in opposite directions (similar to FFT). This weight is applied to the wave travelling in against-wind direction. Set to zero to obtain simple single waves which are useful for shorelines waves.")]
[Predicated(nameof(_Swell), inverted: true)]
[@Range(0f, 1f, order = -4)]
[@Order("Waves")]
[@Disable(nameof(_Swell))]
[@Range(0f, 1f)]
[@GenerateAPI(Getter.Custom)]
[SerializeField]
float _ReverseWaveWeight = 0.5f;
@@ -35,23 +39,28 @@ namespace WaveHarmonic.Crest
// Generation Settings
[Tooltip("How many wave components to generate in each octave.")]
[@Order("Generation Settings")]
[@Delayed]
[@GenerateAPI]
[SerializeField]
int _ComponentsPerOctave = 8;
[Tooltip("Change to get a different set of waves.")]
[@Order("Generation Settings")]
[@GenerateAPI]
[@DecoratedField]
[SerializeField]
int _RandomSeed = 0;
[Tooltip("Prevent data arrays from being written to so one can provide their own.")]
[@Order("Generation Settings")]
[@GenerateAPI]
[@DecoratedField]
[SerializeField]
bool _ManualGeneration;
private protected override int MinimumResolution => 8;
private protected override int MaximumResolution => 64;
private protected override int MaximumResolution => int.MaxValue;
float _WindSpeedWhenGenerated = -1f;
@@ -93,12 +102,7 @@ namespace WaveHarmonic.Crest
float[] _Amplitudes2;
float[] _Phases2;
struct GerstnerCascadeParams
{
public int _StartIndex;
}
ComputeBuffer _BufferCascadeParameters;
readonly GerstnerCascadeParams[] _CascadeParameters = new GerstnerCascadeParams[k_CascadeCount + 1];
readonly int[] _StartIndices = new int[k_CascadeCount];
// Caution - order here impact performance. Rearranging these to match order
// they're read in the compute shader made it 50% slower..
@@ -111,6 +115,7 @@ namespace WaveHarmonic.Crest
public Vector4 _Omega;
public Vector4 _Phase;
public Vector4 _ChopAmplitude;
// Waves are generated in pairs, these values are for the second in the pair
public Vector4 _Amplitude2;
public Vector4 _ChopAmplitude2;
@@ -119,8 +124,7 @@ namespace WaveHarmonic.Crest
ComputeBuffer _BufferWaveData;
readonly GerstnerWaveComponent4[] _WaveData = new GerstnerWaveComponent4[k_MaximumWaveComponents / 4];
ComputeShader _ShaderGerstner;
int _KernelGerstner = -1;
WaterResources.GerstnerCompute _Shader;
private protected override WaveSpectrum DefaultSpectrum => _Swell ? SwellSpectrum : WindSpectrum;
static WaveSpectrum s_SwellSpectrum;
@@ -154,7 +158,7 @@ namespace WaveHarmonic.Crest
{
public static readonly int s_FirstCascadeIndex = Shader.PropertyToID("_Crest_FirstCascadeIndex");
public static readonly int s_TextureRes = Shader.PropertyToID("_Crest_TextureRes");
public static readonly int s_CascadeParams = Shader.PropertyToID("_Crest_GerstnerCascadeParams");
public static readonly int s_StartIndices = Shader.PropertyToID("_Crest_StartIndices");
public static readonly int s_GerstnerWaveData = Shader.PropertyToID("_Crest_GerstnerWaveData");
}
@@ -179,7 +183,7 @@ namespace WaveHarmonic.Crest
{
if (_WaveBuffers == null)
{
_WaveBuffers = new(_Resolution, _Resolution, 0, GraphicsFormat.R16G16B16A16_SFloat);
_WaveBuffers = new(Resolution, Resolution, 0, Helpers.GetCompatibleTextureFormat(GraphicsFormat.R16G16B16A16_SFloat, randomWrite: true));
}
else
{
@@ -187,7 +191,7 @@ namespace WaveHarmonic.Crest
}
{
_WaveBuffers.width = _WaveBuffers.height = _Resolution;
_WaveBuffers.width = _WaveBuffers.height = Resolution;
_WaveBuffers.wrapMode = TextureWrapMode.Clamp;
_WaveBuffers.antiAliasing = 1;
_WaveBuffers.filterMode = FilterMode.Bilinear;
@@ -200,14 +204,11 @@ namespace WaveHarmonic.Crest
_WaveBuffers.Create();
}
_BufferCascadeParameters?.Release();
_BufferWaveData?.Release();
_BufferCascadeParameters = new(k_CascadeCount + 1, UnsafeUtility.SizeOf<GerstnerCascadeParams>());
_BufferWaveData = new(k_MaximumWaveComponents / 4, UnsafeUtility.SizeOf<GerstnerWaveComponent4>());
_ShaderGerstner = WaterResources.Instance.Compute._Gerstner;
_KernelGerstner = _ShaderGerstner.FindKernel("Gerstner");
_Shader = WaterResources.Instance._ComputeLibrary._GerstnerCompute;
}
private protected override void OnUpdate(WaterRenderer water)
@@ -216,7 +217,7 @@ namespace WaveHarmonic.Crest
base.OnUpdate(water);
if (_WaveBuffers == null || _Resolution != _WaveBuffers.width || _BufferCascadeParameters == null || _BufferWaveData == null)
if (_WaveBuffers == null || Resolution != _WaveBuffers.width || _BufferWaveData == null)
{
InitData();
}
@@ -266,7 +267,7 @@ namespace WaveHarmonic.Crest
var cascadeIdx = 0;
var componentIdx = 0;
var outputIdx = 0;
_CascadeParameters[0]._StartIndex = 0;
_StartIndices[0] = 0;
if (_ManualGeneration)
{
@@ -313,12 +314,14 @@ namespace WaveHarmonic.Crest
_WaveData[vi]._Omega[ei] = 0f;
_WaveData[vi]._Phase[ei] = 0f;
_WaveData[vi]._ChopAmplitude[ei] = 0f;
if (!_ManualGeneration)
if (!_ManualGeneration && !_Swell)
{
_WaveData[vi]._Phase2[ei] = 0f;
_WaveData[vi]._Amplitude2[ei] = 0f;
_WaveData[vi]._ChopAmplitude2[ei] = 0f;
}
ei = (ei + 1) % 4;
outputIdx++;
}
@@ -326,7 +329,7 @@ namespace WaveHarmonic.Crest
if (outputIdx > 0 && _FirstCascade < 0) _FirstCascade = cascadeIdx;
cascadeIdx++;
_CascadeParameters[cascadeIdx]._StartIndex = outputIdx / 4;
_StartIndices[cascadeIdx] = outputIdx / 4;
minWl *= 2f;
//Debug.Log($"Crest: {cascadeIdx}: start {_cascadeParams[cascadeIdx]._startIndex} minWL {minWl}");
@@ -343,7 +346,7 @@ namespace WaveHarmonic.Crest
var chopScale = _ActiveSpectrum._ChopScales[componentIdx / _ComponentsPerOctave];
_WaveData[vi]._ChopAmplitude[ei] = -chopScale * _ActiveSpectrum._Chop * _Amplitudes[componentIdx];
if (!_ManualGeneration)
if (!_ManualGeneration && !_Swell)
{
_WaveData[vi]._Amplitude2[ei] = _Amplitudes2[componentIdx];
_WaveData[vi]._ChopAmplitude2[ei] = -chopScale * _ActiveSpectrum._Chop * _Amplitudes2[componentIdx];
@@ -385,7 +388,7 @@ namespace WaveHarmonic.Crest
_WaveData[vi]._Omega[ei] = k * c;
_WaveData[vi]._Phase[ei] = Mathf.Repeat(_Phases[componentIdx], Mathf.PI * 2f);
if (!_ManualGeneration)
if (!_ManualGeneration && !_Swell)
{
_WaveData[vi]._Phase2[ei] = Mathf.Repeat(_Phases2[componentIdx], Mathf.PI * 2f);
}
@@ -410,40 +413,46 @@ namespace WaveHarmonic.Crest
_WaveData[vi]._Omega[ei] = 0f;
_WaveData[vi]._Phase[ei] = 0f;
_WaveData[vi]._ChopAmplitude[ei] = 0f;
if (!_ManualGeneration)
if (!_ManualGeneration && !_Swell)
{
_WaveData[vi]._Phase2[ei] = 0f;
_WaveData[vi]._Amplitude2[ei] = 0f;
_WaveData[vi]._ChopAmplitude2[ei] = 0f;
}
ei = (ei + 1) % 4;
outputIdx++;
}
}
while (cascadeIdx < k_CascadeCount)
// Fill the remaining.
while (cascadeIdx < k_CascadeCount - 1)
{
cascadeIdx++;
minWl *= 2f;
_CascadeParameters[cascadeIdx]._StartIndex = outputIdx / 4;
_StartIndices[cascadeIdx] = outputIdx / 4;
//Debug.Log($"Crest: {cascadeIdx}: start {_cascadeParams[cascadeIdx]._startIndex} minWL {minWl}");
}
_BufferCascadeParameters.SetData(_CascadeParameters);
_BufferWaveData.SetData(_WaveData);
}
void UpdateGenerateWaves(CommandBuffer buf)
void UpdateGenerateWaves(CommandBuffer buffer)
{
// Clear existing waves or they could get copied.
CoreUtils.SetRenderTarget(buf, _WaveBuffers, ClearFlag.Color);
buf.SetComputeFloatParam(_ShaderGerstner, ShaderIDs.s_TextureRes, _WaveBuffers.width);
buf.SetComputeIntParam(_ShaderGerstner, ShaderIDs.s_FirstCascadeIndex, _FirstCascade);
buf.SetComputeBufferParam(_ShaderGerstner, _KernelGerstner, ShaderIDs.s_CascadeParams, _BufferCascadeParameters);
buf.SetComputeBufferParam(_ShaderGerstner, _KernelGerstner, ShaderIDs.s_GerstnerWaveData, _BufferWaveData);
buf.SetComputeTextureParam(_ShaderGerstner, _KernelGerstner, ShapeWaves.ShaderIDs.s_WaveBuffer, _WaveBuffers);
CoreUtils.SetRenderTarget(buffer, _WaveBuffers, ClearFlag.Color);
buf.DispatchCompute(_ShaderGerstner, _KernelGerstner, _WaveBuffers.width / Lod.k_ThreadGroupSizeX, _WaveBuffers.height / Lod.k_ThreadGroupSizeY, _LastCascade - _FirstCascade + 1);
var wrapper = new PropertyWrapperCompute(buffer, _Shader._Shader, _Shader._ExecuteKernel);
wrapper.SetFloat(ShaderIDs.s_TextureRes, _WaveBuffers.width);
wrapper.SetInteger(ShaderIDs.s_FirstCascadeIndex, _FirstCascade);
wrapper.SetIntegers(ShaderIDs.s_StartIndices, _StartIndices);
wrapper.SetBuffer(ShaderIDs.s_GerstnerWaveData, _BufferWaveData);
wrapper.SetTexture(ShapeWaves.ShaderIDs.s_WaveBuffer, _WaveBuffers);
wrapper.SetKeyword(_Shader._WavePairsKeyword, !_Swell && _ReverseWaveWeight > 0f);
wrapper.Dispatch(_WaveBuffers.width / Lod.k_ThreadGroupSizeX, _WaveBuffers.height / Lod.k_ThreadGroupSizeY, _LastCascade - _FirstCascade + 1);
}
/// <summary>
@@ -503,16 +512,16 @@ namespace WaveHarmonic.Crest
{
var amp = _ActiveSpectrum.GetAmplitude(_Wavelengths[i], _ComponentsPerOctave, windSpeed, water.Gravity, out _Powers[i]);
_Amplitudes[i] = Random.value * amp;
_Amplitudes2[i] = Random.value * amp * ReverseWaveWeight;
if (!_Swell)
{
_Amplitudes2[i] = Random.value * amp * ReverseWaveWeight;
}
}
}
void InitPhases()
{
// Set random seed to get repeatable results
var randomStateBkp = Random.state;
Random.InitState(_RandomSeed);
var totalComps = _ComponentsPerOctave * WaveSpectrum.k_NumberOfOctaves;
_Phases = new float[totalComps];
_Phases2 = new float[totalComps];
@@ -524,12 +533,13 @@ namespace WaveHarmonic.Crest
var rnd = (i + Random.value) / _ComponentsPerOctave;
_Phases[index] = 2f * Mathf.PI * rnd;
var rnd2 = (i + Random.value) / _ComponentsPerOctave;
_Phases2[index] = 2f * Mathf.PI * rnd2;
if (!_Swell)
{
var rnd2 = (i + Random.value) / _ComponentsPerOctave;
_Phases2[index] = 2f * Mathf.PI * rnd2;
}
}
}
Random.state = randomStateBkp;
}
private protected override void ReportMaxDisplacement(WaterRenderer water)
@@ -546,23 +556,23 @@ namespace WaveHarmonic.Crest
return;
}
var ampSum = 0f;
MaximumReportedVerticalDisplacement = 0;
MaximumReportedHorizontalDisplacement = 0;
for (var i = 0; i < _Wavelengths.Length; i++)
{
ampSum += _Amplitudes[i] * _ActiveSpectrum._ChopScales[i / _ComponentsPerOctave];
var amplitude = _Amplitudes[i];
MaximumReportedVerticalDisplacement += amplitude;
MaximumReportedHorizontalDisplacement += amplitude * _ActiveSpectrum._ChopScales[i / _ComponentsPerOctave];
}
MaximumReportedHorizontalDisplacement *= _ActiveSpectrum._Chop;
// Apply weight or will cause popping due to scale change.
ampSum *= Weight;
MaximumReportedVerticalDisplacement *= Weight;
MaximumReportedHorizontalDisplacement *= Weight;
MaximumReportedHorizontalDisplacement = ampSum * _ActiveSpectrum._Chop;
MaximumReportedVerticalDisplacement = ampSum;
MaximumReportedWavesDisplacement = ampSum;
if (Mode == LodInputMode.Global)
{
water.ReportMaximumDisplacement(ampSum * _ActiveSpectrum._Chop, ampSum, ampSum);
}
MaximumReportedWavesDisplacement = MaximumReportedVerticalDisplacement;
}
private protected override void Initialize()
@@ -577,11 +587,6 @@ namespace WaveHarmonic.Crest
s_Instances.Remove(this);
if (_BufferCascadeParameters != null && _BufferCascadeParameters.IsValid())
{
_BufferCascadeParameters.Dispose();
_BufferCascadeParameters = null;
}
if (_BufferWaveData != null && _BufferWaveData.IsValid())
{
_BufferWaveData.Dispose();
@@ -623,32 +628,23 @@ namespace WaveHarmonic.Crest
if (s_SwellSpectrum != null)
{
Helpers.Destroy(s_SwellSpectrum);
s_SwellSpectrum = null;
}
}
}
partial class ShapeGerstner : ISerializationCallbackReceiver
partial class ShapeGerstner
{
[SerializeField, HideInInspector]
#pragma warning disable 414
int _Version = 2;
#pragma warning restore 414
private protected override int Version => Mathf.Max(base.Version, 2);
void ISerializationCallbackReceiver.OnAfterDeserialize()
private protected override void OnMigrate()
{
_Version = MigrateV1(_Version);
base.OnMigrate();
if (_Version < 2)
{
_Swell = false;
}
_Version = MigrateV2(_Version);
}
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
// Empty.
}
}
}

View File

@@ -4,6 +4,7 @@
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Serialization;
using WaveHarmonic.Crest.Internal;
namespace WaveHarmonic.Crest
{
@@ -26,8 +27,11 @@ namespace WaveHarmonic.Crest
[Tooltip("Whether to evaluate the spectrum every frame.\n\nWhen false, the wave spectrum is evaluated once on startup in editor play mode and standalone builds, rather than every frame. This is less flexible, but it reduces the performance cost significantly.")]
[@GenerateAPI]
[FormerlySerializedAs("_SpectrumFixedAtRuntime")]
[@DecoratedField]
[SerializeField]
bool _EvaluateSpectrumAtRunTimeEveryFrame;
private protected bool _EvaluateSpectrumAtRunTimeEveryFrame;
[@Space(10)]
[Tooltip("How much these waves respect the shallow water attenuation.\n\nAttenuation is defined on the Animated Waves. Set to zero to ignore attenuation.")]
[@Range(0f, 1f)]
@@ -35,6 +39,15 @@ namespace WaveHarmonic.Crest
[SerializeField]
float _RespectShallowWaterAttenuation = 1f;
[Tooltip("Whether global waves is applied above or below sea level.\n\nWaves are faded out to avoid hard transitionds. They are fully faded by 1m from sea level.")]
[@Enable(nameof(_Mode), nameof(LodInputMode.Global))]
[@GenerateAPI]
[@DecoratedField]
[SerializeField]
bool _SeaLevelOnly = true;
[@Space(10)]
[Tooltip("Whether to use the wind direction on this component rather than the global wind direction.\n\nGlobal wind direction comes from the Water Renderer component.")]
[@GenerateAPI]
[@InlineToggle, SerializeField]
@@ -42,8 +55,8 @@ namespace WaveHarmonic.Crest
[@Label("Wind Direction")]
[Tooltip("Primary wave direction heading (degrees).\n\nThis is the angle from x axis in degrees that the waves are oriented towards. If a spline is being used to place the waves, this angle is relative to the spline.")]
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Paint))]
[@Predicated(nameof(_OverrideGlobalWindDirection), hide: true)]
[@Disable(nameof(_Mode), nameof(LodInputMode.Paint))]
[@Show(nameof(_OverrideGlobalWindDirection))]
[@ShowComputedProperty(nameof(WaveDirectionHeadingAngle))]
[@Range(-180, 180)]
[@GenerateAPI(Getter.Custom)]
@@ -57,22 +70,32 @@ namespace WaveHarmonic.Crest
[Tooltip("Wind speed in km/h. Controls wave conditions.")]
[@ShowComputedProperty(nameof(WindSpeedKPH))]
[@Predicated(nameof(_OverrideGlobalWindSpeed), hide: true)]
[@Show(nameof(_OverrideGlobalWindSpeed))]
[@Range(0, 150f, scale: 2f)]
[@GenerateAPI]
[SerializeField]
float _WindSpeed = 20f;
[Header("Generation Settings")]
[@Heading("Generation Settings")]
[Tooltip("Resolution to use for wave generation buffers.\n\nLow resolutions are more efficient but can result in noticeable patterns in the shape.")]
[@Stepped(16, 512, step: 2, power: true)]
[@GenerateAPI]
[@GenerateAPI(Getter.Custom)]
[SerializeField]
private protected int _Resolution = 128;
[@Heading("Level of Detail")]
[Tooltip("Whether the maximum possible vertical displacement is used for the Drop Detail Height Based On Waves calculation.\n\nThis setting is ignored for global waves, as they always contribute. For local waves, only enable for large areas that are treated like global waves (eg a storm).")]
[@Disable(nameof(_Mode), nameof(LodInputMode.Global))]
[@GenerateAPI]
[@DecoratedField]
[SerializeField]
bool _IncludeInDropDetailHeightBasedOnWaves;
// Debug
[Tooltip("In Editor, shows the wave generation buffers on screen.")]
@@ -92,6 +115,8 @@ namespace WaveHarmonic.Crest
public static readonly int s_RespectShallowWaterAttenuation = Shader.PropertyToID("_Crest_RespectShallowWaterAttenuation");
public static readonly int s_MaximumAttenuationDepth = Shader.PropertyToID("_Crest_MaximumAttenuationDepth");
public static readonly int s_AxisX = Shader.PropertyToID("_Crest_AxisX");
public static readonly int s_SeaLevelOnly = Shader.PropertyToID("_Crest_SeaLevelOnly");
public static readonly int s_WaveBufferAttenuation = Shader.PropertyToID("_Crest_WaveBufferAttenuation");
}
private protected virtual WaveSpectrum DefaultSpectrum => WindSpectrum;
@@ -136,7 +161,7 @@ namespace WaveHarmonic.Crest
/// <summary>
/// The wind speed in meters per second (MPS).
/// </summary>
/// /// <remarks>
/// <remarks>
/// Wind speed can come from this component or the <see cref="WaterRenderer"/>.
/// </remarks>
public float WindSpeedMPS => WindSpeedKPH / 3.6f;
@@ -150,13 +175,32 @@ namespace WaveHarmonic.Crest
{
base.Attach();
_Reporter ??= new(this);
WaterChunkRenderer.DisplacementReporters.Add(_Reporter);
_DisplacementReporter = _Reporter;
_WaveDisplacementReporter = _Reporter;
}
private protected override void Detach()
{
base.Detach();
WaterChunkRenderer.DisplacementReporters.Remove(_Reporter);
_DisplacementReporter = null;
_WaveDisplacementReporter = null;
}
internal enum WindSpeedSource
{
None, // Wind Speed is at maximum.
ShapeWaves,
WaterRenderer,
}
internal WindSpeedSource GetWindSpeedSource()
{
if (WindSpeedKPH >= WaterRenderer.k_MaximumWindSpeedKPH)
{
return WindSpeedSource.None;
}
return OverrideGlobalWindSpeed ? WindSpeedSource.ShapeWaves : WindSpeedSource.WaterRenderer;
}
internal override void Draw(Lod simulation, CommandBuffer buffer, RenderTargetIdentifier target, int pass = -1, float weight = 1f, int slice = -1)
@@ -173,30 +217,32 @@ namespace WaveHarmonic.Crest
return;
}
var lodCount = simulation.Slices;
var shape = (AnimatedWavesLod)simulation;
var wrapper = new PropertyWrapperCompute(buffer, s_TransferWavesComputeShader, 0);
if (_FirstCascade < 0 || _LastCascade < 0)
{
return;
}
var lodCount = simulation.Slices;
var lodResolution = simulation.Resolution;
var shape = (AnimatedWavesLod)simulation;
var wrapper = new PropertyWrapperCompute(buffer, s_TransferWavesComputeShader, 0);
// Write to per-octave _WaveBuffers (ie pre-combined). Not the same as _AnimatedWaves.
wrapper.SetTexture(Crest.ShaderIDs.s_Target, target);
// Input weight. Weight for each octave calculated in compute.
wrapper.SetFloat(LodInput.ShaderIDs.s_Weight, Weight);
wrapper.SetInteger(Crest.ShaderIDs.s_Resolution, Resolution);
var water = shape._Water;
for (var lodIdx = lodCount - 1; lodIdx >= lodCount - slice; lodIdx--)
{
_WaveBufferParameters[lodIdx] = new(-1, -2, 0, 0);
_WaveBufferParameters[lodIdx] = new(-1, -2, 0, 1);
var found = false;
var filter = new AnimatedWavesLod.WavelengthFilter(water, lodIdx);
var filter = new AnimatedWavesLod.WavelengthFilter(water, lodIdx, lodResolution);
for (var i = _FirstCascade; i <= _LastCascade; i++)
{
@@ -221,13 +267,17 @@ namespace WaveHarmonic.Crest
}
// Set transitional weights.
_WaveBufferParameters[lodCount - 2].w = 1f - water.ViewerAltitudeLevelAlpha;
if (!shape.PreserveWaveQuality)
{
_WaveBufferParameters[lodCount - 2].w = 1f - water.ViewerAltitudeLevelAlpha;
}
_WaveBufferParameters[lodCount - 1].w = water.ViewerAltitudeLevelAlpha;
SetRenderParameters(water, wrapper);
wrapper.SetFloat(ShaderIDs.s_WaveResolutionMultiplier, shape.WaveResolutionMultiplier);
wrapper.SetFloat(ShaderIDs.s_TransitionalWavelengthThreshold, water.MaximumWavelength(water.LodLevels - 1) * 0.5f);
wrapper.SetFloat(ShaderIDs.s_TransitionalWavelengthThreshold, water.MaximumWavelength(water.LodLevels - 1, simulation.Resolution) * 0.5f);
wrapper.SetVectorArray(ShaderIDs.s_WaveBufferParameters, _WaveBufferParameters);
var isTexture = Mode is LodInputMode.Paint or LodInputMode.Texture;
@@ -236,6 +286,8 @@ namespace WaveHarmonic.Crest
wrapper.SetKeyword(s_KeywordTexture, isTexture && !isAlphaBlend);
wrapper.SetKeyword(s_KeywordTextureBlend, isTexture && isAlphaBlend);
wrapper.SetFloatArray(ShaderIDs.s_WaveBufferAttenuation, _ActiveSpectrum._Attenuation);
if (isTexture)
{
wrapper.SetInteger(Crest.ShaderIDs.s_Blend, (int)_Blend);
@@ -243,6 +295,8 @@ namespace WaveHarmonic.Crest
if (Mode == LodInputMode.Global)
{
wrapper.SetBoolean(ShaderIDs.s_SeaLevelOnly, _SeaLevelOnly);
var threads = shape.Resolution / Lod.k_ThreadGroupSize;
wrapper.Dispatch(threads, threads, slice);
}
@@ -255,6 +309,7 @@ namespace WaveHarmonic.Crest
void GraphicsDraw(Lod simulation, CommandBuffer buffer, RenderTargetIdentifier target, int pass, float weight, int slice)
{
var lod = simulation as AnimatedWavesLod;
var lodResolution = simulation.Resolution;
var wrapper = new PropertyWrapperBuffer(buffer);
SetRenderParameters(simulation._Water, wrapper);
@@ -266,7 +321,7 @@ namespace WaveHarmonic.Crest
_Wavelength = MinWavelength(i) / lod.WaveResolutionMultiplier;
// Do the weight from scratch because this is the real filter.
weight = AnimatedWavesLod.FilterByWavelength(simulation._Water, slice, _Wavelength) * Weight;
weight = AnimatedWavesLod.FilterByWavelength(simulation._Water, slice, _Wavelength, lodResolution) * Weight;
if (weight <= 0f) continue;
var average = _Wavelength * 1.5f * lod.WaveResolutionMultiplier;
@@ -411,40 +466,101 @@ namespace WaveHarmonic.Crest
s_KeywordTextureBlend = WaterResources.Instance.Keywords.AnimatedWavesTransferWavesTextureBlend;
}
bool ReportDisplacement(ref Rect bounds, ref float horizontal, ref float vertical)
bool ReportDisplacement(WaterRenderer water, ref Rect bounds, ref float horizontal, ref float vertical)
{
if (Mode == LodInputMode.Global || !Enabled)
if (!Enabled)
{
return false;
}
if (Mode == LodInputMode.Global)
{
// Global is always additive.
horizontal += MaximumReportedHorizontalDisplacement;
vertical += MaximumReportedVerticalDisplacement;
return true;
}
_Rect = Data.Rect;
if (bounds.Overlaps(_Rect, false))
{
horizontal = MaximumReportedHorizontalDisplacement;
vertical = MaximumReportedVerticalDisplacement;
var nh = horizontal;
var nv = vertical;
switch (Blend)
{
case LodInputBlend.Off:
nh = MaximumReportedHorizontalDisplacement;
nv = MaximumReportedVerticalDisplacement;
break;
case LodInputBlend.Additive:
nh += MaximumReportedHorizontalDisplacement;
nv += MaximumReportedVerticalDisplacement;
break;
case LodInputBlend.Alpha:
case LodInputBlend.AlphaClip:
nh = Mathf.Max(nh, MaximumReportedHorizontalDisplacement);
nv = Mathf.Max(nh, MaximumReportedVerticalDisplacement);
break;
}
horizontal = Mathf.Max(horizontal, nh);
vertical = Mathf.Max(vertical, nv);
return true;
}
return false;
}
float ReportWaveDisplacement(WaterRenderer water, float displacement)
{
if (Mode == LodInputMode.Global)
{
return displacement + MaximumReportedWavesDisplacement;
}
if (!_IncludeInDropDetailHeightBasedOnWaves)
{
return displacement;
}
// TODO: use bounds to transition slowly to avoid pops.
if (_Rect.Contains(water.Position.XZ()))
{
displacement = Blend switch
{
LodInputBlend.Off => MaximumReportedWavesDisplacement,
LodInputBlend.Additive => displacement + MaximumReportedWavesDisplacement,
LodInputBlend.Alpha or LodInputBlend.AlphaClip => Mathf.Max(displacement, MaximumReportedWavesDisplacement),
_ => MaximumReportedWavesDisplacement,
};
}
return displacement;
}
float GetWaveDirectionHeadingAngle()
{
return _OverrideGlobalWindDirection || WaterRenderer.Instance == null ? _WaveDirectionHeadingAngle : WaterRenderer.Instance.WindDirection;
}
internal int GetResolution()
{
return Mathf.Clamp(_Resolution, MinimumResolution, MaximumResolution);
}
}
partial class ShapeWaves
{
Reporter _Reporter;
sealed class Reporter : IReportsDisplacement
sealed class Reporter : IReportsDisplacement, IReportWaveDisplacement
{
readonly ShapeWaves _Input;
public Reporter(ShapeWaves input) => _Input = input;
public bool ReportDisplacement(ref Rect bounds, ref float horizontal, ref float vertical) => _Input.ReportDisplacement(ref bounds, ref horizontal, ref vertical);
public bool ReportDisplacement(WaterRenderer water, ref Rect bounds, ref float horizontal, ref float vertical) => _Input.ReportDisplacement(water, ref bounds, ref horizontal, ref vertical);
public float ReportWaveDisplacement(WaterRenderer water, float displacement) => _Input.ReportWaveDisplacement(water, displacement);
}
}
@@ -465,6 +581,7 @@ namespace WaveHarmonic.Crest
if (s_WindSpectrum != null)
{
Helpers.Destroy(s_WindSpectrum);
s_WindSpectrum = null;
}
}
}
@@ -472,16 +589,20 @@ namespace WaveHarmonic.Crest
partial class ShapeWaves
{
private protected override int Version => Mathf.Max(base.Version, 3);
[HideInInspector, SerializeField]
AlphaSource _AlphaSource;
enum AlphaSource { AlwaysOne, FromZero, FromZeroNormalized }
private protected int MigrateV1(int version)
private protected override void OnMigrate()
{
base.OnMigrate();
// Version 1
// - Merge Alpha Source into Blend.
// - Rename and invert Spectrum Fixed at Run-Time
if (version < 1)
if (_Version < 1)
{
if (_Blend == LodInputBlend.Alpha)
{
@@ -495,24 +616,19 @@ namespace WaveHarmonic.Crest
}
_EvaluateSpectrumAtRunTimeEveryFrame = !_EvaluateSpectrumAtRunTimeEveryFrame;
version = 1;
}
return version;
}
private protected int MigrateV2(int version)
{
// Version 2
// - Global wind direction
if (version < 2)
if (_Version < 2)
{
_OverrideGlobalWindDirection = true;
version = 2;
}
return version;
if (_Version < 3)
{
_SeaLevelOnly = false;
}
}
}