Files
3d-bxqz/Packages/com.waveharmonic.crest.shallow-water/Runtime/Scripts/ShallowWaterSimulation.cs
zhangjiajia 81ffaaeca6 'push'
2026-05-06 16:56:59 +08:00

1870 lines
80 KiB
C#

// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
using WaveHarmonic.Crest.Internal;
namespace WaveHarmonic.Crest.ShallowWater
{
/// <summary>
/// <see cref="ShallowWaterSimulation"/>'s update mode.
/// </summary>
[@GenerateDoc]
public enum ShallowWaterSimulationMode
{
/// <inheritdoc cref="Generated.ShallowWaterSimulationMode.RealTime"/>
[Tooltip("Simulate every frame.")]
[InspectorName("Real-Time")]
RealTime,
/// <inheritdoc cref="Generated.ShallowWaterSimulationMode.Baked"/>
[Tooltip("Simulation output has been baked to a texture.")]
Baked,
}
/// <summary>
/// Where to inject the displacement.
/// </summary>
[@GenerateDoc]
public enum ShallowWaterSimulationInjection
{
/// <inheritdoc cref="Generated.ShallowWaterSimulationInjection.Waves"/>
[Tooltip("Inject into the Animated Waves.")]
Waves = 1,
/// <inheritdoc cref="Generated.ShallowWaterSimulationInjection.Level"/>
[Tooltip("Inject into Water Level.")]
Level = 2,
}
/// <summary>
/// Preset options for the <see cref="ShallowWaterSimulation"/>
/// </summary>
[@GenerateDoc]
public enum ShallowWaterSimulationPreset
{
/// <inheritdoc cref="Generated.ShallowWaterSimulationPreset.None"/>
[Tooltip("All options available.")]
None,
/// <inheritdoc cref="Generated.ShallowWaterSimulationPreset.Shoreline"/>
[Tooltip("Only shows options for shorelines.")]
Shoreline,
/// <inheritdoc cref="Generated.ShallowWaterSimulationPreset.Stream"/>
[Tooltip("Only shows options for streams.")]
Stream,
}
/// <summary>
/// Runs a shallow water simulation at global sea level in a domain around its transform,
/// and injects the results of the simulation into the water data.
/// </summary>
[@ExecuteDuringEditMode]
[@HelpURL("Packages/ShallowWater/Manual.html")]
[AddComponentMenu(Constants.k_MenuPrefixInputs + "Shallow Water Simulation")]
public sealed partial class ShallowWaterSimulation : ManagedBehaviour<WaterRenderer>
{
[SerializeField, HideInInspector]
#pragma warning disable 414
int _Version = 0;
#pragma warning restore 414
[Group("General", Group.Style.Accordian)]
[Tooltip("Preconfigures the simulation for a specific use case.\n\nRequires resetting the simulation.")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@GenerateAPI(Setter.Custom)]
[@DecoratedField, SerializeField]
ShallowWaterSimulationPreset _Preset;
[@Space(10)]
[Tooltip("The width of the simulation area (m).\n\nEnable gizmos to see a wireframe outline of the domain. Requires resetting the simulation.")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@Range(8, 1024)]
[@GenerateAPI]
[SerializeField]
internal float _Width = 64f;
[Tooltip("The depth of the water in the shallow water simulation (m).\n\nAny underwater surfaces deeper than this depth will not influence the sim. Large values can lead to instabilities / jitter in the result. Requires resetting the simulation.")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@Range(0.1f, 16.0f)]
[@GenerateAPI]
[SerializeField]
float _Depth = 2f;
[Tooltip("Where the simulation is placed.\n\nThe default performs the best.")]
[@Predicated(nameof(_InjectShape), inverted: true, nameof(ShallowWaterSimulationInjection.Waves), hide: true)]
[@ShowComputedProperty(nameof(Placement))]
[@GenerateAPI(Getter.Custom)]
[@DecoratedField, SerializeField]
Placement _Placement;
[Tooltip("Places the simulation at sea level.\n\nRequires resetting the simulation.")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
bool _PlaceAtSeaLevel = true;
[Tooltip("Friction applied to water to prevent dampen velocities.")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
float _Friction = 0.02f;
[Tooltip("Recompute ground heights every frame.\n\nOnly enable this if terrain used by water system changes at runtime.")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
bool _DynamicSeabed = false;
[Tooltip("Whether to sample the Depth Probe directly.\n\nIf disabled then data will come from the LODs which will have precision losses depending on camera position. Only disable if necessary. Requires resetting the simulation.")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
internal bool _SampleDepthProbeDirectly = true;
[Tooltip("Whether to sample water level inputs directly.\n\nIf disabled then data will come from the LODs which will have precision losses depending on camera position. Only disable if necessary. This option only supports water level inputs using the Geometry, Spline or Renderer modes. Requires resetting the simulation.")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@Predicated(nameof(_InjectShape), inverted: true, nameof(ShallowWaterSimulationInjection.Level), hide: true)]
[@GenerateAPI(Getter.Custom)]
[@DecoratedField, SerializeField]
internal bool _SampleWaterLevelInputsDirectly = true;
[@Space(10)]
[Tooltip("Adds meters of additional water into the simulation domain on initialization.\n\nRequires resetting the simulation.")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
float _AdditionalWater;
[Tooltip("Rate at which to remove water at the boundaries of the domain.\n\nUseful for preventing buildup of water when simulating shoreline waves.")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
float _DrainWaterAtBoundaries = -0.01f;
[Tooltip("Rate at which to remove water at any location.\n\nUseful for removing remaining water from lowering tides.")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@Range(0.0f, 0.001f, Range.Clamp.Minimum)]
[@GenerateAPI]
[SerializeField]
float _Evaporation;
[@Heading("Source Blending")]
[@Label("Depth Range")]
[Tooltip("Affects depth-based blending of the simulation and animated waves (m).\n\nFor the minimum, when the water depth is less than this value, animated waves will not contribute at all, water shape will come purely from this simulation.\n\nFor the maximum, when the water depth is greater than this value, this simulation will not contribute at all, water shape will come purely from the normal ocean waves.\n\nNegative depths are valid and occur when surfaces are above sea level.")]
[@Predicated(nameof(_InjectShape), inverted: false, nameof(ShallowWaterSimulationInjection.Level), hide: true)]
[@Range(-10f, 10f)]
[@GenerateAPI]
[SerializeField]
Vector2 _BlendDepthRange = new(0f, 4f);
[@Label("Push Up Strength")]
[Tooltip("The intensity at which waves inject water into the simulation.")]
[@Predicated(nameof(_InjectShape), inverted: false, nameof(ShallowWaterSimulationInjection.Level), hide: true)]
[@Range(0f, 1f, Range.Clamp.Minimum)]
[@GenerateAPI]
[SerializeField]
float _BlendPushUpStrength = 0.1f;
[Heading("Distance Culling")]
[@Label("Enable")]
[Tooltip("Disable simulation when viewpoint far from domain.")]
[@Predicated(nameof(_InjectShape), inverted: false, nameof(ShallowWaterSimulationInjection.Level), hide: true)]
[@Predicated(nameof(_Placement), inverted: true, nameof(Placement.Fixed))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
bool _EnableDistanceCulling = true;
[Tooltip("Disable simulation if viewpoint is more than this distance outside simulation domain.")]
[@Predicated(nameof(_InjectShape), inverted: false, nameof(ShallowWaterSimulationInjection.Level), hide: true)]
[@Predicated(nameof(_Placement), inverted: true, nameof(Placement.Fixed))]
[@Predicated(nameof(_EnableDistanceCulling))]
[@Range(1f, 1024f)]
[@GenerateAPI]
[SerializeField]
float _CullDistance = 75.0f;
[Tooltip("The speed of the transition to its culled state.")]
[@Predicated(nameof(_InjectShape), inverted: false, nameof(ShallowWaterSimulationInjection.Level), hide: true)]
[@Predicated(nameof(_Placement), inverted: true, nameof(Placement.Fixed))]
[@Predicated(nameof(_EnableDistanceCulling))]
[@Range(0.01f, 1f)]
[@GenerateAPI]
[SerializeField]
float _CullTransitionSpeed = 0.5f;
[Group("Stability", Group.Style.Accordian)]
[Tooltip("Time step used for simulation (s).\n\nSmaller values can make simulation more stable but requires more runtime computation.")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@Range(0.001f, 0.03333333f)]
[@GenerateAPI]
[SerializeField]
float _TimeStep = 0.01f;
[Tooltip("Stability measure - limits velocities. Default 0.5.")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
float _CourantNumber = 0.5f;
[Tooltip("Overshoot is artifacts where the water will spike sharply.")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@Range(0.1f, 0.5f)]
[@GenerateAPI]
[SerializeField]
float _OvershootReductionStrength = 0.25f;
[Group("Quality", Group.Style.Accordian)]
[Tooltip("Simulation resolution - width of simulation grid cell (m).\n\nSmaller values will increase resolution but take more computation time and memory, and may lead to instabilities for small values. Requires resetting the simulation.")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@Range(0.01f, 2f)]
[@GenerateAPI]
[SerializeField]
float _TexelSize = 32f / 512f;
[Tooltip("Maximum resolution of simulation grid.\n\nSafety limit to avoid simulation using large amount of video memory. Requires resetting the simulation.")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@Range(16, 4096)]
[@GenerateAPI]
[SerializeField]
int _MaximumResolution = 1024;
[@Space(10)]
[Tooltip("Filters the shape prior to rendering to smooth out sharp features.\n\nAlways enabled when baking.")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
bool _BlurShapeForRender = true;
[Tooltip("Filters the mask.")]
[@Predicated(nameof(_InjectShape), inverted: false, nameof(ShallowWaterSimulationInjection.Level), hide: true)]
[@Range(0, 64)]
[@GenerateAPI]
[SerializeField]
int _BlurMaskIterations = 10;
[@Heading("Water Edge Margin")]
[Tooltip("Adds a margin around the water level to prevent water from degenerating at a distance and prevents water wall creep (px).")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@Label("Enable")]
[@GenerateAPI]
[@DecoratedField, SerializeField]
bool _WaterEdgeMargin = true;
[@Label("Margin Width")]
[Tooltip("Width of the margin.\n\nThe default is the optimal width.")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@Range(1, 64)]
[@GenerateAPI]
[SerializeField]
int _WaterEdgeMarginWidth = 4;
[@Label("Margin Width (Baked)")]
[Tooltip("Same as Margin Width but for baking only.\n\nWill use the larger value of the two.")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@Predicated(nameof(_InjectShape), inverted: true, nameof(ShallowWaterSimulationInjection.Level))]
[@Range(1, 64)]
[@GenerateAPI]
[SerializeField]
#pragma warning disable CS0414
int _WaterEdgeMarginBakedWidth = 32;
#pragma warning restore CS0414
[Group("Output", Group.Style.Accordian)]
[Tooltip("Whether to use the baked textures.\n\nOnly supported by water level.")]
[@Predicated(nameof(_InjectShape), inverted: false, nameof(ShallowWaterSimulationInjection.Waves), hide: false)]
[@GenerateAPI]
[@DecoratedField, SerializeField]
internal ShallowWaterSimulationMode _Mode = ShallowWaterSimulationMode.RealTime;
[@Heading("Shape", alwaysEnabled: true)]
[@Label("Inject")]
[Tooltip("Add the resulting shape to the water system.\n\nIf blending data from waves or height then use Waves or Level respectively. Requires resetting the simulation.")]
[@Predicated(nameof(_Preset), inverted: true, nameof(ShallowWaterSimulationPreset.None), hide: false)]
[@GenerateAPI]
[@DecoratedField, SerializeField]
internal ShallowWaterSimulationInjection _InjectShape = ShallowWaterSimulationInjection.Waves;
[@Label("Baked Texture")]
[Tooltip("The baked texture for water level.")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.RealTime))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
Texture2D _LevelTexture;
[@Label("Report Maximum Displacement")]
[Tooltip("Inform water how much this input will displace the water surface vertically.\n\nThis is used for culling water tiles.")]
[@GenerateAPI]
[@DecoratedField, SerializeField]
float _MaximumDisplacementVertical = 0f;
[@Label("Domain Padding")]
[Tooltip("Padding area for zero activity at the edge of the domain.\n\nAdds padding so that simulation edge can overlap with height inputs without causing a seam (m). It is important that the padding fully overlaps the water level input, or there may be a gap. Visualized by the gizmo.")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@Predicated(nameof(_InjectShape), inverted: true, nameof(ShallowWaterSimulationInjection.Level))]
[@Range(0f, 10f)]
[@GenerateAPI]
[SerializeField]
float _PaddingWidth = 2f;
[@Heading("Flow")]
[@Label("Inject")]
[Tooltip("Add the resulting flow velocities to the water system.")]
[@GenerateAPI]
[@DecoratedField, SerializeField]
internal bool _InjectFlow = true;
[@Label("Baked Texture")]
[Tooltip("The baked texture for flow.")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.RealTime))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
Texture2D _FlowTexture;
[@Label("Strength")]
[Tooltip("Multiplies flow output to scale when injecting.")]
[@Predicated(nameof(_InjectFlow))]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@Range(0, 10f, Range.Clamp.Minimum)]
[@GenerateAPI]
[SerializeField]
float _FlowStrength = 1f;
[@Heading("Foam")]
[@Label("Inject")]
[Tooltip("Add the resulting foam to the water system.")]
[@GenerateAPI]
[@DecoratedField, SerializeField]
internal bool _InjectFoam = true;
[@Label("Baked Texture")]
[Tooltip("The baked texture for foam.")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.RealTime))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
Texture2D _FoamTexture;
[@Label("Strength")]
[Tooltip("Multiplies foam output to scale when injecting.")]
[@Predicated(nameof(_InjectFoam))]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@Range(0, 10f, Range.Clamp.Minimum)]
[@GenerateAPI]
[SerializeField]
float _FoamStrength = 1f;
[@Group("Debug", isCustomFoldout: true)]
[@DecoratedField(isCustomFoldout: true), SerializeField]
internal DebugFields _Debug = new();
[System.Serializable]
internal sealed class DebugFields
{
[@Heading("Stages")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@DecoratedField, SerializeField]
public bool _SkipUpdate;
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@DecoratedField, SerializeField]
public bool _SkipUpdateHeight;
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@DecoratedField, SerializeField]
public bool _SkipUpdateVelocities;
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@DecoratedField, SerializeField]
public bool _SkipAdvect;
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@DecoratedField, SerializeField]
public bool _SkipAdvectHeights;
[@Heading("Stability")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@DecoratedField, SerializeField]
public bool _DisableStabilityImprovements;
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@DecoratedField, SerializeField]
public bool _DisableMacCormackScheme;
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@DecoratedField, SerializeField]
public bool _DisableMacCormackSchemeForHeight;
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@DecoratedField, SerializeField]
public bool _DisableUpwindHeight;
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@DecoratedField, SerializeField]
public bool _DisableDepthLimiter;
[Tooltip("Overshoot is artifacts where the water will spike sharply.")]
[@Predicated(nameof(_Mode), inverted: false, nameof(ShallowWaterSimulationMode.Baked))]
[@DecoratedField, SerializeField]
public bool _DisableOvershootReduction;
[@Heading("Baking")]
[@Label("Execute Simulation")]
[Tooltip("Useful for rebaking.")]
[@DecoratedField, SerializeField]
public bool _AlwaysExecuteSimulation;
[@Heading("Output")]
[@DecoratedField, SerializeField]
public bool _SkipInjectShape;
[@Heading("Debug Overlay")]
[@DecoratedField, SerializeField]
public bool _ShowSimulationData = false;
#if !CREST_DEBUG
[HideInInspector]
#endif
[@DecoratedField, SerializeField]
public SimulationData _ShowSimulationDataInScene;
public enum SimulationData
{
None,
Ground,
Height,
VelocityX,
VelocityY,
Mask,
WaterLevel,
}
}
const string k_DrawSWS = "SWS";
static class ShaderIDs
{
// Shader properties.
public static readonly int s_DomainWidth = Shader.PropertyToID("_Crest_DomainWidth");
public static readonly int s_DomainOrigin = Shader.PropertyToID("_Crest_DomainOrigin");
public static readonly int s_TexelSize = Shader.PropertyToID("_Crest_TexelSize");
public static readonly int s_SimulationDeltaTime = Shader.PropertyToID("_Crest_SimulationDeltaTime");
public static readonly int s_AddAdditionalWater = Shader.PropertyToID("_Crest_AddAdditionalWater");
public static readonly int s_DrainWaterAtBoundaries = Shader.PropertyToID("_Crest_DrainWaterAtBoundaries");
public static readonly int s_Evaporation = Shader.PropertyToID("_Crest_Evaporation");
public static readonly int s_Friction = Shader.PropertyToID("_Crest_Friction");
public static readonly int s_MaximumVelocity = Shader.PropertyToID("_Crest_MaximumVelocity");
public static readonly int s_ShallowMinimumDepth = Shader.PropertyToID("_Crest_ShallowMinimumDepth");
public static readonly int s_ShallowMaximumDepth = Shader.PropertyToID("_Crest_ShallowMaximumDepth");
public static readonly int s_BlendPushUpStrength = Shader.PropertyToID("_Crest_BlendPushUpStrength");
public static readonly int s_MacCormackAdvection = Shader.PropertyToID("_Crest_MacCormackAdvection");
public static readonly int s_MacCormackAdvectionForHeight = Shader.PropertyToID("_Crest_MacCormackAdvectionForHeight");
public static readonly int s_UpwindHeight = Shader.PropertyToID("_Crest_UpwindHeight");
public static readonly int s_DepthLimiter = Shader.PropertyToID("_Crest_DepthLimiter");
public static readonly int s_OvershootReductionStrength = Shader.PropertyToID("_Crest_OvershootReductionStrength");
public static readonly int s_InjectLevel = Shader.PropertyToID("_Crest_InjectLevel");
public static readonly int s_MarginWidth = Shader.PropertyToID("_Crest_MarginWidth");
// Injection
public static readonly int s_InjectionStrength = Shader.PropertyToID("_Crest_InjectionStrength");
public static readonly int s_InjectionMaximum = Shader.PropertyToID("_Crest_InjectionMaximum");
public static readonly int s_SampleFromDepthProbe = Shader.PropertyToID("_Crest_SampleFromDepthProbe");
public static readonly int s_SampleFromLevelInputs = Shader.PropertyToID("_Crest_SampleFromLevelInputs");
// Baking
public static readonly int s_FoamBakeTarget = Shader.PropertyToID("_Crest_FoamBakeTarget");
public static readonly int s_FlowBakeTarget = Shader.PropertyToID("_Crest_FlowBakeTarget");
public static readonly int s_LevelBakeTarget = Shader.PropertyToID("_Crest_LevelBakeTarget");
// Simulation textures
public static readonly int s_GroundHeightSource = Shader.PropertyToID("_Crest_GroundHeightSource");
public static readonly int s_GroundHeightTarget = Shader.PropertyToID("_Crest_GroundHeightTarget");
public static readonly int s_HeightSource = Shader.PropertyToID("_Crest_HeightSource");
public static readonly int s_HeightTarget = Shader.PropertyToID("_Crest_HeightTarget");
public static readonly int s_VelocityXSource = Shader.PropertyToID("_Crest_VelocityXSource");
public static readonly int s_VelocityXTarget = Shader.PropertyToID("_Crest_VelocityXTarget");
public static readonly int s_VelocityYSource = Shader.PropertyToID("_Crest_VelocityYSource");
public static readonly int s_VelocityYTarget = Shader.PropertyToID("_Crest_VelocityYTarget");
public static readonly int s_MaskSource = Shader.PropertyToID("_Crest_MaskSource");
public static readonly int s_MaskTarget = Shader.PropertyToID("_Crest_MaskTarget");
public static readonly int s_LevelSource = Shader.PropertyToID("_Crest_LevelSource");
// External
public static readonly int s_Height = Shader.PropertyToID("_Crest_Height");
public static readonly int s_GroundHeight = Shader.PropertyToID("_Crest_GroundHeight");
public static readonly int s_VelocityX = Shader.PropertyToID("_Crest_VelocityX");
public static readonly int s_VelocityY = Shader.PropertyToID("_Crest_VelocityY");
public static readonly int s_ShapeTarget = Shader.PropertyToID("_Crest_ShapeTarget");
public static readonly int s_LevelTarget = Shader.PropertyToID("_Crest_LevelTarget");
public static readonly int s_FlowTarget = Shader.PropertyToID("_Crest_FlowTarget");
public static readonly int s_FoamTarget = Shader.PropertyToID("_Crest_FoamTarget");
public static readonly int s_Temporary0 = Shader.PropertyToID("_Crest_Temporary0");
public static readonly int s_Temporary1 = Shader.PropertyToID("_Crest_Temporary1");
}
static readonly List<ShallowWaterSimulation> s_Instances = new();
internal static ShallowWaterSimulation Get(Rect rect)
{
foreach (var instance in s_Instances)
{
// Return first.
if (instance.Rect.Overlaps(rect)) return instance;
}
return null;
}
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
static void OnDomainReload()
{
s_Instances.Clear();
}
internal int Resolution { get; private set; } = -1;
float _ActualTexelSize;
// The final height to output (including blur).
internal RenderTexture _Height0;
// Keeps track of height data. Becomes the source for the next frame.
internal RenderTexture _Height1;
internal RenderTexture _VelocityX;
internal RenderTexture _VelocityY;
internal RenderTexture _Mask;
internal RenderTexture _GroundHeight;
internal RenderTexture _WaterLevel;
CommandBuffer _CommandBuffer;
ComputeShader _ComputeShader;
int _KernelInitialize;
int _KernelInitializeGroundHeight;
int _KernelAdvect;
int _KernelUpdateHeight;
int _KernelHeightOvershootReduction;
int _KernelUpdateVelocity;
int _KernelBlurHeight;
int _KernelBlur;
int _KernelMaskEdge;
int _KernelExpandEdge;
int _KernelFinalizeHeight;
int _KernelInjectShape;
int _KernelInjectLevel;
int _KernelInjectFoam;
int _KernelInjectFlow;
int _KernelBakeFoam;
int _KernelBakeFlow;
int _KernelBakeLevel;
float _TimeToSimulate = 0f;
bool _FirstUpdate = true;
bool _UpdateGroundHeight;
Vector3 _PreviousOrigin;
Vector2Int _PreviousTexel;
internal DepthProbe _DepthProbe;
float _Weight = 1f;
#pragma warning disable CS0414
bool _Allocated;
#pragma warning restore CS0414
internal ShallowWaterSimulationInjection DisplacementTarget => _InjectShape;
internal bool Movable => _Placement != Placement.Fixed;
// Currently sim will be placed _depth below global sea level
internal Vector3 Origin
{
get
{
var result = transform.position;
var water = WaterRenderer.Instance;
if (_Placement == Placement.Viewpoint && water != null)
{
result = water.Viewpoint.position.XNZ(transform.position.y);
}
if (Movable)
{
result.x = Mathf.Round(result.x / _ActualTexelSize) * _ActualTexelSize;
result.z = Mathf.Round(result.z / _ActualTexelSize) * _ActualTexelSize;
}
if (water != null && (!water.LevelLod.Enabled || _PlaceAtSeaLevel))
{
result.y = water.transform.position.y;
}
// Sim origin is at 'bottom' of domain, water height/ground height are added
result.y -= _Depth;
return result;
}
}
void UpdateResolution()
{
Resolution = Mathf.CeilToInt(_Width / _TexelSize);
Resolution = Mathf.Min(Resolution, _MaximumResolution);
_ActualTexelSize = _Width / Resolution;
}
void Allocate()
{
if (_Allocated)
{
return;
}
if (_Mode == ShallowWaterSimulationMode.Baked && !_Debug._AlwaysExecuteSimulation)
{
return;
}
if (_ComputeShader == null)
{
_ComputeShader = WaterResources.Instance.Compute._UpdateSWS;
_KernelInitialize = _ComputeShader.FindKernel("CrestInitialize");
_KernelInitializeGroundHeight = _ComputeShader.FindKernel("CrestInitializeGroundHeight");
_KernelAdvect = _ComputeShader.FindKernel("CrestAdvect");
_KernelUpdateHeight = _ComputeShader.FindKernel("CrestUpdateHeight");
_KernelHeightOvershootReduction = _ComputeShader.FindKernel("CrestHeightOvershootReduction");
_KernelUpdateVelocity = _ComputeShader.FindKernel("CrestUpdateVelocity");
_KernelBlurHeight = _ComputeShader.FindKernel("CrestBlurHeight");
_KernelBlur = _ComputeShader.FindKernel("CrestBlur");
_KernelMaskEdge = _ComputeShader.FindKernel("CrestMaskEdge");
_KernelExpandEdge = _ComputeShader.FindKernel("CrestExpandEdge");
_KernelFinalizeHeight = _ComputeShader.FindKernel("CrestFinalizeHeight");
_KernelInjectShape = _ComputeShader.FindKernel("CrestInjectShape");
_KernelInjectLevel = _ComputeShader.FindKernel("CrestInjectLevel");
_KernelInjectFlow = _ComputeShader.FindKernel("CrestInjectFlow");
_KernelInjectFoam = _ComputeShader.FindKernel("CrestInjectFoam");
_KernelBakeFoam = _ComputeShader.FindKernel("CrestBakeFoam");
_KernelBakeFlow = _ComputeShader.FindKernel("CrestBakeFlow");
_KernelBakeLevel = _ComputeShader.FindKernel("CrestBakeLevel");
}
if (_SampleDepthProbeDirectly && TryGetComponent(out _DepthProbe))
{
_DepthProbe.Managed = true;
_DepthProbe.Scale = new(_Width, _Width);
_DepthProbe.OverridePosition = Movable;
}
// Empty constructer will prevent texture from flipping correctly.
var descriptor = new RenderTextureDescriptor(0, 0)
{
height = Resolution,
width = Resolution,
dimension = TextureDimension.Tex2D,
volumeDepth = 1,
enableRandomWrite = true,
colorFormat = RenderTextureFormat.RFloat,
msaaSamples = 1,
mipCount = 2,
};
var mipped = descriptor;
mipped.useMipMap = true;
mipped.autoGenerateMips = false;
Helpers.SafeCreateRenderTexture("_CrestGroundHeight", ref _GroundHeight, descriptor);
Helpers.SafeCreateRenderTexture("_CrestHeight0", ref _Height0, descriptor);
Helpers.SafeCreateRenderTexture("_CrestHeight1", ref _Height1, descriptor);
Helpers.SafeCreateRenderTexture("_CrestVelocityX", ref _VelocityX, mipped);
Helpers.SafeCreateRenderTexture("_CrestVelocityY", ref _VelocityY, mipped);
Helpers.SafeCreateRenderTexture("_CrestMask", ref _Mask, descriptor);
if (SampleWaterLevelInputsDirectly)
{
descriptor.enableRandomWrite = false;
Helpers.SafeCreateRenderTexture("_Crest_ShallowWaterLevel", ref _WaterLevel, descriptor);
}
_Allocated = true;
}
void Release()
{
if (_Height0 != null) _Height0.Release();
if (_Height1 != null) _Height1.Release();
if (_VelocityX != null) _VelocityX.Release();
if (_VelocityY != null) _VelocityY.Release();
if (_GroundHeight != null) _GroundHeight.Release();
if (_Mask != null) _Mask.Release();
if (_WaterLevel != null) _WaterLevel.Release();
if (_SampleDepthProbeDirectly && _DepthProbe != null) _DepthProbe.Managed = false;
_FirstUpdate = true;
_Allocated = false;
}
void Destroy()
{
Helpers.Destroy(_Height0);
Helpers.Destroy(_Height1);
Helpers.Destroy(_VelocityX);
Helpers.Destroy(_VelocityY);
Helpers.Destroy(_GroundHeight);
Helpers.Destroy(_Mask);
}
/// <summary>
/// Resets the simulation. Needs to be called after certain property changes.
/// </summary>
/// <param name="populateDepthProbe">Whether to also re-populate/re-bake the <see cref="DepthProbe"/>, if applicable.</param>
public void ResetSimulation(bool populateDepthProbe = false)
{
if (populateDepthProbe && _SampleDepthProbeDirectly && (_DepthProbe != null || TryGetComponent(out _DepthProbe)))
{
_DepthProbe.Managed = true;
_DepthProbe.Scale = new(_Width, _Width);
_DepthProbe.OverridePosition = Movable;
_DepthProbe.Populate();
}
Release();
Allocate();
}
void InitializeSimulation(CommandBuffer buffer, RenderTargetIdentifier xVelocity, RenderTargetIdentifier yVelocity)
{
// Initialize simulation data - water heights and velocities.
var wrapper = new PropertyWrapperCompute(buffer, _ComputeShader, _KernelInitialize);
wrapper.SetTexture(ShaderIDs.s_GroundHeightSource, _GroundHeight);
wrapper.SetTexture(ShaderIDs.s_HeightTarget, _Height1);
wrapper.SetTexture(ShaderIDs.s_VelocityXTarget, xVelocity);
wrapper.SetTexture(ShaderIDs.s_VelocityYTarget, yVelocity);
wrapper.SetTexture(ShaderIDs.s_LevelSource, SampleWaterLevelInputsDirectly ? _WaterLevel : Texture2D.blackTexture);
wrapper.Dispatch((_Height1.width + 7) / 8, (_Height1.height + 7) / 8, 1);
}
void Execute(WaterRenderer water, CommandBuffer buffer)
{
#if UNITY_EDITOR
if (_Dirty)
{
return;
}
#endif
buffer.BeginSample(k_DrawSWS);
Allocate();
// Safety first.
_TimeStep = Mathf.Max(_TimeStep, 0.001f);
// Compute substeps.
_TimeToSimulate += water.DeltaTime;
var steps = _TimeToSimulate > 0f ? Mathf.CeilToInt(_TimeToSimulate / _TimeStep) : 0;
_TimeToSimulate -= steps * _TimeStep;
var xVelocityTarget = new RenderTargetIdentifier(_VelocityX);
var yVelocityTarget = new RenderTargetIdentifier(_VelocityY);
var origin = Origin;
// NOTE: Initialisation of everything happens in update because it requires Crest to be initialised for
// sea floor depth and numerous other state.
if (_FirstUpdate)
{
_PreviousOrigin = origin;
_PreviousTexel = new
(
Mathf.RoundToInt(_PreviousOrigin.x * (_GroundHeight.width / _Width)),
Mathf.RoundToInt(_PreviousOrigin.z * (_GroundHeight.height / _Width))
);
var wrapper = new PropertyWrapperCompute(buffer, _ComputeShader, 0);
wrapper.SetInteger(Crest.ShaderIDs.s_Resolution, _GroundHeight.width);
wrapper.SetVector(ShaderIDs.s_DomainOrigin, origin);
wrapper.SetFloat(ShaderIDs.s_DomainWidth, _Width);
wrapper.SetFloat(ShaderIDs.s_TexelSize, _ActualTexelSize);
wrapper.SetFloat(ShaderIDs.s_AddAdditionalWater, Mathf.Max(0f, _AdditionalWater));
wrapper.SetBoolean(ShaderIDs.s_InjectLevel, _InjectShape == ShallowWaterSimulationInjection.Level);
wrapper.SetBoolean(ShaderIDs.s_SampleFromLevelInputs, SampleWaterLevelInputsDirectly);
if (SampleWaterLevelInputsDirectly)
{
RenderLocalWaterLevel(water);
}
PopulateGroundHeight(buffer);
InitializeSimulation(buffer, xVelocityTarget, yVelocityTarget);
_FirstUpdate = false;
}
// Set once per frame properties.
{
// Initialize property wrapper.
// Use any kernel as the below value types don't require a kernel.
var wrapper = new PropertyWrapperCompute(buffer, _ComputeShader, 0);
var resolution = _GroundHeight.width;
wrapper.SetInteger(Crest.ShaderIDs.s_Resolution, resolution);
wrapper.SetFloat(ShaderIDs.s_SimulationDeltaTime, _TimeStep);
wrapper.SetFloat(ShaderIDs.s_DomainWidth, _Width);
wrapper.SetFloat(ShaderIDs.s_DrainWaterAtBoundaries, _DrainWaterAtBoundaries);
wrapper.SetFloat(ShaderIDs.s_Evaporation, _Evaporation);
wrapper.SetFloat(ShaderIDs.s_Friction, _Friction);
wrapper.SetFloat(ShaderIDs.s_MaximumVelocity, _CourantNumber * _ActualTexelSize / _TimeStep);
wrapper.SetFloat(ShaderIDs.s_TexelSize, _ActualTexelSize);
wrapper.SetFloat(ShaderIDs.s_BlendPushUpStrength, _InjectShape == ShallowWaterSimulationInjection.Waves ? _BlendPushUpStrength : 1f);
wrapper.SetVector(ShaderIDs.s_DomainOrigin, origin);
wrapper.SetBoolean(ShaderIDs.s_MacCormackAdvection, !_Debug._DisableStabilityImprovements && !_Debug._DisableMacCormackScheme);
wrapper.SetBoolean(ShaderIDs.s_MacCormackAdvectionForHeight, !_Debug._DisableStabilityImprovements && !_Debug._DisableMacCormackSchemeForHeight);
wrapper.SetBoolean(ShaderIDs.s_UpwindHeight, !_Debug._DisableStabilityImprovements && !_Debug._DisableUpwindHeight);
wrapper.SetBoolean(ShaderIDs.s_DepthLimiter, !_Debug._DisableStabilityImprovements && !_Debug._DisableDepthLimiter);
wrapper.SetFloat(ShaderIDs.s_OvershootReductionStrength, _OvershootReductionStrength);
}
// Create temporaries for velocity and anything else after steps.
var temporary0 = new RenderTargetIdentifier(ShaderIDs.s_Temporary0);
var temporary1 = new RenderTargetIdentifier(ShaderIDs.s_Temporary1);
var xVelocitySource = temporary0;
var yVelocitySource = temporary1;
var move = Movable && _PreviousOrigin != origin;
if (move || _DynamicSeabed || _UpdateGroundHeight)
{
// Populate ground height every frame to allow dynamic scene.
PopulateGroundHeight(buffer);
}
if (move || steps > 0)
{
// No need to clear, as overwritten (Initialize or Advect).
buffer.GetTemporaryRT(ShaderIDs.s_Temporary0, _GroundHeight.descriptor);
buffer.GetTemporaryRT(ShaderIDs.s_Temporary1, _GroundHeight.descriptor);
}
if (move)
{
(_Height0, _Height1) = (_Height1, _Height0);
(xVelocitySource, xVelocityTarget, yVelocitySource, yVelocityTarget) = (xVelocityTarget, xVelocitySource, yVelocityTarget, yVelocitySource);
// Initialize temporary textures so revived areas has water.
InitializeSimulation(buffer, xVelocityTarget, yVelocityTarget);
var texel = new Vector2Int
(
Mathf.RoundToInt(origin.x * (_GroundHeight.width / _Width)),
Mathf.RoundToInt(origin.z * (_GroundHeight.height / _Width))
);
var offset = texel - _PreviousTexel;
var source = new RectInt
(
Mathf.Clamp(offset.x, 0, _Height1.width - 1),
Mathf.Clamp(offset.y, 0, _Height1.height - 1),
Mathf.Clamp(_Height1.width - Mathf.Abs(offset.x), 0, _Height1.width),
Mathf.Clamp(_Height1.height - Mathf.Abs(offset.y), 0, _Height1.height)
);
var target = new RectInt
(
Mathf.Clamp(-offset.x, 0, _Height1.width - 1),
Mathf.Clamp(-offset.y, 0, _Height1.height - 1),
source.width,
source.height
);
if (source.width > 0 && source.height > 0)
{
// Swapped. So copying height source (without blur etc).
buffer.CopyTexture(_Height0, 0, 0, source.x, source.y, source.width, source.height, _Height1, 0, 0, target.x, target.y);
buffer.CopyTexture(xVelocitySource, 0, 0, source.x, source.y, source.width, source.height, xVelocityTarget, 0, 0, target.x, target.y);
buffer.CopyTexture(yVelocitySource, 0, 0, source.x, source.y, source.width, source.height, yVelocityTarget, 0, 0, target.x, target.y);
}
// Move completed!
_PreviousOrigin = origin;
_PreviousTexel = texel;
}
// Nothing to do.
if (!move && (steps <= 0 || _Debug._SkipUpdate))
{
buffer.EndSample(k_DrawSWS);
return;
}
for (var i = 0; i < steps; i++)
{
// Each stage block should leave latest state in "Target" buffer.
// Advect
if (!_Debug._SkipAdvect)
{
(xVelocitySource, xVelocityTarget, yVelocitySource, yVelocityTarget) = (xVelocityTarget, xVelocitySource, yVelocityTarget, yVelocitySource);
if (!_Debug._SkipAdvectHeights)
{
(_Height1, _Height0) = (_Height0, _Height1);
}
var wrapper = new PropertyWrapperCompute(buffer, _ComputeShader, _KernelAdvect);
wrapper.SetTexture(ShaderIDs.s_HeightSource, _Height0);
wrapper.SetTexture(ShaderIDs.s_HeightTarget, _Height1);
wrapper.SetTexture(ShaderIDs.s_VelocityXSource, xVelocitySource);
wrapper.SetTexture(ShaderIDs.s_VelocityXTarget, xVelocityTarget);
wrapper.SetTexture(ShaderIDs.s_VelocityYSource, yVelocitySource);
wrapper.SetTexture(ShaderIDs.s_VelocityYTarget, yVelocityTarget);
wrapper.Dispatch((_Height1.width + 7) / 8, (_Height1.height + 7) / 8, _Debug._SkipAdvectHeights ? 2 : 3);
}
// Update H
if (!_Debug._SkipUpdateHeight)
{
(_Height0, _Height1) = (_Height1, _Height0);
var wrapper = new PropertyWrapperCompute(buffer, _ComputeShader, _KernelUpdateHeight);
wrapper.SetTexture(ShaderIDs.s_VelocityXSource, xVelocityTarget);
wrapper.SetTexture(ShaderIDs.s_VelocityYSource, yVelocityTarget);
wrapper.SetTexture(ShaderIDs.s_MaskSource, _Mask);
wrapper.SetTexture(ShaderIDs.s_GroundHeightSource, _GroundHeight);
wrapper.SetTexture(ShaderIDs.s_HeightSource, _Height0);
wrapper.SetTexture(ShaderIDs.s_HeightTarget, _Height1);
wrapper.SetTexture(ShaderIDs.s_LevelSource, SampleWaterLevelInputsDirectly ? _WaterLevel : Texture2D.blackTexture);
wrapper.Dispatch((_Height1.width + 7) / 8, (_Height1.height + 7) / 8, 1);
}
// H overshoot reduction
if (!_Debug._DisableOvershootReduction)
{
(_Height0, _Height1) = (_Height1, _Height0);
var wrapper = new PropertyWrapperCompute(buffer, _ComputeShader, _KernelHeightOvershootReduction);
wrapper.SetTexture(ShaderIDs.s_GroundHeightSource, _GroundHeight);
wrapper.SetTexture(ShaderIDs.s_HeightSource, _Height0);
wrapper.SetTexture(ShaderIDs.s_HeightTarget, _Height1);
wrapper.Dispatch((_Height1.width + 7) / 8, (_Height1.height + 7) / 8, 1);
}
// Update vels
if (!_Debug._SkipUpdateVelocities)
{
var wrapper = new PropertyWrapperCompute(buffer, _ComputeShader, _KernelUpdateVelocity);
wrapper.SetTexture(ShaderIDs.s_HeightSource, _Height1);
wrapper.SetTexture(ShaderIDs.s_GroundHeightSource, _GroundHeight);
wrapper.SetTexture(ShaderIDs.s_VelocityXTarget, xVelocityTarget);
wrapper.SetTexture(ShaderIDs.s_VelocityYTarget, yVelocityTarget);
wrapper.Dispatch((_Height1.width + 7) / 8, (_Height1.height + 7) / 8, 1);
}
}
//
// Post-Steps
//
if (!_Debug._SkipUpdateVelocities)
{
if (xVelocityTarget != _VelocityX)
{
buffer.CopyTexture(xVelocityTarget, 0, 0, _VelocityX, 0, 0);
buffer.CopyTexture(yVelocityTarget, 0, 0, _VelocityY, 0, 0);
}
// This is important. Without this, there is aliasing in the injected vels which, in collaboration
// with the flow in the combine pass which has large period, makes annoying pops. Perhaps an
// alternative would be to just not add flow from SWS to big cascades. I tried something like this
// and it did not seem to help for me, so going with this.
buffer.GenerateMips(_VelocityX);
buffer.GenerateMips(_VelocityY);
}
// Blur H postprocess to smooth out render data - only needs to be done once after any simulation updates
if (_BlurShapeForRender)
{
BlurHeight(buffer);
}
if (_WaterEdgeMargin)
{
ExpandEdge(buffer, _WaterEdgeMarginWidth, _BlurShapeForRender);
}
else
{
// Finalize height by adding ground, origin etc similar to Expand Edge kernel.
var source = new RenderTargetIdentifier(_Height1);
var target = new RenderTargetIdentifier(_Height0);
if (_BlurShapeForRender)
{
source = target;
target = temporary0;
}
var wrapper = new PropertyWrapperCompute(buffer, _ComputeShader, _KernelFinalizeHeight);
wrapper.SetTexture(ShaderIDs.s_GroundHeightSource, _GroundHeight);
wrapper.SetTexture(ShaderIDs.s_HeightSource, source);
wrapper.SetTexture(ShaderIDs.s_HeightTarget, target);
wrapper.Dispatch((_Height0.width + 7) / 8, (_Height0.height + 7) / 8, 1);
if (_BlurShapeForRender)
{
buffer.CopyTexture(target, _Height0);
}
}
buffer.ReleaseTemporaryRT(ShaderIDs.s_Temporary0);
buffer.ReleaseTemporaryRT(ShaderIDs.s_Temporary1);
buffer.EndSample("SWS");
}
void BlurHeight(CommandBuffer buffer)
{
var wrapper = new PropertyWrapperCompute(buffer, _ComputeShader, _KernelBlurHeight);
// Cheekily write to H0, but dont flip. This is a temporary result purely for rendering.
// Next update will flip and overwrite this.
wrapper.SetTexture(ShaderIDs.s_HeightSource, _Height1);
wrapper.SetTexture(ShaderIDs.s_HeightTarget, _Height0);
wrapper.SetTexture(ShaderIDs.s_GroundHeightSource, _GroundHeight);
wrapper.Dispatch((_Height0.width + 7) / 8, (_Height0.height + 7) / 8, 1);
}
void ExpandEdge(CommandBuffer buffer, int iterations, bool blurred)
{
// This will always be available.
var id = ShaderIDs.s_Temporary0;
// Expand edge is performed on the final output (including blur).
var source = new RenderTargetIdentifier(blurred ? _Height0 : _Height1);
var target = new RenderTargetIdentifier(id);
// Mask above ground water.
{
var wrapper = new PropertyWrapperCompute(buffer, _ComputeShader, _KernelMaskEdge);
wrapper.SetTexture(ShaderIDs.s_GroundHeightSource, _GroundHeight);
wrapper.SetTexture(ShaderIDs.s_HeightSource, source);
wrapper.SetTexture(ShaderIDs.s_HeightTarget, target);
wrapper.Dispatch((_Height0.width + 7) / 8, (_Height0.height + 7) / 8, 1);
}
// Height0 is the final target so always use it for ping-pong.
source = new RenderTargetIdentifier(_Height0);
// Expand edge.
{
var wrapper = new PropertyWrapperCompute(buffer, _ComputeShader, _KernelExpandEdge);
wrapper.SetTexture(ShaderIDs.s_GroundHeightSource, _GroundHeight);
for (var i = 0; i < iterations; i++)
{
(source, target) = (target, source);
wrapper.SetTexture(ShaderIDs.s_HeightSource, source);
wrapper.SetTexture(ShaderIDs.s_HeightTarget, target);
wrapper.Dispatch((_Height0.width + 7) / 8, (_Height0.height + 7) / 8, 1);
}
}
if (target == id)
{
buffer.CopyTexture(id, _Height0);
}
}
void PopulateGroundHeight(CommandBuffer buffer)
{
var wrapper = new PropertyWrapperCompute(buffer, _ComputeShader, _KernelInitializeGroundHeight);
wrapper.SetTexture(ShaderIDs.s_GroundHeightTarget, _GroundHeight);
wrapper.SetTexture(ShaderIDs.s_MaskTarget, _Mask);
wrapper.SetFloat(ShaderIDs.s_ShallowMinimumDepth, _BlendDepthRange.x);
wrapper.SetFloat(ShaderIDs.s_ShallowMaximumDepth, _BlendDepthRange.y);
wrapper.SetBoolean(ShaderIDs.s_SampleFromDepthProbe, _SampleDepthProbeDirectly);
if (_SampleDepthProbeDirectly && _DepthProbe != null)
{
_DepthProbe.Bind(wrapper);
}
else
{
wrapper.SetTexture(DepthProbe.ShaderIDs.s_DepthProbe, Texture2D.blackTexture);
}
wrapper.Dispatch((_GroundHeight.width + 7) / 8, (_GroundHeight.height + 7) / 8, 1);
// Blur simulation mask.
if (_BlurMaskIterations > 0 && _InjectShape == ShallowWaterSimulationInjection.Waves)
{
var temporary = ShaderIDs.s_Temporary0;
// No need to clear, as overwritten.
buffer.GetTemporaryRT(ShaderIDs.s_Temporary0, _Mask.descriptor);
var source = new RenderTargetIdentifier(_Mask);
var target = new RenderTargetIdentifier(temporary);
wrapper = new(buffer, _ComputeShader, _KernelBlur);
for (var i = 0; i < _BlurMaskIterations; i++)
{
wrapper.SetTexture(ShaderIDs.s_MaskSource, source);
wrapper.SetTexture(ShaderIDs.s_MaskTarget, target);
(source, target) = (target, source);
wrapper.Dispatch((_Mask.width + 7) / 8, (_Mask.height + 7) / 8, 1);
}
buffer.ReleaseTemporaryRT(temporary);
}
_UpdateGroundHeight = false;
}
internal void SetUniforms(Material material)
{
material.SetTexture(ShaderIDs.s_GroundHeight, _GroundHeight);
material.SetTexture(ShaderIDs.s_MaskSource, _Mask);
// If blurring is enabled, apply the blurred height which was put into H0 until next frame overwrites
material.SetTexture(ShaderIDs.s_Height, _Height1);
material.SetTexture(ShaderIDs.s_VelocityX, _VelocityX);
material.SetTexture(ShaderIDs.s_VelocityY, _VelocityY);
material.SetVector(ShaderIDs.s_DomainOrigin, Origin);
material.SetFloat(ShaderIDs.s_DomainWidth, Width);
}
//
// Input / Behaviour
//
// ILodInput
bool Enabled
{
get
{
var compute = WaterResources.Instance.Compute;
var disabled = _Mode != ShallowWaterSimulationMode.Baked
? _ComputeShader == null || _Weight <= 0f
|| _SampleDepthProbeDirectly && _DepthProbe == null
: compute._LevelTexture == null
|| _InjectFlow && compute._FlowTexture == null
|| _InjectFoam && compute._FoamTexture == null;
return !disabled;
}
}
internal Rect Rect => new(Origin.XZ() - new Vector2(_Width, _Width) * 0.5f, new Vector2(_Width, _Width));
private protected override void Initialize()
{
base.Initialize();
UpdateResolution();
_Input ??= new(this);
Allocate();
// Register injectors.
#if !UNITY_EDITOR
if (_InjectShape == ShallowWaterSimulationInjection.Waves)
#endif
{
ILodInput.Attach(_Input, AnimatedWavesLod.s_Inputs);
}
#if !UNITY_EDITOR
else
#endif
{
ILodInput.Attach(_Input, LevelLod.s_Inputs);
}
ILodInput.Attach(_Input, FlowLod.s_Inputs);
ILodInput.Attach(_Input, FoamLod.s_Inputs);
#if CREST_DEBUG
if (_Debug._ShowSimulationDataInScene != DebugFields.SimulationData.None)
{
ILodInput.Attach(_Input, ClipLod.s_Inputs);
}
#endif
s_Instances.Remove(this);
s_Instances.Add(this);
}
private protected override void OnDisable()
{
base.OnDisable();
// If scene reload is disabled then OnDestroy is skipped.
if (!Application.isPlaying)
{
Release();
}
ILodInput.Detach(_Input, AnimatedWavesLod.s_Inputs);
ILodInput.Detach(_Input, LevelLod.s_Inputs);
ILodInput.Detach(_Input, FlowLod.s_Inputs);
ILodInput.Detach(_Input, FoamLod.s_Inputs);
#if CREST_DEBUG
if (_Debug._ShowSimulationDataInScene != DebugFields.SimulationData.None)
{
ILodInput.Detach(_Input, ClipLod.s_Inputs);
}
#endif
s_Instances.Remove(this);
}
void OnDestroy()
{
Release();
Destroy();
}
private protected override System.Action<WaterRenderer> OnUpdateMethod => OnUpdate;
void OnUpdate(WaterRenderer water)
{
Culling(water);
// Runs before Execute. Overrides DepthProbe.Start data.
// Dynamic Seabed will execute the DepthProbe unlike when Fixed.
if (Movable && _SampleDepthProbeDirectly && _DepthProbe != null && (_DynamicSeabed || _PreviousOrigin != Origin))
{
_DepthProbe.Position = Origin;
_DepthProbe.Scale = new(_Width, _Width);
_DepthProbe.Populate();
}
if (Enabled && _MaximumDisplacementVertical > 0f)
{
water.ReportMaximumDisplacement(0f, _MaximumDisplacementVertical, 0f);
}
#if UNITY_EDITOR
if (!Movable && transform.hasChanged && !_FirstUpdate)
{
RequestDelayedReset(populateDepthProbe: true);
}
else if (_Dirty)
{
_DirtyTime -= Time.deltaTime;
if (_DirtyTime <= 0f)
{
ResetSimulation(_DirtyDepthProbe);
_Dirty = false;
_DirtyDepthProbe = false;
}
}
#endif
}
private protected override System.Action<WaterRenderer> OnLateUpdateMethod => OnLateUpdate;
void OnLateUpdate(WaterRenderer water)
{
transform.hasChanged = false;
}
private protected override System.Action<WaterRenderer> OnEnableMethod => Culling;
void Culling(WaterRenderer water)
{
if (_EnableDistanceCulling && _InjectShape != ShallowWaterSimulationInjection.Level && Placement == Placement.Fixed)
{
var cullRadius = _Width * 0.5f + _CullDistance;
var offset = water.Viewpoint.position - transform.position;
var culled = !(offset.sqrMagnitude < cullRadius * cullRadius);
// Transition to culled state.
_Weight += Time.deltaTime * _CullTransitionSpeed * (culled ? -1f : 1f);
_Weight = Mathf.Clamp01(_Weight);
if (_Allocated && _Weight <= 0f)
{
Release();
}
}
else
{
_Weight = 1f;
}
}
void RenderLocalWaterLevel(WaterRenderer water)
{
_CommandBuffer ??= new();
_CommandBuffer.Clear();
// Parameters override RTI values:
// https://docs.unity3d.com/ScriptReference/Rendering.CommandBuffer.SetRenderTarget.html
_CommandBuffer.SetRenderTarget(_WaterLevel, 0, CubemapFace.Unknown, 0);
_CommandBuffer.ClearRenderTarget(false, true, Color.black);
// This will work for CG but not for HDRP hlsl files.
var scale = _Width * 0.5f;
_CommandBuffer.SetViewProjectionMatrices
(
WaterRenderer.CalculateViewMatrixFromSnappedPositionRHS(Origin),
// 20000 = above + below sea level.
Matrix4x4.Ortho(-scale, scale, -scale, scale, 1f, 20000f)
);
var drawn = false;
foreach (var draw in LevelLod.s_Inputs)
{
var input = draw.Value;
if (!input.Enabled) continue;
// Skip water level input from this SWS.
if (input == _Input) continue;
// Skip compute shaders for now, as we would need an LOD-less kernel.
if (input.IsCompute) continue;
var rect = input.Rect;
if (rect == Rect.zero || rect.Overlaps(Rect))
{
input.Draw(water.LevelLod, _CommandBuffer, _WaterLevel, -1, 1f, 0);
drawn = true;
}
}
if (drawn)
{
Graphics.ExecuteCommandBuffer(_CommandBuffer);
}
}
void Draw(Lod lod, CommandBuffer buffer, RenderTargetIdentifier target, int pass = -1, float weight = 1f, int slices = -1)
{
PropertyWrapperCompute wrapper;
var targetID = Crest.ShaderIDs.s_Target;
if (lod is AnimatedWavesLod)
{
if (_InjectShape != ShallowWaterSimulationInjection.Waves)
{
return;
}
Execute(lod.Water, buffer);
if (_Debug._SkipInjectShape)
{
return;
}
wrapper = new PropertyWrapperCompute(buffer, _ComputeShader, _KernelInjectShape);
targetID = ShaderIDs.s_ShapeTarget;
wrapper.SetTexture(ShaderIDs.s_GroundHeightSource, _GroundHeight);
// The finalized height will always be written to zero.
wrapper.SetTexture(ShaderIDs.s_HeightSource, _Height0);
}
else if (lod is LevelLod)
{
if (_InjectShape != ShallowWaterSimulationInjection.Level)
{
return;
}
if (_Mode == ShallowWaterSimulationMode.RealTime || _Debug._AlwaysExecuteSimulation)
{
Execute(lod.Water, buffer);
}
if (_Debug._SkipInjectShape)
{
return;
}
if (_Mode == ShallowWaterSimulationMode.Baked)
{
if (_LevelTexture == null) return;
wrapper = new PropertyWrapperCompute(buffer, WaterResources.Instance.Compute._LevelTexture, 0);
wrapper.SetInteger(Crest.ShaderIDs.s_Blend, (int)LodInputBlend.Maximum);
wrapper.SetTexture(Crest.ShaderIDs.s_Texture, _LevelTexture);
wrapper.SetKeyword(WaterResources.Instance.Keywords.LevelTextureCatmullRom, false);
}
else
{
wrapper = new PropertyWrapperCompute(buffer, _ComputeShader, _KernelInjectLevel);
targetID = ShaderIDs.s_LevelTarget;
wrapper.SetTexture(ShaderIDs.s_GroundHeightSource, _GroundHeight);
// The finalized height will always be written to zero.
wrapper.SetTexture(ShaderIDs.s_HeightSource, _Height0);
}
}
else if (lod is FlowLod)
{
if (!_InjectFlow)
{
return;
}
if (_Mode == ShallowWaterSimulationMode.Baked)
{
if (_FlowTexture == null) return;
wrapper = new(buffer, WaterResources.Instance.Compute._FlowTexture, 0);
wrapper.SetInteger(Crest.ShaderIDs.s_Blend, (int)LodInputBlend.Additive);
wrapper.SetTexture(Crest.ShaderIDs.s_Texture, _FlowTexture);
wrapper.SetBoolean(Crest.ShaderIDs.s_NegativeValues, true);
}
else
{
// One frame behind as Flow happens before Animated Waves.
wrapper = new(buffer, _ComputeShader, _KernelInjectFlow);
targetID = ShaderIDs.s_FlowTarget;
wrapper.SetTexture(ShaderIDs.s_VelocityXSource, _VelocityX);
wrapper.SetTexture(ShaderIDs.s_VelocityYSource, _VelocityY);
wrapper.SetFloat(ShaderIDs.s_InjectionStrength, _FlowStrength);
}
}
#if CREST_DEBUG
else if (lod is ClipLod)
{
var origin = Origin;
wrapper = new PropertyWrapperCompute(buffer, WaterResources.Instance.Compute._ClipPrimitive, 0);
wrapper.SetMatrix(Crest.ShaderIDs.s_Matrix, Matrix4x4.TRS(origin, Quaternion.identity, (Vector3.one * _Width).XNZ(100f)).inverse);
// For culling.
wrapper.SetVector(Crest.ShaderIDs.s_Position, origin);
wrapper.SetFloat(Crest.ShaderIDs.s_Diameter, _Width);
wrapper.SetKeyword(WaterResources.Instance.Keywords.ClipPrimitiveInverted, false);
wrapper.SetKeyword(WaterResources.Instance.Keywords.ClipPrimitiveSphere, false);
wrapper.SetKeyword(WaterResources.Instance.Keywords.ClipPrimitiveCube, true);
wrapper.SetKeyword(WaterResources.Instance.Keywords.ClipPrimitiveRectangle, false);
}
#endif
else
{
if (!_InjectFoam)
{
return;
}
if (_Mode == ShallowWaterSimulationMode.Baked)
{
if (_FoamTexture == null) return;
wrapper = new(buffer, WaterResources.Instance.Compute._FoamTexture, 0);
wrapper.SetTexture(Crest.ShaderIDs.s_Texture, _FoamTexture);
wrapper.SetInteger(Crest.ShaderIDs.s_Blend, (int)LodInputBlend.Additive);
}
else
{
// It currently does not trigger jacobian foam term. If we added displacement to
// SWS waves it would prob help, but the quality of the shape would have to be much
// better I guess!
wrapper = new(buffer, _ComputeShader, _KernelInjectFoam);
targetID = ShaderIDs.s_FoamTarget;
wrapper.SetTexture(ShaderIDs.s_GroundHeightSource, _GroundHeight);
wrapper.SetTexture(ShaderIDs.s_HeightSource, _Height1);
wrapper.SetTexture(ShaderIDs.s_VelocityXSource, _VelocityX);
wrapper.SetTexture(ShaderIDs.s_VelocityYSource, _VelocityY);
wrapper.SetFloat(ShaderIDs.s_InjectionStrength, _FoamStrength);
}
}
if (_Mode == ShallowWaterSimulationMode.Baked)
{
wrapper.SetVector(Crest.ShaderIDs.s_TextureSize, Vector2.one * Width);
wrapper.SetVector(Crest.ShaderIDs.s_TexturePosition, transform.position.XZ());
wrapper.SetVector(Crest.ShaderIDs.s_TextureRotation, new(0, 1));
wrapper.SetVector(Crest.ShaderIDs.s_Multiplier, Vector4.one);
wrapper.SetFloat(Crest.ShaderIDs.s_FeatherWidth, 0f);
wrapper.SetFloat(LodInput.ShaderIDs.s_Weight, 1f);
}
else
{
// All require the mask.
wrapper.SetTexture(ShaderIDs.s_MaskSource, _Mask);
wrapper.SetFloat(LodInput.ShaderIDs.s_Weight, _Weight);
if (_InjectShape == ShallowWaterSimulationInjection.Level)
{
wrapper.SetFloat(ShaderIDs.s_MarginWidth, _PaddingWidth);
}
}
wrapper.SetTexture(targetID, target);
var xy = lod.Resolution / Lod.k_ThreadGroupSize;
wrapper.Dispatch(xy, xy, slices);
}
Placement GetPlacement()
{
return _InjectShape == ShallowWaterSimulationInjection.Level ? Placement.Fixed : _Placement;
}
bool GetSampleWaterLevelInputsDirectly()
{
return _SampleWaterLevelInputsDirectly && _InjectShape == ShallowWaterSimulationInjection.Level;
}
void SetPreset(ShallowWaterSimulationPreset previous, ShallowWaterSimulationPreset current)
{
if (previous == current) return;
switch (_Preset)
{
case ShallowWaterSimulationPreset.Shoreline:
_InjectShape = ShallowWaterSimulationInjection.Waves;
_Mode = ShallowWaterSimulationMode.RealTime;
break;
case ShallowWaterSimulationPreset.Stream:
_InjectShape = ShallowWaterSimulationInjection.Level;
break;
}
}
}
#if UNITY_EDITOR
sealed partial class ShallowWaterSimulation
{
bool _Dirty;
bool _DirtyDepthProbe;
float _DirtyTime;
void RequestDelayedReset(bool populateDepthProbe = false)
{
_Dirty = true;
_DirtyTime = 0.125f;
_DirtyDepthProbe |= populateDepthProbe;
}
private protected override void Reset()
{
UpdateResolution();
base.Reset();
}
/// <summary>
/// Bake the simulation to a texture. Only works with <see cref="ShallowWaterSimulationInjection.Level"/>
/// and in the Editor.
/// </summary>
public void Bake()
{
Undo.RecordObject(this, "Bake Shallow Water Simulation");
var descriptor = _GroundHeight.descriptor;
descriptor.graphicsFormat = GraphicsFormat.R32G32B32A32_SFloat;
// Bake Level
{
// Redo post-processing for now instead of continuing where left off as it can get
// complicated quickly.
var buffer = new CommandBuffer();
BlurHeight(buffer);
// No need to clear, as overwritten.
buffer.GetTemporaryRT(ShaderIDs.s_Temporary0, _GroundHeight.descriptor);
ExpandEdge(buffer, Mathf.Max(_WaterEdgeMarginWidth, _WaterEdgeMarginBakedWidth), blurred: true);
buffer.ReleaseTemporaryRT(ShaderIDs.s_Temporary0);
Graphics.ExecuteCommandBuffer(buffer);
buffer.Release();
var wrapper = new PropertyWrapperComputeStandalone(_ComputeShader, _KernelBakeLevel);
wrapper.SetTexture(ShaderIDs.s_HeightSource, _Height0);
wrapper.SetFloat(ShaderIDs.s_MarginWidth, _PaddingWidth);
BakeTexture(wrapper, descriptor, ShaderIDs.s_LevelBakeTarget, "Level", ref _LevelTexture, isSingleChannel: true);
}
// Bake Foam
if (_InjectFoam)
{
var wrapper = new PropertyWrapperComputeStandalone(_ComputeShader, _KernelBakeFoam);
wrapper.SetTexture(ShaderIDs.s_GroundHeightSource, _GroundHeight);
wrapper.SetTexture(ShaderIDs.s_HeightSource, _Height1);
wrapper.SetTexture(ShaderIDs.s_VelocityXSource, _VelocityX);
wrapper.SetTexture(ShaderIDs.s_VelocityYSource, _VelocityY);
wrapper.SetFloat(ShaderIDs.s_MarginWidth, _PaddingWidth);
wrapper.SetFloat(ShaderIDs.s_InjectionStrength, _FoamStrength);
BakeTexture(wrapper, descriptor, ShaderIDs.s_FoamBakeTarget, "Foam", ref _FoamTexture, isSingleChannel: true);
}
// Bake Flow
if (_InjectFlow)
{
var wrapper = new PropertyWrapperComputeStandalone(_ComputeShader, _KernelBakeFlow);
wrapper.SetTexture(ShaderIDs.s_VelocityXSource, _VelocityX);
wrapper.SetTexture(ShaderIDs.s_VelocityYSource, _VelocityY);
wrapper.SetFloat(ShaderIDs.s_InjectionStrength, _FlowStrength);
wrapper.SetFloat(ShaderIDs.s_MarginWidth, _PaddingWidth);
BakeTexture(wrapper, descriptor, ShaderIDs.s_FlowBakeTarget, "Flow", ref _FlowTexture, format: TextureImporterFormat.RGFloat);
}
_Mode = ShallowWaterSimulationMode.Baked;
}
void BakeTexture
(
PropertyWrapperComputeStandalone wrapper,
RenderTextureDescriptor descriptor,
int target,
string name,
ref Texture2D output,
TextureImporterFormat format = TextureImporterFormat.Automatic,
bool isSingleChannel = false
)
{
#if d_ModuleUnityImageConversion
// Write to temporary then copy over as we need UAV access.
var temporary = RenderTexture.GetTemporary(descriptor);
// Simulation padding must completely overlap water level inputs, or there may be a gap.
Helpers.ClearRenderTexture(temporary, Color.clear, depth: false);
wrapper.SetTexture(target, temporary);
var threads = Resolution / 8;
wrapper.Dispatch(threads, threads, 1);
// Copy texture. CopyTexture needs both to be readable for CPU copies.
// https://docs.unity3d.com/ScriptReference/Graphics.CopyTexture.html
var texture = new Texture2D(temporary.width, temporary.height, descriptor.graphicsFormat, TextureCreationFlags.None);
RenderTexture.active = temporary;
texture.ReadPixels(new(0, 0, temporary.width, temporary.height), 0, 0);
RenderTexture.active = null;
var path = AssetDatabase.GetAssetPath(output) ?? "";
if (path.Length <= 0) path = $"Assets/ShallowWaterSimulation_{name}_{System.Guid.NewGuid()}.exr";
System.IO.File.WriteAllBytes(path, texture.EncodeToEXR(Texture2D.EXRFlags.OutputAsFloat));
AssetDatabase.ImportAsset(path);
Helpers.Destroy(texture);
output = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
var importer = AssetImporter.GetAtPath(path) as TextureImporter;
importer.textureShape = TextureImporterShape.Texture2D;
importer.textureType = isSingleChannel ? TextureImporterType.SingleChannel : TextureImporterType.Default;
importer.sRGBTexture = false;
importer.alphaSource = TextureImporterAlphaSource.None;
importer.mipmapEnabled = false;
importer.alphaIsTransparency = false;
// Compression will clamp negative values.
importer.textureCompression = TextureImporterCompression.Uncompressed;
importer.filterMode = FilterMode.Bilinear;
importer.wrapMode = TextureWrapMode.Clamp;
// Importer Settings
if (isSingleChannel)
{
var settings = new TextureImporterSettings();
importer.ReadTextureSettings(settings);
settings.singleChannelComponent = TextureImporterSingleChannelComponent.Red;
importer.SetTextureSettings(settings);
format = TextureImporterFormat.RFloat;
}
// Default Platform Settings
{
var settings = importer.GetDefaultPlatformTextureSettings();
settings.format = format;
importer.SetPlatformTextureSettings(settings);
}
importer.SaveAndReimport();
RenderTexture.ReleaseTemporary(temporary);
#else
// The problem is EncodeToEXR. DepthProbe has this without issue likely because the
// code is in an editor only assembly.
throw new System.NotSupportedException("Crest: Requires the com.unity.modules.imageconversion module. Please enable it.");
#endif
}
private protected override void OnValidate()
{
base.OnValidate();
UpdateResolution();
}
[@OnChange(skipIfInactive: false)]
void OnChange(string propertyPath, object previousValue)
{
switch (propertyPath)
{
case nameof(_Mode):
if (_SampleDepthProbeDirectly && TryGetComponent(out _DepthProbe))
{
Undo.RecordObject(_DepthProbe, $"Modified Mode in {gameObject.name}");
_DepthProbe.enabled = _Mode == ShallowWaterSimulationMode.RealTime;
}
break;
case nameof(_InjectShape):
if (_InjectShape == ShallowWaterSimulationInjection.Waves) _Mode = ShallowWaterSimulationMode.RealTime;
break;
case nameof(_Preset):
SetPreset((ShallowWaterSimulationPreset)previousValue, _Preset);
break;
}
if (!isActiveAndEnabled)
{
return;
}
var water = WaterRenderer.Instance;
switch (propertyPath)
{
case nameof(_SampleDepthProbeDirectly):
if (!_SampleDepthProbeDirectly)
{
if (_DepthProbe != null)
{
_DepthProbe.Managed = false;
_DepthProbe.Populate();
}
_DepthProbe = null;
}
ResetSimulation(populateDepthProbe: true);
break;
// Affects initialization, thus needs a reset.
case nameof(_AdditionalWater):
case nameof(_Depth):
case nameof(_MaximumResolution):
case nameof(_SampleWaterLevelInputsDirectly):
case nameof(_TexelSize):
ResetSimulation();
break;
case nameof(_PlaceAtSeaLevel):
if (water != null && transform.position.y != water.SeaLevel) ResetSimulation();
break;
case nameof(_Placement):
if (_SampleDepthProbeDirectly && _DepthProbe != null) _DepthProbe.OverridePosition = Movable;
ResetSimulation(populateDepthProbe: true);
break;
// Needs to allocate if haven't already.
case nameof(_Mode):
Allocate();
break;
// Affects waves only, thus updating the ground height is enough.
case nameof(_BlendDepthRange):
case nameof(_BlurMaskIterations):
_UpdateGroundHeight = true;
break;
// Affects Depth Probe and dynamic control, thus needs delayed reset.
case nameof(_Width):
if (_Width != (float)previousValue) RequestDelayedReset(populateDepthProbe: true);
break;
#if CREST_DEBUG
case nameof(_Debug) + "." + nameof(_Debug._ShowSimulationDataInScene):
ILodInput.Detach(_Input, ClipLod.s_Inputs);
if (_Debug._ShowSimulationDataInScene != DebugFields.SimulationData.None) ILodInput.Attach(_Input, ClipLod.s_Inputs);
break;
#endif
}
}
void OnGUI()
{
if (_Debug._ShowSimulationData && _Height1 != null)
{
var s = 200f;
var y = 0f;
Rect r;
r = new Rect(200, y, s, s);
y += s + 1;
GUI.DrawTexture(r, _Height1); GUI.Label(r, _Height1.name);
r = new Rect(200, y, s, s);
y += s + 1;
GUI.DrawTexture(r, _VelocityX); GUI.Label(r, _VelocityX.name);
r = new Rect(200, y, s, s);
y += s + 1;
GUI.DrawTexture(r, _VelocityY); GUI.Label(r, _VelocityY.name);
r = new Rect(200, y, s, s);
y += s + 1;
GUI.DrawTexture(r, _GroundHeight); GUI.Label(r, _GroundHeight.name);
r = new Rect(200, y, s, s);
GUI.DrawTexture(r, _Mask); GUI.Label(r, _Mask.name);
}
}
#if CREST_DEBUG
[UnityEditor.Callbacks.DidReloadScripts]
static void ScriptsHasBeenReloaded()
{
SceneView.duringSceneGui -= DuringSceneGUI;
SceneView.duringSceneGui += DuringSceneGUI;
}
static void DuringSceneGUI(SceneView sceneView)
{
if (Event.current.type == EventType.KeyUp && Event.current.keyCode == KeyCode.KeypadEnter)
{
var sws = FindFirstObjectByType<ShallowWaterSimulation>();
if (sws != null)
{
FindFirstObjectByType<ShallowWaterSimulation>().ResetSimulation();
}
}
}
#endif
}
#endif // UNITY_EDITOR
partial class ShallowWaterSimulation
{
Input _Input;
sealed class Input : ILodInput
{
readonly ShallowWaterSimulation _Input;
public Input(ShallowWaterSimulation input) => _Input = input;
public bool Enabled => _Input.Enabled;
public bool IsCompute => true;
// Late queue, but not too late to give opportunity to override.
public int Queue => 1000;
public int Pass => (int)DisplacementPass.LodIndependent;
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);
}
}
}