升级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

@@ -1,8 +1,6 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
namespace WaveHarmonic.Crest
{
/// <summary>
@@ -14,11 +12,6 @@ namespace WaveHarmonic.Crest
[@HelpURL("Manual/WaterAppearance.html#volume-color-inputs")]
public sealed partial class AbsorptionLodInput : LodInput
{
[SerializeField, HideInInspector]
#pragma warning disable 414
int _Version = 0;
#pragma warning restore 414
#if d_CrestPaint
internal override LodInputMode DefaultMode => LodInputMode.Paint;
#else

View File

@@ -1,8 +1,6 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
namespace WaveHarmonic.Crest
{
/// <summary>
@@ -14,11 +12,6 @@ namespace WaveHarmonic.Crest
[@HelpURL("Manual/WaterAppearance.html#albedo-inputs")]
public sealed partial class AlbedoLodInput : LodInput
{
[SerializeField, HideInInspector]
#pragma warning disable 414
int _Version = 0;
#pragma warning restore 414
internal override LodInputMode DefaultMode => LodInputMode.Renderer;
}
}

View File

@@ -23,14 +23,14 @@ namespace WaveHarmonic.Crest
DisplacementPass _DisplacementPass = DisplacementPass.LodIndependent;
[Tooltip("Whether to filter this input by wavelength.\n\nIf disabled, it will render to all LODs.")]
[@Predicated(nameof(_DisplacementPass), inverted: true, nameof(DisplacementPass.LodDependent))]
[@Enable(nameof(_DisplacementPass), nameof(DisplacementPass.LodDependent))]
[@GenerateAPI]
[DecoratedField, SerializeField]
bool _FilterByWavelength;
[Tooltip("Which octave to render into.\n\nFor example, set this to 2 to render into the 2m-4m octave. These refer to the same octaves as the wave spectrum editor.")]
[@Predicated(nameof(_DisplacementPass), inverted: true, nameof(DisplacementPass.LodDependent))]
[@Predicated(nameof(_FilterByWavelength))]
[@Enable(nameof(_DisplacementPass), nameof(DisplacementPass.LodDependent))]
[@Enable(nameof(_FilterByWavelength))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
float _OctaveWavelength = 512f;
@@ -49,7 +49,7 @@ namespace WaveHarmonic.Crest
float _MaximumDisplacementHorizontal = 0f;
[Tooltip("Use the bounding box of an attached renderer component to determine the maximum vertical displacement.")]
[@Predicated(nameof(_Mode), inverted: true, nameof(LodInputMode.Renderer))]
[@Enable(nameof(_Mode), nameof(LodInputMode.Renderer))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
bool _ReportRendererBounds = false;
@@ -63,23 +63,35 @@ namespace WaveHarmonic.Crest
_FollowHorizontalWaveMotion = true;
}
internal override float Filter(WaterRenderer water, int slice)
private protected override void Initialize()
{
return AnimatedWavesLod.FilterByWavelength(water, slice, _FilterByWavelength ? _OctaveWavelength : 0f);
base.Initialize();
_Reporter ??= new(this);
_DisplacementReporter = _Reporter;
}
private protected override void OnUpdate(WaterRenderer water)
private protected override void OnDisable()
{
base.OnUpdate(water);
base.OnDisable();
_DisplacementReporter = null;
}
internal override float Filter(WaterRenderer water, int slice)
{
return AnimatedWavesLod.FilterByWavelength(water, slice, _FilterByWavelength ? _OctaveWavelength : 0f, water.AnimatedWavesLod.Resolution);
}
bool ReportDisplacement(WaterRenderer water, ref Rect bounds, ref float horizontal, ref float vertical)
{
if (!Enabled)
{
return;
return false;
}
var maxDispVert = _MaximumDisplacementVertical;
// let water system know how far from the sea level this shape may displace the surface
// TODO: we need separate min/max vertical displacement to be optimal.
if (_ReportRendererBounds)
{
var range = Data.HeightRange;
@@ -89,19 +101,40 @@ namespace WaveHarmonic.Crest
maxDispVert = Mathf.Max(maxDispVert, Mathf.Abs(seaLevel - minY), Mathf.Abs(seaLevel - maxY));
}
if (_MaximumDisplacementHorizontal > 0f || maxDispVert > 0f)
var rect = Data.Rect;
if (bounds.Overlaps(rect, false))
{
water.ReportMaximumDisplacement(_MaximumDisplacementHorizontal, maxDispVert, 0f);
horizontal += _MaximumDisplacementHorizontal;
vertical += maxDispVert;
return true;
}
return false;
}
float ReportWaveDisplacement(WaterRenderer water, float displacement)
{
return displacement;
}
}
partial class AnimatedWavesLodInput : ISerializationCallbackReceiver
partial class AnimatedWavesLodInput
{
[SerializeField, HideInInspector]
#pragma warning disable 414
int _Version = 1;
#pragma warning restore 414
Reporter _Reporter;
sealed class Reporter : IReportsDisplacement, IReportWaveDisplacement
{
readonly AnimatedWavesLodInput _Input;
public Reporter(AnimatedWavesLodInput input) => _Input = input;
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);
}
}
partial class AnimatedWavesLodInput
{
private protected override int Version => Mathf.Max(base.Version, 1);
[System.Obsolete("Please use DisplacementPass instead.")]
[Tooltip("When to render the input into the displacement data.\n\nIf enabled, it renders into all LODs of the simulation after the combine step rather than before with filtering. Furthermore, it will also affect dynamic waves.")]
@@ -110,30 +143,25 @@ namespace WaveHarmonic.Crest
[HideInInspector]
bool _RenderPostCombine = true;
[System.Obsolete]
void SetRenderPostCombine(bool previous, bool current, bool force = false)
{
if (previous == current && !force) return;
_DisplacementPass = current ? DisplacementPass.LodIndependent : DisplacementPass.LodDependent;
}
#pragma warning disable CS0612 // Type or member is obsolete
#pragma warning disable CS0618 // Type or member is obsolete
/// <inheritdoc/>
void ISerializationCallbackReceiver.OnAfterDeserialize()
private protected override void OnMigrate()
{
base.OnMigrate();
if (_Version < 1)
{
SetRenderPostCombine(_RenderPostCombine, _RenderPostCombine, force: true);
_Version = 1;
}
}
#pragma warning restore CS0612 // Type or member is obsolete
#pragma warning restore CS0618 // Type or member is obsolete
/// <inheritdoc/>
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
}
}
}

