修改水
This commit is contained in:
33
Packages/com.waveharmonic.crest/Runtime/Scripts/Assembly.cs
Normal file
33
Packages/com.waveharmonic.crest/Runtime/Scripts/Assembly.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
// Define empty namespaces for when assemblies are not present.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Editor")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Samples")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Samples.Editor")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Samples.Ripples")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.CPUQueries")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.CPUQueries.Editor")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Paint")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Paint.Editor")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Scripting")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.ShallowWater")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.ShallowWater.Editor")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Splines")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Splines.Editor")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Watercraft")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Watercraft.Editor")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Whirlpool")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Whirlpool.Editor")]
|
||||
|
||||
namespace UnityEngine.InputSystem { }
|
||||
namespace UnityEngine.Rendering.HighDefinition { }
|
||||
namespace UnityEngine.Rendering.RenderGraphModule { }
|
||||
namespace UnityEngine.Rendering.Universal { }
|
||||
namespace WaveHarmonic.Crest.Editor { }
|
||||
namespace WaveHarmonic.Crest.Paint { }
|
||||
namespace WaveHarmonic.Crest.Splines { }
|
||||
namespace WaveHarmonic.Crest.RelativeSpace { }
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bc511f9846b8c404091aace6915510df
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
33
Packages/com.waveharmonic.crest/Runtime/Scripts/Constants.cs
Normal file
33
Packages/com.waveharmonic.crest/Runtime/Scripts/Constants.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
static class Constants
|
||||
{
|
||||
#if CREST_OCEAN
|
||||
const string k_Prefix = "Crest 5 ";
|
||||
#else
|
||||
const string k_Prefix = "Crest ";
|
||||
#endif
|
||||
const string k_MenuScripts = "Crest/";
|
||||
public const string k_MenuPrefixScripts = k_MenuScripts + k_Prefix;
|
||||
public const string k_MenuPrefixInternal = k_MenuScripts + "Internal/";
|
||||
public const string k_MenuPrefixDebug = k_MenuScripts + "Debug/" + k_Prefix;
|
||||
public const string k_MenuPrefixInputs = k_MenuScripts + "Inputs/" + k_Prefix;
|
||||
public const string k_MenuPrefixTime = k_MenuScripts + "Time/" + k_Prefix;
|
||||
public const string k_MenuPrefixSpline = k_MenuScripts + "Spline/" + k_Prefix;
|
||||
public const string k_MenuPrefixPhysics = k_MenuScripts + "Physics/" + k_Prefix;
|
||||
public const string k_MenuPrefixSample = k_MenuScripts + "Sample/" + k_Prefix;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public const int k_FieldGroupOrder = Editor.Inspector.k_FieldGroupOrder;
|
||||
#else
|
||||
public const int k_FieldGroupOrder = 0;
|
||||
#endif
|
||||
|
||||
// Unity only supports textures up to a size of 16384, even if maxTextureSize returns a larger size.
|
||||
// https://docs.unity3d.com/ScriptReference/SystemInfo-maxTextureSize.html
|
||||
public const int k_MaximumTextureResolution = 16384;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5b3ea5ad983894f72b6478985b8238d3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 055e56f78a8f84633baaceee3f56970f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,68 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
using WaveHarmonic.Crest.Utility;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Overrides the absorption color.
|
||||
/// </summary>
|
||||
public sealed partial class AbsorptionLod : ColorLod
|
||||
{
|
||||
// Orange
|
||||
internal static readonly Color s_GizmoColor = new(1f, 165f / 255f, 0f, 0.5f);
|
||||
internal static readonly Color s_DefaultColor = new(0.342f, 0.695f, 0.85f, 0.102f);
|
||||
|
||||
static new class ShaderIDs
|
||||
{
|
||||
public static readonly int s_SampleAbsorptionSimulation = Shader.PropertyToID("g_Crest_SampleAbsorptionSimulation");
|
||||
}
|
||||
|
||||
internal override string ID => "Absorption";
|
||||
internal override string Name => "Absorption";
|
||||
internal override Color GizmoColor => s_GizmoColor;
|
||||
private protected override bool NeedToReadWriteTextureData => true;
|
||||
private protected override bool RequiresClearBorder => true;
|
||||
private protected override bool AlwaysClear => true;
|
||||
private protected override Color ClearColor
|
||||
{
|
||||
get
|
||||
{
|
||||
var color = Color.clear;
|
||||
|
||||
if (_Water.Material != null)
|
||||
{
|
||||
color = _Water.Material.GetVector(WaterRenderer.ShaderIDs.s_Absorption);
|
||||
color.a = 0f;
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
private protected override int GlobalShaderID => ShaderIDs.s_SampleAbsorptionSimulation;
|
||||
|
||||
internal AbsorptionLod()
|
||||
{
|
||||
_ShorelineColor = (s_DefaultColor * 1.5f).Clamped01();
|
||||
}
|
||||
|
||||
private protected override void SetShorelineColor(Color previous, Color current)
|
||||
{
|
||||
if (previous == current) return;
|
||||
_ShorelineColorValue = WaterRenderer.CalculateAbsorptionValueFromColor(current);
|
||||
}
|
||||
|
||||
internal static readonly SortedList<int, ILodInput> s_Inputs = new(Helpers.DuplicateComparison);
|
||||
private protected override SortedList<int, ILodInput> Inputs => s_Inputs;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
static void OnLoad()
|
||||
{
|
||||
s_Inputs.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a6548e15197394afb8759ec50e918937
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,44 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
using WaveHarmonic.Crest.Utility;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// A color layer that can be composited onto the water surface.
|
||||
/// </summary>
|
||||
[FilterEnum(nameof(_TextureFormatMode), Filtered.Mode.Include, (int)LodTextureFormatMode.Performance, (int)LodTextureFormatMode.Manual)]
|
||||
public sealed partial class AlbedoLod : Lod
|
||||
{
|
||||
internal static readonly Color s_GizmoColor = new(1f, 0f, 1f, 0.5f);
|
||||
|
||||
internal override string ID => "Albedo";
|
||||
internal override Color GizmoColor => s_GizmoColor;
|
||||
private protected override Color ClearColor => Color.clear;
|
||||
private protected override bool NeedToReadWriteTextureData => false;
|
||||
|
||||
private protected override GraphicsFormat RequestedTextureFormat => _TextureFormatMode switch
|
||||
{
|
||||
LodTextureFormatMode.Manual => _TextureFormat,
|
||||
_ => GraphicsFormat.R8G8B8A8_UNorm,
|
||||
};
|
||||
|
||||
internal AlbedoLod()
|
||||
{
|
||||
_Resolution = 768;
|
||||
_TextureFormat = GraphicsFormat.R8G8B8A8_UNorm;
|
||||
}
|
||||
|
||||
internal static readonly SortedList<int, ILodInput> s_Inputs = new(Helpers.DuplicateComparison);
|
||||
private protected override SortedList<int, ILodInput> Inputs => s_Inputs;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
static void OnLoad()
|
||||
{
|
||||
s_Inputs.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e6e1410b6b6b14d1db0a242b6d4efef5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,581 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
abstract class BakedWaveData : ScriptableObject
|
||||
{
|
||||
public abstract ICollisionProvider CreateCollisionProvider();
|
||||
public abstract float WindSpeed { get; }
|
||||
}
|
||||
|
||||
//
|
||||
// Collision
|
||||
//
|
||||
|
||||
/// <summary>
|
||||
/// The source of collisions (ie water shape).
|
||||
/// </summary>
|
||||
public enum CollisionSource
|
||||
{
|
||||
/// <summary>
|
||||
/// No collision source. Flat water.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
// GerstnerWavesCPU = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Uses AsyncGPUReadback to retrieve data from GPU to CPU.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is the most optimal approach.
|
||||
/// </remarks>
|
||||
GPU = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Computes data entirely on the CPU.
|
||||
/// </summary>
|
||||
CPU = 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The pass to render displacement into.
|
||||
/// </summary>
|
||||
public enum DisplacementPass
|
||||
{
|
||||
/// <summary>
|
||||
/// Displacement that is dependent on an LOD (eg waves).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Uses filtering to determine which LOD to write to.
|
||||
/// </remarks>
|
||||
[Tooltip("Displacement that is dependent on an LOD (eg waves).\n\nUses filtering to determine which LOD to write to.")]
|
||||
LodDependent,
|
||||
|
||||
/// <summary>
|
||||
/// Renders to all LODs.
|
||||
/// </summary>
|
||||
[Tooltip("Renders to all LODs.")]
|
||||
LodIndependent,
|
||||
|
||||
/// <summary>
|
||||
/// Renders to all LODs, but as a separate pass.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Typically used to render visual displacement which does not affect collisions.
|
||||
/// </remarks>
|
||||
[Tooltip("Renders to all LODs, but as a separate pass.\n\nTypically used to render visual displacement which does not affect collisions.")]
|
||||
[InspectorName("Lod Independent (Last)")]
|
||||
LodIndependentLast,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flags to enable extra collsion layers.
|
||||
/// </summary>
|
||||
[System.Flags]
|
||||
public enum CollisionLayers
|
||||
{
|
||||
// NOTE: numbers must be in order for defaults to work (everything first).
|
||||
|
||||
/// <summary>
|
||||
/// All layers.
|
||||
/// </summary>
|
||||
[Tooltip("All layers.")]
|
||||
Everything = -1,
|
||||
|
||||
/// <summary>
|
||||
/// No extra layers (ie single layer).
|
||||
/// </summary>
|
||||
[Tooltip("No extra layers (ie single layer).")]
|
||||
Nothing,
|
||||
|
||||
/// <summary>
|
||||
/// Separate layer for dynamic waves.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Dynamic waves are normally combined together for efficiency. By enabling this
|
||||
/// layer, dynamic waves are combined and added in a separate pass.
|
||||
/// </remarks>
|
||||
[Tooltip("Separate layer for dynamic waves.\n\nDynamic waves are normally combined together for efficiency. By enabling this layer, dynamic waves are combined and added in a separate pass.")]
|
||||
DynamicWaves = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// Extra displacement layer for visual displacement.
|
||||
/// </summary>
|
||||
[Tooltip("Extra displacement layer for visual displacement.")]
|
||||
Displacement = 1 << 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Captures waves/shape that is drawn kinematically - there is no frame-to-frame
|
||||
/// state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// A combine pass is done which combines downwards from low detail LODs into
|
||||
/// the high detail LODs.
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// The LOD data is passed to the water material when the surface is drawn.
|
||||
/// <item>
|
||||
/// </item>
|
||||
/// <see cref="DynamicWavesLod"/> adds its results into this data. They piggy back
|
||||
/// off the combine pass and subsequent assignment to the water material.
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// The RGB channels are the XYZ displacement from a rest plane at water level to
|
||||
/// the corresponding displaced position on the surface.
|
||||
/// </remarks>
|
||||
[@FilterEnum(nameof(_TextureFormatMode), Filtered.Mode.Exclude, (int)LodTextureFormatMode.Automatic)]
|
||||
public sealed partial class AnimatedWavesLod : Lod<ICollisionProvider>
|
||||
{
|
||||
[@Space(10)]
|
||||
|
||||
[Tooltip("Shifts wavelengths to maintain quality for higher resolutions.\n\nSet this to 2 to improve wave quality. In some cases like flowing rivers, this can make a substantial difference to visual stability. We recommend doubling the Resolution on the WaterRenderer component to preserve detail after making this change.")]
|
||||
[@Range(1f, 4f)]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
float _WaveResolutionMultiplier = 1f;
|
||||
|
||||
[Tooltip("How much waves are dampened in shallow water.")]
|
||||
[@Range(0f, 1f)]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
float _AttenuationInShallows = 0.95f;
|
||||
|
||||
[Tooltip("Any water deeper than this will receive full wave strength.\n\nThe lower the value, the less effective the depth cache will be at attenuating very large waves. Set to the maximum value (1,000) to disable.")]
|
||||
[@Range(1f, 1000f)]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
float _ShallowsMaximumDepth = 1000f;
|
||||
|
||||
|
||||
[@Heading("Collisions")]
|
||||
|
||||
[Tooltip("Where to obtain water shape on CPU for physics / gameplay.")]
|
||||
[@GenerateAPI(Setter.Internal)]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal CollisionSource _CollisionSource = CollisionSource.GPU;
|
||||
|
||||
[Tooltip("Collision layers to enable.\n\nSome layers will have overhead with CPU, GPU and memory.")]
|
||||
[@Predicated(nameof(_CollisionSource), inverted: true, nameof(CollisionSource.GPU))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal CollisionLayers _CollisionLayers = CollisionLayers.Everything;
|
||||
|
||||
[Tooltip("Maximum number of wave queries that can be performed when using GPU queries.")]
|
||||
[@Predicated(nameof(_CollisionSource), true, nameof(CollisionSource.GPU))]
|
||||
[@GenerateAPI(Setter.None)]
|
||||
[@DecoratedField, SerializeField]
|
||||
int _MaximumQueryCount = QueryBase.k_DefaultMaximumQueryCount;
|
||||
|
||||
[@Predicated(nameof(_CollisionSource), true, nameof(CollisionSource.CPU))]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal BakedWaveData _BakedWaveData;
|
||||
|
||||
|
||||
internal static new partial class ShaderIDs
|
||||
{
|
||||
public static readonly int s_WaveBuffer = Shader.PropertyToID("_Crest_WaveBuffer");
|
||||
public static readonly int s_DynamicWavesTarget = Shader.PropertyToID("_Crest_DynamicWavesTarget");
|
||||
public static readonly int s_AnimatedWavesTarget = Shader.PropertyToID("_Crest_AnimatedWavesTarget");
|
||||
public static readonly int s_AttenuationInShallows = Shader.PropertyToID("_Crest_AttenuationInShallows");
|
||||
}
|
||||
|
||||
internal static readonly Color s_GizmoColor = new(0f, 1f, 0f, 0.5f);
|
||||
|
||||
/// <summary>
|
||||
/// Turn shape combine pass on/off. Debug only - stripped in builds.
|
||||
/// </summary>
|
||||
internal static bool s_Combine = true;
|
||||
|
||||
internal override string ID => "AnimatedWaves";
|
||||
internal override string Name => "Animated Waves";
|
||||
internal override Color GizmoColor => s_GizmoColor;
|
||||
private protected override bool NeedToReadWriteTextureData => true;
|
||||
private protected override Color ClearColor => Color.black;
|
||||
internal override int BufferCount => _Water.WriteMotionVectors ? 2 : 1;
|
||||
internal override bool RunsInHeadless => true;
|
||||
|
||||
// NOTE: Tried RGB111110Float but errors becomes visible. One option would be to use a UNORM setup.
|
||||
private protected override GraphicsFormat RequestedTextureFormat => _TextureFormatMode switch
|
||||
{
|
||||
LodTextureFormatMode.Performance => GraphicsFormat.R16G16B16A16_SFloat,
|
||||
LodTextureFormatMode.Precision => GraphicsFormat.R32G32B32A32_SFloat,
|
||||
LodTextureFormatMode.Manual => _TextureFormat,
|
||||
_ => throw new System.NotImplementedException(),
|
||||
};
|
||||
|
||||
ComputeShader _CombineShader;
|
||||
|
||||
int _KernalShapeCombine = -1;
|
||||
int _KernalShapeCombine_DISABLE_COMBINE = -1;
|
||||
int _KernalShapeCombine_FLOW_ON = -1;
|
||||
int _KernalShapeCombine_FLOW_ON_DISABLE_COMBINE = -1;
|
||||
int _KernalShapeCombine_DYNAMIC_WAVE_SIM_ON = -1;
|
||||
int _KernalShapeCombine_DYNAMIC_WAVE_SIM_ON_DISABLE_COMBINE = -1;
|
||||
int _KernalShapeCombine_FLOW_ON_DYNAMIC_WAVE_SIM_ON = -1;
|
||||
int _KernalShapeCombine_FLOW_ON_DYNAMIC_WAVE_SIM_ON_DISABLE_COMBINE = -1;
|
||||
|
||||
|
||||
internal AnimatedWavesLod()
|
||||
{
|
||||
_Enabled = true;
|
||||
_OverrideResolution = false;
|
||||
_TextureFormat = GraphicsFormat.R16G16B16A16_SFloat;
|
||||
}
|
||||
|
||||
internal override void Initialize()
|
||||
{
|
||||
_CombineShader = WaterResources.Instance.Compute._ShapeCombine;
|
||||
if (_CombineShader == null)
|
||||
{
|
||||
_Valid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
base.Initialize();
|
||||
}
|
||||
|
||||
private protected override void Allocate()
|
||||
{
|
||||
base.Allocate();
|
||||
|
||||
_KernalShapeCombine = _CombineShader.FindKernel("ShapeCombine");
|
||||
_KernalShapeCombine_DISABLE_COMBINE = _CombineShader.FindKernel("ShapeCombine_DISABLE_COMBINE");
|
||||
_KernalShapeCombine_FLOW_ON = _CombineShader.FindKernel("ShapeCombine_FLOW_ON");
|
||||
_KernalShapeCombine_FLOW_ON_DISABLE_COMBINE = _CombineShader.FindKernel("ShapeCombine_FLOW_ON_DISABLE_COMBINE");
|
||||
_KernalShapeCombine_DYNAMIC_WAVE_SIM_ON = _CombineShader.FindKernel("ShapeCombine_DYNAMIC_WAVE_SIM_ON");
|
||||
_KernalShapeCombine_DYNAMIC_WAVE_SIM_ON_DISABLE_COMBINE = _CombineShader.FindKernel("ShapeCombine_DYNAMIC_WAVE_SIM_ON_DISABLE_COMBINE");
|
||||
_KernalShapeCombine_FLOW_ON_DYNAMIC_WAVE_SIM_ON = _CombineShader.FindKernel("ShapeCombine_FLOW_ON_DYNAMIC_WAVE_SIM_ON");
|
||||
_KernalShapeCombine_FLOW_ON_DYNAMIC_WAVE_SIM_ON_DISABLE_COMBINE = _CombineShader.FindKernel("ShapeCombine_FLOW_ON_DYNAMIC_WAVE_SIM_ON_DISABLE_COMBINE");
|
||||
}
|
||||
|
||||
internal override void BuildCommandBuffer(WaterRenderer water, CommandBuffer buffer)
|
||||
{
|
||||
buffer.BeginSample(Name);
|
||||
|
||||
FlipBuffers();
|
||||
|
||||
Shader.SetGlobalFloat(ShaderIDs.s_AttenuationInShallows, _AttenuationInShallows);
|
||||
|
||||
// Get temporary buffer to store waves. They will be copied in the combine pass.
|
||||
buffer.GetTemporaryRT(ShaderIDs.s_WaveBuffer, DataTexture.descriptor);
|
||||
buffer.SetRenderTarget(ShaderIDs.s_WaveBuffer, 0, CubemapFace.Unknown, -1);
|
||||
buffer.ClearRenderTarget(false, true, ClearColor);
|
||||
|
||||
// LOD dependent data.
|
||||
// Write to per-octave _WaveBuffers. Not the same as _AnimatedWaves.
|
||||
// Draw any data with lod preference.
|
||||
SubmitDraws(buffer, s_Inputs, ShaderIDs.s_WaveBuffer, (int)DisplacementPass.LodDependent, filter: true);
|
||||
|
||||
var lastSlice = Slices - 1;
|
||||
var threadSize = Resolution / k_ThreadGroupSize;
|
||||
|
||||
// Combine the LODs - copy results from biggest LOD down to LOD 0
|
||||
{
|
||||
var combineShaderKernel = _KernalShapeCombine;
|
||||
var combineShaderKernel_lastLOD = _KernalShapeCombine_DISABLE_COMBINE;
|
||||
{
|
||||
var isFlowOn = _Water._FlowLod.Enabled;
|
||||
var isDynamicWavesOn = _Water._DynamicWavesLod.Enabled && !_CollisionLayers.HasFlag(CollisionLayers.DynamicWaves);
|
||||
|
||||
// Set the shader kernels that we will use.
|
||||
if (isFlowOn && isDynamicWavesOn)
|
||||
{
|
||||
combineShaderKernel = _KernalShapeCombine_FLOW_ON_DYNAMIC_WAVE_SIM_ON;
|
||||
combineShaderKernel_lastLOD = _KernalShapeCombine_FLOW_ON_DYNAMIC_WAVE_SIM_ON_DISABLE_COMBINE;
|
||||
}
|
||||
else if (isFlowOn)
|
||||
{
|
||||
combineShaderKernel = _KernalShapeCombine_FLOW_ON;
|
||||
combineShaderKernel_lastLOD = _KernalShapeCombine_FLOW_ON_DISABLE_COMBINE;
|
||||
}
|
||||
else if (isDynamicWavesOn)
|
||||
{
|
||||
combineShaderKernel = _KernalShapeCombine_DYNAMIC_WAVE_SIM_ON;
|
||||
combineShaderKernel_lastLOD = _KernalShapeCombine_DYNAMIC_WAVE_SIM_ON_DISABLE_COMBINE;
|
||||
}
|
||||
}
|
||||
|
||||
buffer.BeginSample("Combine");
|
||||
|
||||
// Combine waves.
|
||||
for (var slice = lastSlice; slice >= 0; slice--)
|
||||
{
|
||||
var kernel = slice < lastSlice && s_Combine
|
||||
? combineShaderKernel : combineShaderKernel_lastLOD;
|
||||
|
||||
var wrapper = new PropertyWrapperCompute(buffer, _CombineShader, kernel);
|
||||
|
||||
// The per-octave wave buffers we read from.
|
||||
wrapper.SetTexture(ShaderIDs.s_WaveBuffer, ShaderIDs.s_WaveBuffer);
|
||||
|
||||
if (_Water._DynamicWavesLod.Enabled) _Water._DynamicWavesLod.Bind(wrapper);
|
||||
|
||||
// Set the animated waves texture where we read/write to combine the results. Use
|
||||
// compute suffix to avoid collision as a file already uses the normal name.
|
||||
wrapper.SetTexture(Crest.ShaderIDs.s_Target, DataTexture);
|
||||
wrapper.SetInteger(Lod.ShaderIDs.s_LodIndex, slice);
|
||||
|
||||
wrapper.Dispatch(threadSize, threadSize, 1);
|
||||
}
|
||||
|
||||
buffer.EndSample("Combine");
|
||||
}
|
||||
|
||||
buffer.ReleaseTemporaryRT(ShaderIDs.s_WaveBuffer);
|
||||
|
||||
// LOD independent data.
|
||||
// Draw any data that did not express a preference for one lod or another.
|
||||
var drawn = SubmitDraws(buffer, s_Inputs, DataTexture, (int)DisplacementPass.LodIndependent);
|
||||
|
||||
// Alpha channel is cleared in combine step, but if any inputs draw in post-combine
|
||||
// step then alpha may have data.
|
||||
var clear = WaterResources.Instance.Compute._Clear;
|
||||
if (drawn && clear != null)
|
||||
{
|
||||
buffer.SetComputeTextureParam(clear, 0, Crest.ShaderIDs.s_Target, DataTexture);
|
||||
buffer.SetComputeVectorParam(clear, Crest.ShaderIDs.s_ClearMask, Color.black);
|
||||
buffer.SetComputeVectorParam(clear, Crest.ShaderIDs.s_ClearColor, Color.clear);
|
||||
buffer.DispatchCompute
|
||||
(
|
||||
clear,
|
||||
0,
|
||||
Resolution / k_ThreadGroupSizeX,
|
||||
Resolution / k_ThreadGroupSizeY,
|
||||
Slices
|
||||
);
|
||||
}
|
||||
|
||||
// Pack height data into alpha channel.
|
||||
// We do not add height to displacement directly for better precision and layering.
|
||||
var heightShader = WaterResources.Instance.Compute._PackLevel;
|
||||
if (_Water._LevelLod.Enabled && heightShader != null)
|
||||
{
|
||||
buffer.SetComputeTextureParam(heightShader, 0, Crest.ShaderIDs.s_Target, DataTexture);
|
||||
buffer.DispatchCompute
|
||||
(
|
||||
heightShader,
|
||||
0,
|
||||
Resolution / k_ThreadGroupSizeX,
|
||||
Resolution / k_ThreadGroupSizeY,
|
||||
Slices
|
||||
);
|
||||
}
|
||||
|
||||
// Query collisions including only Animated Waves.
|
||||
// Requires copying the water level.
|
||||
Provider.UpdateQueries(_Water, CollisionLayer.AfterAnimatedWaves);
|
||||
|
||||
// Transfer Dynamic Waves to Animated Waves.
|
||||
if (_CollisionLayers.HasFlag(CollisionLayers.DynamicWaves) && _Water._DynamicWavesLod.Enabled)
|
||||
{
|
||||
buffer.BeginSample("Combine");
|
||||
// Clearing not required as we overwrite enter texture.
|
||||
buffer.GetTemporaryRT(ShaderIDs.s_DynamicWavesTarget, DataTexture.descriptor);
|
||||
|
||||
var wrapper = new PropertyWrapperCompute(buffer, _CombineShader, 8);
|
||||
|
||||
wrapper.SetTexture(ShaderIDs.s_DynamicWavesTarget, ShaderIDs.s_DynamicWavesTarget);
|
||||
|
||||
_Water._DynamicWavesLod.Bind(wrapper);
|
||||
|
||||
// Compute displacement from Dynamic Waves.
|
||||
for (var slice = lastSlice; slice >= 0; slice--)
|
||||
{
|
||||
wrapper.SetInteger(Lod.ShaderIDs.s_LodIndex, slice);
|
||||
wrapper.Dispatch(threadSize, threadSize, 1);
|
||||
}
|
||||
|
||||
// Copy Dynamic Waves displacement into Animated Waves.
|
||||
{
|
||||
wrapper = new PropertyWrapperCompute(buffer, _CombineShader, 9);
|
||||
wrapper.SetTexture(ShaderIDs.s_AnimatedWavesTarget, DataTexture);
|
||||
wrapper.SetTexture(ShaderIDs.s_DynamicWavesTarget, ShaderIDs.s_DynamicWavesTarget);
|
||||
wrapper.Dispatch(threadSize, threadSize, Slices);
|
||||
}
|
||||
|
||||
buffer.ReleaseTemporaryRT(ShaderIDs.s_DynamicWavesTarget);
|
||||
buffer.EndSample("Combine");
|
||||
|
||||
// Query collisions including Dynamic Waves.
|
||||
// Does not require copying the water level as they are added with zero alpha.
|
||||
_Water.CollisionProvider.UpdateQueries(_Water, CollisionLayer.AfterDynamicWaves);
|
||||
}
|
||||
|
||||
if (_CollisionLayers.HasFlag(CollisionLayers.Displacement))
|
||||
{
|
||||
// LOD independent data.
|
||||
// Draw any data that did not express a preference for one lod or another.
|
||||
drawn = SubmitDraws(buffer, s_Inputs, DataTexture, (int)DisplacementPass.LodIndependentLast);
|
||||
}
|
||||
|
||||
if (_CollisionLayers == CollisionLayers.Nothing || _CollisionLayers.HasFlag(CollisionLayers.Displacement))
|
||||
{
|
||||
Queryable?.UpdateQueries(_Water);
|
||||
}
|
||||
|
||||
if (BufferCount > 1)
|
||||
{
|
||||
// Update current and previous. Latter for MVs and/or VFX.
|
||||
Shader.SetGlobalTexture(_TextureSourceShaderID, _Targets.Previous(1));
|
||||
Shader.SetGlobalTexture(_TextureShaderID, DataTexture);
|
||||
}
|
||||
|
||||
buffer.EndSample(Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides water shape to CPU.
|
||||
/// </summary>
|
||||
private protected override ICollisionProvider CreateProvider(bool enable)
|
||||
{
|
||||
ICollisionProvider result = null;
|
||||
|
||||
Queryable?.CleanUp();
|
||||
|
||||
if (!enable)
|
||||
{
|
||||
return ICollisionProvider.None;
|
||||
}
|
||||
|
||||
switch (_CollisionSource)
|
||||
{
|
||||
case CollisionSource.None:
|
||||
result = ICollisionProvider.None;
|
||||
break;
|
||||
case CollisionSource.GPU:
|
||||
if (_Valid && !_Water.IsRunningWithoutGraphics)
|
||||
{
|
||||
result = new CollisionQueryWithPasses(_Water);
|
||||
}
|
||||
|
||||
if (_Water.IsRunningWithoutGraphics)
|
||||
{
|
||||
Debug.LogError($"Crest: GPU queries not supported in headless/batch mode. To resolve, assign an Animated Wave Settings asset to the {nameof(WaterRenderer)} component and set the Collision Source to be a CPU option.");
|
||||
}
|
||||
break;
|
||||
case CollisionSource.CPU:
|
||||
if (_BakedWaveData != null)
|
||||
{
|
||||
result = _BakedWaveData.CreateCollisionProvider();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
// This should not be hit, but can be if compute shaders aren't loaded correctly.
|
||||
// They will print out appropriate errors. Don't just return null and have null reference
|
||||
// exceptions spamming the logs.
|
||||
return ICollisionProvider.None;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//
|
||||
// DrawFilter
|
||||
//
|
||||
|
||||
internal readonly struct WavelengthFilter
|
||||
{
|
||||
public readonly float _Minimum;
|
||||
public readonly float _Maximum;
|
||||
public readonly float _TransitionThreshold;
|
||||
public readonly float _ViewerAltitudeLevelAlpha;
|
||||
public readonly int _Slice;
|
||||
public readonly int _Slices;
|
||||
|
||||
public WavelengthFilter(WaterRenderer water, int slice)
|
||||
{
|
||||
_Slice = slice;
|
||||
_Slices = water.LodLevels;
|
||||
_Maximum = water.MaximumWavelength(slice);
|
||||
_Minimum = _Maximum * 0.5f;
|
||||
_TransitionThreshold = water.MaximumWavelength(_Slices - 1) * 0.5f;
|
||||
_ViewerAltitudeLevelAlpha = water.ViewerAltitudeLevelAlpha;
|
||||
}
|
||||
}
|
||||
|
||||
internal static float FilterByWavelength(WavelengthFilter filter, float wavelength)
|
||||
{
|
||||
// No wavelength preference - don't draw per-lod
|
||||
if (wavelength == 0f)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
// Too small for this lod
|
||||
if (wavelength < filter._Minimum)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
// If approaching end of lod chain, start smoothly transitioning any large wavelengths across last two lods
|
||||
if (wavelength >= filter._TransitionThreshold)
|
||||
{
|
||||
if (filter._Slice == filter._Slices - 2)
|
||||
{
|
||||
return 1f - filter._ViewerAltitudeLevelAlpha;
|
||||
}
|
||||
|
||||
if (filter._Slice == filter._Slices - 1)
|
||||
{
|
||||
return filter._ViewerAltitudeLevelAlpha;
|
||||
}
|
||||
}
|
||||
else if (wavelength < filter._Maximum)
|
||||
{
|
||||
// Fits in this lod
|
||||
return 1f;
|
||||
}
|
||||
|
||||
return 0f;
|
||||
}
|
||||
|
||||
internal static float FilterByWavelength(WaterRenderer water, int slice, float wavelength)
|
||||
{
|
||||
return FilterByWavelength(new(water, slice), wavelength);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Inputs
|
||||
//
|
||||
|
||||
internal static readonly Utility.SortedList<int, ILodInput> s_Inputs = new(Helpers.DuplicateComparison);
|
||||
private protected override Utility.SortedList<int, ILodInput> Inputs => s_Inputs;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
static void OnLoad()
|
||||
{
|
||||
s_Inputs.Clear();
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[@OnChange]
|
||||
private protected override void OnChange(string propertyPath, object previousValue)
|
||||
{
|
||||
base.OnChange(propertyPath, previousValue);
|
||||
|
||||
switch (propertyPath)
|
||||
{
|
||||
case nameof(_CollisionLayers):
|
||||
case nameof(_CollisionSource):
|
||||
if (_Water == null || !_Water.isActiveAndEnabled || !Enabled) return;
|
||||
Queryable?.CleanUp();
|
||||
InitializeProvider(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 71b85abdc5e234fe8a685d3be0b95443
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,31 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
readonly struct Cascade
|
||||
{
|
||||
public readonly Vector2 _SnappedPosition;
|
||||
public readonly float _Texel;
|
||||
public readonly int _Resolution;
|
||||
public readonly Vector4 Packed => new(_SnappedPosition.x, _SnappedPosition.y, _Texel, 0f);
|
||||
|
||||
public Cascade(Vector2 snapped, float texel, int resolution)
|
||||
{
|
||||
_SnappedPosition = snapped;
|
||||
_Texel = texel;
|
||||
_Resolution = resolution;
|
||||
}
|
||||
|
||||
public readonly Rect TexelRect
|
||||
{
|
||||
get
|
||||
{
|
||||
var w = _Texel * _Resolution;
|
||||
return new(_SnappedPosition.x - w / 2f, _SnappedPosition.y - w / 2f, w, w);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a52c8c04f6c749d7a2bc6e5a4fbe9ae
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
120
Packages/com.waveharmonic.crest/Runtime/Scripts/Data/ClipLod.cs
Normal file
120
Packages/com.waveharmonic.crest/Runtime/Scripts/Data/ClipLod.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
using UnityEngine.Rendering;
|
||||
using WaveHarmonic.Crest.Utility;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// The default state for clipping.
|
||||
/// </summary>
|
||||
public enum DefaultClippingState
|
||||
{
|
||||
/// <summary>
|
||||
/// By default, nothing is clipped. Use clip inputs to remove water.
|
||||
/// </summary>
|
||||
NothingClipped,
|
||||
|
||||
/// <summary>
|
||||
/// By default, everything is clipped. Use clip inputs to add water.
|
||||
/// </summary>
|
||||
EverythingClipped,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drives water surface clipping (carving holes).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 0-1 values, surface clipped when > 0.5.
|
||||
/// </remarks>
|
||||
[FilterEnum(nameof(_TextureFormatMode), Filtered.Mode.Exclude, (int)LodTextureFormatMode.Automatic)]
|
||||
public sealed partial class ClipLod : Lod
|
||||
{
|
||||
[Tooltip("The default clipping behaviour.\n\nWhether to clip nothing by default (and clip inputs remove patches of surface), or to clip everything by default (and clip inputs add patches of surface).")]
|
||||
[@GenerateAPI(Setter.Custom)]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal DefaultClippingState _DefaultClippingState = DefaultClippingState.NothingClipped;
|
||||
|
||||
static new class ShaderIDs
|
||||
{
|
||||
public static readonly int s_ClipByDefault = Shader.PropertyToID("g_Crest_ClipByDefault");
|
||||
}
|
||||
|
||||
internal static readonly Color s_GizmoColor = new(0f, 1f, 1f, 0.5f);
|
||||
|
||||
internal override string ID => "Clip";
|
||||
internal override string Name => "Clip Surface";
|
||||
internal override Color GizmoColor => s_GizmoColor;
|
||||
private protected override Color ClearColor => _DefaultClippingState == DefaultClippingState.EverythingClipped ? Color.white : Color.black;
|
||||
private protected override bool NeedToReadWriteTextureData => true;
|
||||
private protected override bool RequiresClearBorder => true;
|
||||
|
||||
private protected override GraphicsFormat RequestedTextureFormat => _TextureFormatMode switch
|
||||
{
|
||||
// The clip values only really need 8bits (unless using signed distance).
|
||||
LodTextureFormatMode.Performance => GraphicsFormat.R8_UNorm,
|
||||
LodTextureFormatMode.Precision => GraphicsFormat.R16_UNorm,
|
||||
LodTextureFormatMode.Manual => _TextureFormat,
|
||||
_ => throw new System.NotImplementedException(),
|
||||
};
|
||||
|
||||
internal ClipLod()
|
||||
{
|
||||
_TextureFormat = GraphicsFormat.R8_UNorm;
|
||||
}
|
||||
|
||||
internal override void SetGlobals(bool enable)
|
||||
{
|
||||
base.SetGlobals(enable);
|
||||
Shader.SetGlobalFloat(ShaderIDs.s_ClipByDefault, enable && Enabled ? (float)_DefaultClippingState : (float)DefaultClippingState.NothingClipped);
|
||||
}
|
||||
|
||||
internal override void Disable()
|
||||
{
|
||||
base.Disable();
|
||||
Shader.SetGlobalFloat(ShaderIDs.s_ClipByDefault, (float)DefaultClippingState.NothingClipped);
|
||||
}
|
||||
|
||||
internal override void BuildCommandBuffer(WaterRenderer water, CommandBuffer buffer)
|
||||
{
|
||||
base.BuildCommandBuffer(water, buffer);
|
||||
Shader.SetGlobalFloat(ShaderIDs.s_ClipByDefault, (float)_DefaultClippingState);
|
||||
}
|
||||
|
||||
internal static readonly SortedList<int, ILodInput> s_Inputs = new(Helpers.DuplicateComparison);
|
||||
private protected override SortedList<int, ILodInput> Inputs => s_Inputs;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
static void OnLoad()
|
||||
{
|
||||
s_Inputs.Clear();
|
||||
}
|
||||
|
||||
void SetDefaultClippingState(DefaultClippingState previous, DefaultClippingState current)
|
||||
{
|
||||
if (previous == current) return;
|
||||
if (_Water == null || !_Water.isActiveAndEnabled || !Enabled) return;
|
||||
|
||||
// Change default clipping state.
|
||||
_TargetsToClear = Mathf.Max(1, _TargetsToClear);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[@OnChange]
|
||||
private protected override void OnChange(string path, object previous)
|
||||
{
|
||||
base.OnChange(path, previous);
|
||||
|
||||
switch (path)
|
||||
{
|
||||
case nameof(_DefaultClippingState):
|
||||
SetDefaultClippingState((DefaultClippingState)previous, _DefaultClippingState);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ed87ea342278349c4885adef619f19cb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
169
Packages/com.waveharmonic.crest/Runtime/Scripts/Data/ColorLod.cs
Normal file
169
Packages/com.waveharmonic.crest/Runtime/Scripts/Data/ColorLod.cs
Normal file
@@ -0,0 +1,169 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// The source of depth color.
|
||||
/// </summary>
|
||||
public enum ShorelineVolumeColorSource
|
||||
{
|
||||
/// <summary>
|
||||
/// No depth color.
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Depth color based on water depth.
|
||||
/// </summary>
|
||||
Depth,
|
||||
|
||||
/// <summary>
|
||||
/// Depth color based on shoreline distance.
|
||||
/// </summary>
|
||||
Distance,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains shared functionality for <see cref="AbsorptionLod"/> and <see cref="ScatteringLod"/>.
|
||||
/// </summary>
|
||||
[FilterEnum(nameof(_TextureFormatMode), Filtered.Mode.Exclude, (int)LodTextureFormatMode.Automatic)]
|
||||
public abstract partial class ColorLod : Lod
|
||||
{
|
||||
[@Space(10f)]
|
||||
|
||||
[Tooltip("Source of the shoreline color.")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal ShorelineVolumeColorSource _ShorelineColorSource;
|
||||
|
||||
[Tooltip("Color of the shoreline color.")]
|
||||
[@Predicated(nameof(_ShorelineColorSource), inverted: false, nameof(ShorelineVolumeColorSource.None), hide: true)]
|
||||
[@GenerateAPI(Setter.Custom)]
|
||||
[@DecoratedField, SerializeField]
|
||||
private protected Color _ShorelineColor;
|
||||
|
||||
[Tooltip("The maximum distance of the shoreline color.\n\nIf using Depth, then it is maximum depth.")]
|
||||
[@Predicated(nameof(_ShorelineColorSource), inverted: false, nameof(ShorelineVolumeColorSource.None), hide: true)]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
float _ShorelineColorMaximumDistance = 10f;
|
||||
|
||||
[Tooltip("Shoreline color falloff value.")]
|
||||
[@Predicated(nameof(_ShorelineColorSource), inverted: false, nameof(ShorelineVolumeColorSource.None), hide: true)]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
float _ShorelineColorFalloff = 2f;
|
||||
|
||||
static new class ShaderIDs
|
||||
{
|
||||
public static readonly int s_ShorelineColor = Shader.PropertyToID("_Crest_ShorelineColor");
|
||||
public static readonly int s_ShorelineColorMaximumDistance = Shader.PropertyToID("_Crest_ShorelineColorMaximumDistance");
|
||||
public static readonly int s_ShorelineColorFalloff = Shader.PropertyToID("_Crest_ShorelineColorFalloff");
|
||||
}
|
||||
|
||||
private protected abstract int GlobalShaderID { get; }
|
||||
private protected abstract void SetShorelineColor(Color previous, Color current);
|
||||
private protected Vector4 _ShorelineColorValue;
|
||||
ShorelineColorInput _ShorelineColorInput;
|
||||
|
||||
private protected override GraphicsFormat RequestedTextureFormat => _TextureFormatMode switch
|
||||
{
|
||||
LodTextureFormatMode.Manual => _TextureFormat,
|
||||
LodTextureFormatMode.Performance => GraphicsFormat.R8G8B8_UNorm,
|
||||
LodTextureFormatMode.Precision => GraphicsFormat.R16G16B16_UNorm,
|
||||
_ => throw new System.NotImplementedException($"Crest: {_TextureFormatMode} not implemented for {Name}."),
|
||||
};
|
||||
|
||||
internal ColorLod()
|
||||
{
|
||||
// Interpolation banding with lower precision.
|
||||
_TextureFormat = GraphicsFormat.R16G16B16_UNorm;
|
||||
_TextureFormatMode = LodTextureFormatMode.Precision;
|
||||
}
|
||||
|
||||
internal override void Enable()
|
||||
{
|
||||
base.Enable();
|
||||
|
||||
if (Enabled)
|
||||
{
|
||||
_ShorelineColorInput ??= new(this);
|
||||
// Convert color to value.
|
||||
SetShorelineColor(Color.clear, _ShorelineColor);
|
||||
Inputs.Add(_ShorelineColorInput.Queue, _ShorelineColorInput);
|
||||
}
|
||||
}
|
||||
|
||||
internal override void SetGlobals(bool enable)
|
||||
{
|
||||
base.SetGlobals(enable);
|
||||
|
||||
Helpers.SetGlobalBoolean(GlobalShaderID, enable && Enabled);
|
||||
}
|
||||
|
||||
internal override void Disable()
|
||||
{
|
||||
base.Disable();
|
||||
|
||||
Inputs.Remove(_ShorelineColorInput);
|
||||
}
|
||||
|
||||
sealed class ShorelineColorInput : ILodInput
|
||||
{
|
||||
public bool Enabled => _VolumeColorLod._ShorelineColorSource != ShorelineVolumeColorSource.None &&
|
||||
_VolumeColorLod._Water._DepthLod.Enabled;
|
||||
public bool IsCompute => true;
|
||||
public int Queue => int.MinValue;
|
||||
public int Pass => -1;
|
||||
public Rect Rect => Rect.zero;
|
||||
public MonoBehaviour Component => null;
|
||||
public float Filter(WaterRenderer water, int slice) => 1f;
|
||||
|
||||
readonly ColorLod _VolumeColorLod;
|
||||
|
||||
public ShorelineColorInput(ColorLod lod)
|
||||
{
|
||||
_VolumeColorLod = lod;
|
||||
}
|
||||
|
||||
public void Draw(Lod lod, CommandBuffer buffer, RenderTargetIdentifier target, int pass = -1, float weight = 1, int slices = -1)
|
||||
{
|
||||
var resources = WaterResources.Instance;
|
||||
var wrapper = new PropertyWrapperCompute(buffer, resources.Compute._ShorelineColor, 0);
|
||||
|
||||
wrapper.SetVector(ShaderIDs.s_ShorelineColor, _VolumeColorLod._ShorelineColorValue);
|
||||
wrapper.SetFloat(ShaderIDs.s_ShorelineColorMaximumDistance, _VolumeColorLod._ShorelineColorMaximumDistance);
|
||||
wrapper.SetFloat(ShaderIDs.s_ShorelineColorFalloff, _VolumeColorLod._ShorelineColorFalloff);
|
||||
|
||||
wrapper.SetKeyword(WaterResources.Instance.Keywords.ShorelineColorScattering, lod.GetType() == typeof(ScatteringLod));
|
||||
wrapper.SetKeyword(WaterResources.Instance.Keywords.ShorelineColorSourceDistance, _VolumeColorLod._ShorelineColorSource == ShorelineVolumeColorSource.Distance);
|
||||
wrapper.SetTexture(Crest.ShaderIDs.s_Target, target);
|
||||
|
||||
var threads = lod.Resolution / k_ThreadGroupSize;
|
||||
wrapper.Dispatch(threads, threads, slices);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
abstract partial class ColorLod
|
||||
{
|
||||
private protected override void OnChange(string propertyPath, object previousValue)
|
||||
{
|
||||
base.OnChange(propertyPath, previousValue);
|
||||
|
||||
switch (propertyPath)
|
||||
{
|
||||
case nameof(_ShorelineColor):
|
||||
SetShorelineColor((Color)previousValue, _ShorelineColor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e1bc55489896941859aa8af34f8c1083
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
107
Packages/com.waveharmonic.crest/Runtime/Scripts/Data/DepthLod.cs
Normal file
107
Packages/com.waveharmonic.crest/Runtime/Scripts/Data/DepthLod.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
using WaveHarmonic.Crest.Utility;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Data that gives depth of the water (height of sea level above water floor).
|
||||
/// </summary>
|
||||
[FilterEnum(nameof(_TextureFormatMode), Filtered.Mode.Exclude, (int)LodTextureFormatMode.Automatic)]
|
||||
public sealed partial class DepthLod : Lod<IDepthProvider>
|
||||
{
|
||||
[Tooltip("Support signed distance field data generated from the depth probes.\n\nRequires a two component texture format.")]
|
||||
[@GenerateAPI(Setter.Custom)]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal bool _EnableSignedDistanceFields = true;
|
||||
|
||||
// NOTE: Must match CREST_WATER_DEPTH_BASELINE in Constants.hlsl.
|
||||
internal const float k_DepthBaseline = Mathf.Infinity;
|
||||
internal static readonly Color s_GizmoColor = new(1f, 0f, 0f, 0.5f);
|
||||
// We want the clear color to be the mininimum terrain height (-1000m).
|
||||
// Mathf.Infinity can cause problems for distance.
|
||||
static readonly Color s_NullColor = new(-k_DepthBaseline, k_DepthBaseline, 0, 0);
|
||||
|
||||
internal override string ID => "Depth";
|
||||
internal override string Name => "Water Depth";
|
||||
internal override Color GizmoColor => s_GizmoColor;
|
||||
private protected override Color ClearColor => s_NullColor;
|
||||
private protected override bool NeedToReadWriteTextureData => true;
|
||||
internal override bool RunsInHeadless => true;
|
||||
|
||||
private protected override GraphicsFormat RequestedTextureFormat => _TextureFormatMode switch
|
||||
{
|
||||
LodTextureFormatMode.Automatic or
|
||||
LodTextureFormatMode.Performance => _EnableSignedDistanceFields ? GraphicsFormat.R16G16_SFloat : GraphicsFormat.R16_SFloat,
|
||||
LodTextureFormatMode.Precision => _EnableSignedDistanceFields ? GraphicsFormat.R32G32_SFloat : GraphicsFormat.R32_SFloat,
|
||||
LodTextureFormatMode.Manual => _TextureFormat,
|
||||
_ => throw new System.NotImplementedException(),
|
||||
};
|
||||
|
||||
Texture2DArray _NullTexture;
|
||||
private protected override Texture2DArray NullTexture
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_NullTexture == null)
|
||||
{
|
||||
var texture = TextureArrayHelpers.CreateTexture2D(s_NullColor, UnityEngine.TextureFormat.RFloat);
|
||||
texture.name = $"_Crest_{ID}LodTemporaryDefaultTexture";
|
||||
_NullTexture = TextureArrayHelpers.CreateTexture2DArray(texture, k_MaximumSlices);
|
||||
_NullTexture.name = $"_Crest_{ID}LodDefaultTexture";
|
||||
Helpers.Destroy(texture);
|
||||
}
|
||||
|
||||
return _NullTexture;
|
||||
}
|
||||
}
|
||||
|
||||
internal DepthLod()
|
||||
{
|
||||
_Enabled = true;
|
||||
_TextureFormat = GraphicsFormat.R16G16_SFloat;
|
||||
}
|
||||
|
||||
private protected override IDepthProvider CreateProvider(bool enable)
|
||||
{
|
||||
Queryable?.CleanUp();
|
||||
// Depth is GPU only, and can only be queried using the compute path.
|
||||
return enable && Enabled ? new DepthQuery(_Water) : IDepthProvider.None;
|
||||
}
|
||||
|
||||
internal static readonly SortedList<int, ILodInput> s_Inputs = new(Helpers.DuplicateComparison);
|
||||
private protected override SortedList<int, ILodInput> Inputs => s_Inputs;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
static void OnLoad()
|
||||
{
|
||||
s_Inputs.Clear();
|
||||
}
|
||||
|
||||
void SetEnableSignedDistanceFields(bool previous, bool current)
|
||||
{
|
||||
if (previous == current) return;
|
||||
if (_Water == null || !_Water.isActiveAndEnabled || !Enabled) return;
|
||||
|
||||
ReAllocate();
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[@OnChange]
|
||||
private protected override void OnChange(string propertyPath, object previousValue)
|
||||
{
|
||||
base.OnChange(propertyPath, previousValue);
|
||||
|
||||
switch (propertyPath)
|
||||
{
|
||||
case nameof(_EnableSignedDistanceFields):
|
||||
SetEnableSignedDistanceFields((bool)previousValue, _EnableSignedDistanceFields);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 679dfc1207b124b3fa00496f7b93a5cd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,116 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
using WaveHarmonic.Crest.Utility;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// A dynamic shape simulation that moves around with a displacement LOD.
|
||||
/// </summary>
|
||||
public sealed partial class DynamicWavesLod : PersistentLod
|
||||
{
|
||||
[Tooltip("How much waves are dampened in shallow water.")]
|
||||
[@Range(0f, 1f)]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
float _AttenuationInShallows = 1f;
|
||||
|
||||
[Tooltip("Settings for fine tuning this simulation.")]
|
||||
[@Embedded]
|
||||
[@GenerateAPI(Getter.Custom)]
|
||||
[SerializeField]
|
||||
DynamicWavesLodSettings _Settings;
|
||||
|
||||
const string k_DynamicWavesKeyword = "CREST_DYNAMIC_WAVE_SIM_ON_INTERNAL";
|
||||
|
||||
static new class ShaderIDs
|
||||
{
|
||||
public static readonly int s_HorizontalDisplace = Shader.PropertyToID("_Crest_HorizontalDisplace");
|
||||
public static readonly int s_DisplaceClamp = Shader.PropertyToID("_Crest_DisplaceClamp");
|
||||
public static readonly int s_Damping = Shader.PropertyToID("_Crest_Damping");
|
||||
public static readonly int s_Gravity = Shader.PropertyToID("_Crest_Gravity");
|
||||
public static readonly int s_CourantNumber = Shader.PropertyToID("_Crest_CourantNumber");
|
||||
}
|
||||
|
||||
internal static readonly Color s_GizmoColor = new(0f, 1f, 0f, 0.5f);
|
||||
|
||||
internal override string ID => "DynamicWaves";
|
||||
internal override string Name => "Dynamic Waves";
|
||||
internal override Color GizmoColor => s_GizmoColor;
|
||||
private protected override Color ClearColor => Color.black;
|
||||
internal override bool RunsInHeadless => true;
|
||||
private protected override ComputeShader SimulationShader => WaterResources.Instance.Compute._UpdateDynamicWaves;
|
||||
private protected override GraphicsFormat RequestedTextureFormat => _TextureFormatMode switch
|
||||
{
|
||||
// Try and match Animated Waves format as we copy this simulation into it.
|
||||
LodTextureFormatMode.Automatic => Water == null ? GraphicsFormat.None : Water.AnimatedWavesLod.TextureFormatMode switch
|
||||
{
|
||||
LodTextureFormatMode.Precision => GraphicsFormat.R32G32_SFloat,
|
||||
_ => GraphicsFormat.R16G16_SFloat,
|
||||
},
|
||||
LodTextureFormatMode.Performance => GraphicsFormat.R16G16_SFloat,
|
||||
LodTextureFormatMode.Precision => GraphicsFormat.R32G32_SFloat,
|
||||
LodTextureFormatMode.Manual => _TextureFormat,
|
||||
_ => throw new System.NotImplementedException(),
|
||||
};
|
||||
|
||||
internal float TimeLeftToSimulate => _TimeToSimulate;
|
||||
|
||||
internal DynamicWavesLod()
|
||||
{
|
||||
_OverrideResolution = false;
|
||||
_Resolution = 512;
|
||||
_TextureFormatMode = LodTextureFormatMode.Automatic;
|
||||
_TextureFormat = GraphicsFormat.R16G16_SFloat;
|
||||
}
|
||||
|
||||
internal override void Enable()
|
||||
{
|
||||
base.Enable();
|
||||
|
||||
Shader.EnableKeyword(k_DynamicWavesKeyword);
|
||||
}
|
||||
|
||||
internal override void Disable()
|
||||
{
|
||||
base.Disable();
|
||||
|
||||
Shader.DisableKeyword(k_DynamicWavesKeyword);
|
||||
}
|
||||
|
||||
internal override void Bind<T>(T target)
|
||||
{
|
||||
base.Bind(target);
|
||||
target.SetFloat(ShaderIDs.s_HorizontalDisplace, Settings._HorizontalDisplace);
|
||||
target.SetFloat(ShaderIDs.s_DisplaceClamp, Settings._DisplaceClamp);
|
||||
}
|
||||
|
||||
private protected override void SetAdditionalSimulationParameters<T>(T simMaterial)
|
||||
{
|
||||
base.SetAdditionalSimulationParameters(simMaterial);
|
||||
|
||||
simMaterial.SetFloat(ShaderIDs.s_Damping, Settings._Damping);
|
||||
simMaterial.SetFloat(ShaderIDs.s_Gravity, _Water.Gravity * Settings._GravityMultiplier);
|
||||
simMaterial.SetFloat(ShaderIDs.s_CourantNumber, Settings._CourantNumber);
|
||||
simMaterial.SetFloat(AnimatedWavesLod.ShaderIDs.s_AttenuationInShallows, _AttenuationInShallows);
|
||||
}
|
||||
|
||||
private protected override void GetSubstepData(float timeToSimulate, out int substeps, out float delta)
|
||||
{
|
||||
substeps = Mathf.FloorToInt(timeToSimulate * _SimulationFrequency);
|
||||
delta = substeps > 0 ? (1f / _SimulationFrequency) : 0f;
|
||||
}
|
||||
|
||||
internal static readonly SortedList<int, ILodInput> s_Inputs = new(Helpers.DuplicateComparison);
|
||||
private protected override SortedList<int, ILodInput> Inputs => s_Inputs;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
static void OnLoad()
|
||||
{
|
||||
s_Inputs.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9fb0d3abd1aa34af8942b9bab09a8575
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,70 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
using WaveHarmonic.Crest.Utility;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Simulates horizontal motion of water.
|
||||
/// </summary>
|
||||
[FilterEnum(nameof(_TextureFormatMode), Filtered.Mode.Exclude, (int)LodTextureFormatMode.Automatic)]
|
||||
public sealed partial class FlowLod : Lod<IFlowProvider>
|
||||
{
|
||||
const string k_FlowKeyword = "CREST_FLOW_ON_INTERNAL";
|
||||
|
||||
internal static readonly Color s_GizmoColor = new(0f, 0f, 1f, 0.5f);
|
||||
|
||||
internal override string ID => "Flow";
|
||||
internal override Color GizmoColor => s_GizmoColor;
|
||||
private protected override Color ClearColor => Color.black;
|
||||
private protected override bool NeedToReadWriteTextureData => true;
|
||||
internal override bool RunsInHeadless => true;
|
||||
|
||||
private protected override GraphicsFormat RequestedTextureFormat => _TextureFormatMode switch
|
||||
{
|
||||
LodTextureFormatMode.Performance => GraphicsFormat.R16G16_SFloat,
|
||||
LodTextureFormatMode.Precision => GraphicsFormat.R32G32_SFloat,
|
||||
LodTextureFormatMode.Manual => _TextureFormat,
|
||||
_ => throw new System.NotImplementedException(),
|
||||
};
|
||||
|
||||
internal FlowLod()
|
||||
{
|
||||
_Resolution = 128;
|
||||
_TextureFormat = GraphicsFormat.R16G16_SFloat;
|
||||
}
|
||||
|
||||
internal override void Enable()
|
||||
{
|
||||
base.Enable();
|
||||
|
||||
Shader.EnableKeyword(k_FlowKeyword);
|
||||
}
|
||||
|
||||
internal override void Disable()
|
||||
{
|
||||
base.Disable();
|
||||
|
||||
Shader.DisableKeyword(k_FlowKeyword);
|
||||
}
|
||||
|
||||
private protected override IFlowProvider CreateProvider(bool enable)
|
||||
{
|
||||
Queryable?.CleanUp();
|
||||
// Flow is GPU only, and can only be queried using the compute path.
|
||||
return enable && Enabled ? new FlowQuery(_Water) : IFlowProvider.None;
|
||||
}
|
||||
|
||||
internal static readonly SortedList<int, ILodInput> s_Inputs = new(Helpers.DuplicateComparison);
|
||||
private protected override SortedList<int, ILodInput> Inputs => s_Inputs;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
static void OnLoad()
|
||||
{
|
||||
s_Inputs.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 37086036dea5b4927a5cdb3af2d5d869
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,95 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
using WaveHarmonic.Crest.Utility;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// A persistent foam simulation that moves around with a displacement LOD. The input is fully combined water surface shape.
|
||||
/// </summary>
|
||||
[FilterEnum(nameof(_TextureFormatMode), Filtered.Mode.Exclude, (int)LodTextureFormatMode.Automatic)]
|
||||
public sealed partial class FoamLod : PersistentLod
|
||||
{
|
||||
[Tooltip("Prewarms the simulation on load and teleports.\n\nResults are only an approximation.")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
bool _Prewarm = true;
|
||||
|
||||
[Tooltip("Settings for fine tuning this simulation.")]
|
||||
[@Embedded]
|
||||
[@GenerateAPI(Getter.Custom)]
|
||||
[SerializeField]
|
||||
FoamLodSettings _Settings;
|
||||
|
||||
static new class ShaderIDs
|
||||
{
|
||||
public static readonly int s_MinimumWavesSlice = Shader.PropertyToID("_Crest_MinimumWavesSlice");
|
||||
public static readonly int s_FoamMaximum = Shader.PropertyToID("_Crest_FoamMaximum");
|
||||
public static readonly int s_FoamFadeRate = Shader.PropertyToID("_Crest_FoamFadeRate");
|
||||
public static readonly int s_WaveFoamStrength = Shader.PropertyToID("_Crest_WaveFoamStrength");
|
||||
public static readonly int s_WaveFoamCoverage = Shader.PropertyToID("_Crest_WaveFoamCoverage");
|
||||
public static readonly int s_ShorelineFoamMaxDepth = Shader.PropertyToID("_Crest_ShorelineFoamMaxDepth");
|
||||
public static readonly int s_ShorelineFoamStrength = Shader.PropertyToID("_Crest_ShorelineFoamStrength");
|
||||
public static readonly int s_NeedsPrewarming = Shader.PropertyToID("_Crest_NeedsPrewarming");
|
||||
public static readonly int s_FoamNegativeDepthPriming = Shader.PropertyToID("_Crest_FoamNegativeDepthPriming");
|
||||
}
|
||||
|
||||
internal static readonly Color s_GizmoColor = new(1f, 1f, 1f, 0.5f);
|
||||
|
||||
internal override string ID => "Foam";
|
||||
internal override Color GizmoColor => s_GizmoColor;
|
||||
private protected override Color ClearColor => Color.black;
|
||||
private protected override ComputeShader SimulationShader => WaterResources.Instance.Compute._UpdateFoam;
|
||||
|
||||
private protected override GraphicsFormat RequestedTextureFormat => _TextureFormatMode switch
|
||||
{
|
||||
LodTextureFormatMode.Performance => GraphicsFormat.R16_SFloat,
|
||||
LodTextureFormatMode.Precision => GraphicsFormat.R32_SFloat,
|
||||
LodTextureFormatMode.Manual => _TextureFormat,
|
||||
_ => throw new System.NotImplementedException(),
|
||||
};
|
||||
|
||||
private protected override void SetAdditionalSimulationParameters<T>(T properties)
|
||||
{
|
||||
base.SetAdditionalSimulationParameters(properties);
|
||||
|
||||
// Prewarm simulation for first frame or teleporting. It will not be the same results as running the
|
||||
// simulation for multiple frames - but good enough.
|
||||
properties.SetBoolean(ShaderIDs.s_NeedsPrewarming, _Prewarm && _NeedsPrewarmingThisStep);
|
||||
properties.SetFloat(ShaderIDs.s_FoamFadeRate, Settings._FoamFadeRate);
|
||||
properties.SetFloat(ShaderIDs.s_WaveFoamStrength, Settings._WaveFoamStrength);
|
||||
properties.SetFloat(ShaderIDs.s_WaveFoamCoverage, Settings._WaveFoamCoverage);
|
||||
properties.SetFloat(ShaderIDs.s_ShorelineFoamMaxDepth, Settings._ShorelineFoamMaximumDepth);
|
||||
properties.SetFloat(ShaderIDs.s_ShorelineFoamStrength, Settings._ShorelineFoamStrength);
|
||||
properties.SetFloat(ShaderIDs.s_FoamMaximum, Settings.Maximum);
|
||||
properties.SetFloat(ShaderIDs.s_FoamNegativeDepthPriming, -Settings._ShorelineFoamPriming);
|
||||
properties.SetInteger(ShaderIDs.s_MinimumWavesSlice, Settings.FilterWaves);
|
||||
}
|
||||
|
||||
private protected override void GetSubstepData(float timeToSimulate, out int substeps, out float delta)
|
||||
{
|
||||
substeps = Mathf.FloorToInt(timeToSimulate * _SimulationFrequency);
|
||||
|
||||
delta = substeps > 0 ? (1f / _SimulationFrequency) : 0f;
|
||||
}
|
||||
|
||||
internal FoamLod()
|
||||
{
|
||||
_Enabled = true;
|
||||
_TextureFormat = GraphicsFormat.R16_SFloat;
|
||||
_SimulationFrequency = 30;
|
||||
}
|
||||
|
||||
internal static readonly SortedList<int, ILodInput> s_Inputs = new(Helpers.DuplicateComparison);
|
||||
private protected override SortedList<int, ILodInput> Inputs => s_Inputs;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
static void OnLoad()
|
||||
{
|
||||
s_Inputs.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 42f3809132d8b485a822f0bb271f51c5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1ea9618107d2946a98206ac4c7772029
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,66 @@
|
||||
// <auto-generated/>
|
||||
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using WaveHarmonic.Crest.Utility;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
partial class DynamicWavesLod
|
||||
{
|
||||
DynamicWavesLodSettings _DefaultSettings;
|
||||
|
||||
DynamicWavesLodSettings GetSettings()
|
||||
{
|
||||
if (_Settings != null)
|
||||
{
|
||||
return _Settings;
|
||||
}
|
||||
|
||||
if (_DefaultSettings == null)
|
||||
{
|
||||
_DefaultSettings = ScriptableObject.CreateInstance<DynamicWavesLodSettings>();
|
||||
_DefaultSettings.name = $"Default {Name} (instance)";
|
||||
_DefaultSettings.hideFlags = HideFlags.DontSave | HideFlags.NotEditable;
|
||||
}
|
||||
|
||||
return _DefaultSettings;
|
||||
}
|
||||
|
||||
internal override void Destroy()
|
||||
{
|
||||
base.Destroy();
|
||||
Helpers.Destroy(_DefaultSettings);
|
||||
}
|
||||
}
|
||||
|
||||
partial class FoamLod
|
||||
{
|
||||
FoamLodSettings _DefaultSettings;
|
||||
|
||||
FoamLodSettings GetSettings()
|
||||
{
|
||||
if (_Settings != null)
|
||||
{
|
||||
return _Settings;
|
||||
}
|
||||
|
||||
if (_DefaultSettings == null)
|
||||
{
|
||||
_DefaultSettings = ScriptableObject.CreateInstance<FoamLodSettings>();
|
||||
_DefaultSettings.name = $"Default {Name} (instance)";
|
||||
_DefaultSettings.hideFlags = HideFlags.DontSave | HideFlags.NotEditable;
|
||||
}
|
||||
|
||||
return _DefaultSettings;
|
||||
}
|
||||
|
||||
internal override void Destroy()
|
||||
{
|
||||
base.Destroy();
|
||||
Helpers.Destroy(_DefaultSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4e88db203d9474c568c462518871bb5a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 51ed544a28b134d58a41f55650e368b7
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,37 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers a custom input to the <see cref="AbsorptionLod"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Attach this to objects that you want to influence the scattering color.
|
||||
/// </remarks>
|
||||
[@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
|
||||
internal override LodInputMode DefaultMode => LodInputMode.Renderer;
|
||||
#endif
|
||||
|
||||
internal override void InferBlend()
|
||||
{
|
||||
base.InferBlend();
|
||||
_Blend = LodInputBlend.Alpha;
|
||||
}
|
||||
|
||||
// Looks fine moving around.
|
||||
private protected override bool FollowHorizontalMotion => true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 247a7260095bc49429a7f7cb2a4851d5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers a custom input to the <see cref="AlbedoLod"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Attach this to objects that you want to influence the surface color.
|
||||
/// </remarks>
|
||||
[@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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c972e8f11f2447768dc0314b7bc34a4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,139 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers a custom input to the <see cref="AnimatedWavesLod"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Attach this to objects that you want to render into the displacment textures to
|
||||
/// affect the water shape.
|
||||
/// </remarks>
|
||||
[@HelpURL("Manual/Waves.html#animated-waves-inputs")]
|
||||
public sealed partial class AnimatedWavesLodInput : LodInput
|
||||
{
|
||||
[@Space(10)]
|
||||
|
||||
[Tooltip("When to render the input into the displacement data.")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
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))]
|
||||
[@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))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
float _OctaveWavelength = 512f;
|
||||
|
||||
|
||||
[Header("Culling")]
|
||||
|
||||
[Tooltip("Inform the system how much this input will displace the water surface vertically.\n\nThis is used to set bounding box heights for the water chunks.")]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
float _MaximumDisplacementVertical = 0f;
|
||||
|
||||
[Tooltip("Inform the system how much this input will displace the water surface horizontally.\n\nThis is used to set bounding box widths for the water chunks.")]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
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))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
bool _ReportRendererBounds = false;
|
||||
|
||||
|
||||
internal override LodInputMode DefaultMode => LodInputMode.Renderer;
|
||||
internal override int Pass => (int)_DisplacementPass;
|
||||
|
||||
internal AnimatedWavesLodInput() : base()
|
||||
{
|
||||
_FollowHorizontalWaveMotion = true;
|
||||
}
|
||||
|
||||
internal override float Filter(WaterRenderer water, int slice)
|
||||
{
|
||||
return AnimatedWavesLod.FilterByWavelength(water, slice, _FilterByWavelength ? _OctaveWavelength : 0f);
|
||||
}
|
||||
|
||||
private protected override void OnUpdate(WaterRenderer water)
|
||||
{
|
||||
base.OnUpdate(water);
|
||||
|
||||
if (!Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var maxDispVert = _MaximumDisplacementVertical;
|
||||
|
||||
// let water system know how far from the sea level this shape may displace the surface
|
||||
if (_ReportRendererBounds)
|
||||
{
|
||||
var range = Data.HeightRange;
|
||||
var minY = range.x;
|
||||
var maxY = range.y;
|
||||
var seaLevel = water.SeaLevel;
|
||||
maxDispVert = Mathf.Max(maxDispVert, Mathf.Abs(seaLevel - minY), Mathf.Abs(seaLevel - maxY));
|
||||
}
|
||||
|
||||
if (_MaximumDisplacementHorizontal > 0f || maxDispVert > 0f)
|
||||
{
|
||||
water.ReportMaximumDisplacement(_MaximumDisplacementHorizontal, maxDispVert, 0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial class AnimatedWavesLodInput : ISerializationCallbackReceiver
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 1;
|
||||
#pragma warning restore 414
|
||||
|
||||
[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.")]
|
||||
[@GenerateAPI(Setter.Custom)]
|
||||
[@DecoratedField, SerializeField]
|
||||
[HideInInspector]
|
||||
bool _RenderPostCombine = true;
|
||||
|
||||
void SetRenderPostCombine(bool previous, bool current, bool force = false)
|
||||
{
|
||||
if (previous == current && !force) return;
|
||||
_DisplacementPass = current ? DisplacementPass.LodIndependent : DisplacementPass.LodDependent;
|
||||
}
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
|
||||
/// <inheritdoc/>
|
||||
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
||||
{
|
||||
if (_Version < 1)
|
||||
{
|
||||
SetRenderPostCombine(_RenderPostCombine, _RenderPostCombine, force: true);
|
||||
_Version = 1;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
/// <inheritdoc/>
|
||||
void ISerializationCallbackReceiver.OnBeforeSerialize()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3d64d1f50f3b548bcbe279507f0e78da
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,156 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers a custom input to the <see cref="ClipLod"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Attach this to objects that you want to use to clip the surface of the water.
|
||||
/// </remarks>
|
||||
[@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)]
|
||||
[@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)]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
bool _Inverted;
|
||||
|
||||
[@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))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
bool _WaterHeightDistanceCulling = false;
|
||||
|
||||
internal override LodInputMode DefaultMode => LodInputMode.Primitive;
|
||||
// The clip surface samples at the displaced position in the water shader, so the displacement correction is not needed.
|
||||
private protected override bool FollowHorizontalMotion => true;
|
||||
|
||||
readonly SampleCollisionHelper _SampleHeightHelper = new();
|
||||
|
||||
ComputeShader PrimitiveShader => WaterResources.Instance.Compute._ClipPrimitive;
|
||||
static LocalKeyword KeywordInverted => WaterResources.Instance.Keywords.ClipPrimitiveInverted;
|
||||
static LocalKeyword KeywordSphere => WaterResources.Instance.Keywords.ClipPrimitiveSphere;
|
||||
static LocalKeyword KeywordCube => WaterResources.Instance.Keywords.ClipPrimitiveCube;
|
||||
static LocalKeyword KeywordRectangle => WaterResources.Instance.Keywords.ClipPrimitiveRectangle;
|
||||
|
||||
bool _Enabled = true;
|
||||
Rect _Rect;
|
||||
|
||||
internal override void InferBlend()
|
||||
{
|
||||
base.InferBlend();
|
||||
_Blend = LodInputBlend.Maximum;
|
||||
}
|
||||
|
||||
internal override bool Enabled => _Enabled && Mode switch
|
||||
{
|
||||
LodInputMode.Primitive => enabled && PrimitiveShader != null,
|
||||
_ => base.Enabled,
|
||||
};
|
||||
|
||||
internal override Rect Rect
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Mode == LodInputMode.Primitive)
|
||||
{
|
||||
if (_RecalculateBounds)
|
||||
{
|
||||
// This mode has full transform support so need to get rect from bounds.
|
||||
_Rect = transform.Bounds().RectXZ();
|
||||
_RecalculateBounds = false;
|
||||
}
|
||||
|
||||
return _Rect;
|
||||
}
|
||||
|
||||
return base.Rect;
|
||||
}
|
||||
}
|
||||
|
||||
internal override void Draw(Lod simulation, CommandBuffer buffer, RenderTargetIdentifier target, int pass = -1, float weight = 1f, int slices = -1)
|
||||
{
|
||||
if (Mode == LodInputMode.Primitive)
|
||||
{
|
||||
var wrapper = new PropertyWrapperCompute(buffer, PrimitiveShader, 0);
|
||||
|
||||
wrapper.SetMatrix(Crest.ShaderIDs.s_Matrix, transform.worldToLocalMatrix);
|
||||
|
||||
// For culling.
|
||||
wrapper.SetVector(Crest.ShaderIDs.s_Position, transform.position);
|
||||
wrapper.SetFloat(Crest.ShaderIDs.s_Diameter, transform.lossyScale.Maximum());
|
||||
|
||||
wrapper.SetKeyword(KeywordInverted, _Inverted);
|
||||
wrapper.SetKeyword(KeywordSphere, _Primitive == LodInputPrimitive.Sphere);
|
||||
wrapper.SetKeyword(KeywordCube, _Primitive == LodInputPrimitive.Cube);
|
||||
wrapper.SetKeyword(KeywordRectangle, _Primitive == LodInputPrimitive.Quad);
|
||||
|
||||
wrapper.SetTexture(Crest.ShaderIDs.s_Target, target);
|
||||
|
||||
var threads = simulation.Resolution / Lod.k_ThreadGroupSize;
|
||||
wrapper.Dispatch(threads, threads, slices);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.Draw(simulation, buffer, target, pass, weight, slices);
|
||||
}
|
||||
}
|
||||
|
||||
private protected override void OnUpdate(WaterRenderer water)
|
||||
{
|
||||
base.OnUpdate(water);
|
||||
|
||||
if (Mode != LodInputMode.Renderer)
|
||||
{
|
||||
_Enabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!base.Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Data is not RendererLodInputData data || data._Renderer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevents possible conflicts since overlapping doesn't work for every case for convex hull.
|
||||
if (_WaterHeightDistanceCulling)
|
||||
{
|
||||
var position = transform.position;
|
||||
|
||||
if (_SampleHeightHelper.SampleHeight(position, out var waterHeight))
|
||||
{
|
||||
position.y = waterHeight;
|
||||
_Enabled = Mathf.Abs(data._Renderer.bounds.ClosestPoint(position).y - waterHeight) < 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26623fd0e291a478a9f8b68a3df7e8a6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,72 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers a custom input to the <see cref="DepthLod"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Attach this to objects that you want to use to add water depth.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Renders depth every frame and should only be used for dynamic objects. For
|
||||
/// static objects, use a <see cref="DepthProbe"/>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[@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")]
|
||||
[Tooltip("Whether the data is relative to the input height.\n\nUseful for procedural placement.")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal bool _Relative;
|
||||
|
||||
[@Label("Copy Signed Distance Field")]
|
||||
[Tooltip("Whether to copy the signed distance field.")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal bool _CopySignedDistanceField;
|
||||
|
||||
internal static new class ShaderIDs
|
||||
{
|
||||
public static readonly int s_HeightOffset = Shader.PropertyToID("_Crest_HeightOffset");
|
||||
public static readonly int s_SDF = Shader.PropertyToID("_Crest_SDF");
|
||||
}
|
||||
|
||||
internal override LodInputMode DefaultMode => LodInputMode.Geometry;
|
||||
|
||||
internal override void InferBlend()
|
||||
{
|
||||
base.InferBlend();
|
||||
_Blend = LodInputBlend.Maximum;
|
||||
}
|
||||
|
||||
internal override void Draw(Lod simulation, CommandBuffer buffer, RenderTargetIdentifier target, int pass = -1, float weight = 1f, int slice = -1)
|
||||
{
|
||||
var wrapper = new PropertyWrapperBuffer(buffer);
|
||||
|
||||
wrapper.SetFloat(ShaderIDs.s_HeightOffset, _Relative ? transform.position.y : 0f);
|
||||
|
||||
if (IsCompute)
|
||||
{
|
||||
wrapper.SetInteger(ShaderIDs.s_SDF, _CopySignedDistanceField ? 1 : 0);
|
||||
buffer.SetKeyword(WaterResources.Instance.Compute._DepthTexture, WaterResources.Instance.Keywords.DepthTextureSDF, simulation._Water._DepthLod._EnableSignedDistanceFields);
|
||||
}
|
||||
|
||||
base.Draw(simulation, buffer, target, pass, weight, slice);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d13f0b872e9454b5daa77afa95b12943
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70deb49b538e247b9ab1bf7773d16809
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 300
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,25 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers a custom input to the <see cref="DynamicWavesLod"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Attach this to objects that you want to influence the simulation, such as
|
||||
/// ripples etc.
|
||||
/// </remarks>
|
||||
[@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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 82638e366ec3c4f5587d1ff63e01d8b2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers a custom input to the <see cref="FlowLod"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Attach this to objects that you want to influence the horizontal flow of the
|
||||
/// water volume.
|
||||
/// </remarks>
|
||||
[@HelpURL("Manual/TidesAndCurrents.html#flow-inputs")]
|
||||
[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;
|
||||
|
||||
#if d_CrestPaint
|
||||
internal override LodInputMode DefaultMode => LodInputMode.Paint;
|
||||
#else
|
||||
internal override LodInputMode DefaultMode => LodInputMode.Renderer;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a0cdc7659b2c244dbbb625ccd8ae65ae
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,40 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers a custom input to the <see cref="FoamLod"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Attach this to objects that you want to influence the foam simulation, such as
|
||||
/// depositing foam on the surface.
|
||||
/// </remarks>
|
||||
[@HelpURL("Manual/WaterAppearance.html#foam-inputs")]
|
||||
[@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
|
||||
internal override LodInputMode DefaultMode => LodInputMode.Renderer;
|
||||
#endif
|
||||
|
||||
internal override void InferBlend()
|
||||
{
|
||||
base.InferBlend();
|
||||
|
||||
if (_Mode is LodInputMode.Paint)
|
||||
{
|
||||
_Blend = LodInputBlend.Maximum;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 67bc7f0b9724d44ab9f0ddf0b1b5a773
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 20184ba74fed44301b1efff21519c308
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,266 @@
|
||||
// <auto-generated/>
|
||||
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using WaveHarmonic.Crest.Utility;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
[AddComponentMenu(Constants.k_MenuPrefixInputs + "Absorption Input")]
|
||||
[@FilterEnum(nameof(_Mode), Filtered.Mode.Include, (int)LodInputMode.Renderer, (int)LodInputMode.Texture, (int)LodInputMode.Spline, (int)LodInputMode.Paint)]
|
||||
partial class AbsorptionLodInput
|
||||
{
|
||||
internal override Color GizmoColor => AbsorptionLod.s_GizmoColor;
|
||||
private protected override SortedList<int, ILodInput> Inputs => AbsorptionLod.s_Inputs;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[ForLodInput(typeof(AbsorptionLodInput), LodInputMode.Texture)]
|
||||
[System.Serializable]
|
||||
public sealed partial class AbsorptionTextureLodInputData : TextureLodInputData
|
||||
{
|
||||
private protected override ComputeShader TextureShader => WaterResources.Instance.Compute._AbsorptionTexture;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[ForLodInput(typeof(AbsorptionLodInput), LodInputMode.Renderer)]
|
||||
[System.Serializable]
|
||||
public sealed partial class AbsorptionRendererLodInputData : RendererLodInputData
|
||||
{
|
||||
internal override string ShaderPrefix => "Crest/Inputs/Absorption";
|
||||
}
|
||||
|
||||
[AddComponentMenu(Constants.k_MenuPrefixInputs + "Albedo Input")]
|
||||
[@FilterEnum(nameof(_Mode), Filtered.Mode.Include, (int)LodInputMode.Renderer)]
|
||||
partial class AlbedoLodInput
|
||||
{
|
||||
internal override Color GizmoColor => AlbedoLod.s_GizmoColor;
|
||||
private protected override SortedList<int, ILodInput> Inputs => AlbedoLod.s_Inputs;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[ForLodInput(typeof(AlbedoLodInput), LodInputMode.Renderer)]
|
||||
[System.Serializable]
|
||||
public sealed partial class AlbedoRendererLodInputData : RendererLodInputData
|
||||
{
|
||||
internal override string ShaderPrefix => "Crest/Inputs/Albedo";
|
||||
}
|
||||
|
||||
[AddComponentMenu(Constants.k_MenuPrefixInputs + "Animated Waves Input")]
|
||||
[@FilterEnum(nameof(_Mode), Filtered.Mode.Include, (int)LodInputMode.Renderer)]
|
||||
partial class AnimatedWavesLodInput
|
||||
{
|
||||
internal override Color GizmoColor => AnimatedWavesLod.s_GizmoColor;
|
||||
private protected override SortedList<int, ILodInput> Inputs => AnimatedWavesLod.s_Inputs;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[ForLodInput(typeof(AnimatedWavesLodInput), LodInputMode.Renderer)]
|
||||
[System.Serializable]
|
||||
public sealed partial class AnimatedWavesRendererLodInputData : RendererLodInputData
|
||||
{
|
||||
internal override string ShaderPrefix => "Crest/Inputs/Animated Waves";
|
||||
}
|
||||
|
||||
[AddComponentMenu(Constants.k_MenuPrefixInputs + "Clip Input")]
|
||||
[@FilterEnum(nameof(_Mode), Filtered.Mode.Include, (int)LodInputMode.Renderer, (int)LodInputMode.Texture, (int)LodInputMode.Paint, (int)LodInputMode.Primitive)]
|
||||
partial class ClipLodInput
|
||||
{
|
||||
internal override Color GizmoColor => ClipLod.s_GizmoColor;
|
||||
private protected override SortedList<int, ILodInput> Inputs => ClipLod.s_Inputs;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[ForLodInput(typeof(ClipLodInput), LodInputMode.Texture)]
|
||||
[System.Serializable]
|
||||
public sealed partial class ClipTextureLodInputData : TextureLodInputData
|
||||
{
|
||||
private protected override ComputeShader TextureShader => WaterResources.Instance.Compute._ClipTexture;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[ForLodInput(typeof(ClipLodInput), LodInputMode.Renderer)]
|
||||
[System.Serializable]
|
||||
public sealed partial class ClipRendererLodInputData : RendererLodInputData
|
||||
{
|
||||
internal override string ShaderPrefix => "Crest/Inputs/Clip";
|
||||
}
|
||||
|
||||
[AddComponentMenu(Constants.k_MenuPrefixInputs + "Water Depth Input")]
|
||||
[@FilterEnum(nameof(_Mode), Filtered.Mode.Include, (int)LodInputMode.Renderer, (int)LodInputMode.Texture, (int)LodInputMode.Geometry)]
|
||||
partial class DepthLodInput
|
||||
{
|
||||
internal override Color GizmoColor => DepthLod.s_GizmoColor;
|
||||
private protected override SortedList<int, ILodInput> Inputs => DepthLod.s_Inputs;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[ForLodInput(typeof(DepthLodInput), LodInputMode.Texture)]
|
||||
[System.Serializable]
|
||||
public sealed partial class DepthTextureLodInputData : TextureLodInputData
|
||||
{
|
||||
private protected override ComputeShader TextureShader => WaterResources.Instance.Compute._DepthTexture;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[ForLodInput(typeof(DepthLodInput), LodInputMode.Renderer)]
|
||||
[System.Serializable]
|
||||
public sealed partial class DepthRendererLodInputData : RendererLodInputData
|
||||
{
|
||||
internal override string ShaderPrefix => "Crest/Inputs/Depth";
|
||||
}
|
||||
|
||||
[AddComponentMenu(Constants.k_MenuPrefixInputs + "Dynamic Waves Input")]
|
||||
[@FilterEnum(nameof(_Mode), Filtered.Mode.Include, (int)LodInputMode.Renderer)]
|
||||
partial class DynamicWavesLodInput
|
||||
{
|
||||
internal override Color GizmoColor => DynamicWavesLod.s_GizmoColor;
|
||||
private protected override SortedList<int, ILodInput> Inputs => DynamicWavesLod.s_Inputs;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[ForLodInput(typeof(DynamicWavesLodInput), LodInputMode.Renderer)]
|
||||
[System.Serializable]
|
||||
public sealed partial class DynamicWavesRendererLodInputData : RendererLodInputData
|
||||
{
|
||||
internal override string ShaderPrefix => "Crest/Inputs/Dynamic Waves";
|
||||
}
|
||||
|
||||
[AddComponentMenu(Constants.k_MenuPrefixInputs + "Flow Input")]
|
||||
[@FilterEnum(nameof(_Mode), Filtered.Mode.Include, (int)LodInputMode.Renderer, (int)LodInputMode.Texture, (int)LodInputMode.Paint, (int)LodInputMode.Spline)]
|
||||
partial class FlowLodInput
|
||||
{
|
||||
internal override Color GizmoColor => FlowLod.s_GizmoColor;
|
||||
private protected override SortedList<int, ILodInput> Inputs => FlowLod.s_Inputs;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[ForLodInput(typeof(FlowLodInput), LodInputMode.Texture)]
|
||||
[System.Serializable]
|
||||
public sealed partial class FlowTextureLodInputData : DirectionalTextureLodInputData
|
||||
{
|
||||
private protected override ComputeShader TextureShader => WaterResources.Instance.Compute._FlowTexture;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[ForLodInput(typeof(FlowLodInput), LodInputMode.Renderer)]
|
||||
[System.Serializable]
|
||||
public sealed partial class FlowRendererLodInputData : RendererLodInputData
|
||||
{
|
||||
internal override string ShaderPrefix => "Crest/Inputs/Flow";
|
||||
}
|
||||
|
||||
[AddComponentMenu(Constants.k_MenuPrefixInputs + "Foam Input")]
|
||||
[@FilterEnum(nameof(_Mode), Filtered.Mode.Include, (int)LodInputMode.Renderer, (int)LodInputMode.Texture, (int)LodInputMode.Paint, (int)LodInputMode.Spline)]
|
||||
partial class FoamLodInput
|
||||
{
|
||||
internal override Color GizmoColor => FoamLod.s_GizmoColor;
|
||||
private protected override SortedList<int, ILodInput> Inputs => FoamLod.s_Inputs;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[ForLodInput(typeof(FoamLodInput), LodInputMode.Texture)]
|
||||
[System.Serializable]
|
||||
public sealed partial class FoamTextureLodInputData : TextureLodInputData
|
||||
{
|
||||
private protected override ComputeShader TextureShader => WaterResources.Instance.Compute._FoamTexture;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[ForLodInput(typeof(FoamLodInput), LodInputMode.Renderer)]
|
||||
[System.Serializable]
|
||||
public sealed partial class FoamRendererLodInputData : RendererLodInputData
|
||||
{
|
||||
internal override string ShaderPrefix => "Crest/Inputs/Foam";
|
||||
}
|
||||
|
||||
[AddComponentMenu(Constants.k_MenuPrefixInputs + "Water Level Input")]
|
||||
[@FilterEnum(nameof(_Mode), Filtered.Mode.Include, (int)LodInputMode.Renderer, (int)LodInputMode.Texture, (int)LodInputMode.Paint, (int)LodInputMode.Spline, (int)LodInputMode.Geometry)]
|
||||
partial class LevelLodInput
|
||||
{
|
||||
internal override Color GizmoColor => LevelLod.s_GizmoColor;
|
||||
private protected override SortedList<int, ILodInput> Inputs => LevelLod.s_Inputs;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[ForLodInput(typeof(LevelLodInput), LodInputMode.Texture)]
|
||||
[System.Serializable]
|
||||
public sealed partial class LevelTextureLodInputData : TextureLodInputData
|
||||
{
|
||||
private protected override ComputeShader TextureShader => WaterResources.Instance.Compute._LevelTexture;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[ForLodInput(typeof(LevelLodInput), LodInputMode.Renderer)]
|
||||
[System.Serializable]
|
||||
public sealed partial class LevelRendererLodInputData : RendererLodInputData
|
||||
{
|
||||
internal override string ShaderPrefix => "Crest/Inputs/Level";
|
||||
}
|
||||
|
||||
[AddComponentMenu(Constants.k_MenuPrefixInputs + "Scattering Input")]
|
||||
[@FilterEnum(nameof(_Mode), Filtered.Mode.Include, (int)LodInputMode.Renderer, (int)LodInputMode.Texture, (int)LodInputMode.Spline, (int)LodInputMode.Paint)]
|
||||
partial class ScatteringLodInput
|
||||
{
|
||||
internal override Color GizmoColor => ScatteringLod.s_GizmoColor;
|
||||
private protected override SortedList<int, ILodInput> Inputs => ScatteringLod.s_Inputs;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[ForLodInput(typeof(ScatteringLodInput), LodInputMode.Texture)]
|
||||
[System.Serializable]
|
||||
public sealed partial class ScatteringTextureLodInputData : TextureLodInputData
|
||||
{
|
||||
private protected override ComputeShader TextureShader => WaterResources.Instance.Compute._ScatteringTexture;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[ForLodInput(typeof(ScatteringLodInput), LodInputMode.Renderer)]
|
||||
[System.Serializable]
|
||||
public sealed partial class ScatteringRendererLodInputData : RendererLodInputData
|
||||
{
|
||||
internal override string ShaderPrefix => "Crest/Inputs/Scattering";
|
||||
}
|
||||
|
||||
[AddComponentMenu(Constants.k_MenuPrefixInputs + "Shadow Input")]
|
||||
[@FilterEnum(nameof(_Mode), Filtered.Mode.Include, (int)LodInputMode.Renderer)]
|
||||
partial class ShadowLodInput
|
||||
{
|
||||
internal override Color GizmoColor => ShadowLod.s_GizmoColor;
|
||||
private protected override SortedList<int, ILodInput> Inputs => ShadowLod.s_Inputs;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[ForLodInput(typeof(ShadowLodInput), LodInputMode.Renderer)]
|
||||
[System.Serializable]
|
||||
public sealed partial class ShadowRendererLodInputData : RendererLodInputData
|
||||
{
|
||||
internal override string ShaderPrefix => "Crest/Inputs/Shadow";
|
||||
}
|
||||
|
||||
[AddComponentMenu(Constants.k_MenuPrefixInputs + "Shape Waves Input")]
|
||||
[@FilterEnum(nameof(_Mode), Filtered.Mode.Include, (int)LodInputMode.Renderer, (int)LodInputMode.Texture, (int)LodInputMode.Paint, (int)LodInputMode.Spline, (int)LodInputMode.Global)]
|
||||
partial class ShapeWaves
|
||||
{
|
||||
internal override Color GizmoColor => AnimatedWavesLod.s_GizmoColor;
|
||||
private protected override SortedList<int, ILodInput> Inputs => AnimatedWavesLod.s_Inputs;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[ForLodInput(typeof(ShapeWaves), LodInputMode.Texture)]
|
||||
[System.Serializable]
|
||||
public sealed partial class ShapeWavesTextureLodInputData : DirectionalTextureLodInputData
|
||||
{
|
||||
private protected override ComputeShader TextureShader => WaterResources.Instance.Compute._ShapeWavesTransfer;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[ForLodInput(typeof(ShapeWaves), LodInputMode.Renderer)]
|
||||
[System.Serializable]
|
||||
public sealed partial class ShapeWavesRendererLodInputData : RendererLodInputData
|
||||
{
|
||||
internal override string ShaderPrefix => "Crest/Inputs/Shape Waves";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 47424a406f7a145f8a8d6c207b7afc60
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,111 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Data storage for for the Geometry input mode.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public abstract partial class GeometryLodInputData : LodInputData
|
||||
{
|
||||
[Tooltip("Geometry to render into the simulation.")]
|
||||
[@GenerateAPI(Setter.Custom)]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal Mesh _Geometry;
|
||||
|
||||
private protected abstract Shader GeometryShader { get; }
|
||||
|
||||
internal override bool IsEnabled => _Geometry != null;
|
||||
|
||||
Material _Material;
|
||||
|
||||
internal override void Draw(Lod lod, Component component, CommandBuffer buffer, RenderTargetIdentifier target, int slices)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
// Weird things happen when hitting save if this is not set here. Hitting save will
|
||||
// still flicker the input, but without this it nothing renders.
|
||||
if (!Application.isPlaying) LodInput.SetBlendFromPreset(_Material, _Input.Blend);
|
||||
#endif
|
||||
buffer.DrawMesh(_Geometry, component.transform.localToWorldMatrix, _Material);
|
||||
}
|
||||
|
||||
internal override void OnEnable()
|
||||
{
|
||||
if (_Material == null)
|
||||
{
|
||||
_Material = new Material(GeometryShader);
|
||||
}
|
||||
|
||||
LodInput.SetBlendFromPreset(_Material, _Input.Blend);
|
||||
}
|
||||
|
||||
internal override void OnDisable()
|
||||
{
|
||||
// Empty.
|
||||
}
|
||||
|
||||
internal override void RecalculateBounds()
|
||||
{
|
||||
_Bounds = _Input.transform.TransformBounds(_Geometry.bounds);
|
||||
}
|
||||
|
||||
internal override void RecalculateRect()
|
||||
{
|
||||
_Rect = Bounds.RectXZ();
|
||||
}
|
||||
|
||||
void SetGeometry(Mesh previous, Mesh current)
|
||||
{
|
||||
if (previous == current) return;
|
||||
RecalculateCulling();
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
internal override void OnChange(string propertyPath, object previousValue)
|
||||
{
|
||||
if (_Input == null || !_Input.isActiveAndEnabled) return;
|
||||
|
||||
switch (propertyPath)
|
||||
{
|
||||
case nameof(_Geometry):
|
||||
SetGeometry((Mesh)previousValue, _Geometry);
|
||||
break;
|
||||
case "../" + nameof(LodInput._Blend):
|
||||
LodInput.SetBlendFromPreset(_Material, _Input.Blend);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
internal override bool InferMode(Component component, ref LodInputMode mode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
internal override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
|
||||
_Geometry = Helpers.PlaneMesh;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[ForLodInput(typeof(LevelLodInput), LodInputMode.Geometry)]
|
||||
public sealed class LevelGeometryLodInputData : GeometryLodInputData
|
||||
{
|
||||
private protected override Shader GeometryShader => WaterResources.Instance.Shaders._LevelGeometry;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[ForLodInput(typeof(DepthLodInput), LodInputMode.Geometry)]
|
||||
public sealed class DepthGeometryLodInputData : GeometryLodInputData
|
||||
{
|
||||
private protected override Shader GeometryShader => WaterResources.Instance.Shaders._DepthGeometry;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 695da60ea6ccf44ea988e843144969cb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,4 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
// Intentionally empty.
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cc9697a0accb44ec7a0f693182937c94
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,132 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers a custom input to the <see cref="LevelLod"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Attach this to objects that you want to influence the water height.
|
||||
/// </remarks>
|
||||
[@HelpURL("Manual/WaterBodies.html#water-bodies")]
|
||||
[@FilterEnum(nameof(_Blend), Filtered.Mode.Include, (int)LodInputBlend.Off, (int)LodInputBlend.Additive, (int)LodInputBlend.Minimum, (int)LodInputBlend.Maximum)]
|
||||
public sealed partial class LevelLodInput : LodInput
|
||||
{
|
||||
[@Heading("Water Chunk Culling")]
|
||||
|
||||
[Tooltip("Whether to use the manual \"Height Range\" for water chunk culling.\n\nMandatory for non mesh inputs like \"Texture\".")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
bool _OverrideHeight;
|
||||
|
||||
[Tooltip("The minimum and maximum height value to report for water chunk culling.")]
|
||||
[@Predicated(nameof(_OverrideHeight))]
|
||||
[@Range(-100, 100, Range.Clamp.None)]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
Vector2 _HeightRange = new(-100, 100);
|
||||
|
||||
LevelLodInput()
|
||||
{
|
||||
_FollowHorizontalWaveMotion = true;
|
||||
}
|
||||
|
||||
// Water level is packed into alpha using the displaced position.
|
||||
private protected override bool FollowHorizontalMotion => true;
|
||||
internal override LodInputMode DefaultMode => LodInputMode.Geometry;
|
||||
|
||||
internal Rect _Rect;
|
||||
|
||||
internal override void InferBlend()
|
||||
{
|
||||
base.InferBlend();
|
||||
|
||||
_Blend = LodInputBlend.Off;
|
||||
|
||||
if (_Mode is LodInputMode.Paint or LodInputMode.Texture)
|
||||
{
|
||||
_Blend = LodInputBlend.Additive;
|
||||
}
|
||||
}
|
||||
|
||||
private protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_Reporter ??= new(this);
|
||||
WaterChunkRenderer.HeightReporters.Add(_Reporter);
|
||||
}
|
||||
|
||||
private protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
WaterChunkRenderer.HeightReporters.Remove(_Reporter);
|
||||
}
|
||||
|
||||
bool ReportHeight(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 range = _OverrideHeight ? _HeightRange : Data.HeightRange;
|
||||
minimum = range.x;
|
||||
maximum = range.y;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
partial class LevelLodInput
|
||||
{
|
||||
Reporter _Reporter;
|
||||
|
||||
sealed class Reporter : IReportsHeight
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
partial class LevelLodInput : ISerializationCallbackReceiver
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 1;
|
||||
#pragma warning restore 414
|
||||
|
||||
/// <inheritdoc/>
|
||||
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
||||
{
|
||||
// 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.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a4edd034314af4679ba066d79580dc1d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,485 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
interface ILodInput
|
||||
{
|
||||
const int k_QueueMaximumSubIndex = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// Draw the input (the render target will be bound)
|
||||
/// </summary>
|
||||
public void Draw(Lod simulation, CommandBuffer buffer, RenderTargetIdentifier target, int pass = -1, float weight = 1f, int slice = -1);
|
||||
|
||||
float Filter(WaterRenderer water, int slice);
|
||||
|
||||
/// <summary>
|
||||
/// Whether to apply this input.
|
||||
/// </summary>
|
||||
bool Enabled { get; }
|
||||
|
||||
bool IsCompute { get; }
|
||||
|
||||
int Queue { get; }
|
||||
|
||||
int Pass { get; }
|
||||
|
||||
Rect Rect { get; }
|
||||
|
||||
MonoBehaviour Component { get; }
|
||||
|
||||
// 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);
|
||||
|
||||
internal static void Attach(ILodInput input, Utility.SortedList<int, ILodInput> inputs)
|
||||
{
|
||||
inputs.Remove(input);
|
||||
inputs.Add(input.Order, input);
|
||||
}
|
||||
|
||||
internal static void Detach(ILodInput input, Utility.SortedList<int, ILodInput> inputs)
|
||||
{
|
||||
inputs.Remove(input);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for scripts that register inputs to the various LOD data types.
|
||||
/// </summary>
|
||||
[@ExecuteDuringEditMode]
|
||||
[@HelpURL("Manual/WaterInputs.html")]
|
||||
public abstract partial class LodInput : ManagedBehaviour<WaterRenderer>
|
||||
{
|
||||
[Tooltip("The mode for this input.\n\nSee the manual for more details about input modes. Use AddComponent(LodInputMode) to set the mode via scripting. The mode cannot be changed after creation.")]
|
||||
[@Filtered((int)LodInputMode.Unset)]
|
||||
[@GenerateAPI(Setter.None)]
|
||||
[SerializeField]
|
||||
internal LodInputMode _Mode = LodInputMode.Unset;
|
||||
|
||||
// NOTE:
|
||||
// Weight and Feather do not support Depth and Clip as they do not make much sense.
|
||||
// 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)]
|
||||
[@Range(0f, 1f)]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
float _Weight = 1f;
|
||||
|
||||
[Tooltip("The order this input will render.\n\nOrder is Queue plus SiblingIndex")]
|
||||
[@GenerateAPI(Setter.Custom)]
|
||||
[@DecoratedField, SerializeField]
|
||||
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))]
|
||||
[@Filtered]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
internal LodInputBlend _Blend = LodInputBlend.Additive;
|
||||
|
||||
[@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))]
|
||||
[@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))]
|
||||
[@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)]
|
||||
[@Stripped]
|
||||
[SerializeReference]
|
||||
internal LodInputData _Data;
|
||||
|
||||
// Need always visble for space to appear before foldout instead of inside.
|
||||
[@Space(10, isAlwaysVisible: true)]
|
||||
|
||||
[@Group("Debug", order = k_DebugGroupOrder)]
|
||||
|
||||
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Global))]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal bool _DrawBounds;
|
||||
|
||||
internal const int k_DebugGroupOrder = 10;
|
||||
|
||||
internal static class ShaderIDs
|
||||
{
|
||||
public static int s_Weight = Shader.PropertyToID("_Crest_Weight");
|
||||
public static int s_DisplacementAtInputPosition = Shader.PropertyToID("_Crest_DisplacementAtInputPosition");
|
||||
public static readonly int s_BlendSource = Shader.PropertyToID("_Crest_BlendSource");
|
||||
public static readonly int s_BlendTarget = Shader.PropertyToID("_Crest_BlendTarget");
|
||||
public static readonly int s_BlendOperation = Shader.PropertyToID("_Crest_BlendOperation");
|
||||
}
|
||||
|
||||
|
||||
internal abstract Color GizmoColor { get; }
|
||||
internal abstract LodInputMode DefaultMode { get; }
|
||||
private protected abstract Utility.SortedList<int, ILodInput> Inputs { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Disables rendering of input into data, but continues most scripting activities.
|
||||
/// </summary>
|
||||
public bool ForceRenderingOff { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Properties specific to <see cref="Mode"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// You will need to cast to a more specific type to change
|
||||
/// certain properties. Types derive from and end with <see cref="LodInputData"/>.
|
||||
/// Consider using <see cref="GetData{DataType}"/> which will validate and cast.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="LodInputMode.Global"/> and <see cref="LodInputMode.Primitive"/> will
|
||||
/// have no associated data and will be null. The rest will have an
|
||||
/// <see cref="LodInputData"/> type which will be prefixed with the input type and
|
||||
/// then mode (eg <see cref="LodInputMode.Texture"/> mode for
|
||||
/// <see cref="FoamLodInput"/> will be <see cref="FoamTextureLodInputData"/>).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// An exception is <see cref="ShapeGerstner"/> and <see cref="ShapeFFT"/>. They
|
||||
/// are derived from <see cref="ShapeWaves"/> and use it as a prefix. (eg <see
|
||||
/// cref="ShapeWavesTextureLodInputData"/>).
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public LodInputData Data { get => _Data; internal set => _Data = value; }
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the typed data and validates the passed type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Validation is stripped from builds.
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">The data type to cast to.</typeparam>
|
||||
/// <returns>The casted data.</returns>
|
||||
public T GetData<T>() where T : LodInputData
|
||||
{
|
||||
if (_Mode is LodInputMode.Global or LodInputMode.Primitive or LodInputMode.Unset)
|
||||
{
|
||||
Debug.AssertFormat(false, "Crest: {0} has no associated data type.", _Mode);
|
||||
return null;
|
||||
}
|
||||
|
||||
Debug.AssertFormat(Data is T, "Crest: Incorrect data type ({1}). The data type is {0}.", Data.GetType().BaseType.Name, typeof(T).Name);
|
||||
|
||||
return Data as T;
|
||||
}
|
||||
|
||||
internal bool IsCompute => Mode is LodInputMode.Texture or LodInputMode.Paint or LodInputMode.Global or LodInputMode.Primitive;
|
||||
internal virtual int Pass => -1;
|
||||
internal virtual Rect Rect
|
||||
{
|
||||
get
|
||||
{
|
||||
var rect = Rect.zero;
|
||||
if (_Data != null)
|
||||
{
|
||||
rect = _Data.Rect;
|
||||
rect.center -= _Displacement.XZ();
|
||||
}
|
||||
return rect;
|
||||
}
|
||||
}
|
||||
|
||||
readonly SampleCollisionHelper _SampleHeightHelper = new();
|
||||
Vector3 _Displacement;
|
||||
private protected bool _RecalculateBounds = true;
|
||||
|
||||
internal virtual bool Enabled => enabled && !ForceRenderingOff && Mode switch
|
||||
{
|
||||
LodInputMode.Unset => false,
|
||||
_ => Data?.IsEnabled ?? false,
|
||||
};
|
||||
|
||||
// By default do not follow horizontal motion of waves. This means that the water input will appear on the surface at its XZ location, instead
|
||||
// of moving horizontally with the waves.
|
||||
private protected virtual bool FollowHorizontalMotion => Mode is LodInputMode.Global or LodInputMode.Spline || _FollowHorizontalWaveMotion;
|
||||
|
||||
|
||||
//
|
||||
// Event Methods
|
||||
//
|
||||
|
||||
private protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
Data?.OnEnable();
|
||||
Attach();
|
||||
}
|
||||
|
||||
private protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
Detach();
|
||||
Data?.OnDisable();
|
||||
}
|
||||
|
||||
private protected override Action<WaterRenderer> OnUpdateMethod => OnUpdate;
|
||||
private protected virtual void OnUpdate(WaterRenderer water)
|
||||
{
|
||||
if (transform.hasChanged)
|
||||
{
|
||||
_RecalculateBounds = true;
|
||||
}
|
||||
|
||||
// Input culling depends on displacement.
|
||||
if (!FollowHorizontalMotion)
|
||||
{
|
||||
_SampleHeightHelper.SampleDisplacement(transform.position, out _Displacement);
|
||||
}
|
||||
else
|
||||
{
|
||||
_Displacement = Vector3.zero;
|
||||
}
|
||||
|
||||
Data?.OnUpdate();
|
||||
}
|
||||
|
||||
private protected override Action<WaterRenderer> OnLateUpdateMethod => OnLateUpdate;
|
||||
private protected virtual void OnLateUpdate(WaterRenderer water)
|
||||
{
|
||||
Data?.OnLateUpdate();
|
||||
|
||||
transform.hasChanged = false;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// ILodInput
|
||||
//
|
||||
|
||||
private protected virtual void Attach()
|
||||
{
|
||||
_Input ??= new(this);
|
||||
ILodInput.Attach(_Input, Inputs);
|
||||
}
|
||||
|
||||
private protected virtual void Detach()
|
||||
{
|
||||
ILodInput.Detach(_Input, Inputs);
|
||||
}
|
||||
|
||||
internal virtual void Draw(Lod simulation, CommandBuffer buffer, RenderTargetIdentifier target, int pass = -1, float weight = 1f, int slice = -1)
|
||||
{
|
||||
if (weight == 0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Must use global as weight can change per slice for ShapeWaves.
|
||||
var wrapper = new PropertyWrapperBuffer(buffer);
|
||||
wrapper.SetFloat(ShaderIDs.s_Weight, weight * _Weight);
|
||||
wrapper.SetVector(ShaderIDs.s_DisplacementAtInputPosition, _Displacement);
|
||||
|
||||
Data?.Draw(simulation, this, buffer, target, slice);
|
||||
}
|
||||
|
||||
internal virtual float Filter(WaterRenderer water, int slice)
|
||||
{
|
||||
return 1f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the Blend render state using Blend present.
|
||||
/// </summary>
|
||||
internal static void SetBlendFromPreset(Material material, LodInputBlend preset)
|
||||
{
|
||||
// Blend.Additive
|
||||
var source = BlendMode.One;
|
||||
var target = BlendMode.One;
|
||||
var operation = BlendOp.Add;
|
||||
|
||||
switch (preset)
|
||||
{
|
||||
case LodInputBlend.Off:
|
||||
source = BlendMode.One;
|
||||
target = BlendMode.Zero;
|
||||
break;
|
||||
case LodInputBlend.Alpha or LodInputBlend.AlphaClip:
|
||||
source = BlendMode.One; // We apply alpha before blending.
|
||||
target = BlendMode.OneMinusSrcAlpha;
|
||||
break;
|
||||
case LodInputBlend.Maximum:
|
||||
operation = BlendOp.Max;
|
||||
break;
|
||||
case LodInputBlend.Minimum:
|
||||
operation = BlendOp.Min;
|
||||
break;
|
||||
}
|
||||
|
||||
// SetInteger did not appear to work last time. Will need to revisit.
|
||||
material.SetInt(ShaderIDs.s_BlendSource, (int)source);
|
||||
material.SetInt(ShaderIDs.s_BlendTarget, (int)target);
|
||||
material.SetInt(ShaderIDs.s_BlendOperation, (int)operation);
|
||||
}
|
||||
|
||||
void SetQueue(int previous, int current)
|
||||
{
|
||||
if (previous == current) return;
|
||||
if (!isActiveAndEnabled) return;
|
||||
Attach();
|
||||
}
|
||||
|
||||
internal virtual void InferBlend()
|
||||
{
|
||||
// Correct for most cases.
|
||||
_Blend = LodInputBlend.Additive;
|
||||
}
|
||||
|
||||
//
|
||||
// Editor Only Methods
|
||||
//
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[@OnChange(skipIfInactive: false)]
|
||||
void OnChange(string propertyPath, object previousValue)
|
||||
{
|
||||
switch (propertyPath)
|
||||
{
|
||||
case nameof(_Queue):
|
||||
SetQueue((int)previousValue, _Queue);
|
||||
break;
|
||||
case nameof(_Mode):
|
||||
if (!isActiveAndEnabled) { ChangeMode(Mode); break; }
|
||||
OnDisable();
|
||||
ChangeMode(Mode);
|
||||
UnityEditor.EditorTools.ToolManager.RefreshAvailableTools();
|
||||
OnEnable();
|
||||
break;
|
||||
case nameof(_Blend):
|
||||
// TODO: Make compatible with disabled.
|
||||
if (isActiveAndEnabled) Data.OnChange($"../{propertyPath}", previousValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
internal void ChangeMode(LodInputMode mode)
|
||||
{
|
||||
_Data = null;
|
||||
|
||||
// Try to infer the mode.
|
||||
var types = TypeCache.GetTypesWithAttribute<ForLodInput>();
|
||||
var self = GetType();
|
||||
foreach (var type in types)
|
||||
{
|
||||
var attributes = type.GetCustomAttributes<ForLodInput>();
|
||||
foreach (var attribute in attributes)
|
||||
{
|
||||
if (attribute._Mode != mode) continue;
|
||||
if (!attribute._Type.IsAssignableFrom(self)) continue;
|
||||
_Mode = mode;
|
||||
InferBlend();
|
||||
_Data = (LodInputData)Activator.CreateInstance(type);
|
||||
_Data._Input = this;
|
||||
_Data.InferMode(this, ref _Mode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_Mode = DefaultMode;
|
||||
InferBlend();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when component attached in edit mode, or when Reset clicked by user.
|
||||
/// Besides recovering from Unset default value, also does a nice bit of auto-config.
|
||||
/// </summary>
|
||||
private protected override void Reset()
|
||||
{
|
||||
var types = TypeCache.GetTypesWithAttribute<ForLodInput>();
|
||||
var self = GetType();
|
||||
|
||||
// Use inferred mode.
|
||||
foreach (var type in types)
|
||||
{
|
||||
var attributes = type.GetCustomAttributes<ForLodInput>();
|
||||
foreach (var attribute in attributes)
|
||||
{
|
||||
if (!attribute._Type.IsAssignableFrom(self)) continue;
|
||||
|
||||
var instance = (LodInputData)Activator.CreateInstance(type);
|
||||
instance._Input = this;
|
||||
|
||||
if (instance.InferMode(this, ref _Mode))
|
||||
{
|
||||
_Data = instance;
|
||||
InferBlend();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use default mode.
|
||||
ChangeMode(DefaultMode);
|
||||
|
||||
_Data?.Reset();
|
||||
|
||||
base.Reset();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
partial class LodInput
|
||||
{
|
||||
Input _Input;
|
||||
|
||||
sealed class Input : ILodInput
|
||||
{
|
||||
readonly LodInput _Input;
|
||||
public Input(LodInput input) => _Input = input;
|
||||
public bool Enabled => _Input.Enabled;
|
||||
public bool IsCompute => _Input.IsCompute;
|
||||
public int Queue => _Input.Queue;
|
||||
public int Pass => _Input.Pass;
|
||||
public Rect Rect => _Input.Rect;
|
||||
public MonoBehaviour Component => _Input;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 126e7f9b525874cdbb0653a0f1662e18
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,212 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
sealed class ForLodInput : Attribute
|
||||
{
|
||||
public readonly Type _Type;
|
||||
public readonly LodInputMode _Mode;
|
||||
|
||||
public ForLodInput(Type type, LodInputMode mode)
|
||||
{
|
||||
_Type = type;
|
||||
_Mode = mode;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data storage for an input, pertinent to the associated input mode.
|
||||
/// </summary>
|
||||
public abstract class LodInputData
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
internal LodInput _Input;
|
||||
|
||||
private protected Rect _Rect;
|
||||
private protected Bounds _Bounds;
|
||||
private protected bool _RecalculateRect = true;
|
||||
private protected bool _RecalculateBounds = true;
|
||||
|
||||
internal abstract bool IsEnabled { get; }
|
||||
internal abstract void OnEnable();
|
||||
internal abstract void OnDisable();
|
||||
internal abstract void Draw(Lod lod, Component component, CommandBuffer buffer, RenderTargetIdentifier target, int slice);
|
||||
internal abstract void RecalculateRect();
|
||||
internal abstract void RecalculateBounds();
|
||||
|
||||
internal virtual bool HasHeightRange => true;
|
||||
|
||||
internal Rect Rect
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_RecalculateRect)
|
||||
{
|
||||
RecalculateRect();
|
||||
_RecalculateRect = false;
|
||||
}
|
||||
|
||||
return _Rect;
|
||||
}
|
||||
}
|
||||
|
||||
internal Bounds Bounds
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_RecalculateBounds)
|
||||
{
|
||||
RecalculateBounds();
|
||||
_RecalculateBounds = false;
|
||||
}
|
||||
|
||||
return _Bounds;
|
||||
}
|
||||
}
|
||||
|
||||
// Warning: NotImplementedException is thrown for paint and texture types.
|
||||
internal Vector2 HeightRange
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!HasHeightRange) return Vector2.zero;
|
||||
var bounds = Bounds;
|
||||
return new(bounds.min.y, bounds.max.y);
|
||||
}
|
||||
}
|
||||
|
||||
private protected void RecalculateCulling()
|
||||
{
|
||||
_RecalculateRect = _RecalculateBounds = true;
|
||||
}
|
||||
|
||||
internal virtual void OnUpdate()
|
||||
{
|
||||
if (_Input.transform.hasChanged)
|
||||
{
|
||||
RecalculateCulling();
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual void OnLateUpdate()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
internal abstract void OnChange(string propertyPath, object previousValue);
|
||||
internal abstract bool InferMode(Component component, ref LodInputMode mode);
|
||||
internal virtual void Reset() { }
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modes that inputs can use. Not all inputs support all modes. Refer to the UI.
|
||||
/// </summary>
|
||||
public enum LodInputMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Unset is the serialization default.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will be replaced with the default mode automatically. Unset can also be
|
||||
/// used if something is invalid.
|
||||
/// </remarks>
|
||||
Unset = 0,
|
||||
/// <summary>
|
||||
/// Hand-painted data by the user. Currently unused.
|
||||
/// </summary>
|
||||
Paint,
|
||||
/// <summary>
|
||||
/// Driven by a user created spline.
|
||||
/// </summary>
|
||||
Spline,
|
||||
/// <summary>
|
||||
/// Attached 'Renderer' (mesh, particle or other) used to drive data.
|
||||
/// </summary>
|
||||
Renderer,
|
||||
/// <summary>
|
||||
/// Driven by a mathematical primitive such as a cube or sphere.
|
||||
/// </summary>
|
||||
Primitive,
|
||||
/// <summary>
|
||||
/// Covers the entire water area.
|
||||
/// </summary>
|
||||
Global,
|
||||
/// <summary>
|
||||
/// Data driven by a user provided texture.
|
||||
/// </summary>
|
||||
Texture,
|
||||
/// <summary>
|
||||
/// Renders geometry using a default material.
|
||||
/// </summary>
|
||||
Geometry,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blend presets for inputs.
|
||||
/// </summary>
|
||||
public enum LodInputBlend
|
||||
{
|
||||
/// <summary>
|
||||
/// No blending. Overwrites.
|
||||
/// </summary>
|
||||
Off,
|
||||
|
||||
/// <summary>
|
||||
/// Additive blending.
|
||||
/// </summary>
|
||||
Additive,
|
||||
|
||||
/// <summary>
|
||||
/// Takes the minimum value.
|
||||
/// </summary>
|
||||
Minimum,
|
||||
|
||||
/// <summary>
|
||||
/// Takes the maximum value.
|
||||
/// </summary>
|
||||
Maximum,
|
||||
|
||||
/// <summary>
|
||||
/// Applies the inverse weight to the target.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Basically overwrites what is already in the simulation.
|
||||
/// </remarks>
|
||||
Alpha,
|
||||
|
||||
/// <summary>
|
||||
/// Same as alpha except anything above zero will overwrite rather than blend.
|
||||
/// </summary>
|
||||
AlphaClip,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Primitive shapes.
|
||||
/// </summary>
|
||||
// Have this match UnityEngine.PrimitiveType.
|
||||
public enum LodInputPrimitive
|
||||
{
|
||||
/// <summary>
|
||||
/// Spheroid.
|
||||
/// </summary>
|
||||
Sphere = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Cuboid.
|
||||
/// </summary>
|
||||
Cube = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Quad.
|
||||
/// </summary>
|
||||
Quad = 5,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a95cc8f7e50c47d8bfdda73f6da338a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,272 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Data storage for for the Renderer input mode.
|
||||
/// </summary>
|
||||
public abstract partial class RendererLodInputData : LodInputData
|
||||
{
|
||||
[Tooltip("The renderer to use for this input.\n\nCan be anything that inherits from <i>Renderer</i> like <i>MeshRenderer</i>, <i>TrailRenderer</i> etc.")]
|
||||
[@GenerateAPI(Setter.Custom)]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal Renderer _Renderer;
|
||||
|
||||
[Tooltip("Forces the renderer to only render into the LOD data, and not to render in the scene as it normally would.")]
|
||||
[@GenerateAPI(Setter.Custom)]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal bool _DisableRenderer = true;
|
||||
|
||||
[Tooltip("Whether to set the shader pass manually.")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal bool _OverrideShaderPass;
|
||||
|
||||
[Tooltip("The shader pass to execute.\n\nSet to -1 to execute all passes.")]
|
||||
[@Predicated(nameof(_OverrideShaderPass))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal int _ShaderPassIndex;
|
||||
|
||||
#pragma warning disable 414
|
||||
[Tooltip("Check that the shader applied to this object matches the input type.\n\nFor example, an Animated Waves input object has an Animated Waves input shader.")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal bool _CheckShaderName = true;
|
||||
|
||||
[Tooltip("Check that the shader applied to this object has only a single pass, as only the first pass is executed for most inputs.")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal bool _CheckShaderPasses = true;
|
||||
#pragma warning restore 414
|
||||
|
||||
|
||||
static class ShaderIDs
|
||||
{
|
||||
public static readonly int s_Time = Shader.PropertyToID("_Time");
|
||||
}
|
||||
|
||||
|
||||
// Some renderers require multiple materials like particles with trails.
|
||||
// We pass this to GetSharedMaterials to avoid allocations.
|
||||
internal List<Material> _Materials = new();
|
||||
MaterialPropertyBlock _MaterialPropertyBlock;
|
||||
|
||||
internal abstract string ShaderPrefix { get; }
|
||||
|
||||
internal override bool IsEnabled => _Renderer != null && _MaterialPropertyBlock != null;
|
||||
|
||||
internal override void RecalculateRect()
|
||||
{
|
||||
_Rect = Rect.MinMaxRect(_Renderer.bounds.min.x, _Renderer.bounds.min.z, _Renderer.bounds.max.x, _Renderer.bounds.max.z);
|
||||
}
|
||||
|
||||
internal override void RecalculateBounds()
|
||||
{
|
||||
_Bounds = _Renderer.bounds;
|
||||
}
|
||||
|
||||
bool AnyOtherInputsControllingRenderer(Renderer renderer)
|
||||
{
|
||||
for (var index = 0; index < SceneManager.sceneCount; index++)
|
||||
{
|
||||
var scene = SceneManager.GetSceneAt(index);
|
||||
|
||||
if (!scene.isLoaded)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var rootGameObject in scene.GetRootGameObjects())
|
||||
{
|
||||
foreach (var component in rootGameObject.GetComponentsInChildren<LodInput>())
|
||||
{
|
||||
if (component == _Input)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (component.Data is not RendererLodInputData data)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (component.isActiveAndEnabled && data._DisableRenderer && data._Renderer == renderer)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal override void OnEnable()
|
||||
{
|
||||
_MaterialPropertyBlock ??= new();
|
||||
|
||||
if (_Renderer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_Renderer.GetSharedMaterials(_Materials);
|
||||
|
||||
if (_DisableRenderer)
|
||||
{
|
||||
// If we disable using "enabled" then the renderer might not behave correctly (eg line/trail positions
|
||||
// will not be updated). This keeps the scripting side of the component running and just disables the
|
||||
// rendering. Similar to disabling the Renderer module on the Particle System. It also is not serialized.
|
||||
_Renderer.forceRenderingOff = true;
|
||||
}
|
||||
}
|
||||
|
||||
internal override void OnDisable()
|
||||
{
|
||||
if (_Renderer != null && _DisableRenderer && !AnyOtherInputsControllingRenderer(_Renderer))
|
||||
{
|
||||
_Renderer.forceRenderingOff = false;
|
||||
}
|
||||
}
|
||||
|
||||
internal override void OnUpdate()
|
||||
{
|
||||
if (_Renderer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// We have to check this every time as the user could change the materials and it is too difficult to track.
|
||||
// Doing this in LateUpdate could add one frame latency to receiving the change.
|
||||
_Renderer.GetSharedMaterials(_Materials);
|
||||
|
||||
// Always recalculate, as there are too much to track.
|
||||
_RecalculateBounds = true;
|
||||
_RecalculateRect = _Bounds != _Renderer.bounds;
|
||||
}
|
||||
|
||||
internal override void Draw(Lod lod, Component component, CommandBuffer buffer, RenderTargetIdentifier target, int slice)
|
||||
{
|
||||
// NOTE: Inputs will only change the first material (only ShapeWaves at the moment).
|
||||
|
||||
for (var i = 0; i < _Materials.Count; i++)
|
||||
{
|
||||
var material = _Materials[i];
|
||||
Debug.AssertFormat(material != null, _Renderer, "Crest: Attached renderer has an empty material slot which is not allowed.");
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// Empty material slots is a user error, but skip so we do not spam errors.
|
||||
if (material == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
var pass = _ShaderPassIndex;
|
||||
if (ShapeWaves.s_RenderPassOverride > -1)
|
||||
{
|
||||
// Needs to use a second pass to disable blending.
|
||||
pass = ShapeWaves.s_RenderPassOverride;
|
||||
}
|
||||
else if (!_OverrideShaderPass)
|
||||
{
|
||||
// BIRP/URP SG first pass is the right one.
|
||||
pass = 0;
|
||||
|
||||
// Support HDRP SG. It will always have more than one pass.
|
||||
if (RenderPipelineHelper.IsHighDefinition && material.shader.passCount > 1)
|
||||
{
|
||||
var sgPass = material.FindPass("ForwardOnly");
|
||||
if (sgPass > -1) pass = sgPass;
|
||||
}
|
||||
}
|
||||
else if (_ShaderPassIndex > material.shader.passCount - 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Time is not set for us for some reason… Use Time.timeSinceLevelLoad as per:
|
||||
// https://docs.unity3d.com/Manual/SL-UnityShaderVariables.html
|
||||
if (RenderPipelineHelper.IsLegacy)
|
||||
{
|
||||
_Renderer.GetPropertyBlock(_MaterialPropertyBlock);
|
||||
_MaterialPropertyBlock.SetVector(ShaderIDs.s_Time, new
|
||||
(
|
||||
Time.timeSinceLevelLoad / 20,
|
||||
Time.timeSinceLevelLoad,
|
||||
Time.timeSinceLevelLoad * 2f,
|
||||
Time.timeSinceLevelLoad * 3f
|
||||
));
|
||||
_Renderer.SetPropertyBlock(_MaterialPropertyBlock);
|
||||
}
|
||||
|
||||
// By default, shaderPass is -1 which is all passes. Shader Graph will produce multi-pass shaders
|
||||
// for depth etc so we should only render one pass. Unlit SG will have the unlit pass first.
|
||||
// Submesh count generally must equal number of materials.
|
||||
buffer.DrawRenderer(_Renderer, material, submeshIndex: i, pass);
|
||||
}
|
||||
}
|
||||
|
||||
void SetRenderer(Renderer previous, Renderer current)
|
||||
{
|
||||
if (previous == current) return;
|
||||
if (_Input == null || !_Input.isActiveAndEnabled) return;
|
||||
|
||||
if (previous != null && _DisableRenderer && !AnyOtherInputsControllingRenderer(previous))
|
||||
{
|
||||
// Turn off if there are no other inputs have set this value.
|
||||
previous.forceRenderingOff = false;
|
||||
}
|
||||
|
||||
if (current != null)
|
||||
{
|
||||
current.forceRenderingOff = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SetDisableRenderer(bool previous, bool current)
|
||||
{
|
||||
if (previous == current) return;
|
||||
if (_Input == null || !_Input.isActiveAndEnabled) return;
|
||||
|
||||
if (_Renderer != null && !AnyOtherInputsControllingRenderer(_Renderer))
|
||||
{
|
||||
_Renderer.forceRenderingOff = _DisableRenderer;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[@OnChange]
|
||||
internal override void OnChange(string propertyPath, object previousValue)
|
||||
{
|
||||
switch (propertyPath)
|
||||
{
|
||||
case nameof(_Renderer):
|
||||
SetRenderer((Renderer)previousValue, _Renderer);
|
||||
break;
|
||||
case nameof(_DisableRenderer):
|
||||
SetDisableRenderer((bool)previousValue, _DisableRenderer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
internal override bool InferMode(Component component, ref LodInputMode mode)
|
||||
{
|
||||
if (component.TryGetComponent(out _Renderer))
|
||||
{
|
||||
mode = LodInputMode.Renderer;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c0ee0dbacf6784d1a8608ad94109098d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,37 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers a custom input to the <see cref="ScatteringLod"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Attach this to objects that you want to influence the scattering color.
|
||||
/// </remarks>
|
||||
[@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
|
||||
internal override LodInputMode DefaultMode => LodInputMode.Renderer;
|
||||
#endif
|
||||
|
||||
internal override void InferBlend()
|
||||
{
|
||||
base.InferBlend();
|
||||
_Blend = LodInputBlend.Alpha;
|
||||
}
|
||||
|
||||
// Looks fine moving around.
|
||||
private protected override bool FollowHorizontalMotion => true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c85179d3b43c5467da99463c8a2199ab
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers a custom input to the <see cref="ShadowLod"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Attach this objects that you want use to override shadows.
|
||||
/// </remarks>
|
||||
[@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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fcda6c7640ad44106aa39234017df797
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d7eafa182f969495d9e74b145310c516
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,212 @@
|
||||
// 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.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 88bb6e05d83b64105a4d8cbd478f5916
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,523 @@
|
||||
// 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.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 002f2642204d348f3a2fab18595c44cd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences:
|
||||
- _rasterMesh: {instanceID: 0}
|
||||
- _spectrum: {instanceID: 0}
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,490 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c494780f7140b493695a431be2111449
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,293 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Approximates the interaction between a sphere and the water.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Multiple spheres can be used to model the interaction of a non-spherical shape.
|
||||
/// </remarks>
|
||||
[AddComponentMenu(Constants.k_MenuPrefixInputs + "Sphere Water Interaction")]
|
||||
[@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]
|
||||
[SerializeField]
|
||||
internal float _Radius = 1f;
|
||||
|
||||
[Tooltip("Intensity of the forces.\n\nCan be set negative to invert.")]
|
||||
[@Range(-40f, 40f)]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
float _Weight = 1f;
|
||||
|
||||
[Tooltip("Intensity of the forces from vertical motion of the sphere.\n\nScales ripples generated from a sphere moving up or down.")]
|
||||
[@Range(0f, 2f)]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
float _WeightVerticalMultiplier = 0.5f;
|
||||
|
||||
[Tooltip("Model parameter that can be used to modify the shape of the interaction.\n\nInternally the interaction is modelled by a pair of nested spheres. The forces from the two spheres combine to create the final effect. This parameter scales the effect of the inner sphere and can be tweaked to adjust the shape of the result.")]
|
||||
[@Range(0f, 10f)]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
float _InnerSphereMultiplier = 1.55f;
|
||||
|
||||
[Tooltip("Model parameter that can be used to modify the shape of the interaction.\n\nThis parameter controls the size of the inner sphere and can be tweaked to give further control over the result.")]
|
||||
[@Range(0f, 1f)]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
float _InnerSphereOffset = 0.109f;
|
||||
|
||||
[Tooltip("Offset in direction of motion to help ripples appear in front of sphere.\n\nThere is some latency between applying a force to the wave simualtion and the resulting waves appearing. Applying this offset can help to ensure the waves do not lag behind the sphere.")]
|
||||
[@Range(0f, 2f)]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
internal float _VelocityOffset = 0.04f;
|
||||
|
||||
[Tooltip("How much to correct the position for horizontal wave displacement.\n\nIf set to 0, the input will always be applied at a fixed position before any horizontal displacement from waves. If waves are large then their displacement may cause the interactive waves to drift away from the object. This parameter can be increased to compensate for this displacement and combat this issue. However increasing too far can cause a feedback loop which causes strong 'ring' artifacts to appear in the dynamic waves. This parameter can be tweaked to balance this two effects.")]
|
||||
[@Range(0f, 1f)]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
float _CompensateForWaveMotion = 0.45f;
|
||||
|
||||
[Tooltip("Whether to improve visibility in larger LODs.\n\nIf the dynamic waves are not visible far enough in the distance from the camera, this can be used to boost the output.")]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
bool _BoostLargeWaves = false;
|
||||
|
||||
|
||||
[Header("Limits")]
|
||||
|
||||
[Tooltip("Teleport speed (km/h).\n\nIf the calculated speed is larger than this amount, the object is deemed to have teleported and the computed velocity is discarded.")]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
float _TeleportSpeed = 500f;
|
||||
|
||||
[Tooltip("Outputs a warning to the console on teleport.")]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
bool _WarnOnTeleport = false;
|
||||
|
||||
[Tooltip("Maximum speed clamp (km/h).\n\nUseful for controlling/limiting wake.")]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
float _MaximumSpeed = 100f;
|
||||
|
||||
[Tooltip("Outputs a warning to the console on speed clamp.")]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
bool _WarnOnSpeedClamp = false;
|
||||
|
||||
#pragma warning disable 414
|
||||
[Header("Debug")]
|
||||
|
||||
[Tooltip("Draws debug lines at each substep position. Editor only.")]
|
||||
[SerializeField]
|
||||
bool _DebugSubsteps = false;
|
||||
#pragma warning restore 414
|
||||
|
||||
static class ShaderIDs
|
||||
{
|
||||
public static readonly int s_Velocity = Shader.PropertyToID("_Crest_Velocity");
|
||||
public static readonly int s_Weight = Shader.PropertyToID("_Crest_Weight");
|
||||
public static readonly int s_Radius = Shader.PropertyToID("_Crest_Radius");
|
||||
public static readonly int s_InnerSphereOffset = Shader.PropertyToID("_Crest_InnerSphereOffset");
|
||||
public static readonly int s_InnerSphereMultiplier = Shader.PropertyToID("_Crest_InnerSphereMultiplier");
|
||||
public static readonly int s_LargeWaveMultiplier = Shader.PropertyToID("_Crest_LargeWaveMultiplier");
|
||||
}
|
||||
|
||||
internal Vector3 _Velocity;
|
||||
Vector3 _VelocityClamped;
|
||||
Vector3 _PreviousPosition;
|
||||
Vector3 _RelativeVelocity;
|
||||
Vector3 _Displacement;
|
||||
|
||||
float _WeightThisFrame;
|
||||
|
||||
readonly SampleCollisionHelper _SampleHeightHelper = new();
|
||||
readonly SampleFlowHelper _SampleFlowHelper = new();
|
||||
|
||||
static ComputeShader ComputeShader => WaterResources.Instance.Compute._SphereWaterInteraction;
|
||||
Rect Rect => new
|
||||
(
|
||||
transform.position.XZ() - _Displacement.XZ() * _CompensateForWaveMotion - Vector2.one * (_Radius * 4f * 0.5f),
|
||||
Vector2.one * (_Radius * 4f)
|
||||
);
|
||||
|
||||
private protected override System.Action<WaterRenderer> OnUpdateMethod => OnUpdate;
|
||||
void OnUpdate(WaterRenderer water)
|
||||
{
|
||||
_SampleHeightHelper.SampleDisplacement(transform.position, out _Displacement, minimumLength: 2f * _Radius);
|
||||
|
||||
LateUpdateComputeVel(water);
|
||||
|
||||
// Velocity relative to water
|
||||
_RelativeVelocity = _VelocityClamped;
|
||||
{
|
||||
_SampleFlowHelper.Sample(transform.position, out var surfaceFlow, minimumLength: 2f * _Radius);
|
||||
_RelativeVelocity -= new Vector3(surfaceFlow.x, 0, surfaceFlow.y);
|
||||
|
||||
_RelativeVelocity.y *= _WeightVerticalMultiplier;
|
||||
}
|
||||
|
||||
// Use weight from user with a multiplier to make interactions look plausible
|
||||
_WeightThisFrame = 3.75f * _Weight;
|
||||
|
||||
var waterHeight = _Displacement.y + water.SeaLevel;
|
||||
LateUpdateSphereWeight(waterHeight, ref _WeightThisFrame);
|
||||
|
||||
// Weighting with this value helps keep ripples consistent for different gravity values
|
||||
var gravityMul = Mathf.Sqrt(water._DynamicWavesLod.Settings._GravityMultiplier) / 5f;
|
||||
_WeightThisFrame *= gravityMul;
|
||||
|
||||
_PreviousPosition = transform.position;
|
||||
}
|
||||
|
||||
// Velocity of the sphere, relative to the water. Computes on the fly, discards if teleport detected.
|
||||
void LateUpdateComputeVel(WaterRenderer water)
|
||||
{
|
||||
// Compue vel using finite difference
|
||||
_Velocity = (transform.position - _PreviousPosition) / water.DeltaTime;
|
||||
if (water.DeltaTime < 0.0001f)
|
||||
{
|
||||
_Velocity = Vector3.zero;
|
||||
}
|
||||
|
||||
var speedKmh = _Velocity.magnitude * 3.6f;
|
||||
if (speedKmh > _TeleportSpeed)
|
||||
{
|
||||
// teleport detected
|
||||
_Velocity *= 0f;
|
||||
|
||||
if (_WarnOnTeleport)
|
||||
{
|
||||
Debug.LogWarning("Crest: Teleport detected (speed = " + speedKmh.ToString() + "), velocity discarded.", this);
|
||||
}
|
||||
|
||||
speedKmh = _Velocity.magnitude * 3.6f;
|
||||
}
|
||||
|
||||
if (speedKmh > _MaximumSpeed)
|
||||
{
|
||||
// limit speed to max
|
||||
_VelocityClamped = _Velocity * _MaximumSpeed / speedKmh;
|
||||
|
||||
if (_WarnOnSpeedClamp)
|
||||
{
|
||||
Debug.LogWarning("Crest: Speed (" + speedKmh.ToString() + ") exceeded max limited, clamped.", this);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_VelocityClamped = _Velocity;
|
||||
}
|
||||
}
|
||||
|
||||
// Weight based on submerged-amount of sphere
|
||||
void LateUpdateSphereWeight(float waterHeight, ref float weight)
|
||||
{
|
||||
var centerDepthInWater = waterHeight - transform.position.y;
|
||||
|
||||
if (centerDepthInWater >= 0f)
|
||||
{
|
||||
// Center in water - exponential fall off of interaction influence as object gets deeper
|
||||
var prop = centerDepthInWater / _Radius;
|
||||
prop *= 0.5f;
|
||||
weight *= Mathf.Exp(-prop * prop);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Center out of water - ramp off with square root, weight goes to 0 when sphere is just touching water
|
||||
var height = -centerDepthInWater;
|
||||
var heightProp = 1f - Mathf.Clamp01(height / _Radius);
|
||||
weight *= Mathf.Sqrt(heightProp);
|
||||
}
|
||||
}
|
||||
|
||||
private protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_Input ??= new(this);
|
||||
ILodInput.Attach(_Input, DynamicWavesLod.s_Inputs);
|
||||
_PreviousPosition = transform.position;
|
||||
}
|
||||
|
||||
private protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
ILodInput.Detach(_Input, DynamicWavesLod.s_Inputs);
|
||||
}
|
||||
|
||||
void Draw(Lod simulation, CommandBuffer buffer, RenderTargetIdentifier target, int pass = -1, float weight = 1f, int slices = -1)
|
||||
{
|
||||
var waves = simulation as DynamicWavesLod;
|
||||
var timeBeforeCurrentTime = waves.TimeLeftToSimulate;
|
||||
|
||||
var wrapper = new PropertyWrapperCompute(buffer, ComputeShader, 0);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// Draw debug lines at each substep position. Alternate colours each frame so that substeps are clearly visible.
|
||||
if (_DebugSubsteps)
|
||||
{
|
||||
var col = 0.7f * (Time.frameCount % 2 == 1 ? Color.green : Color.red);
|
||||
var pos = transform.position - _Velocity * (timeBeforeCurrentTime - _VelocityOffset);
|
||||
var right = Vector3.Cross(Vector3.up, _Velocity.normalized);
|
||||
Debug.DrawLine(pos - right + transform.up, pos + right + transform.up, col, 0.5f);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Reconstruct the position of this input at the current substep time. This produces
|
||||
// much smoother interaction shapes for moving objects. Increasing sim freq helps further.
|
||||
var offset = _Velocity * (timeBeforeCurrentTime - _VelocityOffset);
|
||||
var displacement = _Displacement.XNZ() * _CompensateForWaveMotion;
|
||||
wrapper.SetVector(Crest.ShaderIDs.s_Position, transform.position - offset - displacement);
|
||||
|
||||
// Enlarge radius slightly - this tends to help waves 'wrap' the sphere slightly better
|
||||
wrapper.SetFloat(ShaderIDs.s_Radius, _Radius * 1.1f);
|
||||
|
||||
wrapper.SetFloat(ShaderIDs.s_Weight, _WeightThisFrame);
|
||||
wrapper.SetFloat(ShaderIDs.s_InnerSphereOffset, _InnerSphereOffset);
|
||||
wrapper.SetFloat(ShaderIDs.s_InnerSphereMultiplier, _InnerSphereMultiplier);
|
||||
wrapper.SetFloat(ShaderIDs.s_LargeWaveMultiplier, _BoostLargeWaves ? 2f : 1f);
|
||||
wrapper.SetVector(ShaderIDs.s_Velocity, _RelativeVelocity);
|
||||
|
||||
wrapper.SetTexture(Crest.ShaderIDs.s_Target, target);
|
||||
|
||||
var threads = simulation.Resolution / Lod.k_ThreadGroupSize;
|
||||
wrapper.Dispatch(threads, threads, slices);
|
||||
}
|
||||
}
|
||||
|
||||
partial class SphereWaterInteraction
|
||||
{
|
||||
Input _Input;
|
||||
|
||||
sealed class Input : ILodInput
|
||||
{
|
||||
readonly SphereWaterInteraction _Input;
|
||||
public Input(SphereWaterInteraction input) => _Input = input;
|
||||
public bool Enabled => _Input.enabled;
|
||||
public bool IsCompute => true;
|
||||
public int Queue => 0;
|
||||
public int Pass => -1;
|
||||
public Rect Rect => _Input.Rect;
|
||||
public MonoBehaviour Component => _Input;
|
||||
public float Filter(WaterRenderer water, int slice) => 1f;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8958fecc82ae04c7d9128101addbdc3b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,104 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Data storage for for the Texture input mode.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public abstract partial class TextureLodInputData : LodInputData
|
||||
{
|
||||
[Tooltip("Texture to render into the simulation.")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal Texture _Texture;
|
||||
|
||||
private protected abstract ComputeShader TextureShader { get; }
|
||||
internal override bool IsEnabled => _Texture != null;
|
||||
internal override bool HasHeightRange => false;
|
||||
|
||||
internal override void RecalculateRect()
|
||||
{
|
||||
_Rect = _Input.transform.RectXZ();
|
||||
}
|
||||
|
||||
internal override void RecalculateBounds()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
internal override void Draw(Lod lod, Component component, CommandBuffer buffer, RenderTargetIdentifier target, int slices)
|
||||
{
|
||||
var transform = component.transform;
|
||||
var wrapper = new PropertyWrapperCompute(buffer, TextureShader, 0);
|
||||
var rotation = new Vector2(transform.localToWorldMatrix.m20, transform.localToWorldMatrix.m00).normalized;
|
||||
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.SetFloat(ShaderIDs.s_FeatherWidth, _Input.FeatherWidth);
|
||||
wrapper.SetTexture(ShaderIDs.s_Texture, _Texture);
|
||||
wrapper.SetInteger(ShaderIDs.s_Blend, (int)_Input.Blend);
|
||||
wrapper.SetTexture(ShaderIDs.s_Target, target);
|
||||
|
||||
if (this is LevelTextureLodInputData height)
|
||||
{
|
||||
wrapper.SetKeyword(WaterResources.Instance.Keywords.LevelTextureCatmullRom, height._UseCatmullRomFiltering);
|
||||
}
|
||||
|
||||
if (this is DirectionalTextureLodInputData data)
|
||||
{
|
||||
wrapper.SetBoolean(ShaderIDs.s_NegativeValues, data._NegativeValues);
|
||||
}
|
||||
|
||||
var threads = lod.Resolution / Lod.k_ThreadGroupSize;
|
||||
wrapper.Dispatch(threads, threads, slices);
|
||||
}
|
||||
|
||||
internal override void OnEnable()
|
||||
{
|
||||
// Empty.
|
||||
}
|
||||
|
||||
internal override void OnDisable()
|
||||
{
|
||||
// Empty.
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
internal override void OnChange(string propertyPath, object previousValue)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
internal override bool InferMode(Component component, ref LodInputMode mode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[System.Serializable]
|
||||
public abstract partial class DirectionalTextureLodInputData : TextureLodInputData
|
||||
{
|
||||
[Tooltip("Whether the texture supports negative values.")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal bool _NegativeValues;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
partial class LevelTextureLodInputData
|
||||
{
|
||||
[Label("Filtering (High Quality)")]
|
||||
[Tooltip("Helps with staircase aliasing.")]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal bool _UseCatmullRomFiltering;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ba0f2ab8138ec440c970acf0e1cd3d38
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,278 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// The mode for <see cref="WatertightHull"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Each mode has its strengths and weaknesses.
|
||||
/// </remarks>
|
||||
public enum WatertightHullMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Uses <see cref="AnimatedWavesLod"/> to remove water.
|
||||
/// </summary>
|
||||
[Tooltip("Use displacement to remove water.\n\nUsing displacement will also affect the underwater and can nest bouyant objects. Requires the displacement layer to be enabled.")]
|
||||
Displacement,
|
||||
|
||||
/// <summary>
|
||||
/// Uses <see cref="ClipLod"/> to remove water.
|
||||
/// </summary>
|
||||
[Tooltip("Clips the surface to remove water.\n\nThis option is more precise and can be submerged.")]
|
||||
Clip,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes water from a provided hull using the clip simulation.
|
||||
/// </summary>
|
||||
[@ExecuteDuringEditMode]
|
||||
[AddComponentMenu(Constants.k_MenuPrefixInputs + "Watertight Hull")]
|
||||
[@HelpURL("Manual/Clipping.html#watertight-hull")]
|
||||
public sealed partial class WatertightHull : ManagedBehaviour<WaterRenderer>
|
||||
{
|
||||
[@Label("Convex Hull")]
|
||||
[Tooltip("The convex hull to keep water out.")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal Mesh _Mesh;
|
||||
|
||||
[Tooltip("Order this input will render.\n\nQueue is 'Queue + SiblingIndex'")]
|
||||
[@GenerateAPI(Setter.Custom)]
|
||||
[@DecoratedField, SerializeField]
|
||||
int _Queue;
|
||||
|
||||
[@Space(10)]
|
||||
|
||||
[Tooltip("Which mode to use.")]
|
||||
[@GenerateAPI(Setter.Custom)]
|
||||
[@DecoratedField, SerializeField]
|
||||
WatertightHullMode _Mode = WatertightHullMode.Displacement;
|
||||
|
||||
[Tooltip("Inverts the effect to remove clipping (ie add water).")]
|
||||
[@Predicated(nameof(_Mode), inverted: true, nameof(WatertightHullMode.Clip), hide: true)]
|
||||
[@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)]
|
||||
[@GenerateAPI(Setter.Custom)]
|
||||
[@DecoratedField, SerializeField]
|
||||
bool _UseClipWithDisplacement = true;
|
||||
|
||||
[@Space(10)]
|
||||
|
||||
[@DecoratedField, SerializeField]
|
||||
internal DebugFields _Debug = new();
|
||||
|
||||
[System.Serializable]
|
||||
internal sealed class DebugFields
|
||||
{
|
||||
[@DecoratedField, SerializeField]
|
||||
public bool _DrawBounds;
|
||||
}
|
||||
|
||||
Material _ClipMaterial;
|
||||
Material _AnimatedWavesMaterial;
|
||||
|
||||
internal bool Enabled => enabled && _Mesh != null;
|
||||
|
||||
bool _RecalculateBounds = true;
|
||||
Rect _Rect;
|
||||
|
||||
internal Rect Rect
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_RecalculateBounds)
|
||||
{
|
||||
_Rect = transform.TransformBounds(_Mesh.bounds).RectXZ();
|
||||
_RecalculateBounds = false;
|
||||
}
|
||||
|
||||
return _Rect;
|
||||
}
|
||||
}
|
||||
|
||||
readonly SampleCollisionHelper _SampleCollisionHelper = new();
|
||||
Vector3 _Displacement;
|
||||
|
||||
internal bool UsesClip => _Mode == WatertightHullMode.Clip || _UseClipWithDisplacement;
|
||||
internal bool UsesDisplacement => _Mode == WatertightHullMode.Displacement;
|
||||
|
||||
static class ShaderIDs
|
||||
{
|
||||
public static int s_Inverted = Shader.PropertyToID("_Crest_Inverted");
|
||||
}
|
||||
|
||||
private protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
if (UsesClip)
|
||||
{
|
||||
_ClipInput ??= new(this);
|
||||
_ClipMaterial = new(WaterResources.Instance.Shaders._ClipConvexHull);
|
||||
ILodInput.Attach(_ClipInput, ClipLod.s_Inputs);
|
||||
}
|
||||
|
||||
if (UsesDisplacement)
|
||||
{
|
||||
_AnimatedWavesInput ??= new(this);
|
||||
_AnimatedWavesMaterial = new(Shader.Find("Crest/Inputs/Animated Waves/Push Water Under Convex Hull"));
|
||||
_AnimatedWavesMaterial.SetFloat(LodInput.ShaderIDs.s_Weight, 1f);
|
||||
ILodInput.Attach(_AnimatedWavesInput, AnimatedWavesLod.s_Inputs);
|
||||
}
|
||||
}
|
||||
|
||||
private protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
Helpers.Destroy(_ClipMaterial);
|
||||
ILodInput.Detach(_ClipInput, ClipLod.s_Inputs);
|
||||
Helpers.Destroy(_AnimatedWavesMaterial);
|
||||
ILodInput.Detach(_AnimatedWavesInput, AnimatedWavesLod.s_Inputs);
|
||||
}
|
||||
|
||||
private protected override System.Action<WaterRenderer> OnUpdateMethod => OnUpdate;
|
||||
void OnUpdate(WaterRenderer water)
|
||||
{
|
||||
if (_Mode == WatertightHullMode.Displacement)
|
||||
{
|
||||
_SampleCollisionHelper.SampleDisplacement(transform.position, out _Displacement);
|
||||
}
|
||||
|
||||
if (transform.hasChanged)
|
||||
{
|
||||
_RecalculateBounds = true;
|
||||
}
|
||||
}
|
||||
|
||||
private protected override System.Action<WaterRenderer> OnLateUpdateMethod => OnLateUpdate;
|
||||
void OnLateUpdate(WaterRenderer water)
|
||||
{
|
||||
transform.hasChanged = false;
|
||||
}
|
||||
|
||||
void DrawClip(Lod simulation, CommandBuffer buffer, RenderTargetIdentifier target, int pass = -1, float weight = 1, int slice = -1)
|
||||
{
|
||||
_ClipMaterial.SetBoolean(ShaderIDs.s_Inverted, _Inverted);
|
||||
buffer.DrawMesh(_Mesh, transform.localToWorldMatrix, _ClipMaterial);
|
||||
}
|
||||
|
||||
void DrawDisplacement(Lod simulation, CommandBuffer buffer, RenderTargetIdentifier target, int pass = -1, float weight = 1, int slice = -1)
|
||||
{
|
||||
_AnimatedWavesMaterial.SetVector(LodInput.ShaderIDs.s_DisplacementAtInputPosition, _Displacement);
|
||||
buffer.DrawMesh(_Mesh, transform.localToWorldMatrix, _AnimatedWavesMaterial);
|
||||
}
|
||||
|
||||
void SetQueue(int previous, int current)
|
||||
{
|
||||
if (previous == current) return;
|
||||
if (_ClipInput == null || !isActiveAndEnabled) return;
|
||||
if (UsesClip) ILodInput.Attach(_ClipInput, ClipLod.s_Inputs);
|
||||
if (UsesDisplacement) ILodInput.Attach(_AnimatedWavesInput, AnimatedWavesLod.s_Inputs);
|
||||
}
|
||||
|
||||
void SetMode(WatertightHullMode previous, WatertightHullMode current)
|
||||
{
|
||||
if (previous == current) return;
|
||||
OnDisable(); OnEnable();
|
||||
}
|
||||
|
||||
void SetUseClipWithDisplacement(bool previous, bool current)
|
||||
{
|
||||
if (previous == current) return;
|
||||
OnDisable(); OnEnable();
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[@OnChange]
|
||||
void OnChange(string propertyPath, object previousValue)
|
||||
{
|
||||
switch (propertyPath)
|
||||
{
|
||||
case nameof(_Queue):
|
||||
SetQueue((int)previousValue, _Queue);
|
||||
break;
|
||||
case nameof(_Mode):
|
||||
SetMode((WatertightHullMode)previousValue, _Mode);
|
||||
break;
|
||||
case nameof(_UseClipWithDisplacement):
|
||||
SetUseClipWithDisplacement((bool)previousValue, _UseClipWithDisplacement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
partial class WatertightHull
|
||||
{
|
||||
ClipInput _ClipInput;
|
||||
|
||||
sealed class ClipInput : ILodInput
|
||||
{
|
||||
readonly WatertightHull _Input;
|
||||
public ClipInput(WatertightHull input) => _Input = input;
|
||||
public bool Enabled => _Input.Enabled;
|
||||
public bool IsCompute => false;
|
||||
public int Queue => _Input.Queue;
|
||||
public int Pass => -1;
|
||||
public Rect Rect => _Input.Rect;
|
||||
public MonoBehaviour Component => _Input;
|
||||
public float Filter(WaterRenderer water, int slice) => 1f;
|
||||
public void Draw(Lod lod, CommandBuffer buffer, RenderTargetIdentifier target, int pass = -1, float weight = 1, int slice = -1) => _Input.DrawClip(lod, buffer, target, pass, weight, slice);
|
||||
}
|
||||
}
|
||||
|
||||
partial class WatertightHull
|
||||
{
|
||||
DisplacementInput _AnimatedWavesInput;
|
||||
|
||||
sealed class DisplacementInput : ILodInput
|
||||
{
|
||||
readonly WatertightHull _Input;
|
||||
public DisplacementInput(WatertightHull input) => _Input = input;
|
||||
public bool Enabled => _Input.Enabled;
|
||||
public bool IsCompute => false;
|
||||
public int Queue => _Input.Queue;
|
||||
public int Pass => (int)DisplacementPass.LodIndependentLast;
|
||||
public Rect Rect => _Input.Rect;
|
||||
public MonoBehaviour Component => _Input;
|
||||
public float Filter(WaterRenderer water, int slice) => 1f;
|
||||
public void Draw(Lod lod, CommandBuffer buffer, RenderTargetIdentifier target, int pass = -1, float weight = 1, int slice = -1) => _Input.DrawDisplacement(lod, buffer, target, pass, weight, slice);
|
||||
}
|
||||
}
|
||||
|
||||
partial class WatertightHull : ISerializationCallbackReceiver
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 1;
|
||||
#pragma warning restore 414
|
||||
|
||||
/// <inheritdoc/>
|
||||
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
||||
{
|
||||
if (_Version < 1)
|
||||
{
|
||||
// Keep clip for existing.
|
||||
_Mode = WatertightHullMode.Clip;
|
||||
_Version = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void ISerializationCallbackReceiver.OnBeforeSerialize()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5ba152b967dd345829964814e3b3a4e6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,56 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
// We do not add height to displacement directly for better precision and layering.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
using WaveHarmonic.Crest.Utility;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Data that gives the water level (water height at rest and gradients).
|
||||
/// </summary>
|
||||
public sealed partial class LevelLod : Lod
|
||||
{
|
||||
// Purple/Indigo
|
||||
internal static readonly Color s_GizmoColor = new(75f / 255f, 0f, 130f / 255f, 0.5f);
|
||||
|
||||
internal override string ID => "Level";
|
||||
internal override Color GizmoColor => s_GizmoColor;
|
||||
private protected override Color ClearColor => Color.black;
|
||||
private protected override bool NeedToReadWriteTextureData => true;
|
||||
internal override bool RunsInHeadless => true;
|
||||
|
||||
private protected override GraphicsFormat RequestedTextureFormat => _TextureFormatMode switch
|
||||
{
|
||||
LodTextureFormatMode.Automatic => Water == null ? GraphicsFormat.None : Water.AnimatedWavesLod.TextureFormatMode switch
|
||||
{
|
||||
LodTextureFormatMode.Precision => GraphicsFormat.R32_SFloat,
|
||||
_ => GraphicsFormat.R16_SFloat,
|
||||
},
|
||||
LodTextureFormatMode.Performance => GraphicsFormat.R16_SFloat,
|
||||
LodTextureFormatMode.Precision => GraphicsFormat.R32_SFloat,
|
||||
LodTextureFormatMode.Manual => _TextureFormat,
|
||||
_ => throw new System.NotImplementedException(),
|
||||
};
|
||||
|
||||
internal LevelLod()
|
||||
{
|
||||
_Enabled = false;
|
||||
_OverrideResolution = false;
|
||||
_TextureFormatMode = LodTextureFormatMode.Automatic;
|
||||
_TextureFormat = GraphicsFormat.R16_SFloat;
|
||||
}
|
||||
|
||||
internal static readonly SortedList<int, ILodInput> s_Inputs = new(Helpers.DuplicateComparison);
|
||||
private protected override SortedList<int, ILodInput> Inputs => s_Inputs;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
static void OnLoad()
|
||||
{
|
||||
s_Inputs.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d555bea1d2da4c5c969a1d7775ea781
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
666
Packages/com.waveharmonic.crest/Runtime/Scripts/Data/Lod.cs
Normal file
666
Packages/com.waveharmonic.crest/Runtime/Scripts/Data/Lod.cs
Normal file
@@ -0,0 +1,666 @@
|
||||
// Crest Water System
|
||||
// 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_2023_2_OR_NEWER
|
||||
using GraphicsFormatUsage = UnityEngine.Experimental.Rendering.FormatUsage;
|
||||
#endif
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
using Inputs = SortedList<int, ILodInput>;
|
||||
|
||||
/// <summary>
|
||||
/// Texture format preset.
|
||||
/// </summary>
|
||||
public enum LodTextureFormatMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Uses <see cref="Lod.TextureFormat"/>.
|
||||
/// </summary>
|
||||
Manual,
|
||||
|
||||
/// <summary>
|
||||
/// Chooses a texture format for performance.
|
||||
/// </summary>
|
||||
Performance = 100,
|
||||
|
||||
/// <summary>
|
||||
/// Chooses a texture format for precision.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This format can reduce artifacts.
|
||||
/// </remarks>
|
||||
Precision = 200,
|
||||
|
||||
/// <summary>
|
||||
/// Chooses a texture format based on another.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For example, Dynamic Waves will match precision of Animated Waves.
|
||||
/// </remarks>
|
||||
Automatic = 300,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for data/behaviours created on each LOD.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public abstract partial class Lod
|
||||
{
|
||||
[Tooltip("Whether the simulation is enabled.")]
|
||||
[@GenerateAPI(Getter.Custom, Setter.Custom)]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal bool _Enabled;
|
||||
|
||||
[Tooltip("Whether to override the resolution.\n\nIf not enabled, then the simulation will use the resolution defined on the Water Renderer.")]
|
||||
[@Predicated(typeof(AnimatedWavesLod), inverted: true, hide: true)]
|
||||
[@GenerateAPI(Setter.Dirty)]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal bool _OverrideResolution = true;
|
||||
|
||||
[Tooltip("The resolution of the simulation data.\n\nSet higher for sharper results at the cost of higher memory usage.")]
|
||||
[@Predicated(typeof(AnimatedWavesLod), inverted: true, hide: true)]
|
||||
[@Delayed]
|
||||
[@GenerateAPI(Getter.Custom, Setter.Dirty)]
|
||||
[SerializeField]
|
||||
internal int _Resolution = 256;
|
||||
|
||||
[Tooltip("Chooses a texture format based on a preset value.")]
|
||||
[@Filtered]
|
||||
[@GenerateAPI(Setter.Dirty)]
|
||||
[SerializeField]
|
||||
private protected LodTextureFormatMode _TextureFormatMode = LodTextureFormatMode.Performance;
|
||||
|
||||
[Tooltip("The render texture format used for this simulation data.\n\nIt will be overriden if the format is incompatible with the platform.")]
|
||||
[@ShowComputedProperty(nameof(RequestedTextureFormat))]
|
||||
[@Predicated(nameof(_TextureFormatMode), inverted: true, nameof(LodTextureFormatMode.Manual), hide: true)]
|
||||
[@GenerateAPI(Setter.Dirty)]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal GraphicsFormat _TextureFormat;
|
||||
|
||||
// NOTE: This MUST match the value in Constants.hlsl, as it
|
||||
// determines the size of the texture arrays in the shaders.
|
||||
internal const int k_MaximumSlices = 15;
|
||||
|
||||
// NOTE: these MUST match the values in Constants.hlsl
|
||||
// 64 recommended as a good common minimum: https://www.reddit.com/r/GraphicsProgramming/comments/aeyfkh/for_compute_shaders_is_there_an_ideal_numthreads/
|
||||
internal const int k_ThreadGroupSize = 8;
|
||||
internal const int k_ThreadGroupSizeX = k_ThreadGroupSize;
|
||||
internal const int k_ThreadGroupSizeY = k_ThreadGroupSize;
|
||||
|
||||
internal static class ShaderIDs
|
||||
{
|
||||
public static readonly int s_LodIndex = Shader.PropertyToID("_Crest_LodIndex");
|
||||
public static readonly int s_LodChange = Shader.PropertyToID("_Crest_LodChange");
|
||||
}
|
||||
|
||||
// Used for creating shader property names etc.
|
||||
internal abstract string ID { get; }
|
||||
internal virtual string Name => ID;
|
||||
|
||||
/// <summary>
|
||||
/// The requested texture format used for this simulation, either by manual mode or
|
||||
/// one of the aliases. It will be overriden if the format is incompatible with the
|
||||
/// platform (<see cref="CompatibleTextureFormat"/>).
|
||||
/// </summary>
|
||||
private protected abstract GraphicsFormat RequestedTextureFormat { get; }
|
||||
|
||||
/// <summary>
|
||||
/// This is the platform compatible texture format that will used.
|
||||
/// </summary>
|
||||
public GraphicsFormat CompatibleTextureFormat { get; private set; }
|
||||
|
||||
private protected abstract Color ClearColor { get; }
|
||||
private protected abstract bool NeedToReadWriteTextureData { get; }
|
||||
private protected abstract Inputs Inputs { get; }
|
||||
internal abstract Color GizmoColor { get; }
|
||||
internal virtual int BufferCount => 1;
|
||||
private protected virtual Texture2DArray NullTexture => BlackTextureArray;
|
||||
private protected virtual bool RequiresClearBorder => false;
|
||||
|
||||
private protected IQueryable Queryable { get; set; }
|
||||
|
||||
// This is used as alternative to Texture2D.blackTexture, as using that
|
||||
// is not possible in some shaders.
|
||||
static Texture2DArray s_BlackTextureArray = null;
|
||||
static Texture2DArray BlackTextureArray
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_BlackTextureArray == null)
|
||||
{
|
||||
s_BlackTextureArray = TextureArrayHelpers.CreateTexture2DArray(Texture2D.blackTexture, k_MaximumSlices);
|
||||
s_BlackTextureArray.name = "_Crest_LodBlackTexture";
|
||||
}
|
||||
|
||||
return s_BlackTextureArray;
|
||||
}
|
||||
}
|
||||
|
||||
static readonly GraphicsFormatUsage s_GraphicsFormatUsage =
|
||||
// Ensures a non compressed format is returned.
|
||||
GraphicsFormatUsage.LoadStore |
|
||||
// All these textures are sampled at some point.
|
||||
GraphicsFormatUsage.Sample |
|
||||
// Always use linear filtering.
|
||||
GraphicsFormatUsage.Linear;
|
||||
|
||||
internal virtual bool RunsInHeadless => false;
|
||||
|
||||
private protected BufferedData<RenderTexture> _Targets;
|
||||
internal RenderTexture DataTexture => _Targets.Current;
|
||||
internal RenderTexture GetDataTexture(int frameDelta) => _Targets.Previous(frameDelta);
|
||||
|
||||
private protected Matrix4x4[] _ViewMatrices = new Matrix4x4[k_MaximumSlices];
|
||||
private protected Cascade[] _Cascades = new Cascade[k_MaximumSlices];
|
||||
internal Cascade[] Cascades => _Cascades;
|
||||
private protected BufferedData<Vector4[]> _SamplingParameters;
|
||||
|
||||
internal int Slices => _Water.LodLevels;
|
||||
|
||||
// Currently use as a failure flag.
|
||||
private protected bool _Valid;
|
||||
|
||||
internal WaterRenderer _Water;
|
||||
internal WaterRenderer Water => _Water;
|
||||
|
||||
private protected int _TargetsToClear;
|
||||
|
||||
private protected readonly int _TextureShaderID;
|
||||
private protected readonly int _TextureSourceShaderID;
|
||||
private protected readonly int _SamplingParametersShaderID;
|
||||
private protected readonly int _SamplingParametersCascadeShaderID;
|
||||
private protected readonly int _SamplingParametersCascadeSourceShaderID;
|
||||
|
||||
readonly string _TextureName;
|
||||
|
||||
internal Lod()
|
||||
{
|
||||
// @Garbage
|
||||
var name = $"g_Crest_Cascade{ID}";
|
||||
_TextureShaderID = Shader.PropertyToID(name);
|
||||
_TextureSourceShaderID = Shader.PropertyToID($"{name}Source");
|
||||
_SamplingParametersShaderID = Shader.PropertyToID($"g_Crest_SamplingParameters{ID}");
|
||||
_SamplingParametersCascadeShaderID = Shader.PropertyToID($"g_Crest_SamplingParametersCascade{ID}");
|
||||
_SamplingParametersCascadeSourceShaderID = Shader.PropertyToID($"g_Crest_SamplingParametersCascade{ID}Source");
|
||||
|
||||
_TextureName = $"_Crest_{ID}Lod";
|
||||
}
|
||||
|
||||
private protected RenderTexture CreateLodDataTextures()
|
||||
{
|
||||
RenderTexture result = new(Resolution, Resolution, 0, CompatibleTextureFormat)
|
||||
{
|
||||
wrapMode = TextureWrapMode.Clamp,
|
||||
antiAliasing = 1,
|
||||
filterMode = FilterMode.Bilinear,
|
||||
anisoLevel = 0,
|
||||
useMipMap = false,
|
||||
name = _TextureName,
|
||||
dimension = TextureDimension.Tex2DArray,
|
||||
volumeDepth = Slices,
|
||||
enableRandomWrite = NeedToReadWriteTextureData
|
||||
};
|
||||
result.Create();
|
||||
return result;
|
||||
}
|
||||
|
||||
private protected void FlipBuffers()
|
||||
{
|
||||
if (_ReAllocateTexture)
|
||||
{
|
||||
ReAllocate();
|
||||
}
|
||||
|
||||
_Targets.Flip();
|
||||
_SamplingParameters.Flip();
|
||||
|
||||
UpdateSamplingParameters();
|
||||
}
|
||||
|
||||
private protected void Clear(RenderTexture target)
|
||||
{
|
||||
Helpers.ClearRenderTexture(target, ClearColor, depth: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears persistent LOD data. Some simulations have persistent data which can linger for a little while after
|
||||
/// being disabled. This will manually clear that data.
|
||||
/// </summary>
|
||||
internal virtual void ClearLodData()
|
||||
{
|
||||
// Empty.
|
||||
}
|
||||
|
||||
private protected virtual bool AlwaysClear => false;
|
||||
|
||||
// Only works with input-only data (ie no simulation steps).
|
||||
internal virtual void BuildCommandBuffer(WaterRenderer water, CommandBuffer buffer)
|
||||
{
|
||||
FlipBuffers();
|
||||
|
||||
buffer.BeginSample(Name);
|
||||
|
||||
if (_TargetsToClear > 0 || AlwaysClear)
|
||||
{
|
||||
buffer.SetRenderTarget(DataTexture, 0, CubemapFace.Unknown, -1);
|
||||
buffer.ClearRenderTarget(RTClearFlags.Color, ClearColor, 0, 0);
|
||||
|
||||
_TargetsToClear--;
|
||||
}
|
||||
|
||||
if (Inputs.Count > 0)
|
||||
{
|
||||
SubmitDraws(buffer, Inputs, DataTexture);
|
||||
|
||||
// Ensure all targets clear when there are no inputs.
|
||||
_TargetsToClear = _Targets.Size;
|
||||
}
|
||||
|
||||
if (RequiresClearBorder)
|
||||
{
|
||||
ClearBorder(buffer);
|
||||
}
|
||||
|
||||
Queryable?.UpdateQueries(_Water);
|
||||
|
||||
buffer.EndSample(Name);
|
||||
}
|
||||
|
||||
private protected bool SubmitDraws(CommandBuffer buffer, Inputs draws, RenderTargetIdentifier target, int pass = -1, bool filter = false)
|
||||
{
|
||||
var drawn = false;
|
||||
|
||||
foreach (var draw in draws)
|
||||
{
|
||||
var input = draw.Value;
|
||||
if (!input.Enabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pass != -1)
|
||||
{
|
||||
var p = input.Pass;
|
||||
if (p != -1 && p != pass) continue;
|
||||
}
|
||||
|
||||
var rect = input.Rect;
|
||||
|
||||
if (input.IsCompute)
|
||||
{
|
||||
var smallest = 0;
|
||||
if (rect != Rect.zero)
|
||||
{
|
||||
smallest = -1;
|
||||
for (var slice = Slices - 1; slice >= 0; slice--)
|
||||
{
|
||||
if (rect != Rect.zero && !rect.Overlaps(Cascades[slice].TexelRect)) break;
|
||||
smallest = slice;
|
||||
}
|
||||
|
||||
if (smallest < 0) continue;
|
||||
}
|
||||
|
||||
// Pass the slice count to only draw to valid slices.
|
||||
input.Draw(this, buffer, target, pass, slice: Slices - smallest);
|
||||
drawn = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var slice = Slices - 1; slice >= 0; slice--)
|
||||
{
|
||||
if (rect != Rect.zero && !rect.Overlaps(Cascades[slice].TexelRect)) break;
|
||||
|
||||
var weight = filter ? input.Filter(_Water, slice) : 1f;
|
||||
if (weight <= 0f) continue;
|
||||
|
||||
// Parameters override RTI values:
|
||||
// https://docs.unity3d.com/ScriptReference/Rendering.CommandBuffer.SetRenderTarget.html
|
||||
buffer.SetRenderTarget(target, 0, CubemapFace.Unknown, slice);
|
||||
buffer.SetGlobalInt(ShaderIDs.s_LodIndex, slice);
|
||||
|
||||
// This will work for CG but not for HDRP hlsl files.
|
||||
buffer.SetViewProjectionMatrices(_ViewMatrices[slice], _Water.GetProjectionMatrix(slice));
|
||||
|
||||
input.Draw(this, buffer, target, pass, weight, slice);
|
||||
drawn = true;
|
||||
}
|
||||
}
|
||||
|
||||
return drawn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a new origin. This is equivalent to subtracting the new origin position from any world position state.
|
||||
/// </summary>
|
||||
internal void SetOrigin(Vector3 newOrigin)
|
||||
{
|
||||
_SamplingParameters.RunLambda(data =>
|
||||
{
|
||||
for (var index = 0; index < _Water.LodLevels; index++)
|
||||
{
|
||||
// We really only care about the previous states, as the current/next frame will be
|
||||
// re-calculated. This realigns the snapped position with the now shifted camera.
|
||||
data[index].x -= newOrigin.x;
|
||||
data[index].y -= newOrigin.z;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ClearBorder(CommandBuffer buffer)
|
||||
{
|
||||
var size = Resolution / 8;
|
||||
|
||||
var wrapper = new PropertyWrapperCompute(buffer, WaterResources.Instance.Compute._Clear, 1);
|
||||
wrapper.SetTexture(Crest.ShaderIDs.s_Target, DataTexture);
|
||||
wrapper.SetVector(Crest.ShaderIDs.s_ClearColor, ClearColor);
|
||||
wrapper.SetInteger(Crest.ShaderIDs.s_Resolution, Resolution);
|
||||
wrapper.SetInteger(Crest.ShaderIDs.s_TargetSlice, Slices - 1);
|
||||
wrapper.Dispatch(size, 1, 1);
|
||||
|
||||
wrapper = new PropertyWrapperCompute(buffer, WaterResources.Instance.Compute._Clear, 2);
|
||||
wrapper.SetTexture(Crest.ShaderIDs.s_Target, DataTexture);
|
||||
wrapper.SetVector(Crest.ShaderIDs.s_ClearColor, ClearColor);
|
||||
wrapper.SetInteger(Crest.ShaderIDs.s_Resolution, Resolution);
|
||||
wrapper.SetInteger(Crest.ShaderIDs.s_TargetSlice, Slices - 1);
|
||||
wrapper.Dispatch(1, size, 1);
|
||||
}
|
||||
|
||||
void UpdateSamplingParameters()
|
||||
{
|
||||
for (var slice = 0; slice < Slices; slice++)
|
||||
{
|
||||
// Find snap period.
|
||||
var texel = 2f * 2f * _Water.CalcLodScale(slice) / Resolution;
|
||||
// Snap so that shape texels are stationary.
|
||||
var snapped = _Water.Root.position - new Vector3(Mathf.Repeat(_Water.Root.position.x, texel), 0, Mathf.Repeat(_Water.Root.position.z, texel));
|
||||
|
||||
var cascade = new Cascade(snapped.XZ(), texel, Resolution);
|
||||
_Cascades[slice] = cascade;
|
||||
_SamplingParameters.Current[slice] = cascade.Packed;
|
||||
|
||||
_ViewMatrices[slice] = WaterRenderer.CalculateViewMatrixFromSnappedPositionRHS(snapped);
|
||||
}
|
||||
|
||||
Shader.SetGlobalVector(_SamplingParametersShaderID, new(_Water.LodLevels, Resolution, 1f / Resolution, 0));
|
||||
Shader.SetGlobalVectorArray(_SamplingParametersCascadeShaderID, _SamplingParameters.Current);
|
||||
|
||||
if (BufferCount > 1)
|
||||
{
|
||||
Shader.SetGlobalVectorArray(_SamplingParametersCascadeSourceShaderID, _SamplingParameters.Previous(1));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns index of lod that completely covers the sample area. If no such lod
|
||||
/// available, returns -1.
|
||||
/// </summary>
|
||||
internal int SuggestIndex(Rect sampleArea)
|
||||
{
|
||||
for (var slice = 0; slice < Slices; slice++)
|
||||
{
|
||||
var cascade = _Cascades[slice];
|
||||
|
||||
// Shape texture needs to completely contain sample area.
|
||||
var rect = cascade.TexelRect;
|
||||
|
||||
// Shrink rect by 1 texel border - this is to make finite differences fit as well.
|
||||
var texel = cascade._Texel;
|
||||
rect.x += texel; rect.y += texel;
|
||||
rect.width -= 2f * texel; rect.height -= 2f * texel;
|
||||
|
||||
if (!rect.Contains(sampleArea.min) || !rect.Contains(sampleArea.max))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return slice;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns index of lod that completely covers the sample area, and contains
|
||||
/// wavelengths that repeat no more than twice across the smaller spatial length. If
|
||||
/// no such lod available, returns -1. This means high frequency wavelengths are
|
||||
/// filtered out, and the lod index can be used for each sample in the sample area.
|
||||
/// </summary>
|
||||
internal int SuggestIndexForWaves(Rect sampleArea)
|
||||
{
|
||||
return SuggestIndexForWaves(sampleArea, Mathf.Min(sampleArea.width, sampleArea.height));
|
||||
}
|
||||
|
||||
internal int SuggestIndexForWaves(Rect sampleArea, float minimumSpatialLength)
|
||||
{
|
||||
var count = Slices;
|
||||
|
||||
for (var index = 0; index < count; index++)
|
||||
{
|
||||
var cascade = _Cascades[index];
|
||||
|
||||
// Shape texture needs to completely contain sample area.
|
||||
var rect = cascade.TexelRect;
|
||||
|
||||
// Shrink rect by 1 texel border - this is to make finite differences fit as well.
|
||||
var texel = cascade._Texel;
|
||||
rect.x += texel; rect.y += texel;
|
||||
rect.width -= 2f * texel; rect.height -= 2f * texel;
|
||||
|
||||
if (!rect.Contains(sampleArea.min) || !rect.Contains(sampleArea.max))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// The smallest wavelengths should repeat no more than twice across the smaller
|
||||
// spatial length. Unless we're in the last LOD - then this is the best we can do.
|
||||
var minimumWavelength = _Water.MaximumWavelength(index) / 2f;
|
||||
if (minimumWavelength < minimumSpatialLength / 2f && index < count - 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bind data needed to load or compute from this simulation.
|
||||
/// </summary>
|
||||
internal virtual void Bind<T>(T target) where T : IPropertyWrapper
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
internal virtual void Initialize()
|
||||
{
|
||||
// All simulations require a GPU so do not proceed any further.
|
||||
if (_Water.IsRunningWithoutGraphics)
|
||||
{
|
||||
_Valid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Some simulations are pointless in non-interactive mode.
|
||||
if (_Water.IsRunningHeadless && !RunsInHeadless)
|
||||
{
|
||||
_Valid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate textures.
|
||||
{
|
||||
// Find a compatible texture format.
|
||||
CompatibleTextureFormat = Helpers.GetCompatibleTextureFormat(RequestedTextureFormat, s_GraphicsFormatUsage, Name, NeedToReadWriteTextureData);
|
||||
|
||||
if (CompatibleTextureFormat == GraphicsFormat.None)
|
||||
{
|
||||
Debug.Log($"Crest: Disabling {Name} simulation due to no valid available texture format.");
|
||||
_Valid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Assert(Slices <= k_MaximumSlices);
|
||||
}
|
||||
|
||||
_Valid = true;
|
||||
|
||||
Allocate();
|
||||
}
|
||||
|
||||
internal virtual void SetGlobals(bool enable)
|
||||
{
|
||||
if (_Water.IsRunningWithoutGraphics) return;
|
||||
// Bind/unbind data texture for all shaders.
|
||||
Shader.SetGlobalTexture(_TextureShaderID, enable && Enabled ? DataTexture : NullTexture);
|
||||
}
|
||||
|
||||
internal virtual void Enable()
|
||||
{
|
||||
// Blank
|
||||
}
|
||||
|
||||
internal virtual void Disable()
|
||||
{
|
||||
// Always clean up provider (CPU may be running).
|
||||
Queryable?.CleanUp();
|
||||
}
|
||||
|
||||
internal virtual void Destroy()
|
||||
{
|
||||
// Release resources and destroy object to avoid reference leak.
|
||||
_Targets?.RunLambda(x =>
|
||||
{
|
||||
if (x != null) x.Release();
|
||||
Helpers.Destroy(x);
|
||||
});
|
||||
}
|
||||
|
||||
private protected virtual void Allocate()
|
||||
{
|
||||
_Targets = new(BufferCount, CreateLodDataTextures);
|
||||
_Targets.RunLambda(Clear);
|
||||
|
||||
_SamplingParameters = new(BufferCount, () => new Vector4[k_MaximumSlices]);
|
||||
|
||||
// Bind globally once here on init, which will bind to all graphics shaders (not compute)
|
||||
Shader.SetGlobalTexture(_TextureShaderID, DataTexture);
|
||||
|
||||
_ReAllocateTexture = false;
|
||||
}
|
||||
|
||||
bool GetEnabled() => _Enabled && _Valid;
|
||||
|
||||
// NOTE: This could be called by the user due to API.
|
||||
void SetEnabled(bool previous, bool current)
|
||||
{
|
||||
if (previous == current) return;
|
||||
if (_Water == null || !_Water.isActiveAndEnabled) return;
|
||||
|
||||
if (current)
|
||||
{
|
||||
Initialize();
|
||||
Enable();
|
||||
}
|
||||
else
|
||||
{
|
||||
Disable();
|
||||
Destroy();
|
||||
}
|
||||
|
||||
SetGlobals(current);
|
||||
}
|
||||
|
||||
int GetResolution() => _OverrideResolution ? _Resolution : Water.LodResolution;
|
||||
|
||||
private protected void ReAllocate()
|
||||
{
|
||||
if (!Enabled) return;
|
||||
CompatibleTextureFormat = Helpers.GetCompatibleTextureFormat(RequestedTextureFormat, s_GraphicsFormatUsage, Name, NeedToReadWriteTextureData);
|
||||
var descriptor = _Targets.Current.descriptor;
|
||||
descriptor.height = descriptor.width = Resolution;
|
||||
descriptor.graphicsFormat = CompatibleTextureFormat;
|
||||
_Targets.RunLambda(texture =>
|
||||
{
|
||||
texture.Release();
|
||||
texture.descriptor = descriptor;
|
||||
texture.Create();
|
||||
});
|
||||
|
||||
_ReAllocateTexture = false;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[@OnChange]
|
||||
private protected virtual void OnChange(string propertyPath, object previousValue)
|
||||
{
|
||||
switch (propertyPath)
|
||||
{
|
||||
case nameof(_Enabled):
|
||||
SetEnabled((bool)previousValue, _Enabled);
|
||||
break;
|
||||
case nameof(_Resolution):
|
||||
case nameof(_OverrideResolution):
|
||||
case nameof(_TextureFormat):
|
||||
case nameof(_TextureFormatMode):
|
||||
ReAllocate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// API
|
||||
partial class Lod
|
||||
{
|
||||
bool _ReAllocateTexture;
|
||||
|
||||
void SetDirty<I>(I previous, I current) where I : System.IEquatable<I>
|
||||
{
|
||||
if (Equals(previous, current)) return;
|
||||
_ReAllocateTexture = true;
|
||||
}
|
||||
|
||||
void SetDirty(System.Enum previous, System.Enum current)
|
||||
{
|
||||
if (previous == current) return;
|
||||
_ReAllocateTexture = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base type for simulations with a provider.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The query provider.</typeparam>
|
||||
public abstract class Lod<T> : Lod where T : IQueryProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data from the GPU to CPU.
|
||||
/// </summary>
|
||||
public T Provider { get; set; }
|
||||
private protected abstract T CreateProvider(bool enable);
|
||||
|
||||
internal override void SetGlobals(bool enable)
|
||||
{
|
||||
base.SetGlobals(enable);
|
||||
// We should always have a provider (null provider if disabled).
|
||||
InitializeProvider(enable);
|
||||
}
|
||||
|
||||
private protected void InitializeProvider(bool enable)
|
||||
{
|
||||
Provider = CreateProvider(enable);
|
||||
// None providers are not IQueryable.
|
||||
Queryable = Provider as IQueryable;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d523fdd33748548f0a78d6412c5420ce
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,181 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// A persistent simulation that moves around with a displacement LOD.
|
||||
/// </summary>
|
||||
public abstract partial class PersistentLod : Lod
|
||||
{
|
||||
[Tooltip("Frequency to run the simulation, in updates per second.\n\nLower frequencies are more efficient but may lead to visible jitter or slowness.")]
|
||||
[@Range(15, 200, order = -1000)]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
private protected int _SimulationFrequency = 60;
|
||||
|
||||
static new class ShaderIDs
|
||||
{
|
||||
public static readonly int s_SimDeltaTime = Shader.PropertyToID("_Crest_SimDeltaTime");
|
||||
public static readonly int s_SimDeltaTimePrev = Shader.PropertyToID("_Crest_SimDeltaTimePrev");
|
||||
public static readonly int s_TemporaryPersistentTarget = Shader.PropertyToID("_Crest_TemporaryPersistentTarget");
|
||||
}
|
||||
|
||||
private protected override bool NeedToReadWriteTextureData => true;
|
||||
internal override int BufferCount => 2;
|
||||
|
||||
float _PreviousSubstepDeltaTime = 1f / 60f;
|
||||
|
||||
// Is this the first step since being enabled?
|
||||
private protected bool _NeedsPrewarmingThisStep = true;
|
||||
|
||||
// This is how far the simulation time is behind Unity's time.
|
||||
private protected float _TimeToSimulate = 0f;
|
||||
|
||||
internal int LastUpdateSubstepCount { get; private set; }
|
||||
|
||||
private protected abstract ComputeShader SimulationShader { get; }
|
||||
private protected abstract void GetSubstepData(float timeToSimulate, out int substeps, out float delta);
|
||||
|
||||
internal override void Initialize()
|
||||
{
|
||||
if (SimulationShader == null)
|
||||
{
|
||||
_Valid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
base.Initialize();
|
||||
|
||||
_NeedsPrewarmingThisStep = true;
|
||||
}
|
||||
|
||||
internal override void ClearLodData()
|
||||
{
|
||||
base.ClearLodData();
|
||||
_Targets.RunLambda(x => Clear(x));
|
||||
}
|
||||
|
||||
internal override void BuildCommandBuffer(WaterRenderer water, CommandBuffer buffer)
|
||||
{
|
||||
buffer.BeginSample(Name);
|
||||
|
||||
FlipBuffers();
|
||||
|
||||
var slices = water.LodLevels;
|
||||
|
||||
// How far are we behind.
|
||||
_TimeToSimulate += water.DeltaTime;
|
||||
|
||||
// Do a set of substeps to catch up.
|
||||
GetSubstepData(_TimeToSimulate, out var substeps, out var delta);
|
||||
|
||||
LastUpdateSubstepCount = substeps;
|
||||
|
||||
// Even if no steps were needed this frame, the simulation still needs to advect to
|
||||
// compensate for camera motion / water scale changes, so do a trivial substep.
|
||||
// This could be a specialised kernel that only advects, or the simulation shader
|
||||
// could have a branch for 0 delta time.
|
||||
if (substeps == 0)
|
||||
{
|
||||
substeps = 1;
|
||||
delta = 0f;
|
||||
}
|
||||
|
||||
if (substeps > 1)
|
||||
{
|
||||
// No need to clear, as the update dispatch overwrites every pixel, but finding
|
||||
// artifacts if not and there is a renderer input. Happens for foam and dynamic
|
||||
// waves. Confusing/concerning.
|
||||
buffer.GetTemporaryRT(ShaderIDs.s_TemporaryPersistentTarget, DataTexture.descriptor);
|
||||
buffer.SetRenderTarget(ShaderIDs.s_TemporaryPersistentTarget, 0, CubemapFace.Unknown, -1);
|
||||
buffer.ClearRenderTarget(RTClearFlags.Color, ClearColor, 0, 0);
|
||||
}
|
||||
|
||||
var target = new RenderTargetIdentifier(DataTexture);
|
||||
var source = new RenderTargetIdentifier(ShaderIDs.s_TemporaryPersistentTarget);
|
||||
var current = target;
|
||||
|
||||
for (var substep = 0; substep < substeps; substep++)
|
||||
{
|
||||
var isFirstStep = substep == 0;
|
||||
var frame = isFirstStep ? 1 : 0;
|
||||
var wrapper = new PropertyWrapperCompute(buffer, SimulationShader, 0);
|
||||
|
||||
// Record how much we caught up
|
||||
_TimeToSimulate -= delta;
|
||||
|
||||
// Buffers are already flipped, but we need to ping-pong for subsequent substeps.
|
||||
if (!isFirstStep)
|
||||
{
|
||||
// Use temporary target for ping-pong instead of flipping buffer. We do not want
|
||||
// to buffer substeps as they will not match buffered cascade data etc. Each buffer
|
||||
// entry must be for a single frame and substeps are "sub-frame".
|
||||
(source, target) = (target, source);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We only want to handle teleports for the first step.
|
||||
_NeedsPrewarmingThisStep = _NeedsPrewarmingThisStep || _Water._HasTeleportedThisFrame;
|
||||
}
|
||||
|
||||
// Both simulation update and input draws need delta time.
|
||||
buffer.SetGlobalFloat(ShaderIDs.s_SimDeltaTime, delta);
|
||||
buffer.SetGlobalFloat(ShaderIDs.s_SimDeltaTimePrev, _PreviousSubstepDeltaTime);
|
||||
|
||||
wrapper.SetTexture(Crest.ShaderIDs.s_Target, target);
|
||||
wrapper.SetTexture(_TextureSourceShaderID, isFirstStep ? _Targets.Previous(1) : source);
|
||||
|
||||
// Compute which LOD data we are sampling source data from. if a scale change has
|
||||
// happened this can be any LOD up or down the chain. This is only valid on the
|
||||
// first update step, after that the scale source/target data are in the right
|
||||
// places.
|
||||
wrapper.SetFloat(Lod.ShaderIDs.s_LodChange, isFirstStep ? _Water.ScaleDifferencePower2 : 0);
|
||||
|
||||
wrapper.SetVectorArray(WaterRenderer.ShaderIDs.s_CascadeDataSource, _Water._CascadeData.Previous(frame));
|
||||
wrapper.SetVectorArray(_SamplingParametersCascadeSourceShaderID, _SamplingParameters.Previous(frame));
|
||||
|
||||
SetAdditionalSimulationParameters(wrapper);
|
||||
|
||||
var threads = Resolution / k_ThreadGroupSize;
|
||||
wrapper.Dispatch(threads, threads, Slices);
|
||||
|
||||
// Only add forces if we did a step.
|
||||
if (delta > 0f)
|
||||
{
|
||||
SubmitDraws(buffer, Inputs, target);
|
||||
}
|
||||
|
||||
// The very first step since being enabled.
|
||||
_NeedsPrewarmingThisStep = false;
|
||||
_PreviousSubstepDeltaTime = delta;
|
||||
}
|
||||
|
||||
// Swap textures if needed.
|
||||
if (target != current)
|
||||
{
|
||||
buffer.CopyTexture(target, DataTexture);
|
||||
}
|
||||
|
||||
if (substeps > 1)
|
||||
{
|
||||
buffer.ReleaseTemporaryRT(ShaderIDs.s_TemporaryPersistentTarget);
|
||||
}
|
||||
|
||||
// Set the target texture as to make sure we catch the 'pong' each frame.
|
||||
Shader.SetGlobalTexture(_TextureShaderID, DataTexture);
|
||||
|
||||
buffer.EndSample(Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set any simulation specific shader parameters.
|
||||
/// </summary>
|
||||
private protected virtual void SetAdditionalSimulationParameters<T>(T properties) where T : IPropertyWrapper
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a33890a171f8e4118b7bdcd3776449d3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ebdcedc44819444f581a6345b643e8b3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 53c547efc6e8b4d068d60adf2aae4695
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,143 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Debug draw crosses in an area around the GameObject on the water surface.
|
||||
/// </summary>
|
||||
[@ExecuteDuringEditMode]
|
||||
[AddComponentMenu(Constants.k_MenuPrefixDebug + "Collision Area Visualizer")]
|
||||
sealed class CollisionAreaVisualizer : ManagedBehaviour<WaterRenderer>
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 0;
|
||||
#pragma warning restore 414
|
||||
|
||||
[Tooltip(ICollisionProvider.k_LayerTooltip)]
|
||||
[SerializeField]
|
||||
internal CollisionLayer _Layer;
|
||||
|
||||
[SerializeField]
|
||||
float _ObjectWidth = 0f;
|
||||
|
||||
[SerializeField]
|
||||
float _StepSize = 5f;
|
||||
|
||||
[SerializeField]
|
||||
int _Steps = 10;
|
||||
|
||||
[SerializeField]
|
||||
bool _UseDisplacements;
|
||||
|
||||
[SerializeField]
|
||||
bool _UseNormals;
|
||||
|
||||
float[] _ResultHeights;
|
||||
Vector3[] _ResultDisplacements;
|
||||
Vector3[] _ResultNormals;
|
||||
Vector3[] _SamplePositions;
|
||||
|
||||
private protected override System.Action<WaterRenderer> OnUpdateMethod => OnUpdate;
|
||||
void OnUpdate(WaterRenderer water)
|
||||
{
|
||||
if (water.AnimatedWavesLod.Provider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_ResultHeights == null || _ResultHeights.Length != _Steps * _Steps)
|
||||
{
|
||||
_ResultHeights = new float[_Steps * _Steps];
|
||||
}
|
||||
if (_ResultDisplacements == null || _ResultDisplacements.Length != _Steps * _Steps)
|
||||
{
|
||||
_ResultDisplacements = new Vector3[_Steps * _Steps];
|
||||
}
|
||||
if (_ResultNormals == null || _ResultNormals.Length != _Steps * _Steps)
|
||||
{
|
||||
_ResultNormals = new Vector3[_Steps * _Steps];
|
||||
|
||||
for (var i = 0; i < _ResultNormals.Length; i++)
|
||||
{
|
||||
_ResultNormals[i] = Vector3.up;
|
||||
}
|
||||
}
|
||||
if (_SamplePositions == null || _SamplePositions.Length != _Steps * _Steps)
|
||||
{
|
||||
_SamplePositions = new Vector3[_Steps * _Steps];
|
||||
}
|
||||
|
||||
var collProvider = water.AnimatedWavesLod.Provider;
|
||||
|
||||
for (var i = 0; i < _Steps; i++)
|
||||
{
|
||||
for (var j = 0; j < _Steps; j++)
|
||||
{
|
||||
_SamplePositions[j * _Steps + i] = new(((i + 0.5f) - _Steps / 2f) * _StepSize, 0f, ((j + 0.5f) - _Steps / 2f) * _StepSize);
|
||||
_SamplePositions[j * _Steps + i].x += transform.position.x;
|
||||
_SamplePositions[j * _Steps + i].z += transform.position.z;
|
||||
}
|
||||
}
|
||||
|
||||
if (_UseDisplacements)
|
||||
{
|
||||
if (collProvider.RetrieveSucceeded(collProvider.Query(GetHashCode(), _ObjectWidth, _SamplePositions, _ResultDisplacements, _UseNormals ? _ResultNormals : null, null, _Layer)))
|
||||
{
|
||||
for (var i = 0; i < _Steps; i++)
|
||||
{
|
||||
for (var j = 0; j < _Steps; j++)
|
||||
{
|
||||
var result = _SamplePositions[j * _Steps + i];
|
||||
result.y = water.SeaLevel;
|
||||
result += _ResultDisplacements[j * _Steps + i];
|
||||
|
||||
var norm = _UseNormals ? _ResultNormals[j * _Steps + i] : Vector3.up;
|
||||
|
||||
DebugDrawCross(result, norm, Mathf.Min(_StepSize / 4f, 1f), Color.green);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (collProvider.RetrieveSucceeded(collProvider.Query(GetHashCode(), _ObjectWidth, _SamplePositions, _ResultHeights, _UseNormals ? _ResultNormals : null, null, _Layer)))
|
||||
{
|
||||
for (var i = 0; i < _Steps; i++)
|
||||
{
|
||||
for (var j = 0; j < _Steps; j++)
|
||||
{
|
||||
var result = _SamplePositions[j * _Steps + i];
|
||||
result.y = _ResultHeights[j * _Steps + i];
|
||||
|
||||
var norm = _UseNormals ? _ResultNormals[j * _Steps + i] : Vector3.up;
|
||||
|
||||
DebugDrawCross(result, norm, Mathf.Min(_StepSize / 4f, 1f), Color.green);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DebugDrawCross(Vector3 pos, float r, Color col, float duration = 0f)
|
||||
{
|
||||
Debug.DrawLine(pos - Vector3.up * r, pos + Vector3.up * r, col, duration);
|
||||
Debug.DrawLine(pos - Vector3.right * r, pos + Vector3.right * r, col, duration);
|
||||
Debug.DrawLine(pos - Vector3.forward * r, pos + Vector3.forward * r, col, duration);
|
||||
}
|
||||
|
||||
public static void DebugDrawCross(Vector3 pos, Vector3 up, float r, Color col, float duration = 0f)
|
||||
{
|
||||
up.Normalize();
|
||||
var right = Vector3.Normalize(Vector3.Cross(up, Vector3.forward));
|
||||
var forward = Vector3.Cross(up, right);
|
||||
Debug.DrawLine(pos - up * r, pos + up * r, col, duration);
|
||||
Debug.DrawLine(pos - right * r, pos + right * r, col, duration);
|
||||
Debug.DrawLine(pos - forward * r, pos + forward * r, col, duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 02e47243a77e64c84b8a2d351386a074
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,82 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
// NOTE: DWP2 depends on this file. Any API changes need to be communicated to the DWP2 authors in advance.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
// Linter does not support mixing inheritdoc plus defining own parameters.
|
||||
#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// A layer/event where queries are executed.
|
||||
/// </summary>
|
||||
public enum CollisionLayer
|
||||
{
|
||||
/// <summary>
|
||||
/// Include all displacement.
|
||||
/// </summary>
|
||||
[Tooltip("Include all displacement.")]
|
||||
Everything,
|
||||
|
||||
/// <summary>
|
||||
/// Only include Animated Waves.
|
||||
/// </summary>
|
||||
[Tooltip("Only include Animated Waves.")]
|
||||
AfterAnimatedWaves,
|
||||
|
||||
/// <summary>
|
||||
/// Include Animated Waves and Dynamic Waves.
|
||||
/// </summary>
|
||||
[Tooltip("Include Animated Waves and Dynamic Waves.")]
|
||||
AfterDynamicWaves,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for an object that returns water surface displacement and height.
|
||||
/// </summary>
|
||||
public interface ICollisionProvider : IQueryProvider
|
||||
{
|
||||
internal const string k_LayerTooltip = "Which water collision layer to target.";
|
||||
|
||||
internal static NoneProvider None { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gives a flat, still water.
|
||||
/// </summary>
|
||||
internal sealed class NoneProvider : ICollisionProvider
|
||||
{
|
||||
public int Query(int _0, float _1, Vector3[] _2, Vector3[] result0, Vector3[] result1, Vector3[] result2, CollisionLayer _3 = CollisionLayer.Everything)
|
||||
{
|
||||
if (result0 != null) System.Array.Fill(result0, Vector3.zero);
|
||||
if (result1 != null) System.Array.Fill(result1, Vector3.up);
|
||||
if (result2 != null) System.Array.Fill(result2, Vector3.zero);
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int Query(int _0, float _1, Vector3[] _2, float[] result0, Vector3[] result1, Vector3[] result2, CollisionLayer _3 = CollisionLayer.Everything)
|
||||
{
|
||||
if (result0 != null) System.Array.Fill(result0, WaterRenderer.Instance.SeaLevel);
|
||||
if (result1 != null) System.Array.Fill(result1, Vector3.up);
|
||||
if (result2 != null) System.Array.Fill(result2, Vector3.zero);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query water physical data at a set of points. Pass in null to any out parameters that are not required.
|
||||
/// </summary>
|
||||
/// <param name="heights">Resulting heights (displacement Y + sea level) at the query positions. Pass null if this information is not required.</param>
|
||||
/// <param name="normals">Resulting normals at the query positions. Pass null if this information is not required.</param>
|
||||
/// <param name="velocities">Resulting velocities at the query positions. Pass null if this information is not required.</param>
|
||||
/// <inheritdoc cref="IQueryProvider.Query(int, float, Vector3[], int)" />
|
||||
int Query(int hash, float minimumLength, Vector3[] points, float[] heights, Vector3[] normals, Vector3[] velocities, CollisionLayer layer = CollisionLayer.Everything);
|
||||
|
||||
/// <param name="displacements">Resulting displacement vectors at the query positions. Add sea level to Y to get world space height.</param>
|
||||
/// <inheritdoc cref="IQueryProvider.Query(int, float, Vector3[], int)" />
|
||||
/// <inheritdoc cref="Query(int, float, Vector3[], float[], Vector3[], Vector3[], CollisionLayer)" />
|
||||
int Query(int hash, float minimumLength, Vector3[] points, Vector3[] displacements, Vector3[] normals, Vector3[] velocities, CollisionLayer layer = CollisionLayer.Everything);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 55075bf334a1445828a0ad179248fe9c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,148 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Samples water surface shape - displacement, height, normal, velocity.
|
||||
/// </summary>
|
||||
sealed class CollisionQuery : QueryBase, ICollisionProvider
|
||||
{
|
||||
public CollisionQuery(WaterRenderer water) : base(water) { }
|
||||
protected override int Kernel => 0;
|
||||
|
||||
public int Query(int ownerHash, float minSpatialLength, Vector3[] queryPoints, Vector3[] resultDisplacements, Vector3[] resultNormals, Vector3[] resultVelocities, CollisionLayer layer = CollisionLayer.Everything)
|
||||
{
|
||||
var result = (int)QueryStatus.OK;
|
||||
|
||||
if (!UpdateQueryPoints(ownerHash, minSpatialLength, queryPoints, resultNormals != null ? queryPoints : null))
|
||||
{
|
||||
result |= (int)QueryStatus.PostFailed;
|
||||
}
|
||||
|
||||
if (!RetrieveResults(ownerHash, resultDisplacements, null, resultNormals))
|
||||
{
|
||||
result |= (int)QueryStatus.RetrieveFailed;
|
||||
}
|
||||
|
||||
if (resultVelocities != null)
|
||||
{
|
||||
result |= CalculateVelocities(ownerHash, resultVelocities);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public int Query(int ownerHash, float minimumSpatialLength, Vector3[] queryPoints, float[] resultHeights, Vector3[] resultNormals, Vector3[] resultVelocities, CollisionLayer layer = CollisionLayer.Everything)
|
||||
{
|
||||
var result = (int)QueryStatus.OK;
|
||||
|
||||
if (!UpdateQueryPoints(ownerHash, minimumSpatialLength, queryPoints, resultNormals != null ? queryPoints : null))
|
||||
{
|
||||
result |= (int)QueryStatus.PostFailed;
|
||||
}
|
||||
|
||||
if (!RetrieveResults(ownerHash, null, resultHeights, resultNormals))
|
||||
{
|
||||
result |= (int)QueryStatus.RetrieveFailed;
|
||||
}
|
||||
|
||||
if (resultVelocities != null)
|
||||
{
|
||||
result |= CalculateVelocities(ownerHash, resultVelocities);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
sealed class CollisionQueryWithPasses : ICollisionProvider, IQueryable
|
||||
{
|
||||
readonly CollisionQuery _AnimatedWaves;
|
||||
readonly CollisionQuery _DynamicWaves;
|
||||
readonly CollisionQuery _Displacement;
|
||||
readonly WaterRenderer _Water;
|
||||
|
||||
public int ResultGuidCount => _AnimatedWaves.ResultGuidCount + _DynamicWaves.ResultGuidCount + _Displacement.ResultGuidCount;
|
||||
public int RequestCount => _AnimatedWaves.RequestCount + _DynamicWaves.RequestCount + _Displacement.RequestCount;
|
||||
public int QueryCount => _AnimatedWaves.QueryCount + _DynamicWaves.QueryCount + _Displacement.QueryCount;
|
||||
|
||||
public CollisionQueryWithPasses(WaterRenderer water)
|
||||
{
|
||||
_Water = water;
|
||||
_AnimatedWaves = new(water);
|
||||
_DynamicWaves = new(water);
|
||||
_Displacement = new(water);
|
||||
}
|
||||
|
||||
// Gets the provider for the given layer, falling back to previous layer when needed.
|
||||
CollisionQuery GetProvider(CollisionLayer layer)
|
||||
{
|
||||
var layers = _Water.AnimatedWavesLod._CollisionLayers;
|
||||
|
||||
if (layers == CollisionLayers.Nothing)
|
||||
{
|
||||
return _Displacement;
|
||||
}
|
||||
|
||||
if (layer == CollisionLayer.Everything)
|
||||
{
|
||||
if (layers.HasFlag(CollisionLayers.Displacement))
|
||||
{
|
||||
return _Displacement;
|
||||
}
|
||||
}
|
||||
|
||||
if (layer >= CollisionLayer.AfterDynamicWaves)
|
||||
{
|
||||
if (layers.HasFlag(CollisionLayers.DynamicWaves) && _Water.DynamicWavesLod.Enabled)
|
||||
{
|
||||
return _DynamicWaves;
|
||||
}
|
||||
}
|
||||
|
||||
return _AnimatedWaves;
|
||||
}
|
||||
|
||||
public int Query(int hash, float minimumLength, Vector3[] points, float[] heights, Vector3[] normals, Vector3[] velocities, CollisionLayer layer = CollisionLayer.Everything)
|
||||
{
|
||||
return GetProvider(layer).Query(hash, minimumLength, points, heights, normals, velocities);
|
||||
}
|
||||
|
||||
public int Query(int hash, float minimumLength, Vector3[] points, Vector3[] displacements, Vector3[] normals, Vector3[] velocities, CollisionLayer layer = CollisionLayer.Everything)
|
||||
{
|
||||
return GetProvider(layer).Query(hash, minimumLength, points, displacements, normals, velocities);
|
||||
}
|
||||
|
||||
public void UpdateQueries(WaterRenderer water, CollisionLayer layer)
|
||||
{
|
||||
switch (layer)
|
||||
{
|
||||
case CollisionLayer.Everything: _Displacement.UpdateQueries(water); break;
|
||||
case CollisionLayer.AfterAnimatedWaves: _AnimatedWaves.UpdateQueries(water); break;
|
||||
case CollisionLayer.AfterDynamicWaves: _DynamicWaves.UpdateQueries(water); break;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateQueries(WaterRenderer water)
|
||||
{
|
||||
_Displacement.UpdateQueries(water);
|
||||
}
|
||||
|
||||
public void CleanUp()
|
||||
{
|
||||
_AnimatedWaves.CleanUp();
|
||||
_DynamicWaves.CleanUp();
|
||||
_Displacement.CleanUp();
|
||||
}
|
||||
}
|
||||
|
||||
static partial class Extensions
|
||||
{
|
||||
public static void UpdateQueries(this ICollisionProvider self, WaterRenderer water, CollisionLayer layer) => (self as CollisionQueryWithPasses)?.UpdateQueries(water, layer);
|
||||
public static void UpdateQueries(this ICollisionProvider self, WaterRenderer water) => (self as IQueryable)?.UpdateQueries(water);
|
||||
public static void CleanUp(this ICollisionProvider self) => (self as IQueryable)?.CleanUp();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3cddd187674954ce1a0e2e19017a744a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,98 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper to trace a ray against the water surface.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Works by sampling at a set of points along the ray and interpolating the
|
||||
/// intersection location.
|
||||
/// </remarks>
|
||||
public sealed class RayCastHelper : Internal.SampleHelper
|
||||
{
|
||||
readonly float _RayStepSize;
|
||||
readonly float _MinimumLength;
|
||||
|
||||
/// <summary>
|
||||
/// The length of the ray and the step size must be given here. The smaller the step size, the greater the accuracy.
|
||||
/// </summary>
|
||||
/// <param name="rayLength">Length of the ray.</param>
|
||||
/// <param name="rayStepSize">Size of the step. With length the number of steps is computed.</param>
|
||||
public RayCastHelper(float rayLength, float rayStepSize = 2f) : base(ComputeQueryCount(rayLength, ref rayStepSize))
|
||||
{
|
||||
_RayStepSize = rayStepSize;
|
||||
// Waves go max double along min length. Thats too much - only allow half of a wave per step.
|
||||
_MinimumLength = _RayStepSize * 4f;
|
||||
}
|
||||
|
||||
static int ComputeQueryCount(float rayLength, ref float rayStepSize)
|
||||
{
|
||||
Debug.Assert(rayLength > 0f);
|
||||
Debug.Assert(rayStepSize > 0f);
|
||||
|
||||
var stepCount = Mathf.CeilToInt(rayLength / rayStepSize) + 1;
|
||||
|
||||
var maxStepCount = 128;
|
||||
if (stepCount > maxStepCount)
|
||||
{
|
||||
stepCount = maxStepCount;
|
||||
rayStepSize = rayLength / (stepCount - 1f);
|
||||
Debug.LogWarning($"Crest: RayTraceHelper: ray steps exceed maximum ({maxStepCount}), step size increased to {rayStepSize} to reduce step count.");
|
||||
}
|
||||
|
||||
return stepCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call this once each frame to do the query.
|
||||
/// </summary>
|
||||
/// <param name="origin">World space position of ray origin</param>
|
||||
/// <param name="direction">World space ray direction</param>
|
||||
/// <param name="distance">The distance along the ray to the first intersection with the water surface.</param>
|
||||
/// <param name="layer">The layer this ray targets.</param>
|
||||
/// <returns>True if the results have come back from the GPU, and if the ray intersects the water surface.</returns>
|
||||
public bool RayCast(Vector3 origin, Vector3 direction, out float distance, CollisionLayer layer = CollisionLayer.Everything)
|
||||
{
|
||||
distance = -1f;
|
||||
|
||||
Validate(allowMultipleCallsPerFrame: false);
|
||||
|
||||
var water = WaterRenderer.Instance;
|
||||
var provider = water == null ? null : water.AnimatedWavesLod.Provider;
|
||||
if (provider == null) return false;
|
||||
|
||||
for (var i = 0; i < _QueryPosition.Length; i++)
|
||||
{
|
||||
_QueryPosition[i] = origin + i * _RayStepSize * direction;
|
||||
}
|
||||
|
||||
var status = provider.Query(GetHashCode(), _MinimumLength, _QueryPosition, _QueryResult, null, null, layer);
|
||||
|
||||
if (!provider.RetrieveSucceeded(status))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now that data is available, compare the height of the water to the height of each point of the ray. If
|
||||
// the ray crosses the surface, the distance to the intersection is interpolated from the heights.
|
||||
for (var i = 1; i < _QueryPosition.Length; i++)
|
||||
{
|
||||
var height0 = _QueryResult[i - 1].y + water.SeaLevel - _QueryPosition[i - 1].y;
|
||||
var height1 = _QueryResult[i].y + water.SeaLevel - _QueryPosition[i].y;
|
||||
|
||||
if (Mathf.Sign(height0) != Mathf.Sign(height1))
|
||||
{
|
||||
var prop = Mathf.Abs(height0) / (Mathf.Abs(height0) + Mathf.Abs(height1));
|
||||
distance = (i - 1 + prop) * _RayStepSize;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return distance >= 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f1f458d6962ed46be8f00bdff45b2725
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,44 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Debug draw a line trace from this gameobjects position, in this gameobjects forward direction.
|
||||
/// </summary>
|
||||
[@ExecuteDuringEditMode]
|
||||
[AddComponentMenu(Constants.k_MenuPrefixDebug + "Ray Cast Visualizer")]
|
||||
sealed class RayTraceVisualizer : ManagedBehaviour<WaterRenderer>
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 0;
|
||||
#pragma warning restore 414
|
||||
|
||||
readonly RayCastHelper _RayCast = new(50f, 2f);
|
||||
|
||||
private protected override System.Action<WaterRenderer> OnUpdateMethod => OnUpdate;
|
||||
void OnUpdate(WaterRenderer water)
|
||||
{
|
||||
if (water.AnimatedWavesLod.Provider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Even if only a single ray trace is desired, this still must be called every frame until it returns true
|
||||
if (_RayCast.RayCast(transform.position, transform.forward, out var dist))
|
||||
{
|
||||
var endPos = transform.position + transform.forward * dist;
|
||||
Debug.DrawLine(transform.position, endPos, Color.green);
|
||||
CollisionAreaVisualizer.DebugDrawCross(endPos, 2f, Color.green, 0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.DrawLine(transform.position, transform.position + transform.forward * 50f, Color.red);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 159faa53a32b34aff9d79e974c14d100
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9f84066852dc4c839cf39679aa347db
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
// Linter does not support mixing inheritdoc plus defining own parameters.
|
||||
#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for an object that returns water depth and distance to water edge.
|
||||
/// </summary>
|
||||
public interface IDepthProvider : IQueryProvider
|
||||
{
|
||||
internal static NoneProvider None { get; } = new();
|
||||
|
||||
internal sealed class NoneProvider : IDepthProvider
|
||||
{
|
||||
public int Query(int _0, float _1, Vector3[] _2, Vector3[] result)
|
||||
{
|
||||
if (result != null) System.Array.Clear(result, 0, result.Length);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query water depth data at a set of points.
|
||||
/// </summary>
|
||||
/// <param name="results">Water depth and distance to shoreline (XY respectively). Both are signed.</param>
|
||||
/// <inheritdoc cref="IQueryProvider.Query(int, float, Vector3[], int)" />
|
||||
int Query(int hash, float minimumLength, Vector3[] points, Vector3[] results);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8c8c76a0ab417499bb25ecea61560108
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user