移除水
This commit is contained in:
@@ -1,212 +0,0 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// FFT wave shape.
|
||||
/// </summary>
|
||||
[AddComponentMenu(Constants.k_MenuPrefixInputs + "Shape FFT")]
|
||||
public sealed partial class ShapeFFT : ShapeWaves
|
||||
{
|
||||
// Waves
|
||||
|
||||
[Tooltip("How turbulent/chaotic the waves are.")]
|
||||
[@Range(0, 1, order = -3)]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
float _WindTurbulence = 0.145f;
|
||||
|
||||
[Tooltip("How aligned the waves are with wind.")]
|
||||
[@Range(0, 1, order = -4)]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
float _WindAlignment;
|
||||
|
||||
|
||||
// Generation
|
||||
|
||||
[Tooltip("FFT waves will loop with a period of this many seconds.")]
|
||||
[@Range(4f, 128f, Range.Clamp.Minimum)]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
float _TimeLoopLength = Mathf.Infinity;
|
||||
|
||||
|
||||
[Header("Culling")]
|
||||
|
||||
[Tooltip("Maximum amount the surface will be displaced vertically from sea level.\n\nIncrease this if gaps appear at bottom of screen.")]
|
||||
[@GenerateAPI]
|
||||
[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]
|
||||
[SerializeField]
|
||||
float _MaximumHorizontalDisplacement = 15f;
|
||||
|
||||
|
||||
[@Heading("Collision Data Baking")]
|
||||
|
||||
[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)]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal bool _EnableBakedCollision = false;
|
||||
|
||||
[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)]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal int _TimeResolution = 4;
|
||||
|
||||
[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)]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal float _SmallestWavelengthRequired = 2f;
|
||||
|
||||
[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)]
|
||||
[@Range(4f, 128f)]
|
||||
[SerializeField]
|
||||
internal float _BakedTimeLoopLength = 32f;
|
||||
|
||||
internal float LoopPeriod => _EnableBakedCollision ? _BakedTimeLoopLength : _TimeLoopLength;
|
||||
|
||||
private protected override int MinimumResolution => 16;
|
||||
private protected override int MaximumResolution => int.MaxValue;
|
||||
|
||||
FFTCompute.Parameters _OldFFTParameters;
|
||||
internal FFTCompute.Parameters FFTParameters => new
|
||||
(
|
||||
_ActiveSpectrum,
|
||||
Resolution,
|
||||
_TimeLoopLength,
|
||||
WindSpeedMPS,
|
||||
WindDirRadForFFT,
|
||||
_WindTurbulence,
|
||||
_WindAlignment
|
||||
);
|
||||
|
||||
private protected override void OnUpdate(WaterRenderer water)
|
||||
{
|
||||
base.OnUpdate(water);
|
||||
|
||||
// We do not filter FFTs.
|
||||
_FirstCascade = 0;
|
||||
_LastCascade = k_CascadeCount - 1;
|
||||
|
||||
ReportMaxDisplacement(water);
|
||||
|
||||
// If geometry is being used, the water input shader will rotate the waves to align to geo
|
||||
var parameters = FFTParameters;
|
||||
|
||||
// Don't create tons of generators when values are varying. Notify so that existing generators may be adapted.
|
||||
if (parameters.GetHashCode() != _OldFFTParameters.GetHashCode())
|
||||
{
|
||||
FFTCompute.OnGenerationDataUpdated(_OldFFTParameters, parameters);
|
||||
}
|
||||
|
||||
_OldFFTParameters = parameters;
|
||||
}
|
||||
|
||||
internal override void Draw(Lod lod, CommandBuffer buffer, RenderTargetIdentifier target, int pass = -1, float weight = 1, int slice = -1)
|
||||
{
|
||||
if (_LastGenerateFrameCount != Time.frameCount)
|
||||
{
|
||||
_WaveBuffers = FFTCompute.GenerateDisplacements
|
||||
(
|
||||
buffer,
|
||||
lod.Water.CurrentTime,
|
||||
FFTParameters,
|
||||
UpdateDataEachFrame
|
||||
);
|
||||
|
||||
_LastGenerateFrameCount = Time.frameCount;
|
||||
}
|
||||
|
||||
base.Draw(lod, buffer, target, pass, weight, slice);
|
||||
}
|
||||
|
||||
private protected override void SetRenderParameters<T>(WaterRenderer water, T wrapper)
|
||||
{
|
||||
base.SetRenderParameters(water, wrapper);
|
||||
|
||||
// If using geometry, the primary wave direction is used by the input shader to
|
||||
// rotate the waves relative to the geo rotation. If not, the wind direction is
|
||||
// already used in the FFT generation.
|
||||
var waveDir = (Mode is LodInputMode.Spline or LodInputMode.Paint) ? PrimaryWaveDirection : Vector2.right;
|
||||
wrapper.SetVector(ShaderIDs.s_AxisX, waveDir);
|
||||
}
|
||||
|
||||
private protected override void ReportMaxDisplacement(WaterRenderer water)
|
||||
{
|
||||
if (!Enabled) return;
|
||||
|
||||
// Apply weight or will cause popping due to scale change.
|
||||
MaximumReportedHorizontalDisplacement = _MaximumHorizontalDisplacement * Weight;
|
||||
MaximumReportedVerticalDisplacement = MaximumReportedWavesDisplacement = _MaximumVerticalDisplacement * Weight;
|
||||
|
||||
if (Mode == LodInputMode.Global)
|
||||
{
|
||||
water.ReportMaximumDisplacement(MaximumReportedHorizontalDisplacement, MaximumReportedVerticalDisplacement, MaximumReportedVerticalDisplacement);
|
||||
}
|
||||
}
|
||||
|
||||
private protected override void DestroySharedResources()
|
||||
{
|
||||
FFTCompute.CleanUpAll();
|
||||
}
|
||||
|
||||
float WindDirRadForFFT
|
||||
{
|
||||
get
|
||||
{
|
||||
// These input types use a wave direction provided by geometry or the painted user direction
|
||||
if (Mode is LodInputMode.Spline or LodInputMode.Paint)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
return _WaveDirectionHeadingAngle * Mathf.Deg2Rad;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void OnGUI()
|
||||
{
|
||||
if (_DrawSlicesInEditor)
|
||||
{
|
||||
FFTCompute.GetInstance(FFTParameters)?.OnGUI();
|
||||
}
|
||||
}
|
||||
|
||||
internal FFTCompute GetFFTComputeInstance()
|
||||
{
|
||||
return FFTCompute.GetInstance(FFTParameters);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
partial class ShapeFFT : ISerializationCallbackReceiver
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 1;
|
||||
#pragma warning restore 414
|
||||
|
||||
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
||||
{
|
||||
_Version = MigrateV1(_Version);
|
||||
}
|
||||
|
||||
void ISerializationCallbackReceiver.OnBeforeSerialize()
|
||||
{
|
||||
// Empty.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 88bb6e05d83b64105a4d8cbd478f5916
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,523 +0,0 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
using UnityEngine.Rendering;
|
||||
using WaveHarmonic.Crest.Utility;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gerstner wave shape.
|
||||
/// </summary>
|
||||
[AddComponentMenu(Constants.k_MenuPrefixInputs + "Shape Gerstner")]
|
||||
public sealed partial class ShapeGerstner : ShapeWaves
|
||||
{
|
||||
// Waves
|
||||
|
||||
[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.")]
|
||||
[@Range(0f, 1f, order = -3)]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
float _ReverseWaveWeight = 0.5f;
|
||||
|
||||
|
||||
// Generation Settings
|
||||
|
||||
[Tooltip("How many wave components to generate in each octave.")]
|
||||
[@Delayed]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
int _ComponentsPerOctave = 8;
|
||||
|
||||
[Tooltip("Change to get a different set of waves.")]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
int _RandomSeed = 0;
|
||||
|
||||
private protected override int MinimumResolution => 8;
|
||||
private protected override int MaximumResolution => 64;
|
||||
|
||||
float _WindSpeedWhenGenerated = -1f;
|
||||
|
||||
const int k_MaximumWaveComponents = 1024;
|
||||
|
||||
// Data for all components
|
||||
float[] _Wavelengths;
|
||||
float[] _Amplitudes;
|
||||
float[] _Amplitudes2;
|
||||
float[] _Powers;
|
||||
float[] _AngleDegrees;
|
||||
float[] _Phases;
|
||||
float[] _Phases2;
|
||||
|
||||
struct GerstnerCascadeParams
|
||||
{
|
||||
public int _StartIndex;
|
||||
}
|
||||
ComputeBuffer _BufferCascadeParameters;
|
||||
readonly GerstnerCascadeParams[] _CascadeParameters = new GerstnerCascadeParams[k_CascadeCount + 1];
|
||||
|
||||
// Caution - order here impact performance. Rearranging these to match order
|
||||
// they're read in the compute shader made it 50% slower..
|
||||
struct GerstnerWaveComponent4
|
||||
{
|
||||
public Vector4 _TwoPiOverWavelength;
|
||||
public Vector4 _Amplitude;
|
||||
public Vector4 _WaveDirectionX;
|
||||
public Vector4 _WaveDirectionZ;
|
||||
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;
|
||||
public Vector4 _Phase2;
|
||||
}
|
||||
ComputeBuffer _BufferWaveData;
|
||||
readonly GerstnerWaveComponent4[] _WaveData = new GerstnerWaveComponent4[k_MaximumWaveComponents / 4];
|
||||
|
||||
ComputeShader _ShaderGerstner;
|
||||
int _KernelGerstner = -1;
|
||||
|
||||
|
||||
static new class ShaderIDs
|
||||
{
|
||||
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_GerstnerWaveData = Shader.PropertyToID("_Crest_GerstnerWaveData");
|
||||
}
|
||||
|
||||
|
||||
readonly float _TwoPi = 2f * Mathf.PI;
|
||||
readonly float _ReciprocalTwoPi = 1f / (2f * Mathf.PI);
|
||||
|
||||
internal static readonly SortedList<int, ShapeGerstner> s_Instances = new(Helpers.SiblingIndexComparison);
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
static void InitStatics()
|
||||
{
|
||||
s_Instances.Clear();
|
||||
}
|
||||
|
||||
void InitData()
|
||||
{
|
||||
if (_WaveBuffers == null)
|
||||
{
|
||||
_WaveBuffers = new(_Resolution, _Resolution, 0, GraphicsFormat.R16G16B16A16_SFloat);
|
||||
}
|
||||
else
|
||||
{
|
||||
_WaveBuffers.Release();
|
||||
}
|
||||
|
||||
{
|
||||
_WaveBuffers.width = _WaveBuffers.height = _Resolution;
|
||||
_WaveBuffers.wrapMode = TextureWrapMode.Clamp;
|
||||
_WaveBuffers.antiAliasing = 1;
|
||||
_WaveBuffers.filterMode = FilterMode.Bilinear;
|
||||
_WaveBuffers.anisoLevel = 0;
|
||||
_WaveBuffers.useMipMap = false;
|
||||
_WaveBuffers.name = "_Crest_GerstnerCascades";
|
||||
_WaveBuffers.dimension = TextureDimension.Tex2DArray;
|
||||
_WaveBuffers.volumeDepth = k_CascadeCount;
|
||||
_WaveBuffers.enableRandomWrite = true;
|
||||
_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");
|
||||
}
|
||||
|
||||
private protected override void OnUpdate(WaterRenderer water)
|
||||
{
|
||||
var isFirstUpdate = _FirstUpdate;
|
||||
|
||||
base.OnUpdate(water);
|
||||
|
||||
if (_WaveBuffers == null || _Resolution != _WaveBuffers.width || _BufferCascadeParameters == null || _BufferWaveData == null)
|
||||
{
|
||||
InitData();
|
||||
}
|
||||
|
||||
var windSpeed = WindSpeedMPS;
|
||||
if (isFirstUpdate || UpdateDataEachFrame || windSpeed != _WindSpeedWhenGenerated)
|
||||
{
|
||||
UpdateWaveData(water, windSpeed);
|
||||
_WindSpeedWhenGenerated = windSpeed;
|
||||
}
|
||||
|
||||
ReportMaxDisplacement(water);
|
||||
}
|
||||
|
||||
internal override void Draw(Lod lod, CommandBuffer buffer, RenderTargetIdentifier target, int pass = -1, float weight = 1, int slice = -1)
|
||||
{
|
||||
if (_LastGenerateFrameCount != Time.frameCount)
|
||||
{
|
||||
if (_FirstCascade >= 0 && _LastCascade >= 0)
|
||||
{
|
||||
UpdateGenerateWaves(buffer);
|
||||
// Above changes the render target. Change it back if necessary.
|
||||
if (!IsCompute) buffer.SetRenderTarget(target, 0, CubemapFace.Unknown, slice);
|
||||
}
|
||||
|
||||
_LastGenerateFrameCount = Time.frameCount;
|
||||
}
|
||||
|
||||
base.Draw(lod, buffer, target, pass, weight, slice);
|
||||
}
|
||||
|
||||
private protected override void SetRenderParameters<T>(WaterRenderer water, T wrapper)
|
||||
{
|
||||
base.SetRenderParameters(water, wrapper);
|
||||
wrapper.SetVector(ShapeWaves.ShaderIDs.s_AxisX, PrimaryWaveDirection);
|
||||
}
|
||||
|
||||
void SliceUpWaves(WaterRenderer water, float windSpeed)
|
||||
{
|
||||
// Do not filter cascades if blending as the blend operation might be skipped.
|
||||
// Same for renderer as we do not know the blend operation.
|
||||
var isFilterable = Blend != LodInputBlend.Alpha && _Mode != LodInputMode.Renderer;
|
||||
|
||||
_FirstCascade = isFilterable ? -1 : 0;
|
||||
_LastCascade = -2;
|
||||
|
||||
var cascadeIdx = 0;
|
||||
var componentIdx = 0;
|
||||
var outputIdx = 0;
|
||||
_CascadeParameters[0]._StartIndex = 0;
|
||||
|
||||
// Seek forward to first wavelength that is big enough to render into current cascades
|
||||
var minWl = MinWavelength(cascadeIdx);
|
||||
while (componentIdx < _Wavelengths.Length && _Wavelengths[componentIdx] < minWl)
|
||||
{
|
||||
componentIdx++;
|
||||
}
|
||||
//Debug.Log($"Crest: {cascadeIdx}: start {_cascadeParams[cascadeIdx]._startIndex} minWL {minWl}");
|
||||
|
||||
for (; componentIdx < _Wavelengths.Length; componentIdx++)
|
||||
{
|
||||
// Skip small amplitude waves
|
||||
while (componentIdx < _Wavelengths.Length && _Amplitudes[componentIdx] < 0.001f)
|
||||
{
|
||||
componentIdx++;
|
||||
}
|
||||
if (componentIdx >= _Wavelengths.Length) break;
|
||||
|
||||
// Check if we need to move to the next cascade
|
||||
while (cascadeIdx < k_CascadeCount && _Wavelengths[componentIdx] >= 2f * minWl)
|
||||
{
|
||||
// Wrap up this cascade and begin next
|
||||
|
||||
// Fill remaining elements of current vector4 with 0s
|
||||
var vi = outputIdx / 4;
|
||||
var ei = outputIdx - vi * 4;
|
||||
|
||||
while (ei != 0)
|
||||
{
|
||||
_WaveData[vi]._TwoPiOverWavelength[ei] = 1f;
|
||||
_WaveData[vi]._Amplitude[ei] = 0f;
|
||||
_WaveData[vi]._WaveDirectionX[ei] = 0f;
|
||||
_WaveData[vi]._WaveDirectionZ[ei] = 0f;
|
||||
_WaveData[vi]._Omega[ei] = 0f;
|
||||
_WaveData[vi]._Phase[ei] = 0f;
|
||||
_WaveData[vi]._Phase2[ei] = 0f;
|
||||
_WaveData[vi]._ChopAmplitude[ei] = 0f;
|
||||
_WaveData[vi]._Amplitude2[ei] = 0f;
|
||||
_WaveData[vi]._ChopAmplitude2[ei] = 0f;
|
||||
ei = (ei + 1) % 4;
|
||||
outputIdx++;
|
||||
}
|
||||
|
||||
if (outputIdx > 0 && _FirstCascade < 0) _FirstCascade = cascadeIdx;
|
||||
|
||||
cascadeIdx++;
|
||||
_CascadeParameters[cascadeIdx]._StartIndex = outputIdx / 4;
|
||||
minWl *= 2f;
|
||||
|
||||
//Debug.Log($"Crest: {cascadeIdx}: start {_cascadeParams[cascadeIdx]._startIndex} minWL {minWl}");
|
||||
}
|
||||
if (cascadeIdx == k_CascadeCount) break;
|
||||
|
||||
{
|
||||
// Pack into vector elements
|
||||
var vi = outputIdx / 4;
|
||||
var ei = outputIdx - vi * 4;
|
||||
|
||||
_WaveData[vi]._Amplitude[ei] = _Amplitudes[componentIdx];
|
||||
_WaveData[vi]._Amplitude2[ei] = _Amplitudes2[componentIdx];
|
||||
|
||||
var chopScale = _ActiveSpectrum._ChopScales[componentIdx / _ComponentsPerOctave];
|
||||
_WaveData[vi]._ChopAmplitude[ei] = -chopScale * _ActiveSpectrum._Chop * _Amplitudes[componentIdx];
|
||||
_WaveData[vi]._ChopAmplitude2[ei] = -chopScale * _ActiveSpectrum._Chop * _Amplitudes2[componentIdx];
|
||||
|
||||
var angle = Mathf.Deg2Rad * _AngleDegrees[componentIdx];
|
||||
var dx = Mathf.Cos(angle);
|
||||
var dz = Mathf.Sin(angle);
|
||||
|
||||
var gravityScale = _ActiveSpectrum._GravityScales[componentIdx / _ComponentsPerOctave];
|
||||
var gravity = water.Gravity * _ActiveSpectrum._GravityScale;
|
||||
var c = Mathf.Sqrt(_Wavelengths[componentIdx] * gravity * gravityScale * _ReciprocalTwoPi);
|
||||
var k = _TwoPi / _Wavelengths[componentIdx];
|
||||
|
||||
// Constrain wave vector (wavelength and wave direction) to ensure wave tiles across domain
|
||||
{
|
||||
var kx = k * dx;
|
||||
var kz = k * dz;
|
||||
var diameter = 0.5f * (1 << cascadeIdx);
|
||||
|
||||
// Number of times wave repeats across domain in x and z
|
||||
var n = kx / (_TwoPi / diameter);
|
||||
var m = kz / (_TwoPi / diameter);
|
||||
// Ensure the wave repeats an integral number of times across domain
|
||||
kx = _TwoPi * Mathf.Round(n) / diameter;
|
||||
kz = _TwoPi * Mathf.Round(m) / diameter;
|
||||
|
||||
// Compute new wave vector and direction
|
||||
k = Mathf.Sqrt(kx * kx + kz * kz);
|
||||
dx = kx / k;
|
||||
dz = kz / k;
|
||||
}
|
||||
|
||||
_WaveData[vi]._TwoPiOverWavelength[ei] = k;
|
||||
_WaveData[vi]._WaveDirectionX[ei] = dx;
|
||||
_WaveData[vi]._WaveDirectionZ[ei] = dz;
|
||||
|
||||
// Repeat every 2pi to keep angle bounded - helps precision on 16bit platforms
|
||||
_WaveData[vi]._Omega[ei] = k * c;
|
||||
_WaveData[vi]._Phase[ei] = Mathf.Repeat(_Phases[componentIdx], Mathf.PI * 2f);
|
||||
_WaveData[vi]._Phase2[ei] = Mathf.Repeat(_Phases2[componentIdx], Mathf.PI * 2f);
|
||||
|
||||
outputIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
_LastCascade = isFilterable ? cascadeIdx : k_CascadeCount - 1;
|
||||
|
||||
{
|
||||
// Fill remaining elements of current vector4 with 0s
|
||||
var vi = outputIdx / 4;
|
||||
var ei = outputIdx - vi * 4;
|
||||
|
||||
while (ei != 0)
|
||||
{
|
||||
_WaveData[vi]._TwoPiOverWavelength[ei] = 1f;
|
||||
_WaveData[vi]._Amplitude[ei] = 0f;
|
||||
_WaveData[vi]._WaveDirectionX[ei] = 0f;
|
||||
_WaveData[vi]._WaveDirectionZ[ei] = 0f;
|
||||
_WaveData[vi]._Omega[ei] = 0f;
|
||||
_WaveData[vi]._Phase[ei] = 0f;
|
||||
_WaveData[vi]._Phase2[ei] = 0f;
|
||||
_WaveData[vi]._ChopAmplitude[ei] = 0f;
|
||||
_WaveData[vi]._Amplitude2[ei] = 0f;
|
||||
_WaveData[vi]._ChopAmplitude2[ei] = 0f;
|
||||
ei = (ei + 1) % 4;
|
||||
outputIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
while (cascadeIdx < k_CascadeCount)
|
||||
{
|
||||
cascadeIdx++;
|
||||
minWl *= 2f;
|
||||
_CascadeParameters[cascadeIdx]._StartIndex = outputIdx / 4;
|
||||
//Debug.Log($"Crest: {cascadeIdx}: start {_cascadeParams[cascadeIdx]._startIndex} minWL {minWl}");
|
||||
}
|
||||
|
||||
_BufferCascadeParameters.SetData(_CascadeParameters);
|
||||
_BufferWaveData.SetData(_WaveData);
|
||||
}
|
||||
|
||||
void UpdateGenerateWaves(CommandBuffer buf)
|
||||
{
|
||||
// Clear existing waves or they could get copied.
|
||||
buf.SetRenderTarget(_WaveBuffers, 0, CubemapFace.Unknown, -1);
|
||||
buf.ClearRenderTarget(RTClearFlags.Color, Color.black, 0, 0);
|
||||
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);
|
||||
|
||||
buf.DispatchCompute(_ShaderGerstner, _KernelGerstner, _WaveBuffers.width / Lod.k_ThreadGroupSizeX, _WaveBuffers.height / Lod.k_ThreadGroupSizeY, _LastCascade - _FirstCascade + 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resamples wave spectrum
|
||||
/// </summary>
|
||||
/// <param name="water">The water renderer.</param>
|
||||
/// <param name="windSpeed">Wind speed in m/s</param>
|
||||
void UpdateWaveData(WaterRenderer water, float windSpeed)
|
||||
{
|
||||
// Set random seed to get repeatable results
|
||||
var randomStateBkp = Random.state;
|
||||
Random.InitState(_RandomSeed);
|
||||
|
||||
_ActiveSpectrum.GenerateWaveData(_ComponentsPerOctave, ref _Wavelengths, ref _AngleDegrees);
|
||||
|
||||
UpdateAmplitudes();
|
||||
|
||||
// Won't run every time so put last in the random sequence
|
||||
if (_Phases == null || _Phases.Length != _Wavelengths.Length || _Phases2 == null || _Phases2.Length != _Wavelengths.Length)
|
||||
{
|
||||
InitPhases();
|
||||
}
|
||||
|
||||
Random.state = randomStateBkp;
|
||||
|
||||
SliceUpWaves(water, windSpeed);
|
||||
}
|
||||
|
||||
void UpdateAmplitudes()
|
||||
{
|
||||
if (_Amplitudes == null || _Amplitudes.Length != _Wavelengths.Length)
|
||||
{
|
||||
_Amplitudes = new float[_Wavelengths.Length];
|
||||
}
|
||||
if (_Amplitudes2 == null || _Amplitudes2.Length != _Wavelengths.Length)
|
||||
{
|
||||
_Amplitudes2 = new float[_Wavelengths.Length];
|
||||
}
|
||||
if (_Powers == null || _Powers.Length != _Wavelengths.Length)
|
||||
{
|
||||
_Powers = new float[_Wavelengths.Length];
|
||||
}
|
||||
|
||||
var windSpeed = WindSpeedMPS;
|
||||
|
||||
for (var i = 0; i < _Wavelengths.Length; i++)
|
||||
{
|
||||
var amp = _ActiveSpectrum.GetAmplitude(_Wavelengths[i], _ComponentsPerOctave, windSpeed, out _Powers[i]);
|
||||
_Amplitudes[i] = Random.value * amp;
|
||||
_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];
|
||||
for (var octave = 0; octave < WaveSpectrum.k_NumberOfOctaves; octave++)
|
||||
{
|
||||
for (var i = 0; i < _ComponentsPerOctave; i++)
|
||||
{
|
||||
var index = octave * _ComponentsPerOctave + i;
|
||||
var rnd = (i + Random.value) / _ComponentsPerOctave;
|
||||
_Phases[index] = 2f * Mathf.PI * rnd;
|
||||
|
||||
var rnd2 = (i + Random.value) / _ComponentsPerOctave;
|
||||
_Phases2[index] = 2f * Mathf.PI * rnd2;
|
||||
}
|
||||
}
|
||||
|
||||
Random.state = randomStateBkp;
|
||||
}
|
||||
|
||||
private protected override void ReportMaxDisplacement(WaterRenderer water)
|
||||
{
|
||||
if (!Enabled) return;
|
||||
|
||||
if (_ActiveSpectrum._ChopScales.Length != WaveSpectrum.k_NumberOfOctaves)
|
||||
{
|
||||
Debug.LogError($"Crest: {nameof(WaveSpectrum)} {_ActiveSpectrum.name} is out of date, please open this asset and resave in editor.", _ActiveSpectrum);
|
||||
}
|
||||
|
||||
if (_Wavelengths == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var ampSum = 0f;
|
||||
for (var i = 0; i < _Wavelengths.Length; i++)
|
||||
{
|
||||
ampSum += _Amplitudes[i] * _ActiveSpectrum._ChopScales[i / _ComponentsPerOctave];
|
||||
}
|
||||
|
||||
// Apply weight or will cause popping due to scale change.
|
||||
ampSum *= Weight;
|
||||
|
||||
MaximumReportedHorizontalDisplacement = ampSum * _ActiveSpectrum._Chop;
|
||||
MaximumReportedVerticalDisplacement = ampSum;
|
||||
MaximumReportedWavesDisplacement = ampSum;
|
||||
|
||||
if (Mode == LodInputMode.Global)
|
||||
{
|
||||
water.ReportMaximumDisplacement(ampSum * _ActiveSpectrum._Chop, ampSum, ampSum);
|
||||
}
|
||||
}
|
||||
|
||||
private protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
s_Instances.Add(transform.GetSiblingIndex(), this);
|
||||
}
|
||||
|
||||
private protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
|
||||
s_Instances.Remove(this);
|
||||
|
||||
if (_BufferCascadeParameters != null && _BufferCascadeParameters.IsValid())
|
||||
{
|
||||
_BufferCascadeParameters.Dispose();
|
||||
_BufferCascadeParameters = null;
|
||||
}
|
||||
if (_BufferWaveData != null && _BufferWaveData.IsValid())
|
||||
{
|
||||
_BufferWaveData.Dispose();
|
||||
_BufferWaveData = null;
|
||||
}
|
||||
|
||||
if (_WaveBuffers != null)
|
||||
{
|
||||
Helpers.Destroy(_WaveBuffers);
|
||||
_WaveBuffers = null;
|
||||
}
|
||||
}
|
||||
|
||||
private protected override void DestroySharedResources() { }
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void OnGUI()
|
||||
{
|
||||
if (_DrawSlicesInEditor && _WaveBuffers != null && _WaveBuffers.IsCreated())
|
||||
{
|
||||
DebugGUI.DrawTextureArray(_WaveBuffers, 8, 0.5f);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
partial class ShapeGerstner : ISerializationCallbackReceiver
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 1;
|
||||
#pragma warning restore 414
|
||||
|
||||
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
||||
{
|
||||
_Version = MigrateV1(_Version);
|
||||
}
|
||||
|
||||
void ISerializationCallbackReceiver.OnBeforeSerialize()
|
||||
{
|
||||
// Empty.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 002f2642204d348f3a2fab18595c44cd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences:
|
||||
- _rasterMesh: {instanceID: 0}
|
||||
- _spectrum: {instanceID: 0}
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,490 +0,0 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for Shape components.
|
||||
/// </summary>
|
||||
[@ExecuteDuringEditMode(ExecuteDuringEditMode.Include.None)]
|
||||
[@HelpURL("Manual/Waves.html#wave-conditions")]
|
||||
[@FilterEnum(nameof(_Blend), Filtered.Mode.Include, (int)LodInputBlend.Off, (int)LodInputBlend.Additive, (int)LodInputBlend.Alpha, (int)LodInputBlend.AlphaClip)]
|
||||
public abstract partial class ShapeWaves : LodInput
|
||||
{
|
||||
[@Heading("Waves")]
|
||||
|
||||
[Tooltip("The spectrum that defines the water surface shape.")]
|
||||
[@Embedded]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
internal WaveSpectrum _Spectrum;
|
||||
|
||||
[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")]
|
||||
[SerializeField]
|
||||
bool _EvaluateSpectrumAtRunTimeEveryFrame;
|
||||
|
||||
[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)]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
float _RespectShallowWaterAttenuation = 1f;
|
||||
|
||||
[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))]
|
||||
[@Range(-180, 180)]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
private protected float _WaveDirectionHeadingAngle = 0f;
|
||||
|
||||
[Tooltip("Whether to use the wind speed on this component rather than the global wind speed.\n\nGlobal wind speed comes from the Water Renderer component.")]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
bool _OverrideGlobalWindSpeed = false;
|
||||
|
||||
[Tooltip("Wind speed in km/h. Controls wave conditions.")]
|
||||
[@ShowComputedProperty(nameof(WindSpeedKPH))]
|
||||
[@Predicated(nameof(_OverrideGlobalWindSpeed), hide: true)]
|
||||
[@Range(0, 150f, scale: 2f)]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
float _WindSpeed = 20f;
|
||||
|
||||
|
||||
[Header("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]
|
||||
[SerializeField]
|
||||
private protected int _Resolution = 128;
|
||||
|
||||
|
||||
// Debug
|
||||
|
||||
[Tooltip("In Editor, shows the wave generation buffers on screen.")]
|
||||
[@DecoratedField(order = k_DebugGroupOrder * Constants.k_FieldGroupOrder), SerializeField]
|
||||
internal bool _DrawSlicesInEditor = false;
|
||||
|
||||
|
||||
private protected static new class ShaderIDs
|
||||
{
|
||||
public static readonly int s_TransitionalWavelengthThreshold = Shader.PropertyToID("_Crest_TransitionalWavelengthThreshold");
|
||||
public static readonly int s_WaveResolutionMultiplier = Shader.PropertyToID("_Crest_WaveResolutionMultiplier");
|
||||
public static readonly int s_WaveBufferParameters = Shader.PropertyToID("_Crest_WaveBufferParameters");
|
||||
public static readonly int s_AlphaSource = Shader.PropertyToID("_Crest_AlphaSource");
|
||||
public static readonly int s_WaveBuffer = Shader.PropertyToID("_Crest_WaveBuffer");
|
||||
public static readonly int s_WaveBufferSliceIndex = Shader.PropertyToID("_Crest_WaveBufferSliceIndex");
|
||||
public static readonly int s_AverageWavelength = Shader.PropertyToID("_Crest_AverageWavelength");
|
||||
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");
|
||||
}
|
||||
|
||||
static WaveSpectrum s_DefaultSpectrum;
|
||||
private protected static WaveSpectrum DefaultSpectrum
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_DefaultSpectrum == null)
|
||||
{
|
||||
s_DefaultSpectrum = ScriptableObject.CreateInstance<WaveSpectrum>();
|
||||
s_DefaultSpectrum.name = "Default Waves (instance)";
|
||||
s_DefaultSpectrum.hideFlags = HideFlags.DontSave | HideFlags.NotEditable;
|
||||
}
|
||||
|
||||
return s_DefaultSpectrum;
|
||||
}
|
||||
}
|
||||
|
||||
private protected abstract int MinimumResolution { get; }
|
||||
private protected abstract int MaximumResolution { get; }
|
||||
|
||||
static ComputeShader s_TransferWavesComputeShader;
|
||||
static LocalKeyword s_KeywordTexture;
|
||||
static LocalKeyword s_KeywordTextureBlend;
|
||||
readonly Vector4[] _WaveBufferParameters = new Vector4[Lod.k_MaximumSlices];
|
||||
|
||||
internal static int s_RenderPassOverride = -1;
|
||||
|
||||
private protected WaveSpectrum _ActiveSpectrum = null;
|
||||
private protected Vector2 PrimaryWaveDirection => new(Mathf.Cos(Mathf.PI * _WaveDirectionHeadingAngle / 180f), Mathf.Sin(Mathf.PI * _WaveDirectionHeadingAngle / 180f));
|
||||
|
||||
/// <summary>
|
||||
/// The wind speed in kilometers per hour (KPH).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Wind speed can come from this component or the <see cref="WaterRenderer"/>.
|
||||
/// </remarks>
|
||||
public float WindSpeedKPH => _OverrideGlobalWindSpeed || WaterRenderer.Instance == null ? _WindSpeed : WaterRenderer.Instance.WindSpeedKPH;
|
||||
|
||||
/// <summary>
|
||||
/// The wind speed in meters per second (MPS).
|
||||
/// </summary>
|
||||
/// /// <remarks>
|
||||
/// Wind speed can come from this component or the <see cref="WaterRenderer"/>.
|
||||
/// </remarks>
|
||||
public float WindSpeedMPS => WindSpeedKPH / 3.6f;
|
||||
|
||||
private protected ShapeWaves()
|
||||
{
|
||||
_FollowHorizontalWaveMotion = true;
|
||||
}
|
||||
|
||||
private protected override void Attach()
|
||||
{
|
||||
base.Attach();
|
||||
_Reporter ??= new(this);
|
||||
WaterChunkRenderer.DisplacementReporters.Add(_Reporter);
|
||||
}
|
||||
|
||||
private protected override void Detach()
|
||||
{
|
||||
base.Detach();
|
||||
WaterChunkRenderer.DisplacementReporters.Remove(_Reporter);
|
||||
}
|
||||
|
||||
internal override void Draw(Lod simulation, CommandBuffer buffer, RenderTargetIdentifier target, int pass = -1, float weight = 1f, int slice = -1)
|
||||
{
|
||||
if (weight * Weight <= 0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Iterating over slices which means this is non compute so pass to graphics draw.
|
||||
if (!IsCompute)
|
||||
{
|
||||
GraphicsDraw(simulation, buffer, target, pass, weight, slice);
|
||||
return;
|
||||
}
|
||||
|
||||
var lodCount = simulation.Slices;
|
||||
|
||||
var shape = (AnimatedWavesLod)simulation;
|
||||
|
||||
var wrapper = new PropertyWrapperCompute(buffer, s_TransferWavesComputeShader, 0);
|
||||
|
||||
if (_FirstCascade < 0 || _LastCascade < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
var water = shape._Water;
|
||||
|
||||
for (var lodIdx = lodCount - 1; lodIdx >= lodCount - slice; lodIdx--)
|
||||
{
|
||||
_WaveBufferParameters[lodIdx] = new(-1, -2, 0, 0);
|
||||
|
||||
var found = false;
|
||||
var filter = new AnimatedWavesLod.WavelengthFilter(water, lodIdx);
|
||||
|
||||
for (var i = _FirstCascade; i <= _LastCascade; i++)
|
||||
{
|
||||
_Wavelength = MinWavelength(i) / shape.WaveResolutionMultiplier;
|
||||
|
||||
// Do the weight from scratch because this is the real filter.
|
||||
var w = AnimatedWavesLod.FilterByWavelength(filter, _Wavelength) * Weight;
|
||||
|
||||
if (w <= 0f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
_WaveBufferParameters[lodIdx].x = i;
|
||||
found = true;
|
||||
}
|
||||
|
||||
_WaveBufferParameters[lodIdx].y = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Set transitional weights.
|
||||
_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.SetVectorArray(ShaderIDs.s_WaveBufferParameters, _WaveBufferParameters);
|
||||
|
||||
var isTexture = Mode is LodInputMode.Paint or LodInputMode.Texture;
|
||||
var isAlphaBlend = Blend is LodInputBlend.Off or LodInputBlend.Alpha or LodInputBlend.AlphaClip;
|
||||
|
||||
wrapper.SetKeyword(s_KeywordTexture, isTexture && !isAlphaBlend);
|
||||
wrapper.SetKeyword(s_KeywordTextureBlend, isTexture && isAlphaBlend);
|
||||
|
||||
if (isTexture)
|
||||
{
|
||||
wrapper.SetInteger(Crest.ShaderIDs.s_Blend, (int)_Blend);
|
||||
}
|
||||
|
||||
if (Mode == LodInputMode.Global)
|
||||
{
|
||||
var threads = shape.Resolution / Lod.k_ThreadGroupSize;
|
||||
wrapper.Dispatch(threads, threads, slice);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.Draw(simulation, buffer, target, pass, weight, slice);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsDraw(Lod simulation, CommandBuffer buffer, RenderTargetIdentifier target, int pass, float weight, int slice)
|
||||
{
|
||||
var lod = simulation as AnimatedWavesLod;
|
||||
|
||||
var wrapper = new PropertyWrapperBuffer(buffer);
|
||||
SetRenderParameters(simulation._Water, wrapper);
|
||||
|
||||
var isFirst = true;
|
||||
|
||||
for (var i = _FirstCascade; i <= _LastCascade; i++)
|
||||
{
|
||||
_Wavelength = MinWavelength(i) / lod.WaveResolutionMultiplier;
|
||||
|
||||
// Do the weight from scratch because this is the real filter.
|
||||
weight = AnimatedWavesLod.FilterByWavelength(simulation._Water, slice, _Wavelength) * Weight;
|
||||
if (weight <= 0f) continue;
|
||||
|
||||
var average = _Wavelength * 1.5f * lod.WaveResolutionMultiplier;
|
||||
// We only have one renderer so we need to use global.
|
||||
buffer.SetGlobalFloat(ShaderIDs.s_AverageWavelength, average);
|
||||
buffer.SetGlobalInt(ShaderIDs.s_WaveBufferSliceIndex, i);
|
||||
|
||||
// Only apply blend mode once per component / LOD. Multiple passes can happen to gather all
|
||||
// wavelengths and is incorrect to apply blend mode to those subsequent passes (ie component
|
||||
// would be blending against itself).
|
||||
if (!isFirst)
|
||||
{
|
||||
s_RenderPassOverride = 1;
|
||||
}
|
||||
|
||||
isFirst = false;
|
||||
|
||||
base.Draw(simulation, buffer, target, pass, weight, slice);
|
||||
}
|
||||
|
||||
// Wavelength must be zero or waves will be filtered beforehand and not be written to every LOD.
|
||||
_Wavelength = 0;
|
||||
s_RenderPassOverride = -1;
|
||||
}
|
||||
|
||||
internal override float Filter(WaterRenderer water, int slice)
|
||||
{
|
||||
return 1f;
|
||||
}
|
||||
|
||||
private protected const int k_CascadeCount = 16;
|
||||
|
||||
// First cascade of wave buffer that has waves and will be rendered.
|
||||
private protected int _FirstCascade = -1;
|
||||
// Last cascade of wave buffer that has waves and will be rendered.
|
||||
// Default to lower than first default to break loops.
|
||||
private protected int _LastCascade = -2;
|
||||
|
||||
// Used to populate data on first frame.
|
||||
private protected bool _FirstUpdate = true;
|
||||
|
||||
// Wave generation done in Draw. Keeps track to limit to once per frame.
|
||||
private protected int _LastGenerateFrameCount = -1;
|
||||
|
||||
internal override bool Enabled => _FirstCascade > -1 && WaterRenderer.Instance.Gravity != 0f && Mode switch
|
||||
{
|
||||
LodInputMode.Global => enabled && s_TransferWavesComputeShader != null,
|
||||
_ => base.Enabled,
|
||||
};
|
||||
|
||||
internal override LodInputMode DefaultMode => LodInputMode.Global;
|
||||
internal override int Pass => (int)DisplacementPass.LodDependent;
|
||||
|
||||
private protected override bool FollowHorizontalMotion => true;
|
||||
|
||||
float _Wavelength;
|
||||
|
||||
private protected RenderTexture _WaveBuffers;
|
||||
internal RenderTexture WaveBuffer => _WaveBuffers;
|
||||
|
||||
internal Rect _Rect;
|
||||
|
||||
private protected Vector2 _MaximumDisplacement;
|
||||
private protected float MaximumReportedHorizontalDisplacement { get; set; }
|
||||
private protected float MaximumReportedVerticalDisplacement { get; set; }
|
||||
private protected float MaximumReportedWavesDisplacement { get; set; }
|
||||
|
||||
static int s_InstanceCount = 0;
|
||||
|
||||
private protected bool UpdateDataEachFrame
|
||||
{
|
||||
get
|
||||
{
|
||||
var updateDataEachFrame = _EvaluateSpectrumAtRunTimeEveryFrame;
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying) updateDataEachFrame = true;
|
||||
#endif
|
||||
return updateDataEachFrame;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Min wavelength for a cascade in the wave buffer. Does not depend on viewpoint.
|
||||
/// </summary>
|
||||
private protected float MinWavelength(int cascadeIdx)
|
||||
{
|
||||
var diameter = 0.5f * (1 << cascadeIdx);
|
||||
// Matches constant WAVE_SAMPLE_FACTOR in FFTSpectrum.compute
|
||||
return diameter / 8f;
|
||||
}
|
||||
|
||||
private protected abstract void ReportMaxDisplacement(WaterRenderer water);
|
||||
private protected abstract void DestroySharedResources();
|
||||
|
||||
private protected override void OnUpdate(WaterRenderer water)
|
||||
{
|
||||
base.OnUpdate(water);
|
||||
|
||||
_ActiveSpectrum = _Spectrum != null ? _Spectrum : DefaultSpectrum;
|
||||
|
||||
_FirstUpdate = false;
|
||||
}
|
||||
|
||||
private protected virtual void SetRenderParameters<T>(WaterRenderer water, T wrapper) where T : IPropertyWrapper
|
||||
{
|
||||
wrapper.SetTexture(ShaderIDs.s_WaveBuffer, _WaveBuffers);
|
||||
wrapper.SetFloat(ShaderIDs.s_RespectShallowWaterAttenuation, _RespectShallowWaterAttenuation);
|
||||
wrapper.SetFloat(ShaderIDs.s_MaximumAttenuationDepth, water._AnimatedWavesLod.ShallowsMaximumDepth);
|
||||
}
|
||||
|
||||
private protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
s_InstanceCount++;
|
||||
}
|
||||
|
||||
private protected void OnDestroy()
|
||||
{
|
||||
// Since FFTCompute resources are shared we will clear after last ShapeFFT is destroyed.
|
||||
if (--s_InstanceCount <= 0)
|
||||
{
|
||||
DestroySharedResources();
|
||||
|
||||
if (s_DefaultSpectrum != null)
|
||||
{
|
||||
Helpers.Destroy(s_DefaultSpectrum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
WaterResources.Instance.AfterEnabled -= InitializeResources;
|
||||
WaterResources.Instance.AfterEnabled += InitializeResources;
|
||||
InitializeResources();
|
||||
|
||||
_FirstUpdate = true;
|
||||
|
||||
// Initialise with spectrum
|
||||
if (_Spectrum != null)
|
||||
{
|
||||
_ActiveSpectrum = _Spectrum;
|
||||
}
|
||||
|
||||
if (_ActiveSpectrum == null)
|
||||
{
|
||||
_ActiveSpectrum = DefaultSpectrum;
|
||||
}
|
||||
}
|
||||
|
||||
private protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
|
||||
WaterResources.Instance.AfterEnabled -= InitializeResources;
|
||||
}
|
||||
|
||||
void InitializeResources()
|
||||
{
|
||||
s_TransferWavesComputeShader = WaterResources.Instance.Compute._ShapeWavesTransfer;
|
||||
s_KeywordTexture = WaterResources.Instance.Keywords.AnimatedWavesTransferWavesTexture;
|
||||
s_KeywordTextureBlend = WaterResources.Instance.Keywords.AnimatedWavesTransferWavesTextureBlend;
|
||||
}
|
||||
|
||||
bool ReportDisplacement(ref Rect bounds, ref float horizontal, ref float vertical)
|
||||
{
|
||||
if (Mode == LodInputMode.Global || !Enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_Rect = Data.Rect;
|
||||
|
||||
if (bounds.Overlaps(_Rect, false))
|
||||
{
|
||||
horizontal = MaximumReportedHorizontalDisplacement;
|
||||
vertical = MaximumReportedVerticalDisplacement;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
partial class ShapeWaves
|
||||
{
|
||||
Reporter _Reporter;
|
||||
|
||||
sealed class Reporter : IReportsDisplacement
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
partial class ShapeWaves
|
||||
{
|
||||
[HideInInspector, SerializeField]
|
||||
AlphaSource _AlphaSource;
|
||||
enum AlphaSource { AlwaysOne, FromZero, FromZeroNormalized }
|
||||
|
||||
private protected int MigrateV1(int version)
|
||||
{
|
||||
// Version 1
|
||||
// - Merge Alpha Source into Blend.
|
||||
// - Rename and invert Spectrum Fixed at Run-Time
|
||||
if (version < 1)
|
||||
{
|
||||
if (_Blend == LodInputBlend.Alpha)
|
||||
{
|
||||
_Blend = _AlphaSource switch
|
||||
{
|
||||
AlphaSource.AlwaysOne => LodInputBlend.Off,
|
||||
AlphaSource.FromZero => LodInputBlend.Alpha,
|
||||
AlphaSource.FromZeroNormalized => LodInputBlend.AlphaClip,
|
||||
_ => _Blend, // Linter complained (linter has one off error).
|
||||
};
|
||||
}
|
||||
|
||||
_EvaluateSpectrumAtRunTimeEveryFrame = !_EvaluateSpectrumAtRunTimeEveryFrame;
|
||||
|
||||
version = 1;
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c494780f7140b493695a431be2111449
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user