View File

@@ -16,22 +16,17 @@ namespace WaveHarmonic.Crest
[@HelpURL("Manual/Clipping.html#clip-inputs")]
public sealed partial class ClipLodInput : LodInput
{
[SerializeField, HideInInspector]
#pragma warning disable 414
int _Version = 0;
#pragma warning restore 414
[@Heading("Primitive")]
[Tooltip("The primitive to render (signed distance) into the simulation.")]
[@Predicated(nameof(_Mode), inverted: true, nameof(LodInputMode.Primitive), hide: true)]
[@Show(nameof(_Mode), nameof(LodInputMode.Primitive))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
internal LodInputPrimitive _Primitive = LodInputPrimitive.Cube;
// Only Mode.Primitive SDF supports inverted.
[Tooltip("Removes clip surface data instead of adding it.")]
[@Predicated(nameof(_Mode), inverted: true, nameof(LodInputMode.Primitive), hide: true)]
[@Show(nameof(_Mode), nameof(LodInputMode.Primitive))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
bool _Inverted;
@@ -39,8 +34,7 @@ namespace WaveHarmonic.Crest
[@Heading("Culling")]
[Tooltip("Prevents inputs from cancelling each other out when aligned vertically.\n\nIt is imperfect so custom logic might be needed for your use case.")]
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Paint), hide: true)]
[@Predicated(nameof(_Mode), inverted: true, nameof(LodInputMode.Renderer))]
[@Show(nameof(_Mode), nameof(LodInputMode.Renderer))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
bool _WaterHeightDistanceCulling = false;

View File

@@ -21,11 +21,6 @@ namespace WaveHarmonic.Crest
[@HelpURL("Manual/ShallowsAndShorelines.html#sea-floor-depth")]
public sealed partial class DepthLodInput : LodInput
{
[SerializeField, HideInInspector]
#pragma warning disable 414
int _Version = 0;
#pragma warning restore 414
[@Space(10)]
[@Label("Relative Height")]

View File

@@ -1,8 +1,6 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
namespace WaveHarmonic.Crest
{
/// <summary>
@@ -15,11 +13,6 @@ namespace WaveHarmonic.Crest
[@HelpURL("Manual/Waves.html#dynamic-waves-inputs")]
public sealed partial class DynamicWavesLodInput : LodInput
{
[SerializeField, HideInInspector]
#pragma warning disable 414
int _Version = 0;
#pragma warning restore 414
internal override LodInputMode DefaultMode => LodInputMode.Renderer;
}
}

View File

@@ -1,8 +1,6 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
namespace WaveHarmonic.Crest
{
/// <summary>
@@ -16,11 +14,6 @@ namespace WaveHarmonic.Crest
[FilterEnum(nameof(_Blend), Filtered.Mode.Include, (int)LodInputBlend.Additive, (int)LodInputBlend.Alpha)]
public sealed partial class FlowLodInput : LodInput
{
[SerializeField, HideInInspector]
#pragma warning disable 414
int _Version = 0;
#pragma warning restore 414
// Countering will incur thrashing. Previously we allowed the option so the
// serialized value could be "false".
private protected override bool FollowHorizontalMotion => true;

View File

@@ -1,8 +1,6 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
namespace WaveHarmonic.Crest
{
/// <summary>
@@ -16,11 +14,6 @@ namespace WaveHarmonic.Crest
[@FilterEnum(nameof(_Blend), Filtered.Mode.Include, (int)LodInputBlend.Additive, (int)LodInputBlend.Maximum)]
public sealed partial class FoamLodInput : LodInput
{
[SerializeField, HideInInspector]
#pragma warning disable 414
int _Version = 0;
#pragma warning restore 414
#if d_CrestPaint
internal override LodInputMode DefaultMode => LodInputMode.Paint;
#else

View File

@@ -19,11 +19,12 @@ namespace WaveHarmonic.Crest
[Tooltip("Whether to use the manual \"Height Range\" for water chunk culling.\n\nMandatory for non mesh inputs like \"Texture\".")]
[@GenerateAPI]
[@DecoratedField, SerializeField]
[@InlineToggle]
[@SerializeField]
bool _OverrideHeight;
[Tooltip("The minimum and maximum height value to report for water chunk culling.")]
[@Predicated(nameof(_OverrideHeight))]
[@Enable(nameof(_OverrideHeight))]
[@Range(-100, 100, Range.Clamp.None)]
[@GenerateAPI]
[SerializeField]
@@ -38,8 +39,6 @@ namespace WaveHarmonic.Crest
private protected override bool FollowHorizontalMotion => true;
internal override LodInputMode DefaultMode => LodInputMode.Geometry;
internal Rect _Rect;
internal override void InferBlend()
{
base.InferBlend();
@@ -56,35 +55,52 @@ namespace WaveHarmonic.Crest
{
base.Initialize();
_Reporter ??= new(this);
WaterChunkRenderer.HeightReporters.Add(_Reporter);
_HeightReporter = _Reporter;
}
private protected override void OnDisable()
{
base.OnDisable();
WaterChunkRenderer.HeightReporters.Remove(_Reporter);
_HeightReporter = null;
}
bool ReportHeight(ref Rect bounds, ref float minimum, ref float maximum)
bool ReportHeight(WaterRenderer water, ref Rect bounds, ref float minimum, ref float maximum)
{
if (!Enabled)
{
return false;
}
_Rect = Data.Rect;
// These modes do not provide a height yet.
if (!Data.HasHeightRange && !_OverrideHeight)
{
return false;
}
if (bounds.Overlaps(_Rect, false))
var rect = Data.Rect;
if (bounds.Overlaps(rect, false))
{
var range = _OverrideHeight ? _HeightRange : Data.HeightRange;
minimum = range.x;
maximum = range.y;
range *= Weight;
// Make relative to sea level.
range.x -= water.SeaLevel;
range.y -= water.SeaLevel;
var current = new Vector2(minimum, maximum);
range = _Blend switch
{
LodInputBlend.Additive => range + current,
LodInputBlend.Minimum => Vector2.Min(range, current),
LodInputBlend.Maximum => Vector2.Max(range, current),
_ => range,
};
minimum = Mathf.Min(minimum, range.x);
maximum = Mathf.Max(maximum, range.y);
return true;
}
@@ -100,33 +116,25 @@ namespace WaveHarmonic.Crest
{
readonly LevelLodInput _Input;
public Reporter(LevelLodInput input) => _Input = input;
public bool ReportHeight(ref Rect bounds, ref float minimum, ref float maximum) => _Input.ReportHeight(ref bounds, ref minimum, ref maximum);
public bool ReportHeight(WaterRenderer water, ref Rect bounds, ref float minimum, ref float maximum) =>
_Input.ReportHeight(water, ref bounds, ref minimum, ref maximum);
}
}
partial class LevelLodInput : ISerializationCallbackReceiver
partial class LevelLodInput
{
[SerializeField, HideInInspector]
#pragma warning disable 414
int _Version = 1;
#pragma warning restore 414
private protected override int Version => Mathf.Max(base.Version, 1);
/// <inheritdoc/>
void ISerializationCallbackReceiver.OnAfterDeserialize()
private protected override void OnMigrate()
{
base.OnMigrate();
// Version 1
// - Implemented blend mode but default value was serialized as Additive.
if (_Version < 1)
{
if (_Mode is LodInputMode.Spline or LodInputMode.Renderer) _Blend = LodInputBlend.Off;
_Version = 1;
}
}
/// <inheritdoc/>
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
// Empty.
}
}
}

View File

@@ -36,6 +36,10 @@ namespace WaveHarmonic.Crest
MonoBehaviour Component { get; }
IReportsHeight HeightReporter => null;
IReportsDisplacement DisplacementReporter => null;
IReportWaveDisplacement WaveDisplacementReporter => null;
// Allow sorting within a queue. Callers can pass in things like sibling index to
// get deterministic sorting.
int Order => Queue * k_QueueMaximumSubIndex + Mathf.Min(Component.transform.GetSiblingIndex(), k_QueueMaximumSubIndex - 1);
@@ -70,9 +74,9 @@ namespace WaveHarmonic.Crest
// For others it is a case of only supporting unsupported mode(s).
[Tooltip("Scales the input.")]
[@Predicated(typeof(AlbedoLodInput), inverted: true, hide: true)]
[@Predicated(typeof(ClipLodInput), inverted: true, hide: true)]
[@Predicated(typeof(DepthLodInput), inverted: true, hide: true)]
[@Hide(typeof(AlbedoLodInput))]
[@Hide(typeof(ClipLodInput))]
[@Hide(typeof(DepthLodInput))]
[@Range(0f, 1f)]
[@GenerateAPI]
[SerializeField]
@@ -84,17 +88,17 @@ namespace WaveHarmonic.Crest
int _Queue;
[Tooltip("How this input blends into existing data.\n\nSimilar to blend operations in shaders. For inputs which have materials, use the blend functionality on the shader/material.")]
[@Predicated(typeof(AbsorptionLodInput), inverted: true, hide: true)]
[@Predicated(typeof(AlbedoLodInput), inverted: true, hide: true)]
[@Predicated(typeof(AnimatedWavesLodInput), inverted: true, hide: true)]
[@Predicated(typeof(ClipLodInput), inverted: true, hide: true)]
[@Predicated(typeof(DepthLodInput), inverted: true, hide: true)]
[@Predicated(typeof(DynamicWavesLodInput), inverted: true, hide: true)]
[@Predicated(typeof(ScatteringLodInput), inverted: true, hide: true)]
[@Predicated(typeof(ShadowLodInput), inverted: true, hide: true)]
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Global))]
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Primitive))]
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Renderer))]
[@Hide(typeof(AbsorptionLodInput))]
[@Hide(typeof(AlbedoLodInput))]
[@Hide(typeof(AnimatedWavesLodInput))]
[@Hide(typeof(ClipLodInput))]
[@Hide(typeof(DepthLodInput))]
[@Hide(typeof(DynamicWavesLodInput))]
[@Hide(typeof(ScatteringLodInput))]
[@Hide(typeof(ShadowLodInput))]
[@Disable(nameof(_Mode), nameof(LodInputMode.Global))]
[@Disable(nameof(_Mode), nameof(LodInputMode.Primitive))]
[@Disable(nameof(_Mode), nameof(LodInputMode.Renderer))]
[@Filtered]
[@GenerateAPI]
[SerializeField]
@@ -102,35 +106,35 @@ namespace WaveHarmonic.Crest
[@Label("Feather")]
[Tooltip("The width of the feathering to soften the edges to blend inputs.\n\nInputs that do not support feathering will have this field disabled or hidden in UI.")]
[@Predicated(typeof(AlbedoLodInput), inverted: true, hide: true)]
[@Predicated(typeof(AnimatedWavesLodInput), inverted: true, hide: true)]
[@Predicated(typeof(ClipLodInput), inverted: true, hide: true)]
[@Predicated(typeof(DepthLodInput), inverted: true, hide: true)]
[@Predicated(typeof(DynamicWavesLodInput), inverted: true, hide: true)]
[@Predicated(typeof(LevelLodInput), inverted: true, hide: true)]
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Renderer))]
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Global))]
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Primitive))]
[@Hide(typeof(AlbedoLodInput))]
[@Hide(typeof(AnimatedWavesLodInput))]
[@Hide(typeof(ClipLodInput))]
[@Hide(typeof(DepthLodInput))]
[@Hide(typeof(DynamicWavesLodInput))]
[@Hide(typeof(LevelLodInput))]
[@Disable(nameof(_Mode), nameof(LodInputMode.Renderer))]
[@Disable(nameof(_Mode), nameof(LodInputMode.Global))]
[@Disable(nameof(_Mode), nameof(LodInputMode.Primitive))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
float _FeatherWidth = 0.1f;
[Tooltip("How this input responds to horizontal displacement.\n\nIf false, data will not move horizontally with the waves. Has a small performance overhead when disabled. Only suitable for inputs of small size.")]
[@Predicated(typeof(ClipLodInput), inverted: true, hide: true)]
[@Predicated(typeof(FlowLodInput), inverted: true, hide: true)]
[@Predicated(typeof(LevelLodInput), inverted: true, hide: true)]
[@Predicated(typeof(ShapeWaves), inverted: true, hide: true)]
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Global))]
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Spline))]
[@Hide(typeof(ClipLodInput))]
[@Hide(typeof(FlowLodInput))]
[@Hide(typeof(LevelLodInput))]
[@Hide(typeof(ShapeWaves))]
[@Disable(nameof(_Mode), nameof(LodInputMode.Global))]
[@Disable(nameof(_Mode), nameof(LodInputMode.Spline))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
private protected bool _FollowHorizontalWaveMotion = false;
[@Heading("Mode")]
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Unset), hide: true)]
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Primitive), hide: true)]
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Global), hide: true)]
[@Hide(nameof(_Mode), nameof(LodInputMode.Unset))]
[@Hide(nameof(_Mode), nameof(LodInputMode.Primitive))]
[@Hide(nameof(_Mode), nameof(LodInputMode.Global))]
[@Stripped]
[SerializeReference]
internal LodInputData _Data;
@@ -140,7 +144,7 @@ namespace WaveHarmonic.Crest
[@Group("Debug", order = k_DebugGroupOrder)]
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Global))]
[@Disable(nameof(_Mode), nameof(LodInputMode.Global))]
[@DecoratedField, SerializeField]
internal bool _DrawBounds;
@@ -376,6 +380,16 @@ namespace WaveHarmonic.Crest
//
#if UNITY_EDITOR
void SetMode(LodInputMode previous, LodInputMode current)
{
if (previous == current) return;
if (!isActiveAndEnabled) { ChangeMode(Mode); return; }
OnDisable();
ChangeMode(Mode);
UnityEditor.EditorTools.ToolManager.RefreshAvailableTools();
OnEnable();
}
[@OnChange(skipIfInactive: false)]
void OnChange(string propertyPath, object previousValue)
{
@@ -385,15 +399,11 @@ namespace WaveHarmonic.Crest
SetQueue((int)previousValue, _Queue);
break;
case nameof(_Mode):
if (!isActiveAndEnabled) { ChangeMode(Mode); break; }
OnDisable();
ChangeMode(Mode);
UnityEditor.EditorTools.ToolManager.RefreshAvailableTools();
OnEnable();
SetMode((LodInputMode)previousValue, Mode);
break;
case nameof(_Blend):
// TODO: Make compatible with disabled.
if (isActiveAndEnabled) Data.OnChange($"../{propertyPath}", previousValue);
if (isActiveAndEnabled) Data?.OnChange($"../{propertyPath}", previousValue);
break;
}
}
@@ -467,6 +477,9 @@ namespace WaveHarmonic.Crest
partial class LodInput
{
Input _Input;
private protected IReportsHeight _HeightReporter;
internal IReportsDisplacement _DisplacementReporter;
private protected IReportWaveDisplacement _WaveDisplacementReporter;
sealed class Input : ILodInput
{
@@ -478,6 +491,9 @@ namespace WaveHarmonic.Crest
public int Pass => _Input.Pass;
public Rect Rect => _Input.Rect;
public MonoBehaviour Component => _Input;
public IReportsHeight HeightReporter => _Input._HeightReporter;
public IReportsDisplacement DisplacementReporter => _Input._DisplacementReporter;
public IReportWaveDisplacement WaveDisplacementReporter => _Input._WaveDisplacementReporter;
public float Filter(WaterRenderer water, int slice) => _Input.Filter(water, slice);
public void Draw(Lod lod, CommandBuffer buffer, RenderTargetIdentifier target, int pass = -1, float weight = 1, int slice = -1) => _Input.Draw(lod, buffer, target, pass, weight, slice);
}

View File

@@ -4,6 +4,7 @@
using System;
using UnityEngine;
using UnityEngine.Rendering;
using WaveHarmonic.Crest.Internal;
namespace WaveHarmonic.Crest
{
@@ -24,7 +25,7 @@ namespace WaveHarmonic.Crest
/// Data storage for an input, pertinent to the associated input mode.
/// </summary>
[Serializable]
public abstract class LodInputData
public abstract partial class LodInputData : Versioned
{
[SerializeField, HideInInspector]
internal LodInput _Input;

View File

@@ -2,10 +2,15 @@
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
using WaveHarmonic.Crest.Internal;
using WaveHarmonic.Crest.Utility;
#if !UNITY_6000_0_OR_NEWER
using GraphicsFormatUsage = UnityEngine.Experimental.Rendering.FormatUsage;
#endif
namespace WaveHarmonic.Crest
{
/// <summary>
@@ -33,7 +38,9 @@ namespace WaveHarmonic.Crest
[Tooltip("Populates the DepthProbe in Start.")]
OnStart = 0,
// EveryFrame = 1,
/// <inheritdoc cref="Generated.DepthProbeRefreshMode.EveryFrame"/>
[Tooltip("Populates the DepthProbe every frame.")]
EveryFrame = 1,
/// <inheritdoc cref="Generated.DepthProbeRefreshMode.ViaScripting"/>
[Tooltip("Requires manual updating via DepthProbe.Populate.")]
@@ -77,8 +84,13 @@ namespace WaveHarmonic.Crest
[SerializeField]
internal DepthProbeMode _Type = DepthProbeMode.RealTime;
[Tooltip("Controls how the probe is refreshed in the Player.\n\nCall Populate() if scripting.")]
[@Predicated(nameof(_Type), inverted: true, nameof(DepthProbeMode.RealTime), hide: true)]
[Tooltip("Where the Depth Probe is placed.\n\nThe default performs the best.")]
[@GenerateAPI]
[@DecoratedField, SerializeField]
Placement _Placement;
[Tooltip("Controls how the probe is refreshed in the Player.\n\nCall Populate() if scripting.\n\nWhen Placement is not set to Fixed, EveryFrame is still applicable, but the others are not (update only happens on position change).")]
[@Show(nameof(_Type), nameof(DepthProbeMode.RealTime))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
internal DepthProbeRefreshMode _RefreshMode = DepthProbeRefreshMode.OnStart;
@@ -87,26 +99,26 @@ namespace WaveHarmonic.Crest
[@Heading("Capture")]
[Tooltip("The layers to render into the probe.")]
[@Predicated(nameof(_Type), inverted: true, nameof(DepthProbeMode.RealTime))]
[@Enable(nameof(_Type), nameof(DepthProbeMode.RealTime))]
[@GenerateAPI(Setter.Dirty)]
[@DecoratedField, SerializeField]
internal LayerMask _Layers = 1; // Default
[Tooltip("The resolution of the probe.\n\nLower will be more efficient.")]
[@Predicated(nameof(_Type), inverted: true, nameof(DepthProbeMode.RealTime))]
[@Enable(nameof(_Type), nameof(DepthProbeMode.RealTime))]
[@GenerateAPI(Setter.Dirty)]
[@DecoratedField, SerializeField]
internal int _Resolution = 512;
[Tooltip("The far and near plane of the depth probe camera respectively, relative to the transform.\n\nDepth is captured top-down and orthographically. The gizmo will visualize this range as the bottom box.")]
[@Predicated(nameof(_Type), inverted: true, nameof(DepthProbeMode.RealTime))]
[@Enable(nameof(_Type), nameof(DepthProbeMode.RealTime))]
[@Range(-100f, 100f, Range.Clamp.None)]
[@GenerateAPI(Setter.Dirty)]
[SerializeField]
internal Vector2 _CaptureRange = new(-1000f, 1000f);
[Tooltip("Fills holes left by the maximum of the capture range.\n\nSetting the maximum capture range lower than the highest point of geometry can be useful for eliminating depth artifacts from overhangs, but the side effect is there will be a hole in the depth data where geometry is clipped by the near plane. This will only capture where the holes are to fill them in. This height is relative to the maximum capture range. Set to zero to skip.")]
[@Predicated(nameof(_Type), inverted: true, nameof(DepthProbeMode.RealTime))]
[@Enable(nameof(_Type), nameof(DepthProbeMode.RealTime))]
[@Range(0f, 100f, Range.Clamp.Minimum)]
[UnityEngine.Serialization.FormerlySerializedAs("_MaximumHeight")]
[@GenerateAPI(Setter.Dirty)]
@@ -115,14 +127,14 @@ namespace WaveHarmonic.Crest
[@Label("Enable Back-Face Inclusion")]
[Tooltip("Increase coverage by testing mesh back faces within the Fill Holes area.\n\nUses the back-faces to include meshes where the front-face is within the Fill Holes area and the back-face is within the capture area. An example would be an upright cylinder not over a hole but was not captured due to the top being clipped by the near plane.")]
[@Predicated(nameof(_Type), inverted: true, nameof(DepthProbeMode.RealTime))]
[@Predicated(nameof(_FillHolesCaptureHeight), inverted: false, 0f)]
[@Enable(nameof(_Type), nameof(DepthProbeMode.RealTime))]
[@Disable(nameof(_FillHolesCaptureHeight), 0f)]
[@GenerateAPI(Setter.Dirty)]
[@DecoratedField, SerializeField]
bool _EnableBackFaceInclusion = true;
[Tooltip("Overrides global quality settings.")]
[@Predicated(nameof(_Type), inverted: true, nameof(DepthProbeMode.RealTime))]
[@Enable(nameof(_Type), nameof(DepthProbeMode.RealTime))]
[GenerateAPI(Setter.None)]
[@DecoratedField, SerializeField]
QualitySettingsOverride _QualitySettingsOverride = new()
@@ -139,7 +151,7 @@ namespace WaveHarmonic.Crest
[Tooltip("Baked probe.\n\nCan only bake in edit mode.")]
[@Disabled]
[@Predicated(nameof(_Type), inverted: true, nameof(DepthProbeMode.Baked), hide: true)]
[@Show(nameof(_Type), nameof(DepthProbeMode.Baked))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
#pragma warning disable 649
@@ -151,7 +163,7 @@ namespace WaveHarmonic.Crest
[@Label("Generate")]
[Tooltip("Generate a signed distance field for the shoreline.")]
[@Predicated(nameof(_Type), inverted: true, nameof(DepthProbeMode.RealTime))]
[@Enable(nameof(_Type), nameof(DepthProbeMode.RealTime))]
[@GenerateAPI(Setter.Dirty)]
[@DecoratedField, SerializeField]
internal bool _GenerateSignedDistanceField = true;
@@ -159,8 +171,8 @@ namespace WaveHarmonic.Crest
// Additional rounds of JFA, over the standard log2(resolution), can help reduce
// innacuracies from JFA, see paper for details.
[Tooltip("How many additional Jump Flood rounds to use.\n\nThe standard number of rounds is log2(resolution). Additional rounds can reduce innaccuracies.")]
[@Predicated(nameof(_Type), inverted: true, nameof(DepthProbeMode.RealTime))]
[@Predicated(nameof(_GenerateSignedDistanceField))]
[@Enable(nameof(_Type), nameof(DepthProbeMode.RealTime))]
[@Enable(nameof(_GenerateSignedDistanceField))]
[@GenerateAPI(Setter.Dirty)]
[@DecoratedField, SerializeField]
int _AdditionalJumpFloodRounds = 7;
@@ -174,11 +186,6 @@ namespace WaveHarmonic.Crest
[System.Serializable]
internal sealed class DebugFields
{
[Tooltip("Will render into the probe every frame. Intended for debugging, will generate garbage.")]
[@Predicated(nameof(_Type), inverted: true, nameof(DepthProbeMode.RealTime))]
[@DecoratedField, SerializeField]
public bool _ForceAlwaysUpdateDebug;
[Tooltip("Shows hidden objects like the camera which renders into the probe.")]
[@DecoratedField, SerializeField]
public bool _ShowHiddenObjects;
@@ -342,6 +349,11 @@ namespace WaveHarmonic.Crest
if (transform.hasChanged)
{
_RecalculateBounds = true;
if (_Placement == Placement.Transform)
{
UpdatePosition(water, transform);
}
}
}
@@ -351,6 +363,14 @@ namespace WaveHarmonic.Crest
transform.hasChanged = false;
}
void OnBeforeBuildCommandBuffer(WaterRenderer water, Camera camera)
{
if (_Placement == Placement.Viewpoint)
{
UpdatePosition(water, camera.transform);
}
}
internal bool Outdated => _CurrentStateHash != _RenderedStateHash;
bool IsTextureOutdated(RenderTexture texture, bool target)
@@ -358,16 +378,19 @@ namespace WaveHarmonic.Crest
return texture != null &&
texture.width != _Resolution ||
texture.height != _Resolution ||
texture.format != (target ? RenderTextureFormat.Depth : FinalFormat);
texture.graphicsFormat != (target ? SystemInfo.GetGraphicsFormat(DefaultFormat.DepthStencil) : FinalFormat);
}
RenderTextureFormat FinalFormat => _GenerateSignedDistanceField ? RenderTextureFormat.RGFloat : RenderTextureFormat.RFloat;
GraphicsFormat FinalFormat => _GenerateSignedDistanceField
? Helpers.GetCompatibleTextureFormat(GraphicsFormat.R32G32_SFloat, Helpers.s_DataGraphicsFormatUsage, "Depth Probe", randomWrite: true)
: GraphicsFormat.R32_SFloat;
void MakeRT(RenderTexture texture, bool target)
{
var format = target ? RenderTextureFormat.Depth : FinalFormat;
var format = target ? SystemInfo.GetGraphicsFormat(DefaultFormat.DepthStencil) : FinalFormat;
var descriptor = texture.descriptor;
descriptor.colorFormat = format;
descriptor.graphicsFormat = target ? GraphicsFormat.None : format;
descriptor.depthStencilFormat = target ? format : GraphicsFormat.None;
descriptor.width = descriptor.height = _Resolution;
descriptor.depthBufferBits = target ? 24 : 0;
descriptor.useMipMap = false;
@@ -375,7 +398,11 @@ namespace WaveHarmonic.Crest
descriptor.enableRandomWrite = !target;
texture.descriptor = descriptor;
texture.Create();
Debug.Assert(SystemInfo.SupportsRenderTextureFormat(format), "Crest: The graphics device does not support the render texture format " + format.ToString());
if (!target)
{
Debug.Assert(SystemInfo.IsFormatSupported(format, GraphicsFormatUsage.Sample), "Crest: The graphics device does not support the render texture format " + format.ToString());
}
}
bool InitObjects()
@@ -704,7 +731,7 @@ namespace WaveHarmonic.Crest
var descriptor = new RenderTextureDescriptor(_Resolution, _Resolution)
{
autoGenerateMips = false,
colorFormat = RenderTextureFormat.RGHalf,
graphicsFormat = Helpers.GetCompatibleTextureFormat(GraphicsFormat.R16G16_SFloat, Helpers.s_DataGraphicsFormatUsage, "Depth Probe SDF", randomWrite: true),
useMipMap = false,
enableRandomWrite = true,
depthBufferBits = 0,
@@ -838,6 +865,9 @@ namespace WaveHarmonic.Crest
ILodInput.Attach(_Input, DepthLod.s_Inputs);
HashState(ref _CurrentStateHash);
WaterRenderer.s_OnBeforeBuildCommandBuffer -= OnBeforeBuildCommandBuffer;
WaterRenderer.s_OnBeforeBuildCommandBuffer += OnBeforeBuildCommandBuffer;
#if CREST_DEBUG
if (_Debug._ShowSimulationDataInScene)
{
@@ -852,6 +882,8 @@ namespace WaveHarmonic.Crest
base.OnDisable();
ILodInput.Detach(_Input, DepthLod.s_Inputs);
WaterRenderer.s_OnBeforeBuildCommandBuffer -= OnBeforeBuildCommandBuffer;
#if CREST_DEBUG
if (_Debug._ShowSimulationDataInScene)
{
@@ -926,6 +958,29 @@ namespace WaveHarmonic.Crest
}
}
sealed partial class DepthProbe
{
Vector3 _PreviousPosition;
void UpdatePosition(WaterRenderer water, Transform target)
{
var position = target.position.XNZ(water.transform.position.y);
var texelSize = Scale / _Resolution;
position.x = Mathf.Round(position.x / texelSize.x) * texelSize.x;
position.z = Mathf.Round(position.z / texelSize.y) * texelSize.y;
if ((_Placement == Placement.Viewpoint && !water.IsSingleViewpointMode) || _RefreshMode == DepthProbeRefreshMode.EveryFrame || _PreviousPosition != position)
{
Managed = true;
OverridePosition = true;
Position = position;
Scale = new(transform.lossyScale.x, transform.lossyScale.z);
Populate();
_PreviousPosition = position;
}
}
}
sealed partial class DepthProbe
{
// Hash is used to notify whether the probe is outdated in the UI.
@@ -974,7 +1029,7 @@ namespace WaveHarmonic.Crest
void Update()
{
if (_Debug._ForceAlwaysUpdateDebug && _Type != DepthProbeMode.Baked)
if (_RefreshMode == DepthProbeRefreshMode.EveryFrame && _Placement == Placement.Fixed && _Type != DepthProbeMode.Baked)
{
Populate();
}
@@ -987,30 +1042,21 @@ namespace WaveHarmonic.Crest
#endif
}
partial class DepthProbe : ISerializationCallbackReceiver
partial class DepthProbe
{
[SerializeField, HideInInspector]
#pragma warning disable 414
int _Version = 1;
#pragma warning restore 414
private protected override int Version => Mathf.Max(base.Version, 1);
/// <inheritdoc/>
void ISerializationCallbackReceiver.OnAfterDeserialize()
private protected override void OnMigrate()
{
base.OnMigrate();
// Version 1 (2024.06.04)
// - Added new capture options replacing Maximum Height's behaviour.
if (_Version < 1)
{
_CaptureRange.y = _FillHolesCaptureHeight;
_FillHolesCaptureHeight = 0f;
_Version = 1;
}
}
/// <inheritdoc/>
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
}
}
}

View File

@@ -26,11 +26,12 @@ namespace WaveHarmonic.Crest
[Tooltip("Whether to set the shader pass manually.")]
[@GenerateAPI]
[@DecoratedField, SerializeField]
[@InlineToggle]
[@SerializeField]
internal bool _OverrideShaderPass;
[Tooltip("The shader pass to execute.\n\nSet to -1 to execute all passes.")]
[@Predicated(nameof(_OverrideShaderPass))]
[@Enable(nameof(_OverrideShaderPass))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
internal int _ShaderPassIndex;

View File

@@ -1,8 +1,6 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
namespace WaveHarmonic.Crest
{
/// <summary>
@@ -14,11 +12,6 @@ namespace WaveHarmonic.Crest
[@HelpURL("Manual/WaterAppearance.html#volume-color-inputs")]
public sealed partial class ScatteringLodInput : LodInput
{
[SerializeField, HideInInspector]
#pragma warning disable 414
int _Version = 0;
#pragma warning restore 414
#if d_CrestPaint
internal override LodInputMode DefaultMode => LodInputMode.Paint;
#else

View File

@@ -1,8 +1,6 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
namespace WaveHarmonic.Crest
{
/// <summary>
@@ -14,11 +12,6 @@ namespace WaveHarmonic.Crest
[@HelpURL("Manual/WaterAppearance.html#shadows-lod")]
public sealed partial class ShadowLodInput : LodInput
{
[SerializeField, HideInInspector]
#pragma warning disable 414
int _Version = 0;
#pragma warning restore 414
internal override LodInputMode DefaultMode => LodInputMode.Renderer;
}
}

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;
}
}
}

View File

@@ -17,11 +17,6 @@ namespace WaveHarmonic.Crest
[@HelpURL("Manual/Waves.html#adding-interaction-forces")]
public sealed partial class SphereWaterInteraction : ManagedBehaviour<WaterRenderer>
{
[SerializeField, HideInInspector]
#pragma warning disable 414
int _Version = 0;
#pragma warning restore 414
[Tooltip("Radius of the sphere that is modelled from which the interaction forces are calculated.")]
[@Range(0.01f, 50f)]
[@GenerateAPI]

View File

@@ -34,7 +34,10 @@ namespace WaveHarmonic.Crest
internal override void RecalculateBounds()
{
throw new System.NotImplementedException();
var transform = _Input.transform;
var scale = transform.lossyScale.XZ();
scale = Helpers.RotateAndEncapsulateXZ(scale, transform.rotation.eulerAngles.y);
_Bounds = new(_Input.transform.position, scale.XNZ());
}
internal override void Draw(Lod lod, Component component, CommandBuffer buffer, RenderTargetIdentifier target, int slices)
@@ -45,7 +48,7 @@ namespace WaveHarmonic.Crest
wrapper.SetVector(ShaderIDs.s_TextureSize, transform.lossyScale.XZ());
wrapper.SetVector(ShaderIDs.s_TexturePosition, transform.position.XZ());
wrapper.SetVector(ShaderIDs.s_TextureRotation, rotation);
wrapper.SetVector(ShaderIDs.s_Resolution, new(_Texture.width, _Texture.height));
wrapper.SetVector(ShaderIDs.s_TextureResolution, new(_Texture.width, _Texture.height));
wrapper.SetVector(ShaderIDs.s_Multiplier, _Multiplier);
wrapper.SetFloat(ShaderIDs.s_FeatherWidth, _Input.FeatherWidth);
wrapper.SetTexture(ShaderIDs.s_Texture, _Texture);

View File

@@ -52,14 +52,14 @@ namespace WaveHarmonic.Crest
WatertightHullMode _Mode = WatertightHullMode.Displacement;
[Tooltip("Inverts the effect to remove clipping (ie add water).")]
[@Predicated(nameof(_Mode), inverted: true, nameof(WatertightHullMode.Clip), hide: true)]
[@Show(nameof(_Mode), nameof(WatertightHullMode.Clip))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
bool _Inverted;
[@Label("Use Clip")]
[Tooltip("Whether to also to clip the surface when using displacement mode.\n\nDisplacement mode can have a leaky hull by allowing chop top push waves across the hull boundaries slightly. Clipping the surface will remove these interior leaks.")]
[@Predicated(nameof(_Mode), inverted: true, nameof(WatertightHullMode.Displacement), hide: true)]
[@Show(nameof(_Mode), nameof(WatertightHullMode.Displacement))]
[@GenerateAPI(Setter.Custom)]
[@DecoratedField, SerializeField]
bool _UseClipWithDisplacement = true;
@@ -248,28 +248,19 @@ namespace WaveHarmonic.Crest
}
}
partial class WatertightHull : ISerializationCallbackReceiver
partial class WatertightHull
{
[SerializeField, HideInInspector]
#pragma warning disable 414
int _Version = 1;
#pragma warning restore 414
private protected override int Version => Mathf.Max(base.Version, 1);
/// <inheritdoc/>
void ISerializationCallbackReceiver.OnAfterDeserialize()
private protected override void OnMigrate()
{
base.OnMigrate();
if (_Version < 1)
{
// Keep clip for existing.
_Mode = WatertightHullMode.Clip;
_Version = 1;
}
}
/// <inheritdoc/>
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
}
}
}