移除水
This commit is contained in:
@@ -1,426 +0,0 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
// Inspired by https://github.com/speps/GX-EncinoWaves
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Runs FFT to generate water surface displacements
|
||||
/// </summary>
|
||||
sealed class FFTCompute
|
||||
{
|
||||
// Must match 'SIZE' param of first kernel in FFTCompute.compute
|
||||
const int k_Kernel0Resolution = 8;
|
||||
|
||||
// Must match CASCADE_COUNT in FFTCompute.compute
|
||||
const int k_CascadeCount = 16;
|
||||
|
||||
bool _Initialized = false;
|
||||
|
||||
RenderTexture _SpectrumInitial;
|
||||
|
||||
/// <summary>
|
||||
/// Generated 'raw', uncombined, wave data. Input for putting into AnimWaves data before combine pass.
|
||||
/// </summary>
|
||||
public RenderTexture WaveBuffers { get; private set; }
|
||||
|
||||
bool _SpectrumInitialized = false;
|
||||
|
||||
ComputeShader _ShaderSpectrum;
|
||||
ComputeShader _ShaderFFT;
|
||||
|
||||
int _KernelSpectrumInitial;
|
||||
int _KernelSpectrumUpdate;
|
||||
|
||||
Parameters _Parameters;
|
||||
|
||||
float _GenerationTime = -1f;
|
||||
|
||||
static readonly bool s_SupportsRandomWriteRGFloat =
|
||||
SystemInfo.SupportsRandomWriteOnRenderTextureFormat(RenderTextureFormat.RGFloat);
|
||||
|
||||
public static class ShaderIDs
|
||||
{
|
||||
public static readonly int s_Size = Shader.PropertyToID("_Crest_Size");
|
||||
public static readonly int s_WindSpeed = Shader.PropertyToID("_Crest_WindSpeed");
|
||||
public static readonly int s_Turbulence = Shader.PropertyToID("_Crest_Turbulence");
|
||||
public static readonly int s_Alignment = Shader.PropertyToID("_Crest_Alignment");
|
||||
public static readonly int s_Gravity = Shader.PropertyToID("_Crest_Gravity");
|
||||
public static readonly int s_Period = Shader.PropertyToID("_Crest_Period");
|
||||
public static readonly int s_WindDir = Shader.PropertyToID("_Crest_WindDir");
|
||||
public static readonly int s_SpectrumControls = Shader.PropertyToID("_Crest_SpectrumControls");
|
||||
public static readonly int s_ResultInit = Shader.PropertyToID("_Crest_ResultInit");
|
||||
public static readonly int s_Time = Shader.PropertyToID("_Crest_Time");
|
||||
public static readonly int s_Chop = Shader.PropertyToID("_Crest_Chop");
|
||||
public static readonly int s_Init0 = Shader.PropertyToID("_Crest_Init0");
|
||||
public static readonly int s_ResultHeight = Shader.PropertyToID("_Crest_ResultHeight");
|
||||
public static readonly int s_ResultDisplaceX = Shader.PropertyToID("_Crest_ResultDisplaceX");
|
||||
public static readonly int s_ResultDisplaceZ = Shader.PropertyToID("_Crest_ResultDisplaceZ");
|
||||
public static readonly int s_InputH = Shader.PropertyToID("_Crest_InputH");
|
||||
public static readonly int s_InputX = Shader.PropertyToID("_Crest_InputX");
|
||||
public static readonly int s_InputZ = Shader.PropertyToID("_Crest_InputZ");
|
||||
public static readonly int s_InputButterfly = Shader.PropertyToID("_Crest_InputButterfly");
|
||||
public static readonly int s_Output1 = Shader.PropertyToID("_Crest_Output1");
|
||||
public static readonly int s_Output2 = Shader.PropertyToID("_Crest_Output2");
|
||||
public static readonly int s_Output3 = Shader.PropertyToID("_Crest_Output3");
|
||||
public static readonly int s_Output = Shader.PropertyToID("_Crest_Output");
|
||||
|
||||
public static readonly int s_TemporaryFFT1 = Shader.PropertyToID("_Crest_TemporaryFFT1");
|
||||
public static readonly int s_TemporaryFFT2 = Shader.PropertyToID("_Crest_TemporaryFFT2");
|
||||
public static readonly int s_TemporaryFFT3 = Shader.PropertyToID("_Crest_TemporaryFFT3");
|
||||
}
|
||||
|
||||
internal readonly struct Parameters
|
||||
{
|
||||
public readonly WaveSpectrum _Spectrum;
|
||||
public readonly int _Resolution;
|
||||
public readonly float _LoopPeriod;
|
||||
public readonly float _WindSpeed;
|
||||
public readonly float _WindDirectionRadians;
|
||||
public readonly float _WindTurbulence;
|
||||
public readonly float _WindAlignment;
|
||||
|
||||
public Parameters(WaveSpectrum spectrum, int resolution, float period, float speed, float direction, float turbulence, float alignment)
|
||||
{
|
||||
_Spectrum = spectrum;
|
||||
_Resolution = resolution;
|
||||
_LoopPeriod = period;
|
||||
_WindSpeed = speed;
|
||||
_WindDirectionRadians = direction;
|
||||
_WindTurbulence = turbulence;
|
||||
_WindAlignment = alignment;
|
||||
}
|
||||
|
||||
// Implement custom or incur allocations.
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return System.HashCode.Combine(_Spectrum, _LoopPeriod, _WindSpeed, _WindDirectionRadians, _WindTurbulence, _WindAlignment, _Resolution);
|
||||
}
|
||||
|
||||
public int GetHashCode(int resolution)
|
||||
{
|
||||
return System.HashCode.Combine(_Spectrum, _LoopPeriod, _WindSpeed, _WindDirectionRadians, _WindTurbulence, _WindAlignment, resolution);
|
||||
}
|
||||
}
|
||||
|
||||
public FFTCompute(Parameters parameters)
|
||||
{
|
||||
Debug.Assert(Mathf.NextPowerOfTwo(parameters._Resolution) == parameters._Resolution, "Crest: FFTCompute resolution must be power of 2");
|
||||
_Parameters = parameters;
|
||||
}
|
||||
|
||||
public void Release()
|
||||
{
|
||||
if (_SpectrumInitial != null)
|
||||
{
|
||||
_SpectrumInitial.Release();
|
||||
}
|
||||
|
||||
if (WaveBuffers != null)
|
||||
{
|
||||
WaveBuffers.Release();
|
||||
}
|
||||
|
||||
Helpers.Destroy(_SpectrumInitial);
|
||||
Helpers.Destroy(WaveBuffers);
|
||||
|
||||
_SpectrumInitialized = false;
|
||||
_Initialized = false;
|
||||
}
|
||||
|
||||
internal static void CleanUpAll()
|
||||
{
|
||||
foreach (var generator in s_Generators)
|
||||
{
|
||||
generator.Value.Release();
|
||||
}
|
||||
|
||||
s_Generators?.Clear();
|
||||
|
||||
foreach (var texture in s_ButterflyTextures?.Values)
|
||||
{
|
||||
Helpers.Destroy(texture);
|
||||
}
|
||||
|
||||
s_ButterflyTextures?.Clear();
|
||||
}
|
||||
|
||||
static readonly Dictionary<int, FFTCompute> s_Generators = new();
|
||||
static readonly Dictionary<int, Texture2D> s_ButterflyTextures = new();
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
static void InitStatics()
|
||||
{
|
||||
CleanUpAll();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes water surface displacement, with wave components split across slices of the output texture array
|
||||
/// </summary>
|
||||
public static RenderTexture GenerateDisplacements(CommandBuffer buf, float time, Parameters parameters, bool updateSpectrum)
|
||||
{
|
||||
var conditionsHash = parameters.GetHashCode();
|
||||
// All static data arguments should be hashed here and passed to the generator constructor
|
||||
if (!s_Generators.TryGetValue(conditionsHash, out var generator))
|
||||
{
|
||||
// No generator for these params - create one
|
||||
generator = new(parameters);
|
||||
s_Generators.Add(conditionsHash, generator);
|
||||
}
|
||||
|
||||
// The remaining dynamic data arguments should be passed in to the generation here
|
||||
return generator.GenerateDisplacementsInternal(buf, time, updateSpectrum);
|
||||
}
|
||||
|
||||
RenderTexture GenerateDisplacementsInternal(CommandBuffer buffer, float time, bool updateSpectrum)
|
||||
{
|
||||
// Check if already generated, and we're not being asked to re-update the spectrum
|
||||
if (_GenerationTime == time && !updateSpectrum)
|
||||
{
|
||||
return WaveBuffers;
|
||||
}
|
||||
|
||||
var resolution = _Parameters._Resolution;
|
||||
var period = _Parameters._LoopPeriod;
|
||||
|
||||
// Initialize.
|
||||
if (!_Initialized || _SpectrumInitial == null)
|
||||
{
|
||||
Release();
|
||||
|
||||
_ShaderSpectrum = WaterResources.Instance.Compute._FFTSpectrum;
|
||||
_KernelSpectrumInitial = _ShaderSpectrum.FindKernel("SpectrumInitalize");
|
||||
_KernelSpectrumUpdate = _ShaderSpectrum.FindKernel("SpectrumUpdate");
|
||||
_ShaderFFT = WaterResources.Instance.Compute._FFT;
|
||||
|
||||
var rtd = new RenderTextureDescriptor(0, 0);
|
||||
rtd.width = rtd.height = resolution;
|
||||
rtd.dimension = TextureDimension.Tex2DArray;
|
||||
rtd.enableRandomWrite = true;
|
||||
rtd.depthBufferBits = 0;
|
||||
rtd.volumeDepth = k_CascadeCount;
|
||||
rtd.colorFormat = RenderTextureFormat.ARGBFloat;
|
||||
rtd.msaaSamples = 1;
|
||||
|
||||
Helpers.SafeCreateRenderTexture(ref _SpectrumInitial, rtd);
|
||||
_SpectrumInitial.name = "_Crest_FFTSpectrumInit";
|
||||
_SpectrumInitial.Create();
|
||||
|
||||
// Raw wave data buffer
|
||||
WaveBuffers = new(resolution, resolution, 0, GraphicsFormat.R16G16B16A16_SFloat)
|
||||
{
|
||||
wrapMode = TextureWrapMode.Repeat,
|
||||
antiAliasing = 1,
|
||||
filterMode = FilterMode.Bilinear,
|
||||
anisoLevel = 0,
|
||||
useMipMap = false,
|
||||
name = "_Crest_FFTCascades",
|
||||
dimension = TextureDimension.Tex2DArray,
|
||||
volumeDepth = k_CascadeCount,
|
||||
enableRandomWrite = true,
|
||||
};
|
||||
WaveBuffers.Create();
|
||||
|
||||
// Initialize bufferfly. Cached per resolution.
|
||||
if (!s_ButterflyTextures.ContainsKey(resolution))
|
||||
{
|
||||
// Computes the offsets used for the FFT calculation.
|
||||
var size = Mathf.RoundToInt(Mathf.Log(resolution, 2));
|
||||
var colors = new Color[resolution * size];
|
||||
|
||||
int offset = 1, iterations = resolution >> 1;
|
||||
for (var index = 0; index < size; index++)
|
||||
{
|
||||
var rowOffset = index * resolution;
|
||||
{
|
||||
int start = 0, end = 2 * offset;
|
||||
for (var iteration = 0; iteration < iterations; iteration++)
|
||||
{
|
||||
var bigK = 0f;
|
||||
for (var k = start; k < end; k += 2)
|
||||
{
|
||||
var phase = 2.0f * Mathf.PI * bigK * iterations / resolution;
|
||||
var cos = Mathf.Cos(phase);
|
||||
var sin = Mathf.Sin(phase);
|
||||
colors[rowOffset + k / 2] = new(cos, -sin, 0, 1);
|
||||
colors[rowOffset + k / 2 + offset] = new(-cos, sin, 0, 1);
|
||||
|
||||
bigK += 1f;
|
||||
}
|
||||
start += 4 * offset;
|
||||
end = start + 2 * offset;
|
||||
}
|
||||
}
|
||||
iterations >>= 1;
|
||||
offset <<= 1;
|
||||
}
|
||||
|
||||
var texture = new Texture2D(resolution, Mathf.RoundToInt(Mathf.Log(resolution, 2)), TextureFormat.RGBAFloat, false, true);
|
||||
texture.SetPixels(colors);
|
||||
texture.Apply();
|
||||
s_ButterflyTextures.Add(resolution, texture);
|
||||
}
|
||||
|
||||
_Initialized = true;
|
||||
}
|
||||
|
||||
// Initialize spectrum.
|
||||
// Computes base spectrum values based on wind speed and turbulence and spectrum controls.
|
||||
if (!_SpectrumInitialized || updateSpectrum)
|
||||
{
|
||||
var wrapper = new PropertyWrapperCompute(buffer, _ShaderSpectrum, _KernelSpectrumInitial);
|
||||
wrapper.SetInteger(ShaderIDs.s_Size, resolution);
|
||||
wrapper.SetFloat(ShaderIDs.s_WindSpeed, _Parameters._WindSpeed);
|
||||
wrapper.SetFloat(ShaderIDs.s_Turbulence, _Parameters._WindTurbulence);
|
||||
wrapper.SetFloat(ShaderIDs.s_Alignment, _Parameters._WindAlignment);
|
||||
wrapper.SetFloat(ShaderIDs.s_Gravity, WaterRenderer.Instance.Gravity);
|
||||
wrapper.SetFloat(ShaderIDs.s_Period, period < Mathf.Infinity ? period : -1);
|
||||
wrapper.SetVector(ShaderIDs.s_WindDir, new(Mathf.Cos(_Parameters._WindDirectionRadians), Mathf.Sin(_Parameters._WindDirectionRadians)));
|
||||
wrapper.SetTexture(ShaderIDs.s_SpectrumControls, _Parameters._Spectrum.ControlsTexture);
|
||||
wrapper.SetTexture(ShaderIDs.s_ResultInit, _SpectrumInitial);
|
||||
wrapper.Dispatch(resolution / 8, resolution / 8, k_CascadeCount);
|
||||
|
||||
_SpectrumInitialized = true;
|
||||
}
|
||||
|
||||
// Update Spectrum.
|
||||
// Computes a spectrum for the current time which can be FFT'd into the final surface.
|
||||
{
|
||||
var wrapper = new PropertyWrapperCompute(buffer, _ShaderSpectrum, _KernelSpectrumUpdate);
|
||||
|
||||
var descriptor = _SpectrumInitial.descriptor;
|
||||
|
||||
if (s_SupportsRandomWriteRGFloat)
|
||||
{
|
||||
descriptor.colorFormat = RenderTextureFormat.RGFloat;
|
||||
}
|
||||
|
||||
// No need to clear as overwritten.
|
||||
buffer.GetTemporaryRT(ShaderIDs.s_TemporaryFFT1, descriptor);
|
||||
buffer.GetTemporaryRT(ShaderIDs.s_TemporaryFFT2, descriptor);
|
||||
buffer.GetTemporaryRT(ShaderIDs.s_TemporaryFFT3, descriptor);
|
||||
|
||||
wrapper.SetInteger(ShaderIDs.s_Size, resolution);
|
||||
wrapper.SetFloat(ShaderIDs.s_Time, time * _Parameters._Spectrum._GravityScale);
|
||||
wrapper.SetFloat(ShaderIDs.s_Chop, _Parameters._Spectrum._Chop);
|
||||
wrapper.SetFloat(ShaderIDs.s_Period, period < Mathf.Infinity ? period : -1);
|
||||
wrapper.SetTexture(ShaderIDs.s_Init0, _SpectrumInitial);
|
||||
wrapper.SetTexture(ShaderIDs.s_ResultHeight, ShaderIDs.s_TemporaryFFT1);
|
||||
wrapper.SetTexture(ShaderIDs.s_ResultDisplaceX, ShaderIDs.s_TemporaryFFT2);
|
||||
wrapper.SetTexture(ShaderIDs.s_ResultDisplaceZ, ShaderIDs.s_TemporaryFFT3);
|
||||
wrapper.Dispatch(resolution / 8, resolution / 8, k_CascadeCount);
|
||||
}
|
||||
|
||||
// Dispatch FFT.
|
||||
// FFT the spectrum into surface displacements.
|
||||
{
|
||||
var kernel = 2 * Mathf.RoundToInt(Mathf.Log(resolution / k_Kernel0Resolution, 2f));
|
||||
var wrapper = new PropertyWrapperCompute(buffer, _ShaderFFT, kernel);
|
||||
|
||||
var butterfly = s_ButterflyTextures[resolution];
|
||||
|
||||
wrapper.SetTexture(ShaderIDs.s_InputButterfly, butterfly);
|
||||
wrapper.SetTexture(ShaderIDs.s_Output1, ShaderIDs.s_TemporaryFFT1);
|
||||
wrapper.SetTexture(ShaderIDs.s_Output2, ShaderIDs.s_TemporaryFFT2);
|
||||
wrapper.SetTexture(ShaderIDs.s_Output3, ShaderIDs.s_TemporaryFFT3);
|
||||
wrapper.Dispatch(1, resolution, k_CascadeCount);
|
||||
|
||||
wrapper = new PropertyWrapperCompute(buffer, _ShaderFFT, kernel + 1);
|
||||
wrapper.SetTexture(ShaderIDs.s_InputH, ShaderIDs.s_TemporaryFFT1);
|
||||
wrapper.SetTexture(ShaderIDs.s_InputX, ShaderIDs.s_TemporaryFFT2);
|
||||
wrapper.SetTexture(ShaderIDs.s_InputZ, ShaderIDs.s_TemporaryFFT3);
|
||||
wrapper.SetTexture(ShaderIDs.s_InputButterfly, butterfly);
|
||||
wrapper.SetTexture(ShaderIDs.s_Output, WaveBuffers);
|
||||
wrapper.Dispatch(resolution, 1, k_CascadeCount);
|
||||
|
||||
buffer.ReleaseTemporaryRT(ShaderIDs.s_TemporaryFFT1);
|
||||
buffer.ReleaseTemporaryRT(ShaderIDs.s_TemporaryFFT2);
|
||||
buffer.ReleaseTemporaryRT(ShaderIDs.s_TemporaryFFT3);
|
||||
}
|
||||
|
||||
_GenerationTime = time;
|
||||
|
||||
return WaveBuffers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changing wave gen data can result in creating lots of new generators. This gives a way to notify
|
||||
/// that a parameter has changed. If there is no existing generator for the new param values, but there
|
||||
/// is one for the old param values, this old generator is repurposed.
|
||||
/// </summary>
|
||||
public static void OnGenerationDataUpdated(Parameters oldParameters, Parameters newParameters)
|
||||
{
|
||||
// If multiple wave components share one FFT, then one of them changes its settings, it will
|
||||
// actually steal the generator from the rest. Then the first from the rest which request the
|
||||
// old settings will trigger creation of a new generator, and the remaining ones will use this
|
||||
// new generator. In the end one new generator is created, but it's created for the old settings.
|
||||
// Generators are requested single threaded so there should not be a race condition. Odd pattern
|
||||
// but I don't think any other way works without ugly checks to see if old generators are still
|
||||
// used, or other complicated things.
|
||||
|
||||
// Check if no generator exists for new values
|
||||
var newHash = newParameters.GetHashCode();
|
||||
if (!s_Generators.TryGetValue(newHash, out var oldGenerator))
|
||||
{
|
||||
// Try to adapt an existing generator rather than default to creating a new one
|
||||
// Adapting requires the resolution to the same.
|
||||
var oldHash = oldParameters.GetHashCode(newParameters._Resolution);
|
||||
if (s_Generators.TryGetValue(oldHash, out var generator))
|
||||
{
|
||||
// Hash will change for this generator, so remove the current one
|
||||
s_Generators.Remove(oldHash);
|
||||
|
||||
// Update params
|
||||
generator._Parameters = newParameters;
|
||||
|
||||
// Trigger generator to re-init the spectrum
|
||||
generator._SpectrumInitialized = false;
|
||||
|
||||
// Re-add with new hash
|
||||
s_Generators.Add(newHash, generator);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// There is already a new generator which will be used. Remove the previous one - if it really is needed
|
||||
// then it will be created later.
|
||||
oldGenerator.Release();
|
||||
s_Generators.Remove(oldParameters.GetHashCode());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number of FFT generators
|
||||
/// </summary>
|
||||
public static int GeneratorCount => s_Generators != null ? s_Generators.Count : 0;
|
||||
|
||||
public static FFTCompute GetInstance(Parameters parameters)
|
||||
{
|
||||
return s_Generators.GetValueOrDefault(parameters.GetHashCode(), null);
|
||||
}
|
||||
|
||||
public bool HasData()
|
||||
{
|
||||
return WaveBuffers != null && WaveBuffers.IsCreated();
|
||||
}
|
||||
|
||||
internal void OnGUI()
|
||||
{
|
||||
if (WaveBuffers != null && WaveBuffers.IsCreated())
|
||||
{
|
||||
DebugGUI.DrawTextureArray(WaveBuffers, 8, 0.5f, 20f);
|
||||
}
|
||||
|
||||
if (_Parameters._Spectrum != null && _Parameters._Spectrum.ControlsTexture != null)
|
||||
{
|
||||
GUI.DrawTexture(new(0f, 0f, 100f, 10f), _Parameters._Spectrum.ControlsTexture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d7d810630b3004f91923306f6f064752
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,304 +0,0 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Water shape representation - power values for each octave of wave components.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "Waves", menuName = "Crest/Wave Spectrum", order = 10000)]
|
||||
[@HelpURL("Manual/Waves.html#wave-conditions")]
|
||||
public sealed partial class WaveSpectrum : ScriptableObject
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 0;
|
||||
#pragma warning restore 414
|
||||
|
||||
// These must match corresponding constants in FFTSpectrum.compute
|
||||
internal const int k_NumberOfOctaves = 14;
|
||||
internal const float k_SmallestWavelengthPower2 = -4f;
|
||||
|
||||
internal static readonly float s_MinimumPowerLog = -8f;
|
||||
internal static readonly float s_MaximumPowerLog = 5f;
|
||||
|
||||
[Tooltip("Variance of wave directions, in degrees.")]
|
||||
[@Range(0f, 180f)]
|
||||
[SerializeField, HideInInspector]
|
||||
internal float _WaveDirectionVariance = 90f;
|
||||
|
||||
[Tooltip("More gravity means faster waves.")]
|
||||
[@Range(0f, 25f)]
|
||||
[SerializeField, HideInInspector]
|
||||
internal float _GravityScale = 1f;
|
||||
|
||||
[Tooltip("Multiplier which scales waves")]
|
||||
[@Range(0f, 10f)]
|
||||
[SerializeField]
|
||||
internal float _Multiplier = 1f;
|
||||
|
||||
[SerializeField, HideInInspector]
|
||||
internal float[] _PowerLogarithmicScales = new float[k_NumberOfOctaves] { -7.10794f, -6.42794f, -5.93794f, -5.27794f, -4.67794f, -3.71794f, -3.17794f, -2.60794f, -1.93794f, -1.11794f, -0.85794f, -0.36794f, 0.04206f, -8f };
|
||||
|
||||
[SerializeField, HideInInspector]
|
||||
internal bool[] _PowerDisabled = new bool[k_NumberOfOctaves];
|
||||
|
||||
[SerializeField, HideInInspector]
|
||||
internal float[] _ChopScales = new float[k_NumberOfOctaves] { 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f };
|
||||
|
||||
[SerializeField, HideInInspector]
|
||||
internal float[] _GravityScales = new float[k_NumberOfOctaves] { 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f };
|
||||
|
||||
[Tooltip("Scales horizontal displacement")]
|
||||
[@Range(0f, 2f)]
|
||||
[SerializeField]
|
||||
internal float _Chop = 1.6f;
|
||||
|
||||
#pragma warning disable 414
|
||||
[SerializeField, HideInInspector]
|
||||
internal bool _ShowAdvancedControls = false;
|
||||
#pragma warning restore 414
|
||||
|
||||
#pragma warning disable 414
|
||||
// We need to serialize if we want undo/redo.
|
||||
[SerializeField, HideInInspector]
|
||||
internal SpectrumModel _Model;
|
||||
#pragma warning restore 414
|
||||
|
||||
internal enum SpectrumModel
|
||||
{
|
||||
None,
|
||||
PiersonMoskowitz,
|
||||
}
|
||||
|
||||
internal static float SmallWavelength(float octaveIndex) => Mathf.Pow(2f, k_SmallestWavelengthPower2 + octaveIndex);
|
||||
|
||||
static int GetOctaveIndex(float wavelength)
|
||||
{
|
||||
Debug.AssertFormat(wavelength > 0f, "Crest: {0} wavelength must be > 0.", nameof(WaveSpectrum));
|
||||
var wl_pow2 = Mathf.Log(wavelength) / Mathf.Log(2f);
|
||||
return (int)(wl_pow2 - k_SmallestWavelengthPower2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the amplitude of a wave described by wavelength.
|
||||
/// </summary>
|
||||
/// <param name="wavelength">Wavelength in m</param>
|
||||
/// <param name="componentsPerOctave">How many waves we're sampling, used to conserve energy for different sampling rates</param>
|
||||
/// <param name="windSpeed">Wind speed in m/s</param>
|
||||
/// <param name="power">The energy of the wave in J</param>
|
||||
/// <returns>The amplitude of the wave in m</returns>
|
||||
internal float GetAmplitude(float wavelength, float componentsPerOctave, float windSpeed, out float power)
|
||||
{
|
||||
Debug.AssertFormat(wavelength > 0f, this, "Crest: {0} wavelength must be > 0.", nameof(WaveSpectrum));
|
||||
|
||||
var wl_pow2 = Mathf.Log(wavelength) / Mathf.Log(2f);
|
||||
wl_pow2 = Mathf.Clamp(wl_pow2, k_SmallestWavelengthPower2, k_SmallestWavelengthPower2 + k_NumberOfOctaves - 1f);
|
||||
|
||||
var lower = Mathf.Pow(2f, Mathf.Floor(wl_pow2));
|
||||
|
||||
var index = (int)(wl_pow2 - k_SmallestWavelengthPower2);
|
||||
|
||||
if (_PowerLogarithmicScales.Length < k_NumberOfOctaves || _PowerDisabled.Length < k_NumberOfOctaves)
|
||||
{
|
||||
Debug.LogWarning($"Crest: Wave spectrum {name} is out of date, please open this asset and resave in editor.", this);
|
||||
}
|
||||
|
||||
if (index >= _PowerLogarithmicScales.Length || index >= _PowerDisabled.Length)
|
||||
{
|
||||
Debug.AssertFormat(index < _PowerLogarithmicScales.Length && index < _PowerDisabled.Length, this, $"Crest: {0} index {index} is out of range.", nameof(WaveSpectrum));
|
||||
power = 0f;
|
||||
return 0f;
|
||||
}
|
||||
|
||||
// Get the first power for interpolation if available
|
||||
var thisPower = !_PowerDisabled[index] ? _PowerLogarithmicScales[index] : s_MinimumPowerLog;
|
||||
|
||||
// Get the next power for interpolation if available
|
||||
var nextIndex = index + 1;
|
||||
var hasNextIndex = nextIndex < _PowerLogarithmicScales.Length;
|
||||
var nextPower = hasNextIndex && !_PowerDisabled[nextIndex] ? _PowerLogarithmicScales[nextIndex] : s_MinimumPowerLog;
|
||||
|
||||
// The amplitude calculation follows this nice paper from Frechot:
|
||||
// https://hal.archives-ouvertes.fr/file/index/docid/307938/filename/frechot_realistic_simulation_of_ocean_surface_using_wave_spectra.pdf
|
||||
var wl_lo = Mathf.Pow(2f, Mathf.Floor(wl_pow2));
|
||||
var k_lo = 2f * Mathf.PI / wl_lo;
|
||||
var c_lo = ComputeWaveSpeed(wl_lo);
|
||||
var omega_lo = k_lo * c_lo;
|
||||
var wl_hi = 2f * wl_lo;
|
||||
var k_hi = 2f * Mathf.PI / wl_hi;
|
||||
var c_hi = ComputeWaveSpeed(wl_hi);
|
||||
var omega_hi = k_hi * c_hi;
|
||||
|
||||
var domega = (omega_lo - omega_hi) / componentsPerOctave;
|
||||
|
||||
// Alpha used to interpolate between power values
|
||||
var alpha = (wavelength - lower) / lower;
|
||||
|
||||
// Power
|
||||
power = hasNextIndex ? Mathf.Lerp(thisPower, nextPower, alpha) : thisPower;
|
||||
power = Mathf.Pow(10f, power);
|
||||
|
||||
// Empirical wind influence based on alpha-beta spectrum that underlies empirical spectra
|
||||
var gravity = _GravityScale * WaterRenderer.Instance.Gravity;
|
||||
|
||||
// Zero gravity will cause NaNs, and they have always been flat.
|
||||
if (gravity <= 0f) return 0f;
|
||||
|
||||
var b = 1.291f;
|
||||
var wm = 0.87f * gravity / windSpeed;
|
||||
DeepDispersion(2f * Mathf.PI / wavelength, gravity, out var w);
|
||||
power *= Mathf.Exp(-b * Mathf.Pow(wm / w, 4.0f));
|
||||
|
||||
var a2 = 2f * power * domega;
|
||||
|
||||
// Amplitude
|
||||
var a = Mathf.Sqrt(a2);
|
||||
|
||||
// Gerstner fudge - one hack to get Gerstners looking on par with FFT
|
||||
a *= 5f;
|
||||
|
||||
return a * _Multiplier;
|
||||
}
|
||||
|
||||
static float ComputeWaveSpeed(float wavelength, float gravityMultiplier = 1f)
|
||||
{
|
||||
// wave speed of deep sea water waves: https://en.wikipedia.org/wiki/Wind_wave
|
||||
// https://en.wikipedia.org/wiki/Dispersion_(water_waves)#Wave_propagation_and_dispersion
|
||||
var g = WaterRenderer.Instance.Gravity * gravityMultiplier;
|
||||
var k = 2f * Mathf.PI / wavelength;
|
||||
//float h = max(depth, 0.01);
|
||||
//float cp = sqrt(abs(tanh_clamped(h * k)) * g / k);
|
||||
var cp = Mathf.Sqrt(g / k);
|
||||
return cp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Samples spectrum to generate wave data. Wavelengths will be in ascending order.
|
||||
/// </summary>
|
||||
internal void GenerateWaveData(int componentsPerOctave, ref float[] wavelengths, ref float[] anglesDeg)
|
||||
{
|
||||
var totalComponents = k_NumberOfOctaves * componentsPerOctave;
|
||||
|
||||
if (wavelengths == null || wavelengths.Length != totalComponents) wavelengths = new float[totalComponents];
|
||||
if (anglesDeg == null || anglesDeg.Length != totalComponents) anglesDeg = new float[totalComponents];
|
||||
|
||||
var minWavelength = Mathf.Pow(2f, k_SmallestWavelengthPower2);
|
||||
var invComponentsPerOctave = 1f / componentsPerOctave;
|
||||
|
||||
for (var octave = 0; octave < k_NumberOfOctaves; octave++)
|
||||
{
|
||||
for (var i = 0; i < componentsPerOctave; i++)
|
||||
{
|
||||
var index = octave * componentsPerOctave + i;
|
||||
|
||||
// Stratified random sampling - should give a better distribution of wavelengths, and also means i can generate
|
||||
// the wavelengths in ascending order!
|
||||
var minWavelengthi = minWavelength + invComponentsPerOctave * minWavelength * i;
|
||||
var maxWavelengthi = Mathf.Min(minWavelengthi + invComponentsPerOctave * minWavelength, 2f * minWavelength);
|
||||
wavelengths[index] = Mathf.Lerp(minWavelengthi, maxWavelengthi, Random.value);
|
||||
|
||||
var rnd = (i + Random.value) * invComponentsPerOctave;
|
||||
anglesDeg[index] = (2f * rnd - 1f) * _WaveDirectionVariance;
|
||||
}
|
||||
|
||||
minWavelength *= 2f;
|
||||
}
|
||||
}
|
||||
|
||||
// This applies the correct PM spectrum powers, validated against a separate implementation
|
||||
internal void ApplyPiersonMoskowitzSpectrum()
|
||||
{
|
||||
var gravity = WaterRenderer.Instance != null ? WaterRenderer.Instance.Gravity : Mathf.Abs(Physics.gravity.y);
|
||||
|
||||
for (var octave = 0; octave < k_NumberOfOctaves; octave++)
|
||||
{
|
||||
var wl = SmallWavelength(octave);
|
||||
|
||||
var pow = PiersonMoskowitzSpectrum(gravity, wl);
|
||||
|
||||
// we store power on logarithmic scale. this does not include 0, we represent 0 as min value
|
||||
pow = Mathf.Max(pow, Mathf.Pow(10f, s_MinimumPowerLog));
|
||||
|
||||
_PowerLogarithmicScales[octave] = Mathf.Log10(pow);
|
||||
}
|
||||
}
|
||||
|
||||
// Alpha-beta spectrum without the beta. Beta represents wind influence and is evaluated at runtime
|
||||
// for 'current' wind conditions
|
||||
static float AlphaSpectrum(float a, float g, float w)
|
||||
{
|
||||
return a * g * g / Mathf.Pow(w, 5.0f);
|
||||
}
|
||||
|
||||
static void DeepDispersion(float k, float gravity, out float w)
|
||||
{
|
||||
w = Mathf.Sqrt(gravity * k);
|
||||
}
|
||||
|
||||
static float PiersonMoskowitzSpectrum(float gravity, float wavelength)
|
||||
{
|
||||
var k = 2f * Mathf.PI / wavelength;
|
||||
DeepDispersion(k, gravity, out var w);
|
||||
var phillipsConstant = 8.1e-3f;
|
||||
return AlphaSpectrum(phillipsConstant, gravity, w);
|
||||
}
|
||||
}
|
||||
|
||||
sealed partial class WaveSpectrum
|
||||
{
|
||||
[System.NonSerialized]
|
||||
internal Texture2D _ControlsTexture;
|
||||
|
||||
[System.NonSerialized]
|
||||
readonly Color[] _ScratchData = new Color[k_NumberOfOctaves];
|
||||
|
||||
internal Texture2D ControlsTexture
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_ControlsTexture == null)
|
||||
{
|
||||
_ControlsTexture = new(k_NumberOfOctaves, 1, TextureFormat.RFloat, mipChain: false, linear: true);
|
||||
InitializeHandControls();
|
||||
}
|
||||
|
||||
return _ControlsTexture;
|
||||
}
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
Helpers.Destroy(_ControlsTexture);
|
||||
}
|
||||
|
||||
internal void InitializeHandControls()
|
||||
{
|
||||
for (var i = 0; i < k_NumberOfOctaves; i++)
|
||||
{
|
||||
var power = _PowerDisabled[i] ? 0f : Mathf.Pow(10f, _PowerLogarithmicScales[i]);
|
||||
power *= _Multiplier * _Multiplier;
|
||||
_ScratchData[i] = power * Color.white;
|
||||
}
|
||||
|
||||
ControlsTexture.SetPixels(_ScratchData);
|
||||
ControlsTexture.Apply();
|
||||
}
|
||||
|
||||
[@OnChange(skipIfInactive: false)]
|
||||
internal void OnChange(string path, object previous)
|
||||
{
|
||||
InitializeHandControls();
|
||||
}
|
||||
|
||||
internal void OnGUI()
|
||||
{
|
||||
if (ControlsTexture != null)
|
||||
{
|
||||
GUI.DrawTexture(new(0f, 0f, 100f, 10f), ControlsTexture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 382a5d8b1147b4e78a31353c022b8e15
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 72a325a76c6624c768822a08fe625d55, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user