还原水插件

This commit is contained in:
2026-03-05 00:14:42 +08:00
parent 0de35591e7
commit e82f2ea6b7
270 changed files with 2773 additions and 12445 deletions

View File

@@ -9,7 +9,6 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Samples")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Samples.Editor")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Samples.Ripples")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Samples.Waterfall")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.CPUQueries")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.CPUQueries.Editor")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Paint")]

View File

@@ -31,16 +31,5 @@ namespace WaveHarmonic.Crest
// 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;
public static class Symbols
{
public const string k_Refraction =
#if d_Crest_SimpleTransparency
"CREST_NOOP";
#else
"UNITY_2022_3_OR_NEWER";
#endif
}
}
}

View File

@@ -19,7 +19,6 @@ namespace WaveHarmonic.Crest
internal override Color GizmoColor => s_GizmoColor;
private protected override Color ClearColor => Color.clear;
private protected override bool NeedToReadWriteTextureData => false;
internal override bool SkipEndOfFrame => true;
private protected override GraphicsFormat RequestedTextureFormat => _TextureFormatMode switch
{

View File

@@ -20,7 +20,6 @@ namespace WaveHarmonic.Crest
/// <summary>
/// The source of collisions (ie water shape).
/// </summary>
[System.Obsolete("Please use QuerySource and LodQuerySource.")]
[@GenerateDoc]
public enum CollisionSource
{
@@ -68,9 +67,13 @@ namespace WaveHarmonic.Crest
{
// NOTE: numbers must be in order for defaults to work (everything first).
/// <inheritdoc cref="Generated.CollisionLayers.Everything"/>
[Tooltip("All layers.")]
Everything = -1,
/// <inheritdoc cref="Generated.CollisionLayers.Nothing"/>
[Tooltip("No extra layers (ie single layer).")]
Nothing = 0,
Nothing,
/// <inheritdoc cref="Generated.CollisionLayers.DynamicWaves"/>
[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.")]
@@ -79,29 +82,6 @@ namespace WaveHarmonic.Crest
/// <inheritdoc cref="Generated.CollisionLayers.Displacement"/>
[Tooltip("Extra displacement layer for visual displacement.")]
Displacement = 1 << 2,
/// <inheritdoc cref="Generated.CollisionLayers.Everything"/>
[Tooltip("All layers.")]
Everything = ~0,
}
/// <summary>
/// The wave sampling method to determine quality and performance.
/// </summary>
[@GenerateDoc]
public enum WaveSampling
{
/// <inheritdoc cref="Generated.WaveSampling.Automatic"/>
[Tooltip("Automatically chooses the other options as needed (512+ resolution needs precision).")]
Automatic,
/// <inheritdoc cref="Generated.WaveSampling.Performance"/>
[Tooltip("Reduces samples by copying waves from higher LODs to lower LODs.\n\nBest for resolutions lower than 512.")]
Performance,
/// <inheritdoc cref="Generated.WaveSampling.Precision"/>
[Tooltip("Samples directly from the wave buffers to preserve wave quality.\n\nNeeded for higher resolutions (512+). Higher LOD counts can also benefit with this enabled.")]
Precision,
}
/// <summary>
@@ -128,33 +108,14 @@ namespace WaveHarmonic.Crest
[@FilterEnum(nameof(_TextureFormatMode), Filtered.Mode.Exclude, (int)LodTextureFormatMode.Automatic)]
public sealed partial class AnimatedWavesLod : Lod<ICollisionProvider>
{
[Tooltip("Collision layers to enable.\n\nSome layers will have overhead with CPU, GPU and memory.")]
[@Predicated(nameof(_QuerySource), inverted: true, nameof(LodQuerySource.GPU))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
internal CollisionLayers _CollisionLayers = CollisionLayers.Everything;
[@Predicated(nameof(_QuerySource), true, nameof(LodQuerySource.CPU))]
[@DecoratedField, SerializeField]
internal BakedWaveData _BakedWaveData;
[@Space(10)]
[Tooltip("The wave sampling method to determine quality and performance.")]
[@GenerateAPI]
[@DecoratedField]
[SerializeField]
internal WaveSampling _WaveSampling;
[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.")]
[@Predicated(nameof(_WaveSampling), inverted: true, nameof(WaveSampling.Performance))]
[@Range(1f, 4f)]
[@GenerateAPI(Getter.Custom)]
[@GenerateAPI]
[SerializeField]
float _WaveResolutionMultiplier = 1f;
[@Space(10)]
[Tooltip("How much waves are dampened in shallow water.")]
[@Range(0f, 1f)]
[@GenerateAPI]
@@ -168,6 +129,30 @@ namespace WaveHarmonic.Crest
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;
const string k_DrawCombine = "Combine";
@@ -175,6 +160,7 @@ namespace WaveHarmonic.Crest
{
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");
}
@@ -185,8 +171,6 @@ namespace WaveHarmonic.Crest
/// </summary>
internal static bool s_Combine = true;
WaterResources.ShapeCombineCompute _CombineShader;
internal override string ID => "AnimatedWaves";
internal override string Name => "Animated Waves";
internal override Color GizmoColor => s_GizmoColor;
@@ -203,13 +187,17 @@ namespace WaveHarmonic.Crest
_ => throw new System.NotImplementedException(),
};
internal bool PreserveWaveQuality => WaveSampling switch
{
WaveSampling.Automatic => Resolution >= 512,
WaveSampling.Performance => false,
WaveSampling.Precision => true,
_ => 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()
{
@@ -220,8 +208,8 @@ namespace WaveHarmonic.Crest
internal override void Initialize()
{
_CombineShader = WaterResources.Instance._ComputeLibrary._ShapeCombineCompute;
if (_CombineShader._Shader == null)
_CombineShader = WaterResources.Instance.Compute._ShapeCombine;
if (_CombineShader == null)
{
_Valid = false;
return;
@@ -230,11 +218,25 @@ namespace WaveHarmonic.Crest
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(ID);
FlipBuffers(buffer);
FlipBuffers();
Shader.SetGlobalFloat(ShaderIDs.s_AttenuationInShallows, _AttenuationInShallows);
@@ -242,18 +244,6 @@ namespace WaveHarmonic.Crest
buffer.GetTemporaryRT(ShaderIDs.s_WaveBuffer, DataTexture.descriptor);
CoreUtils.SetRenderTarget(buffer, ShaderIDs.s_WaveBuffer, ClearFlag.Color, ClearColor);
// Custom clear because clear not working.
if (Helpers.IsWebGPU && WaterResources.Instance.Compute._Clear != null)
{
var compute = WaterResources.Instance._ComputeLibrary._ClearCompute;
var wrapper = new PropertyWrapperCompute(buffer, compute._Shader, compute._KernelClearTarget);
compute.SetVariantForFormat(wrapper, DataTexture.graphicsFormat);
wrapper.SetTexture(Crest.ShaderIDs.s_Target, ShaderIDs.s_WaveBuffer);
wrapper.SetVector(Crest.ShaderIDs.s_ClearMask, Color.white);
wrapper.SetVector(Crest.ShaderIDs.s_ClearColor, ClearColor);
wrapper.Dispatch(Resolution / k_ThreadGroupSizeX, Resolution / k_ThreadGroupSizeY, Slices);
}
// LOD dependent data.
// Write to per-octave _WaveBuffers. Not the same as _AnimatedWaves.
// Draw any data with lod preference.
@@ -264,54 +254,54 @@ namespace WaveHarmonic.Crest
// Combine the LODs - copy results from biggest LOD down to LOD 0
{
var wrapper = new PropertyWrapperCompute
(
buffer,
_CombineShader._Shader,
PreserveWaveQuality
? _CombineShader._CopyAnimatedWavesKernel
: _CombineShader._CombineAnimatedWavesKernel
);
if (_Water._DynamicWavesLod.Enabled)
var combineShaderKernel = _KernalShapeCombine;
var combineShaderKernel_lastLOD = _KernalShapeCombine_DISABLE_COMBINE;
{
_Water._DynamicWavesLod.Bind(wrapper);
}
var isFlowOn = _Water._FlowLod.Enabled;
var isDynamicWavesOn = _Water._DynamicWavesLod.Enabled && !_CollisionLayers.HasFlag(CollisionLayers.DynamicWaves);
// Start with last LOD which does not combine.
wrapper.SetKeyword(_CombineShader._CombineKeyword, false);
wrapper.SetKeyword(_CombineShader._FlowKeyword, _Water._FlowLod.Enabled);
wrapper.SetKeyword(_CombineShader._DynamicWavesKeyword, _Water._DynamicWavesLod.Enabled && !PreserveWaveQuality && !_CollisionLayers.HasFlag(CollisionLayers.DynamicWaves));
// The per-octave wave buffers we read from.
wrapper.SetTexture(ShaderIDs.s_WaveBuffer, ShaderIDs.s_WaveBuffer);
// Set the animated waves texture where we read/write to combine the results.
wrapper.SetTexture(Crest.ShaderIDs.s_Target, DataTexture);
if (PreserveWaveQuality)
{
wrapper.Dispatch(threadSize, threadSize, Slices);
}
else
{
buffer.BeginSample(k_DrawCombine);
// Combine waves.
for (var slice = lastSlice; slice >= 0; slice--)
// Set the shader kernels that we will use.
if (isFlowOn && isDynamicWavesOn)
{
wrapper.SetInteger(Lod.ShaderIDs.s_LodIndex, slice);
wrapper.Dispatch(threadSize, threadSize, 1);
if (slice == lastSlice)
{
// From here on, use combine.
wrapper.SetKeyword(_CombineShader._CombineKeyword, s_Combine);
}
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.EndSample(k_DrawCombine);
}
buffer.BeginSample(k_DrawCombine);
// 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(k_DrawCombine);
}
buffer.ReleaseTemporaryRT(ShaderIDs.s_WaveBuffer);
@@ -322,15 +312,20 @@ namespace WaveHarmonic.Crest
// Alpha channel is cleared in combine step, but if any inputs draw in post-combine
// step then alpha may have data.
if (drawn && WaterResources.Instance.Compute._Clear != null)
var clear = WaterResources.Instance.Compute._Clear;
if (drawn && clear != null)
{
var compute = WaterResources.Instance._ComputeLibrary._ClearCompute;
var wrapper = new PropertyWrapperCompute(buffer, compute._Shader, compute._KernelClearTarget);
compute.SetVariantForFormat(wrapper, DataTexture.graphicsFormat);
wrapper.SetTexture(Crest.ShaderIDs.s_Target, DataTexture);
wrapper.SetVector(Crest.ShaderIDs.s_ClearMask, Color.black);
wrapper.SetVector(Crest.ShaderIDs.s_ClearColor, Color.clear);
wrapper.Dispatch(Resolution / k_ThreadGroupSizeX, Resolution / k_ThreadGroupSizeY, Slices);
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.
@@ -360,21 +355,15 @@ namespace WaveHarmonic.Crest
}
// Transfer Dynamic Waves to Animated Waves.
if ((_CollisionLayers.HasFlag(CollisionLayers.DynamicWaves) || PreserveWaveQuality) && _Water._DynamicWavesLod.Enabled)
if (_CollisionLayers.HasFlag(CollisionLayers.DynamicWaves) && _Water._DynamicWavesLod.Enabled)
{
buffer.BeginSample(k_DrawCombine);
// Clearing not required as we overwrite enter texture.
buffer.GetTemporaryRT(ShaderIDs.s_DynamicWavesTarget, DataTexture.descriptor);
var wrapper = new PropertyWrapperCompute(buffer, _CombineShader._Shader, _CombineShader._CombineDynamicWavesKernel);
var wrapper = new PropertyWrapperCompute(buffer, _CombineShader, 9);
// Flow keyword is already set, and Dynamic Waves already bound. If binding Dynamic
// Waves becomes kernel specific (eg binding textures), then we need to rebind.
// Start with last LOD which does not combine.
wrapper.SetKeyword(_CombineShader._CombineKeyword, false);
wrapper.SetTexture(Crest.ShaderIDs.s_Target, ShaderIDs.s_DynamicWavesTarget);
wrapper.SetTexture(ShaderIDs.s_DynamicWavesTarget, ShaderIDs.s_DynamicWavesTarget);
_Water._DynamicWavesLod.Bind(wrapper);
@@ -384,21 +373,18 @@ namespace WaveHarmonic.Crest
wrapper.SetInteger(Lod.ShaderIDs.s_LodIndex, slice);
wrapper.Dispatch(threadSize, threadSize, 1);
// Change to kernel with combine enabled.
if (slice == lastSlice)
{
// From here on, use combine.
wrapper.SetKeyword(_CombineShader._CombineKeyword, s_Combine);
wrapper = new(buffer, _CombineShader, 8);
}
}
// Copy Dynamic Waves displacement into Animated Waves.
if (WaterResources.Instance.Compute._Blit != null)
{
var compute = WaterResources.Instance._ComputeLibrary._BlitCompute;
wrapper = new(buffer, compute._Shader, 0);
compute.SetVariantForFormat(wrapper, DataTexture.graphicsFormat);
wrapper.SetTexture(Crest.ShaderIDs.s_Source, ShaderIDs.s_DynamicWavesTarget);
wrapper.SetTexture(Crest.ShaderIDs.s_Target, DataTexture);
wrapper = new(buffer, _CombineShader, 10);
wrapper.SetTexture(ShaderIDs.s_AnimatedWavesTarget, DataTexture);
wrapper.SetTexture(ShaderIDs.s_DynamicWavesTarget, ShaderIDs.s_DynamicWavesTarget);
wrapper.Dispatch(threadSize, threadSize, Slices);
}
@@ -407,10 +393,7 @@ namespace WaveHarmonic.Crest
// Query collisions including Dynamic Waves.
// Does not require copying the water level as they are added with zero alpha.
if (_CollisionLayers.HasFlag(CollisionLayers.DynamicWaves))
{
Provider.UpdateQueries(_Water, CollisionLayer.AfterDynamicWaves);
}
Provider.UpdateQueries(_Water, CollisionLayer.AfterDynamicWaves);
}
if (_CollisionLayers.HasFlag(CollisionLayers.Displacement))
@@ -449,20 +432,20 @@ namespace WaveHarmonic.Crest
Queryable?.CleanUp();
if (!enable || _Water.Surface.IsQuadMesh)
if (!enable)
{
return ICollisionProvider.None;
}
switch (QuerySource)
switch (_CollisionSource)
{
case LodQuerySource.None:
case CollisionSource.None:
result = ICollisionProvider.None;
break;
case LodQuerySource.GPU:
case CollisionSource.GPU:
if (_Valid && !_Water.IsRunningWithoutGraphics)
{
result = ICollisionProvider.Create(_Water);
result = new CollisionQueryWithPasses(_Water);
}
if (_Water.IsRunningWithoutGraphics)
@@ -470,7 +453,7 @@ namespace WaveHarmonic.Crest
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 LodQuerySource.CPU:
case CollisionSource.CPU:
if (_BakedWaveData != null)
{
result = _BakedWaveData.CreateCollisionProvider();
@@ -501,7 +484,6 @@ namespace WaveHarmonic.Crest
public readonly float _ViewerAltitudeLevelAlpha;
public readonly int _Slice;
public readonly int _Slices;
public readonly bool _HighQualityCombine;
public WavelengthFilter(WaterRenderer water, int slice)
{
@@ -511,7 +493,6 @@ namespace WaveHarmonic.Crest
_Minimum = _Maximum * 0.5f;
_TransitionThreshold = water.MaximumWavelength(_Slices - 1) * 0.5f;
_ViewerAltitudeLevelAlpha = water.ViewerAltitudeLevelAlpha;
_HighQualityCombine = water.AnimatedWavesLod.PreserveWaveQuality;
}
}
@@ -532,7 +513,7 @@ namespace WaveHarmonic.Crest
// 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 && !filter._HighQualityCombine)
if (filter._Slice == filter._Slices - 2)
{
return 1f - filter._ViewerAltitudeLevelAlpha;
}
@@ -548,7 +529,7 @@ namespace WaveHarmonic.Crest
return 1f;
}
return filter._HighQualityCombine ? 1f : 0f;
return 0f;
}
internal static float FilterByWavelength(WaterRenderer water, int slice, float wavelength)
@@ -569,31 +550,8 @@ namespace WaveHarmonic.Crest
{
s_Inputs.Clear();
}
}
// API
partial class AnimatedWavesLod
{
float GetWaveResolutionMultiplier()
{
return PreserveWaveQuality ? 1f : _WaveResolutionMultiplier;
}
}
partial class AnimatedWavesLod
{
[@HideInInspector]
[@System.Obsolete("Please use QuerySource instead.")]
[Tooltip("Where to obtain water shape on CPU for physics / gameplay.")]
[@GenerateAPI(Setter.Internal)]
[@DecoratedField, SerializeField]
internal CollisionSource _CollisionSource = CollisionSource.GPU;
}
#if UNITY_EDITOR
// Editor
partial class AnimatedWavesLod
{
[@OnChange]
private protected override void OnChange(string propertyPath, object previousValue)
{
@@ -602,10 +560,13 @@ namespace WaveHarmonic.Crest
switch (propertyPath)
{
case nameof(_CollisionLayers):
ResetQueryChange();
case nameof(_CollisionSource):
if (_Water == null || !_Water.isActiveAndEnabled || !Enabled) return;
Queryable?.CleanUp();
InitializeProvider(true);
break;
}
}
}
#endif
}
}

View File

@@ -50,7 +50,6 @@ namespace WaveHarmonic.Crest
private protected override Color ClearColor => _DefaultClippingState == DefaultClippingState.EverythingClipped ? Color.white : Color.black;
private protected override bool NeedToReadWriteTextureData => true;
private protected override bool RequiresClearBorder => true;
internal override bool SkipEndOfFrame => true;
private protected override GraphicsFormat RequestedTextureFormat => _TextureFormatMode switch
{

View File

@@ -69,7 +69,6 @@ namespace WaveHarmonic.Crest
private protected abstract void SetShorelineColor(Color previous, Color current);
private protected Vector4 _ShorelineColorValue;
ShorelineColorInput _ShorelineColorInput;
internal override bool SkipEndOfFrame => true;
private protected override GraphicsFormat RequestedTextureFormat => _TextureFormatMode switch
{

View File

@@ -12,7 +12,6 @@ namespace WaveHarmonic.Crest
/// <summary>
/// Data that gives depth of the water (height of sea level above water floor).
/// </summary>
[FilterEnum(nameof(_QuerySource), Filtered.Mode.Exclude, (int)LodQuerySource.CPU)]
[FilterEnum(nameof(_TextureFormatMode), Filtered.Mode.Exclude, (int)LodTextureFormatMode.Automatic)]
public sealed partial class DepthLod : Lod<IDepthProvider>
{
@@ -34,12 +33,11 @@ namespace WaveHarmonic.Crest
// 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);
static Color NullColor => Helpers.IsWebGPU ? new(float.MinValue, float.MaxValue, 0, 0) : s_NullColor;
internal override string ID => "Depth";
internal override string Name => "Water Depth";
internal override Color GizmoColor => s_GizmoColor;
private protected override Color ClearColor => NullColor;
private protected override Color ClearColor => s_NullColor;
private protected override bool NeedToReadWriteTextureData => true;
private protected override GraphicsFormat RequestedTextureFormat => _TextureFormatMode switch
@@ -58,7 +56,7 @@ namespace WaveHarmonic.Crest
{
if (_NullTexture == null)
{
var texture = TextureArrayHelpers.CreateTexture2D(NullColor, UnityEngine.TextureFormat.RFloat);
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";
@@ -73,16 +71,13 @@ namespace WaveHarmonic.Crest
{
_Enabled = true;
_TextureFormat = GraphicsFormat.R16G16_SFloat;
_MaximumQueryCount = 512;
}
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 && QuerySource == LodQuerySource.GPU
? IDepthProvider.Create(_Water)
: IDepthProvider.None;
return enable && Enabled ? new DepthQuery(_Water) : IDepthProvider.None;
}
internal static readonly SortedList<int, ILodInput> s_Inputs = new(Helpers.DuplicateComparison);

View File

@@ -10,7 +10,6 @@ namespace WaveHarmonic.Crest
/// <summary>
/// Simulates horizontal motion of water.
/// </summary>
[FilterEnum(nameof(_QuerySource), Filtered.Mode.Exclude, (int)LodQuerySource.CPU)]
[FilterEnum(nameof(_TextureFormatMode), Filtered.Mode.Exclude, (int)LodTextureFormatMode.Automatic)]
public sealed partial class FlowLod : Lod<IFlowProvider>
{
@@ -35,7 +34,6 @@ namespace WaveHarmonic.Crest
{
_Resolution = 128;
_TextureFormat = GraphicsFormat.R16G16_SFloat;
_MaximumQueryCount = 1024;
}
internal override void Enable()
@@ -56,9 +54,7 @@ namespace WaveHarmonic.Crest
{
Queryable?.CleanUp();
// Flow is GPU only, and can only be queried using the compute path.
return enable && Enabled && QuerySource == LodQuerySource.GPU
? IFlowProvider.Create(_Water)
: IFlowProvider.None;
return enable && Enabled ? new FlowQuery(_Water) : IFlowProvider.None;
}
internal static readonly SortedList<int, ILodInput> s_Inputs = new(Helpers.DuplicateComparison);

View File

@@ -63,35 +63,23 @@ namespace WaveHarmonic.Crest
_FollowHorizontalWaveMotion = true;
}
private protected override void Initialize()
{
base.Initialize();
_Reporter ??= new(this);
_DisplacementReporter = _Reporter;
}
private protected override void OnDisable()
{
base.OnDisable();
_DisplacementReporter = null;
}
internal override float Filter(WaterRenderer water, int slice)
{
return AnimatedWavesLod.FilterByWavelength(water, slice, _FilterByWavelength ? _OctaveWavelength : 0f);
}
bool ReportDisplacement(WaterRenderer water, ref Rect bounds, ref float horizontal, ref float vertical)
private protected override void OnUpdate(WaterRenderer water)
{
base.OnUpdate(water);
if (!Enabled)
{
return false;
return;
}
var maxDispVert = _MaximumDisplacementVertical;
// let water system know how far from the sea level this shape may displace the surface
// TODO: we need separate min/max vertical displacement to be optimal.
if (_ReportRendererBounds)
{
var range = Data.HeightRange;
@@ -101,34 +89,10 @@ namespace WaveHarmonic.Crest
maxDispVert = Mathf.Max(maxDispVert, Mathf.Abs(seaLevel - minY), Mathf.Abs(seaLevel - maxY));
}
var rect = Data.Rect;
if (bounds.Overlaps(rect, false))
if (_MaximumDisplacementHorizontal > 0f || maxDispVert > 0f)
{
horizontal += _MaximumDisplacementHorizontal;
vertical += maxDispVert;
return true;
water.ReportMaximumDisplacement(_MaximumDisplacementHorizontal, maxDispVert, 0f);
}
return false;
}
float ReportWaveDisplacement(WaterRenderer water, float displacement)
{
return displacement;
}
}
partial class AnimatedWavesLodInput
{
Reporter _Reporter;
sealed class Reporter : IReportsDisplacement, IReportWaveDisplacement
{
readonly AnimatedWavesLodInput _Input;
public Reporter(AnimatedWavesLodInput input) => _Input = input;
public bool ReportDisplacement(WaterRenderer water, ref Rect bounds, ref float horizontal, ref float vertical) => _Input.ReportDisplacement(water, ref bounds, ref horizontal, ref vertical);
public float ReportWaveDisplacement(WaterRenderer water, float displacement) => _Input.ReportWaveDisplacement(water, displacement);
}
}

View File

@@ -2,7 +2,6 @@
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
using WaveHarmonic.Crest.Internal;
namespace WaveHarmonic.Crest
{
@@ -39,6 +38,8 @@ namespace WaveHarmonic.Crest
private protected override bool FollowHorizontalMotion => true;
internal override LodInputMode DefaultMode => LodInputMode.Geometry;
internal Rect _Rect;
internal override void InferBlend()
{
base.InferBlend();
@@ -55,60 +56,35 @@ namespace WaveHarmonic.Crest
{
base.Initialize();
_Reporter ??= new(this);
_HeightReporter = _Reporter;
WaterChunkRenderer.HeightReporters.Add(_Reporter);
}
private protected override void OnDisable()
{
base.OnDisable();
_HeightReporter = null;
WaterChunkRenderer.HeightReporters.Remove(_Reporter);
}
bool ReportHeight(WaterRenderer water, ref Rect bounds, ref float minimum, ref float maximum)
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;
}
var rect = Data.Rect;
if (bounds.Overlaps(rect, false))
if (bounds.Overlaps(_Rect, false))
{
var range = _OverrideHeight ? _HeightRange : Data.HeightRange;
range *= Weight;
// Make relative to sea level.
range.x -= water.SeaLevel;
range.y -= water.SeaLevel;
var r = new Vector2(minimum, maximum);
range = _Blend switch
{
LodInputBlend.Additive => range + r,
LodInputBlend.Minimum => Vector2.Min(range, r),
LodInputBlend.Maximum => Vector2.Max(range, r),
_ => range,
};
if (rect.Encapsulates(bounds))
{
minimum = range.x;
maximum = range.y;
}
else
{
minimum = Mathf.Min(minimum, range.x);
maximum = Mathf.Max(maximum, range.y);
}
minimum = range.x;
maximum = range.y;
return true;
}
@@ -124,8 +100,7 @@ namespace WaveHarmonic.Crest
{
readonly LevelLodInput _Input;
public Reporter(LevelLodInput input) => _Input = input;
public bool ReportHeight(WaterRenderer water, ref Rect bounds, ref float minimum, ref float maximum) =>
_Input.ReportHeight(water, ref bounds, ref minimum, ref maximum);
public bool ReportHeight(ref Rect bounds, ref float minimum, ref float maximum) => _Input.ReportHeight(ref bounds, ref minimum, ref maximum);
}
}

View File

@@ -36,10 +36,6 @@ namespace WaveHarmonic.Crest
MonoBehaviour Component { get; }
IReportsHeight HeightReporter => null;
IReportsDisplacement DisplacementReporter => null;
IReportWaveDisplacement WaveDisplacementReporter => null;
// Allow sorting within a queue. Callers can pass in things like sibling index to
// get deterministic sorting.
int Order => Queue * k_QueueMaximumSubIndex + Mathf.Min(Component.transform.GetSiblingIndex(), k_QueueMaximumSubIndex - 1);
@@ -380,16 +376,6 @@ namespace WaveHarmonic.Crest
//
#if UNITY_EDITOR
void SetMode(LodInputMode previous, LodInputMode current)
{
if (previous == current) return;
if (!isActiveAndEnabled) { ChangeMode(Mode); return; }
OnDisable();
ChangeMode(Mode);
UnityEditor.EditorTools.ToolManager.RefreshAvailableTools();
OnEnable();
}
[@OnChange(skipIfInactive: false)]
void OnChange(string propertyPath, object previousValue)
{
@@ -399,7 +385,11 @@ namespace WaveHarmonic.Crest
SetQueue((int)previousValue, _Queue);
break;
case nameof(_Mode):
SetMode((LodInputMode)previousValue, Mode);
if (!isActiveAndEnabled) { ChangeMode(Mode); break; }
OnDisable();
ChangeMode(Mode);
UnityEditor.EditorTools.ToolManager.RefreshAvailableTools();
OnEnable();
break;
case nameof(_Blend):
// TODO: Make compatible with disabled.
@@ -477,9 +467,6 @@ namespace WaveHarmonic.Crest
partial class LodInput
{
Input _Input;
private protected IReportsHeight _HeightReporter;
internal IReportsDisplacement _DisplacementReporter;
private protected IReportWaveDisplacement _WaveDisplacementReporter;
sealed class Input : ILodInput
{
@@ -491,9 +478,6 @@ namespace WaveHarmonic.Crest
public int Pass => _Input.Pass;
public Rect Rect => _Input.Rect;
public MonoBehaviour Component => _Input;
public IReportsHeight HeightReporter => _Input._HeightReporter;
public IReportsDisplacement DisplacementReporter => _Input._DisplacementReporter;
public IReportWaveDisplacement WaveDisplacementReporter => _Input._WaveDisplacementReporter;
public float Filter(WaterRenderer water, int slice) => _Input.Filter(water, slice);
public void Draw(Lod lod, CommandBuffer buffer, RenderTargetIdentifier target, int pass = -1, float weight = 1, int slice = -1) => _Input.Draw(lod, buffer, target, pass, weight, slice);
}

View File

@@ -2,15 +2,10 @@
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
using WaveHarmonic.Crest.Internal;
using WaveHarmonic.Crest.Utility;
#if !UNITY_6000_0_OR_NEWER
using GraphicsFormatUsage = UnityEngine.Experimental.Rendering.FormatUsage;
#endif
namespace WaveHarmonic.Crest
{
/// <summary>
@@ -363,17 +358,16 @@ namespace WaveHarmonic.Crest
return texture != null &&
texture.width != _Resolution ||
texture.height != _Resolution ||
texture.graphicsFormat != (target ? SystemInfo.GetGraphicsFormat(DefaultFormat.DepthStencil) : FinalFormat);
texture.format != (target ? RenderTextureFormat.Depth : FinalFormat);
}
GraphicsFormat FinalFormat => _GenerateSignedDistanceField ? Helpers.GetCompatibleTextureFormat(GraphicsFormat.R32G32_SFloat, true) : GraphicsFormat.R32_SFloat;
RenderTextureFormat FinalFormat => _GenerateSignedDistanceField ? RenderTextureFormat.RGFloat : RenderTextureFormat.RFloat;
void MakeRT(RenderTexture texture, bool target)
{
var format = target ? SystemInfo.GetGraphicsFormat(DefaultFormat.DepthStencil) : FinalFormat;
var format = target ? RenderTextureFormat.Depth : FinalFormat;
var descriptor = texture.descriptor;
descriptor.graphicsFormat = target ? GraphicsFormat.None : format;
descriptor.depthStencilFormat = target ? format : GraphicsFormat.None;
descriptor.colorFormat = format;
descriptor.width = descriptor.height = _Resolution;
descriptor.depthBufferBits = target ? 24 : 0;
descriptor.useMipMap = false;
@@ -381,11 +375,7 @@ namespace WaveHarmonic.Crest
descriptor.enableRandomWrite = !target;
texture.descriptor = descriptor;
texture.Create();
if (!target)
{
Debug.Assert(SystemInfo.IsFormatSupported(format, GraphicsFormatUsage.Sample), "Crest: The graphics device does not support the render texture format " + format.ToString());
}
Debug.Assert(SystemInfo.SupportsRenderTextureFormat(format), "Crest: The graphics device does not support the render texture format " + format.ToString());
}
bool InitObjects()
@@ -714,7 +704,7 @@ namespace WaveHarmonic.Crest
var descriptor = new RenderTextureDescriptor(_Resolution, _Resolution)
{
autoGenerateMips = false,
graphicsFormat = Helpers.GetCompatibleTextureFormat(GraphicsFormat.R16G16_SFloat, true),
colorFormat = RenderTextureFormat.RGHalf,
useMipMap = false,
enableRandomWrite = true,
depthBufferBits = 0,

View File

@@ -16,19 +16,19 @@ namespace WaveHarmonic.Crest
[Tooltip("Whether to use the wind turbulence on this component rather than the global wind turbulence.\n\nGlobal wind turbulence comes from the Water Renderer component.")]
[@GenerateAPI]
[@InlineToggle(order = -4), SerializeField]
[@InlineToggle(order = -3), SerializeField]
bool _OverrideGlobalWindTurbulence;
[Tooltip("How turbulent/chaotic the waves are.")]
[@Predicated(nameof(_OverrideGlobalWindTurbulence), hide: true)]
[@ShowComputedProperty(nameof(WindTurbulence))]
[@Range(0, 1, order = -5)]
[@Range(0, 1, order = -4)]
[@GenerateAPI(Getter.Custom)]
[SerializeField]
float _WindTurbulence = 0.145f;
[Tooltip("How aligned the waves are with wind.")]
[@Range(0, 1, order = -6)]
[@Range(0, 1, order = -5)]
[@GenerateAPI]
[SerializeField]
float _WindAlignment;
@@ -37,7 +37,7 @@ namespace WaveHarmonic.Crest
// Generation
[Tooltip("FFT waves will loop with a period of this many seconds.")]
[@Range(4f, 128f, Range.Clamp.Minimum, order = -6)]
[@Range(4f, 128f, Range.Clamp.Minimum)]
[@GenerateAPI]
[SerializeField]
float _TimeLoopLength = Mathf.Infinity;
@@ -45,11 +45,6 @@ namespace WaveHarmonic.Crest
[Header("Culling")]
[Tooltip("Whether to override automatic culling based on heuristics.")]
[@GenerateAPI]
[SerializeField]
bool _OverrideCulling;
[Tooltip("Maximum amount the surface will be displaced vertically from sea level.\n\nIncrease this if gaps appear at bottom of screen.")]
[@GenerateAPI]
[SerializeField]
@@ -109,9 +104,8 @@ namespace WaveHarmonic.Crest
#endif
_TimeLoopLength;
// WebGPU will crash above at 128.
private protected override int MinimumResolution => 16;
private protected override int MaximumResolution => Helpers.IsWebGPU ? 64 : int.MaxValue;
private protected override int MaximumResolution => int.MaxValue;
FFTCompute _FFTCompute;
@@ -195,28 +189,13 @@ namespace WaveHarmonic.Crest
{
if (!Enabled) return;
if (_OverrideCulling)
{
// Apply weight or will cause popping due to scale change.
MaximumReportedHorizontalDisplacement = _MaximumHorizontalDisplacement * Weight;
MaximumReportedVerticalDisplacement = MaximumReportedWavesDisplacement = _MaximumVerticalDisplacement * Weight;
}
else
{
var powerLinear = 0f;
// Apply weight or will cause popping due to scale change.
MaximumReportedHorizontalDisplacement = _MaximumHorizontalDisplacement * Weight;
MaximumReportedVerticalDisplacement = MaximumReportedWavesDisplacement = _MaximumVerticalDisplacement * Weight;
for (var i = 0; i < WaveSpectrum.k_NumberOfOctaves; i++)
{
powerLinear += _ActiveSpectrum._PowerLinearScales[i];
}
// Empirical multiplier (3-5), went with 5 to be safe.
// We may be missing some more multipliers from the compute shader.
// Look there if this proves insufficient.
var wind = Mathf.Clamp01(WindSpeedKPH / 150f);
var rms = Mathf.Sqrt(powerLinear) * 5f;
MaximumReportedHorizontalDisplacement = rms * _ActiveSpectrum._Chop * Weight * wind;
MaximumReportedVerticalDisplacement = MaximumReportedWavesDisplacement = rms * Weight * wind;
if (Mode == LodInputMode.Global)
{
water.ReportMaximumDisplacement(MaximumReportedHorizontalDisplacement, MaximumReportedVerticalDisplacement, MaximumReportedVerticalDisplacement);
}
}
@@ -288,7 +267,6 @@ namespace WaveHarmonic.Crest
}
_Version = MigrateV2(_Version);
_Version = MigrateV3(_Version);
}
void ISerializationCallbackReceiver.OnBeforeSerialize()

View File

@@ -21,12 +21,12 @@ namespace WaveHarmonic.Crest
[Tooltip("Use a swell spectrum as the default.\n\nUses a swell spectrum as default (when none is assigned), and disabled reverse waves.")]
[@GenerateAPI]
[@DecoratedField(order = -4), SerializeField]
[@DecoratedField(order = -3), SerializeField]
bool _Swell = true;
[Tooltip("The weight of the opposing, second pair of Gerstner waves.\n\nEach Gerstner wave is actually a pair of waves travelling in opposite directions (similar to FFT). This weight is applied to the wave travelling in against-wind direction. Set to zero to obtain simple single waves which are useful for shorelines waves.")]
[Predicated(nameof(_Swell), inverted: true)]
[@Range(0f, 1f, order = -5)]
[@Range(0f, 1f, order = -4)]
[@GenerateAPI(Getter.Custom)]
[SerializeField]
float _ReverseWaveWeight = 0.5f;
@@ -35,25 +35,23 @@ namespace WaveHarmonic.Crest
// Generation Settings
[Tooltip("How many wave components to generate in each octave.")]
[@Delayed(order = -5)]
[@Delayed]
[@GenerateAPI]
[SerializeField]
int _ComponentsPerOctave = 8;
[Tooltip("Change to get a different set of waves.")]
[@GenerateAPI]
[@DecoratedField(order = -6)]
[SerializeField]
int _RandomSeed = 0;
[Tooltip("Prevent data arrays from being written to so one can provide their own.")]
[@GenerateAPI]
[@DecoratedField(order = -7)]
[SerializeField]
bool _ManualGeneration;
private protected override int MinimumResolution => 8;
private protected override int MaximumResolution => int.MaxValue;
private protected override int MaximumResolution => 64;
float _WindSpeedWhenGenerated = -1f;
@@ -181,7 +179,7 @@ namespace WaveHarmonic.Crest
{
if (_WaveBuffers == null)
{
_WaveBuffers = new(Resolution, Resolution, 0, Helpers.GetCompatibleTextureFormat(GraphicsFormat.R16G16B16A16_SFloat, randomWrite: true));
_WaveBuffers = new(_Resolution, _Resolution, 0, GraphicsFormat.R16G16B16A16_SFloat);
}
else
{
@@ -189,7 +187,7 @@ namespace WaveHarmonic.Crest
}
{
_WaveBuffers.width = _WaveBuffers.height = Resolution;
_WaveBuffers.width = _WaveBuffers.height = _Resolution;
_WaveBuffers.wrapMode = TextureWrapMode.Clamp;
_WaveBuffers.antiAliasing = 1;
_WaveBuffers.filterMode = FilterMode.Bilinear;
@@ -218,7 +216,7 @@ namespace WaveHarmonic.Crest
base.OnUpdate(water);
if (_WaveBuffers == null || Resolution != _WaveBuffers.width || _BufferCascadeParameters == null || _BufferWaveData == null)
if (_WaveBuffers == null || _Resolution != _WaveBuffers.width || _BufferCascadeParameters == null || _BufferWaveData == null)
{
InitData();
}
@@ -548,23 +546,23 @@ namespace WaveHarmonic.Crest
return;
}
MaximumReportedVerticalDisplacement = 0;
MaximumReportedHorizontalDisplacement = 0;
var ampSum = 0f;
for (var i = 0; i < _Wavelengths.Length; i++)
{
var amplitude = _Amplitudes[i];
MaximumReportedVerticalDisplacement += amplitude;
MaximumReportedHorizontalDisplacement += amplitude * _ActiveSpectrum._ChopScales[i / _ComponentsPerOctave];
ampSum += _Amplitudes[i] * _ActiveSpectrum._ChopScales[i / _ComponentsPerOctave];
}
MaximumReportedHorizontalDisplacement *= _ActiveSpectrum._Chop;
// Apply weight or will cause popping due to scale change.
MaximumReportedVerticalDisplacement *= Weight;
MaximumReportedHorizontalDisplacement *= Weight;
ampSum *= Weight;
MaximumReportedWavesDisplacement = MaximumReportedVerticalDisplacement;
MaximumReportedHorizontalDisplacement = ampSum * _ActiveSpectrum._Chop;
MaximumReportedVerticalDisplacement = ampSum;
MaximumReportedWavesDisplacement = ampSum;
if (Mode == LodInputMode.Global)
{
water.ReportMaximumDisplacement(ampSum * _ActiveSpectrum._Chop, ampSum, ampSum);
}
}
private protected override void Initialize()
@@ -646,7 +644,6 @@ namespace WaveHarmonic.Crest
}
_Version = MigrateV2(_Version);
_Version = MigrateV3(_Version);
}
void ISerializationCallbackReceiver.OnBeforeSerialize()

View File

@@ -4,7 +4,6 @@
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Serialization;
using WaveHarmonic.Crest.Internal;
namespace WaveHarmonic.Crest
{
@@ -36,13 +35,6 @@ namespace WaveHarmonic.Crest
[SerializeField]
float _RespectShallowWaterAttenuation = 1f;
[Tooltip("Whether global waves is applied above or below sea level.\n\nWaves are faded out to avoid hard transitionds. They are fully faded by 1m from sea level.")]
[@Predicated(nameof(_Mode), inverted: true, nameof(LodInputMode.Global))]
[@GenerateAPI]
[@DecoratedField]
[SerializeField]
bool _SeaLevelOnly = true;
[Tooltip("Whether to use the wind direction on this component rather than the global wind direction.\n\nGlobal wind direction comes from the Water Renderer component.")]
[@GenerateAPI]
[@InlineToggle, SerializeField]
@@ -76,21 +68,11 @@ namespace WaveHarmonic.Crest
[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(Getter.Custom)]
[@GenerateAPI]
[SerializeField]
private protected int _Resolution = 128;
[@Heading("Level of Detail")]
[Tooltip("Whether the maximum possible vertical displacement is used for the Drop Detail Height Based On Waves calculation.\n\nThis setting is ignored for global waves, as they always contribute. For local waves, only enable for large areas that are treated like global waves (eg a storm).")]
[@Predicated(nameof(_Mode), inverted: false, nameof(LodInputMode.Global))]
[@GenerateAPI]
[@DecoratedField]
[SerializeField]
bool _IncludeInDropDetailHeightBasedOnWaves;
// Debug
[Tooltip("In Editor, shows the wave generation buffers on screen.")]
@@ -110,7 +92,6 @@ namespace WaveHarmonic.Crest
public static readonly int s_RespectShallowWaterAttenuation = Shader.PropertyToID("_Crest_RespectShallowWaterAttenuation");
public static readonly int s_MaximumAttenuationDepth = Shader.PropertyToID("_Crest_MaximumAttenuationDepth");
public static readonly int s_AxisX = Shader.PropertyToID("_Crest_AxisX");
public static readonly int s_SeaLevelOnly = Shader.PropertyToID("_Crest_SeaLevelOnly");
}
private protected virtual WaveSpectrum DefaultSpectrum => WindSpectrum;
@@ -155,7 +136,7 @@ namespace WaveHarmonic.Crest
/// <summary>
/// The wind speed in meters per second (MPS).
/// </summary>
/// <remarks>
/// /// <remarks>
/// Wind speed can come from this component or the <see cref="WaterRenderer"/>.
/// </remarks>
public float WindSpeedMPS => WindSpeedKPH / 3.6f;
@@ -169,15 +150,13 @@ namespace WaveHarmonic.Crest
{
base.Attach();
_Reporter ??= new(this);
_DisplacementReporter = _Reporter;
_WaveDisplacementReporter = _Reporter;
WaterChunkRenderer.DisplacementReporters.Add(_Reporter);
}
private protected override void Detach()
{
base.Detach();
_DisplacementReporter = null;
_WaveDisplacementReporter = null;
WaterChunkRenderer.DisplacementReporters.Remove(_Reporter);
}
internal override void Draw(Lod simulation, CommandBuffer buffer, RenderTargetIdentifier target, int pass = -1, float weight = 1f, int slice = -1)
@@ -210,13 +189,11 @@ namespace WaveHarmonic.Crest
// Input weight. Weight for each octave calculated in compute.
wrapper.SetFloat(LodInput.ShaderIDs.s_Weight, Weight);
wrapper.SetInteger(Crest.ShaderIDs.s_Resolution, Resolution);
var water = shape._Water;
for (var lodIdx = lodCount - 1; lodIdx >= lodCount - slice; lodIdx--)
{
_WaveBufferParameters[lodIdx] = new(-1, -2, 0, 1);
_WaveBufferParameters[lodIdx] = new(-1, -2, 0, 0);
var found = false;
var filter = new AnimatedWavesLod.WavelengthFilter(water, lodIdx);
@@ -244,11 +221,7 @@ namespace WaveHarmonic.Crest
}
// Set transitional weights.
if (!shape.PreserveWaveQuality)
{
_WaveBufferParameters[lodCount - 2].w = 1f - water.ViewerAltitudeLevelAlpha;
}
_WaveBufferParameters[lodCount - 2].w = 1f - water.ViewerAltitudeLevelAlpha;
_WaveBufferParameters[lodCount - 1].w = water.ViewerAltitudeLevelAlpha;
SetRenderParameters(water, wrapper);
@@ -270,8 +243,6 @@ namespace WaveHarmonic.Crest
if (Mode == LodInputMode.Global)
{
wrapper.SetBoolean(ShaderIDs.s_SeaLevelOnly, _SeaLevelOnly);
var threads = shape.Resolution / Lod.k_ThreadGroupSize;
wrapper.Dispatch(threads, threads, slice);
}
@@ -440,109 +411,40 @@ namespace WaveHarmonic.Crest
s_KeywordTextureBlend = WaterResources.Instance.Keywords.AnimatedWavesTransferWavesTextureBlend;
}
bool ReportDisplacement(WaterRenderer water, ref Rect bounds, ref float horizontal, ref float vertical)
bool ReportDisplacement(ref Rect bounds, ref float horizontal, ref float vertical)
{
if (!Enabled)
if (Mode == LodInputMode.Global || !Enabled)
{
return false;
}
if (Mode == LodInputMode.Global)
{
// Global is always additive.
horizontal += MaximumReportedHorizontalDisplacement;
vertical += MaximumReportedVerticalDisplacement;
return true;
}
_Rect = Data.Rect;
if (bounds.Overlaps(_Rect, false))
{
var nh = horizontal;
var nv = vertical;
switch (Blend)
{
case LodInputBlend.Off:
nh = MaximumReportedHorizontalDisplacement;
nv = MaximumReportedVerticalDisplacement;
break;
case LodInputBlend.Additive:
nh += MaximumReportedHorizontalDisplacement;
nv += MaximumReportedVerticalDisplacement;
break;
case LodInputBlend.Alpha:
case LodInputBlend.AlphaClip:
nh = Mathf.Max(nh, MaximumReportedHorizontalDisplacement);
nv = Mathf.Max(nh, MaximumReportedVerticalDisplacement);
break;
}
if (_Rect.Encapsulates(bounds))
{
horizontal = nh;
vertical = nv;
}
else
{
horizontal = Mathf.Max(horizontal, nh);
vertical = Mathf.Max(vertical, nv);
}
horizontal = MaximumReportedHorizontalDisplacement;
vertical = MaximumReportedVerticalDisplacement;
return true;
}
return false;
}
float ReportWaveDisplacement(WaterRenderer water, float displacement)
{
if (Mode == LodInputMode.Global)
{
return displacement + MaximumReportedWavesDisplacement;
}
if (!_IncludeInDropDetailHeightBasedOnWaves)
{
return displacement;
}
// TODO: use bounds to transition slowly to avoid pops.
if (_Rect.Contains(water.Position.XZ()))
{
displacement = Blend switch
{
LodInputBlend.Off => MaximumReportedWavesDisplacement,
LodInputBlend.Additive => displacement + MaximumReportedWavesDisplacement,
LodInputBlend.Alpha or LodInputBlend.AlphaClip => Mathf.Max(displacement, MaximumReportedWavesDisplacement),
_ => MaximumReportedWavesDisplacement,
};
}
return displacement;
}
float GetWaveDirectionHeadingAngle()
{
return _OverrideGlobalWindDirection || WaterRenderer.Instance == null ? _WaveDirectionHeadingAngle : WaterRenderer.Instance.WindDirection;
}
internal int GetResolution()
{
return Mathf.Clamp(_Resolution, MinimumResolution, MaximumResolution);
}
}
partial class ShapeWaves
{
Reporter _Reporter;
sealed class Reporter : IReportsDisplacement, IReportWaveDisplacement
sealed class Reporter : IReportsDisplacement
{
readonly ShapeWaves _Input;
public Reporter(ShapeWaves input) => _Input = input;
public bool ReportDisplacement(WaterRenderer water, ref Rect bounds, ref float horizontal, ref float vertical) => _Input.ReportDisplacement(water, ref bounds, ref horizontal, ref vertical);
public float ReportWaveDisplacement(WaterRenderer water, float displacement) => _Input.ReportWaveDisplacement(water, displacement);
public bool ReportDisplacement(ref Rect bounds, ref float horizontal, ref float vertical) => _Input.ReportDisplacement(ref bounds, ref horizontal, ref vertical);
}
}
@@ -612,17 +514,6 @@ namespace WaveHarmonic.Crest
return version;
}
private protected int MigrateV3(int version)
{
if (version < 3)
{
_SeaLevelOnly = false;
version = 3;
}
return version;
}
}
#if UNITY_EDITOR

View File

@@ -45,7 +45,7 @@ namespace WaveHarmonic.Crest
wrapper.SetVector(ShaderIDs.s_TextureSize, transform.lossyScale.XZ());
wrapper.SetVector(ShaderIDs.s_TexturePosition, transform.position.XZ());
wrapper.SetVector(ShaderIDs.s_TextureRotation, rotation);
wrapper.SetVector(ShaderIDs.s_TextureResolution, new(_Texture.width, _Texture.height));
wrapper.SetVector(ShaderIDs.s_Resolution, new(_Texture.width, _Texture.height));
wrapper.SetVector(ShaderIDs.s_Multiplier, _Multiplier);
wrapper.SetFloat(ShaderIDs.s_FeatherWidth, _Input.FeatherWidth);
wrapper.SetTexture(ShaderIDs.s_Texture, _Texture);

View File

@@ -41,7 +41,6 @@ namespace WaveHarmonic.Crest
_OverrideResolution = false;
_TextureFormatMode = LodTextureFormatMode.Automatic;
_TextureFormat = GraphicsFormat.R16_SFloat;
_BlurIterations = 4;
}
internal static readonly SortedList<int, ILodInput> s_Inputs = new(Helpers.DuplicateComparison);

View File

@@ -1,20 +1,19 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
using WaveHarmonic.Crest.Internal;
using WaveHarmonic.Crest.Utility;
#if !UNITY_6000_0_OR_NEWER
#if !UNITY_2023_2_OR_NEWER
using GraphicsFormatUsage = UnityEngine.Experimental.Rendering.FormatUsage;
#endif
namespace WaveHarmonic.Crest
{
using Inputs = Utility.SortedList<int, ILodInput>;
using Inputs = SortedList<int, ILodInput>;
/// <summary>
/// Texture format preset.
@@ -53,7 +52,7 @@ namespace WaveHarmonic.Crest
[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)]
[@InlineToggle, SerializeField]
[@InlineToggle(fix: true), 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.")]
@@ -78,28 +77,12 @@ namespace WaveHarmonic.Crest
[@DecoratedField, SerializeField]
internal GraphicsFormat _TextureFormat;
[@Space(10)]
[Tooltip("Blurs the output.\n\nEnable if blurring is desired or intolerable artifacts are present.\nThe blur is optimized to only run on inner LODs and at lower scales.")]
[@Predicated(typeof(AnimatedWavesLod), inverted: true, hide: true)]
[@Predicated(typeof(DynamicWavesLod), inverted: true, hide: true)]
[@GenerateAPI]
[@DecoratedField]
[SerializeField]
private protected bool _Blur;
[Tooltip("Number of blur iterations.\n\nBlur iterations are optimized to only run maximum iterations on the inner LODs.")]
[@Predicated(typeof(AnimatedWavesLod), inverted: true, hide: true)]
[@Predicated(typeof(DynamicWavesLod), inverted: true, hide: true)]
[@Predicated(nameof(_Blur))]
[@GenerateAPI]
[@DecoratedField]
[SerializeField]
private protected int _BlurIterations = 1;
// 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;
@@ -108,7 +91,6 @@ namespace WaveHarmonic.Crest
{
public static readonly int s_LodIndex = Shader.PropertyToID("_Crest_LodIndex");
public static readonly int s_LodChange = Shader.PropertyToID("_Crest_LodChange");
public static readonly int s_TemporaryBlurLodTexture = Shader.PropertyToID("_Crest_TemporaryBlurLodTexture");
}
// Used for creating shader property names etc.
@@ -162,9 +144,6 @@ namespace WaveHarmonic.Crest
// Always use linear filtering.
GraphicsFormatUsage.Linear;
private protected virtual bool Persistent => BufferCount > 1;
internal virtual bool SkipEndOfFrame => false;
private protected BufferedData<RenderTexture> _Targets;
internal RenderTexture DataTexture => _Targets.Current;
internal RenderTexture GetDataTexture(int frameDelta) => _Targets.Previous(frameDelta);
@@ -223,7 +202,7 @@ namespace WaveHarmonic.Crest
return result;
}
private protected void FlipBuffers(CommandBuffer commands)
private protected void FlipBuffers()
{
if (_ReAllocateTexture)
{
@@ -239,9 +218,7 @@ namespace WaveHarmonic.Crest
_SamplingParameters.Flip();
}
UpdateSamplingParameters(commands);
commands.SetGlobalTexture(_TextureShaderID, DataTexture);
UpdateSamplingParameters();
}
private protected void Clear(RenderTexture target)
@@ -249,12 +226,21 @@ namespace WaveHarmonic.Crest
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);
FlipBuffers();
buffer.BeginSample(ID);
@@ -262,18 +248,6 @@ namespace WaveHarmonic.Crest
{
CoreUtils.SetRenderTarget(buffer, DataTexture, ClearFlag.Color, ClearColor);
// Custom clear because clear not working.
if (Helpers.IsWebGPU && WaterResources.Instance.Compute._Clear != null)
{
var compute = WaterResources.Instance._ComputeLibrary._ClearCompute;
var wrapper = new PropertyWrapperCompute(buffer, compute._Shader, compute._KernelClearTarget);
compute.SetVariantForFormat(wrapper, CompatibleTextureFormat);
wrapper.SetTexture(Crest.ShaderIDs.s_Target, DataTexture);
wrapper.SetVector(Crest.ShaderIDs.s_ClearMask, Color.white);
wrapper.SetVector(Crest.ShaderIDs.s_ClearColor, ClearColor);
wrapper.Dispatch(Resolution / k_ThreadGroupSizeX, Resolution / k_ThreadGroupSizeY, Slices);
}
_TargetsToClear--;
}
@@ -285,8 +259,6 @@ namespace WaveHarmonic.Crest
_TargetsToClear = _Targets.Size;
}
TryBlur(buffer);
if (RequiresClearBorder)
{
ClearBorder(buffer);
@@ -382,18 +354,14 @@ namespace WaveHarmonic.Crest
{
var size = Resolution / 8;
var compute = WaterResources.Instance._ComputeLibrary._ClearCompute;
var wrapper = new PropertyWrapperCompute(buffer, compute._Shader, compute._KernelClearTargetBoundaryX);
// Only need to be done once.
compute.SetVariantForFormat(wrapper, DataTexture.graphicsFormat);
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(buffer, compute._Shader, compute._KernelClearTargetBoundaryY);
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);
@@ -401,7 +369,7 @@ namespace WaveHarmonic.Crest
wrapper.Dispatch(1, size, 1);
}
void UpdateSamplingParameters(CommandBuffer commands, bool initialize = false)
void UpdateSamplingParameters(bool initialize = false)
{
var position = _Water.Position;
var resolution = _Enabled ? Resolution : TextureArrayHelpers.k_SmallTextureSize;
@@ -412,7 +380,7 @@ namespace WaveHarmonic.Crest
for (var slice = 0; slice < levels; slice++)
{
// Find snap period.
var texel = 2f * 2f * _Water.CascadeData.Current[slice].x / resolution;
var texel = 2f * 2f * _Water._CascadeData.Current[slice].x / resolution;
// Snap so that shape texels are stationary.
var snapped = position - new Vector3(Mathf.Repeat(position.x, texel), 0, Mathf.Repeat(position.z, texel));
@@ -424,20 +392,11 @@ namespace WaveHarmonic.Crest
_ViewMatrices[slice] = WaterRenderer.CalculateViewMatrixFromSnappedPositionRHS(snapped);
}
if (!initialize)
if (initialize)
{
commands.SetGlobalVector(_SamplingParametersShaderID, new(levels, resolution, 1f / resolution, 0));
commands.SetGlobalVectorArray(_SamplingParametersCascadeShaderID, parameters);
if (BufferCount > 1)
{
commands.SetGlobalVectorArray(_SamplingParametersCascadeSourceShaderID, _SamplingParameters.Previous(1));
}
return;
Shader.SetGlobalVector(_SamplingParametersShaderID, new(levels, resolution, 1f / resolution, 0));
}
Shader.SetGlobalVector(_SamplingParametersShaderID, new(levels, resolution, 1f / resolution, 0));
Shader.SetGlobalVectorArray(_SamplingParametersCascadeShaderID, parameters);
if (BufferCount > 1)
@@ -521,50 +480,6 @@ namespace WaveHarmonic.Crest
return -1;
}
// Blurs the output if enabled.
private protected void TryBlur(CommandBuffer commands)
{
if (!_Blur || _Water.Scale >= 32)
{
return;
}
var rt = DataTexture;
var compute = WaterResources.Instance._ComputeLibrary._BlurCompute;
var horizontal = new PropertyWrapperCompute(commands, compute._Shader, compute._KernelHorizontal);
var vertical = new PropertyWrapperCompute(commands, compute._Shader, compute._KernelVertical);
var temporary = ShaderIDs.s_TemporaryBlurLodTexture;
commands.GetTemporaryRT(temporary, rt.descriptor);
commands.CopyTexture(rt, temporary);
// Applies to both.
compute.SetVariantForFormat(horizontal, rt.graphicsFormat);
horizontal.SetInteger(Crest.ShaderIDs.s_Resolution, rt.width);
horizontal.SetTexture(Crest.ShaderIDs.s_Source, temporary);
horizontal.SetTexture(Crest.ShaderIDs.s_Target, rt);
vertical.SetTexture(Crest.ShaderIDs.s_Source, rt);
vertical.SetTexture(Crest.ShaderIDs.s_Target, temporary);
var x = rt.width / 8;
var y = rt.height / 8;
// Skip outer LODs.
var z = Mathf.Min(rt.volumeDepth, 4);
for (var i = 0; i < _BlurIterations; i++)
{
// Limit number of iterations for outer LODs.
horizontal.Dispatch(x, y, Mathf.Max(z - i, 1));
vertical.Dispatch(x, y, Mathf.Max(z - i, 1));
}
commands.CopyTexture(temporary, rt);
commands.ReleaseTemporaryRT(temporary);
}
/// <summary>
/// Bind data needed to load or compute from this simulation.
/// </summary>
@@ -621,7 +536,7 @@ namespace WaveHarmonic.Crest
// For safety. Disable to see if we are sampling outside of LOD chain.
_SamplingParameters.RunLambda(x => System.Array.Fill(x, Vector4.zero));
UpdateSamplingParameters(null, initialize: true);
UpdateSamplingParameters(initialize: true);
}
internal virtual void Enable()
@@ -643,17 +558,6 @@ namespace WaveHarmonic.Crest
if (x != null) x.Release();
Helpers.Destroy(x);
});
foreach (var data in _AdditionalCameraData.Values)
{
data._Targets?.RunLambda(x =>
{
if (x != null) x.Release();
Helpers.Destroy(x);
});
}
_AdditionalCameraData.Clear();
}
internal virtual void AfterExecute()
@@ -663,13 +567,6 @@ namespace WaveHarmonic.Crest
private protected virtual void Allocate()
{
// Use per-camera data.
if (_Water.SeparateViewpoint && Persistent)
{
_ReAllocateTexture = false;
return;
}
_Targets = new(BufferCount, CreateLodDataTextures);
_Targets.RunLambda(Clear);
@@ -717,19 +614,9 @@ namespace WaveHarmonic.Crest
texture.Create();
});
foreach (var data in _AdditionalCameraData.Values)
{
data._Targets.RunLambda(texture =>
{
texture.Release();
texture.descriptor = descriptor;
texture.Create();
});
}
_ReAllocateTexture = false;
UpdateSamplingParameters(null, initialize: true);
UpdateSamplingParameters(initialize: true);
}
#if UNITY_EDITOR
@@ -752,70 +639,6 @@ namespace WaveHarmonic.Crest
#endif
}
partial class Lod
{
class AdditionalCameraData
{
public BufferedData<RenderTexture> _Targets;
public BufferedData<Vector4[]> _SamplingParameters;
}
readonly Dictionary<Camera, AdditionalCameraData> _AdditionalCameraData = new();
internal virtual void LoadCameraData(Camera camera)
{
Queryable?.Initialize(_Water);
// For non-persistent sims, we do not need to store per camera data.
if (!_Water.SeparateViewpoint || !Persistent)
{
return;
}
AdditionalCameraData data;
if (!_AdditionalCameraData.ContainsKey(camera))
{
data = new()
{
_Targets = new(BufferCount, CreateLodDataTextures),
_SamplingParameters = new(BufferCount, () => new Vector4[k_MaximumSlices]),
};
data._Targets.RunLambda(Clear);
_AdditionalCameraData.Add(camera, data);
}
else
{
data = _AdditionalCameraData[camera];
}
_Targets = data._Targets;
_SamplingParameters = data._SamplingParameters;
}
internal virtual void StoreCameraData(Camera camera)
{
}
internal void RemoveCameraData(Camera camera)
{
if (_AdditionalCameraData.ContainsKey(camera))
{
var acd = _AdditionalCameraData[camera];
acd._Targets.RunLambda(x =>
{
if (x != null) x.Release();
Helpers.Destroy(x);
});
_AdditionalCameraData.Remove(camera);
}
}
}
// API
partial class Lod
{
@@ -834,64 +657,17 @@ namespace WaveHarmonic.Crest
}
}
interface IQueryableLod<out T> where T : IQueryProvider
{
string Name { get; }
bool Enabled { get; }
WaterRenderer Water { get; }
int MaximumQueryCount { get; }
LodQuerySource QuerySource { get; }
}
/// <summary>
/// The source of collisions (ie water shape).
/// </summary>
[@GenerateDoc]
public enum LodQuerySource
{
/// <inheritdoc cref="Generated.LodQuerySource.None"/>
[Tooltip("No query source.")]
None,
/// <inheritdoc cref="Generated.LodQuerySource.GPU"/>
[Tooltip("Uses AsyncGPUReadback to retrieve data from GPU to CPU.\n\nThis is the most optimal approach.")]
GPU,
/// <inheritdoc cref="Generated.LodQuerySource.CPU"/>
[Tooltip("Computes data entirely on the CPU.")]
CPU,
}
/// <summary>
/// Base type for simulations with a provider.
/// </summary>
/// <typeparam name="T">The query provider.</typeparam>
[System.Serializable]
public abstract partial class Lod<T> : Lod, IQueryableLod<T> where T : IQueryProvider
public abstract class Lod<T> : Lod where T : IQueryProvider
{
[@Space(10)]
[Tooltip("Where to obtain water data on CPU for physics / gameplay.")]
[@GenerateAPI(Setter.Internal)]
[@Filtered]
[SerializeField]
private protected LodQuerySource _QuerySource = LodQuerySource.GPU;
[Tooltip("Maximum number of queries that can be performed when using GPU queries.")]
[@Predicated(nameof(_QuerySource), true, nameof(LodQuerySource.GPU))]
[@GenerateAPI(Setter.None)]
[@DecoratedField]
[SerializeField]
private protected int _MaximumQueryCount = QueryBase.k_DefaultMaximumQueryCount;
/// <summary>
/// Provides data from the GPU to CPU.
/// </summary>
public T Provider { get; set; }
WaterRenderer IQueryableLod<T>.Water => Water;
string IQueryableLod<T>.Name => Name;
private protected abstract T CreateProvider(bool enable);
internal override void SetGlobals(bool enable)
@@ -913,30 +689,4 @@ namespace WaveHarmonic.Crest
Queryable?.SendReadBack(_Water);
}
}
#if UNITY_EDITOR
partial class Lod<T>
{
private protected void ResetQueryChange()
{
if (_Water == null || !_Water.isActiveAndEnabled || !Enabled) return;
Queryable?.CleanUp();
InitializeProvider(true);
}
[@OnChange]
private protected override void OnChange(string path, object previous)
{
base.OnChange(path, previous);
switch (path)
{
case nameof(_QuerySource):
case nameof(_MaximumQueryCount):
ResetQueryChange();
break;
}
}
}
#endif
}

View File

@@ -43,8 +43,6 @@ namespace WaveHarmonic.Crest
private protected abstract ComputeShader SimulationShader { get; }
private protected abstract void GetSubstepData(float timeToSimulate, out int substeps, out float delta);
private protected override bool Persistent => true;
internal override void Initialize()
{
if (SimulationShader == null)
@@ -58,13 +56,19 @@ namespace WaveHarmonic.Crest
_NeedsPrewarmingThisStep = true;
}
internal override void ClearLodData()
{
base.ClearLodData();
_Targets.RunLambda(x => Clear(x));
}
internal override void BuildCommandBuffer(WaterRenderer water, CommandBuffer buffer)
{
buffer.BeginSample(ID);
if (!SkipFlipBuffers)
{
FlipBuffers(buffer);
FlipBuffers();
}
var slices = water.LodLevels;
@@ -137,7 +141,7 @@ namespace WaveHarmonic.Crest
// places.
wrapper.SetFloat(Lod.ShaderIDs.s_LodChange, isFirstStep ? _Water.ScaleDifferencePower2 : 0);
wrapper.SetVectorArray(WaterRenderer.ShaderIDs.s_CascadeDataSource, _Water.CascadeData.Previous(frame));
wrapper.SetVectorArray(WaterRenderer.ShaderIDs.s_CascadeDataSource, _Water._CascadeData.Previous(frame));
wrapper.SetVectorArray(_SamplingParametersCascadeSourceShaderID, _SamplingParameters.Previous(frame));
SetAdditionalSimulationParameters(wrapper);
@@ -170,8 +174,6 @@ namespace WaveHarmonic.Crest
// Set the target texture as to make sure we catch the 'pong' each frame.
Shader.SetGlobalTexture(_TextureShaderID, DataTexture);
TryBlur(buffer);
buffer.EndSample(ID);
}
@@ -183,58 +185,4 @@ namespace WaveHarmonic.Crest
}
}
partial class PersistentLod
{
class AdditionalCameraData
{
public float _TimeToSimulate;
public float _PreviousSubstepDeltaTime;
}
readonly System.Collections.Generic.Dictionary<Camera, AdditionalCameraData> _AdditionalCameraData = new();
internal override void LoadCameraData(Camera camera)
{
base.LoadCameraData(camera);
if (!_Water.SeparateViewpoint)
{
return;
}
AdditionalCameraData data;
if (!_AdditionalCameraData.ContainsKey(camera))
{
data = new()
{
_TimeToSimulate = _TimeToSimulate,
_PreviousSubstepDeltaTime = _PreviousSubstepDeltaTime,
};
_AdditionalCameraData.Add(camera, data);
}
else
{
data = _AdditionalCameraData[camera];
}
_TimeToSimulate = data._TimeToSimulate;
_PreviousSubstepDeltaTime = data._PreviousSubstepDeltaTime;
}
internal override void StoreCameraData(Camera camera)
{
base.StoreCameraData(camera);
if (!_Water.SeparateViewpoint)
{
return;
}
_AdditionalCameraData[camera]._TimeToSimulate = _TimeToSimulate;
_AdditionalCameraData[camera]._PreviousSubstepDeltaTime = _PreviousSubstepDeltaTime;
}
}
}

View File

@@ -38,17 +38,12 @@ namespace WaveHarmonic.Crest
internal static NoneProvider None { get; } = new();
internal static ICollisionProvider Create(WaterRenderer water)
{
return water.MultipleViewpoints ? new CollisionQueryPerCamera(water) : new CollisionQueryWithPasses(water);
}
/// <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, Vector3? _4 = null)
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);
@@ -56,7 +51,7 @@ namespace WaveHarmonic.Crest
return 0;
}
public int Query(int _0, float _1, Vector3[] _2, float[] result0, Vector3[] result1, Vector3[] result2, CollisionLayer _3 = CollisionLayer.Everything, Vector3? _4 = null)
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);
@@ -71,12 +66,12 @@ namespace WaveHarmonic.Crest
/// <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, Vector3?)" />
int Query(int hash, float minimumLength, Vector3[] points, float[] heights, Vector3[] normals, Vector3[] velocities, CollisionLayer layer = CollisionLayer.Everything, Vector3? center = null);
/// <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, Vector3?)" />
/// <inheritdoc cref="Query(int, float, Vector3[], float[], Vector3[], Vector3[], CollisionLayer, Vector3?)" />
int Query(int hash, float minimumLength, Vector3[] points, Vector3[] displacements, Vector3[] normals, Vector3[] velocities, CollisionLayer layer = CollisionLayer.Everything, Vector3? center = null);
/// <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);
}
}

View File

@@ -2,7 +2,6 @@
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
using WaveHarmonic.Crest.Internal;
namespace WaveHarmonic.Crest
{
@@ -11,10 +10,10 @@ namespace WaveHarmonic.Crest
/// </summary>
sealed class CollisionQuery : QueryBase, ICollisionProvider
{
public CollisionQuery(WaterRenderer water) : base(water.AnimatedWavesLod) { }
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, Vector3? center = null)
public int Query(int ownerHash, float minSpatialLength, Vector3[] queryPoints, Vector3[] resultDisplacements, Vector3[] resultNormals, Vector3[] resultVelocities, CollisionLayer layer = CollisionLayer.Everything)
{
var result = (int)QueryStatus.OK;
@@ -36,7 +35,7 @@ namespace WaveHarmonic.Crest
return result;
}
public int Query(int ownerHash, float minimumSpatialLength, Vector3[] queryPoints, float[] resultHeights, Vector3[] resultNormals, Vector3[] resultVelocities, CollisionLayer layer = CollisionLayer.Everything, Vector3? center = null)
public int Query(int ownerHash, float minimumSpatialLength, Vector3[] queryPoints, float[] resultHeights, Vector3[] resultNormals, Vector3[] resultVelocities, CollisionLayer layer = CollisionLayer.Everything)
{
var result = (int)QueryStatus.OK;
@@ -59,102 +58,6 @@ namespace WaveHarmonic.Crest
}
}
sealed class CollisionQueryPerCamera : QueryPerCamera<CollisionQueryWithPasses>, ICollisionProvider
{
public CollisionQueryPerCamera() : base(WaterRenderer.Instance) { }
public CollisionQueryPerCamera(WaterRenderer water) : base(water) { }
public int Query(int hash, float minimumLength, Vector3[] points, float[] heights, Vector3[] normals, Vector3[] velocities, CollisionLayer layer = CollisionLayer.Everything, Vector3? center = null)
{
if (_Water._InCameraLoop)
{
return _Providers[_Water.CurrentCamera].Query(hash, minimumLength, points, heights, normals, velocities, layer, center);
}
var lastStatus = -1;
var lastDistance = Mathf.Infinity;
var newCenter = FindCenter(points, center);
foreach (var provider in _Providers)
{
var camera = provider.Key;
if (!_Water.ShouldExecuteQueries(camera))
{
continue;
}
var distance = (newCenter - camera.transform.position.XZ()).sqrMagnitude;
if (lastStatus == (int)QueryBase.QueryStatus.OK && lastDistance < distance)
{
continue;
}
var status = provider.Value.Query(hash, minimumLength, points, heights, normals, velocities, layer, center);
if (lastStatus < 0 || status == (int)QueryBase.QueryStatus.OK)
{
lastStatus = status;
lastDistance = distance;
}
}
return lastStatus;
}
public int Query(int hash, float minimumLength, Vector3[] points, Vector3[] displacements, Vector3[] normals, Vector3[] velocities, CollisionLayer layer = CollisionLayer.Everything, Vector3? center = null)
{
if (_Water._InCameraLoop)
{
return _Providers[_Water.CurrentCamera].Query(hash, minimumLength, points, displacements, normals, velocities, layer, center);
}
var lastStatus = -1;
var lastDistance = Mathf.Infinity;
var newCenter = FindCenter(points, center);
foreach (var provider in _Providers)
{
var camera = provider.Key;
if (!_Water.ShouldExecuteQueries(camera))
{
continue;
}
var distance = (newCenter - camera.transform.position.XZ()).sqrMagnitude;
if (lastStatus == (int)QueryBase.QueryStatus.OK && lastDistance < distance)
{
continue;
}
var status = provider.Value.Query(hash, minimumLength, points, displacements, normals, velocities, layer, center);
if (lastStatus < 0 || status == (int)QueryBase.QueryStatus.OK)
{
lastStatus = status;
lastDistance = distance;
}
}
return lastStatus;
}
public void SendReadBack(WaterRenderer water, CollisionLayers layers)
{
_Providers[water.CurrentCamera].SendReadBack(water, layers);
}
public void UpdateQueries(WaterRenderer water, CollisionLayer layer)
{
_Providers[water.CurrentCamera].UpdateQueries(water, layer);
}
}
sealed class CollisionQueryWithPasses : ICollisionProvider, IQueryable
{
readonly CollisionQuery _AnimatedWaves;
@@ -166,14 +69,6 @@ namespace WaveHarmonic.Crest
public int RequestCount => _AnimatedWaves.RequestCount + _DynamicWaves.RequestCount + _Displacement.RequestCount;
public int QueryCount => _AnimatedWaves.QueryCount + _DynamicWaves.QueryCount + _Displacement.QueryCount;
public CollisionQueryWithPasses()
{
_Water = WaterRenderer.Instance;
_AnimatedWaves = new(_Water);
_DynamicWaves = new(_Water);
_Displacement = new(_Water);
}
public CollisionQueryWithPasses(WaterRenderer water)
{
_Water = water;
@@ -212,12 +107,12 @@ namespace WaveHarmonic.Crest
return _AnimatedWaves;
}
public int Query(int hash, float minimumLength, Vector3[] points, float[] heights, Vector3[] normals, Vector3[] velocities, CollisionLayer layer = CollisionLayer.Everything, Vector3? center = null)
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, Vector3? center = null)
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);
}
@@ -256,55 +151,13 @@ namespace WaveHarmonic.Crest
_DynamicWaves.CleanUp();
_Displacement.CleanUp();
}
public void Initialize(WaterRenderer water)
{
}
}
// These are required because of collision layer.
static partial class Extensions
{
public static void UpdateQueries(this ICollisionProvider self, WaterRenderer water, CollisionLayer layer)
{
if (self is CollisionQueryPerCamera a)
{
a.UpdateQueries(water, layer);
}
else if (self is CollisionQueryWithPasses b)
{
b.UpdateQueries(water, layer);
}
else if (self is ICollisionProvider.NoneProvider c)
{
}
else
{
Debug.LogError("Crest: no valid query provider. Report this to developers!");
}
}
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 SendReadBack(this ICollisionProvider self, WaterRenderer water, CollisionLayers layer)
{
if (self is CollisionQueryPerCamera a)
{
a.SendReadBack(water, layer);
}
else if (self is CollisionQueryWithPasses b)
{
b.SendReadBack(water, layer);
}
else if (self is ICollisionProvider.NoneProvider c)
{
}
else
{
Debug.LogError("Crest: no valid query provider. Report this to developers!");
}
}
public static void SendReadBack(this ICollisionProvider self, WaterRenderer water, CollisionLayers layer) => (self as CollisionQueryWithPasses)?.SendReadBack(water, layer);
public static void CleanUp(this ICollisionProvider self) => (self as IQueryable)?.CleanUp();
}
}

View File

@@ -59,9 +59,7 @@ namespace WaveHarmonic.Crest
{
distance = -1f;
var id = GetHashCode();
Validate(allowMultipleCallsPerFrame: false, id);
Validate(allowMultipleCallsPerFrame: false);
var water = WaterRenderer.Instance;
var provider = water == null ? null : water.AnimatedWavesLod.Provider;
@@ -72,7 +70,7 @@ namespace WaveHarmonic.Crest
_QueryPosition[i] = origin + i * _RayStepSize * direction;
}
var status = provider.Query(id, _MinimumLength, _QueryPosition, _QueryResult, null, null, layer);
var status = provider.Query(GetHashCode(), _MinimumLength, _QueryPosition, _QueryResult, null, null, layer);
if (!provider.RetrieveSucceeded(status))
{

View File

@@ -15,14 +15,9 @@ namespace WaveHarmonic.Crest
{
internal static NoneProvider None { get; } = new();
internal static IDepthProvider Create(WaterRenderer water)
{
return water.MultipleViewpoints ? new DepthQueryPerCamera(water) : new DepthQuery(water);
}
internal sealed class NoneProvider : IDepthProvider
{
public int Query(int _0, float _1, Vector3[] _2, Vector3[] result, Vector3? _3 = null)
public int Query(int _0, float _1, Vector3[] _2, Vector3[] result)
{
if (result != null) System.Array.Clear(result, 0, result.Length);
return 0;
@@ -33,7 +28,7 @@ namespace WaveHarmonic.Crest
/// 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, Vector3?)" />
int Query(int hash, float minimumLength, Vector3[] points, Vector3[] results, Vector3? center = null);
/// <inheritdoc cref="IQueryProvider.Query(int, float, Vector3[], int)" />
int Query(int hash, float minimumLength, Vector3[] points, Vector3[] results);
}
}

View File

@@ -5,15 +5,14 @@ using UnityEngine;
namespace WaveHarmonic.Crest
{
sealed class DepthQuery : QueryBaseSimple, IDepthProvider
sealed class DepthQuery : QueryBase, IDepthProvider
{
public DepthQuery() : base(WaterRenderer.Instance.DepthLod) { }
public DepthQuery(WaterRenderer water) : base(water.DepthLod) { }
public DepthQuery(WaterRenderer water) : base(water) { }
protected override int Kernel => 2;
public override int Query(int hash, float minimumSpatialLength, Vector3[] queries, Vector3[] results, Vector3? center = null)
public override int Query(int hash, float minimumSpatialLength, Vector3[] queries, Vector3[] results)
{
var id = base.Query(hash, minimumSpatialLength, queries, results, center);
var id = base.Query(hash, minimumSpatialLength, queries, results);
// Infinity will become NaN. Convert back to infinity.
// Negative infinity should not happen.
@@ -28,9 +27,4 @@ namespace WaveHarmonic.Crest
return id;
}
}
sealed class DepthQueryPerCamera : QueryPerCameraSimple<DepthQuery>, IDepthProvider
{
public DepthQueryPerCamera(WaterRenderer water) : base(water) { }
}
}

View File

@@ -15,17 +15,12 @@ namespace WaveHarmonic.Crest
{
internal static NoneProvider None { get; } = new();
internal static IFlowProvider Create(WaterRenderer water)
{
return water.MultipleViewpoints ? new FlowQueryPerCamera(water) : new FlowQuery(water);
}
/// <summary>
/// Gives a stationary water (no horizontal flow).
/// </summary>
internal sealed class NoneProvider : IFlowProvider
{
public int Query(int _0, float _1, Vector3[] _2, Vector3[] result, Vector3? _3 = null)
public int Query(int _0, float _1, Vector3[] _2, Vector3[] result)
{
if (result != null) System.Array.Clear(result, 0, result.Length);
return 0;
@@ -36,7 +31,7 @@ namespace WaveHarmonic.Crest
/// Query water flow data (horizontal motion) at a set of points.
/// </summary>
/// <param name="results">Water surface flow velocities at the query positions.</param>
/// <inheritdoc cref="IQueryProvider.Query(int, float, Vector3[], int, Vector3?)" />
int Query(int hash, float minimumLength, Vector3[] points, Vector3[] results, Vector3? center = null);
/// <inheritdoc cref="IQueryProvider.Query(int, float, Vector3[], int)" />
int Query(int hash, float minimumLength, Vector3[] points, Vector3[] results);
}
}

View File

@@ -6,15 +6,9 @@ namespace WaveHarmonic.Crest
/// <summary>
/// Samples horizontal motion of water volume
/// </summary>
sealed class FlowQuery : QueryBaseSimple, IFlowProvider
sealed class FlowQuery : QueryBase, IFlowProvider
{
public FlowQuery() : base(WaterRenderer.Instance.FlowLod) { }
public FlowQuery(WaterRenderer water) : base(water.FlowLod) { }
public FlowQuery(WaterRenderer water) : base(water) { }
protected override int Kernel => 1;
}
sealed class FlowQueryPerCamera : QueryPerCameraSimple<FlowQuery>, IFlowProvider
{
public FlowQueryPerCamera(WaterRenderer water) : base(water) { }
}
}

View File

@@ -9,7 +9,6 @@ using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Rendering;
using WaveHarmonic.Crest.Internal;
namespace WaveHarmonic.Crest
{
@@ -23,9 +22,8 @@ namespace WaveHarmonic.Crest
/// <param name="minimumLength">The minimum spatial length of the object, such as the width of a boat. Useful for filtering out detail when not needed. Set to zero to get full available detail.</param>
/// <param name="points">The world space points that will be queried.</param>
/// <param name="layer">The layer this query targets.</param>
/// <param name="center">The center of all the query positions. Used to choose the closest query provider.</param>
/// <returns>The status of the query.</returns>
internal static int Query(int hash, float minimumLength, Vector3[] points, int layer, Vector3? center) => throw new System.NotImplementedException("Crest: this method is for documentation reuse only. Do not invoke.");
internal static int Query(int hash, float minimumLength, Vector3[] points, int layer) => 0;
/// <summary>
/// Check if the query results could be retrieved successfully using the return code
@@ -47,12 +45,6 @@ namespace WaveHarmonic.Crest
void UpdateQueries(WaterRenderer water);
void SendReadBack(WaterRenderer water);
void CleanUp();
void Initialize(WaterRenderer water);
}
interface IQueryableSimple : IQueryable
{
int Query(int hash, float minimumLength, Vector3[] queries, Vector3[] results, Vector3? center);
}
/// <summary>
@@ -72,7 +64,6 @@ namespace WaveHarmonic.Crest
const int k_NormalAdditionalQueryCount = 2;
readonly WaterRenderer _Water;
readonly IQueryableLod<IQueryProvider> _Lod;
readonly PropertyWrapperCompute _Wrapper;
@@ -94,8 +85,8 @@ namespace WaveHarmonic.Crest
internal const int k_DefaultMaximumQueryCount = 4096;
readonly int _MaximumQueryCount;
readonly Vector3[] _QueryPositionXZ_MinimumGridSize;
readonly int _MaximumQueryCount = k_DefaultMaximumQueryCount;
readonly Vector3[] _QueryPositionXZ_MinimumGridSize = new Vector3[k_DefaultMaximumQueryCount];
/// <summary>
/// Holds information about all query points. Maps from unique hash code to position in point array.
@@ -262,15 +253,17 @@ namespace WaveHarmonic.Crest
InvalidDtForVelocity = 16,
}
public QueryBase(IQueryableLod<IQueryProvider> lod)
public QueryBase(WaterRenderer water)
{
_Water = lod.Water;
_Lod = lod;
_Water = water;
_DataArrivedAction = new(DataArrived);
_MaximumQueryCount = lod.MaximumQueryCount;
_QueryPositionXZ_MinimumGridSize = new Vector3[_MaximumQueryCount];
if (_MaximumQueryCount != water._AnimatedWavesLod.MaximumQueryCount)
{
_MaximumQueryCount = water._AnimatedWavesLod.MaximumQueryCount;
_QueryPositionXZ_MinimumGridSize = new Vector3[_MaximumQueryCount];
}
_ComputeBufferQueries = new(_MaximumQueryCount, 12, ComputeBufferType.Default);
_ComputeBufferResults = new(_MaximumQueryCount, 12, ComputeBufferType.Default);
@@ -284,13 +277,7 @@ namespace WaveHarmonic.Crest
Debug.LogError($"Crest: Could not load Query compute shader");
return;
}
_Wrapper = new(_Water.SimulationBuffer, shader, Kernel);
}
void LogMaximumQueryCountExceededError()
{
Debug.LogError($"Crest: Maximum query count ({_MaximumQueryCount}) exceeded, increase the <i>{nameof(WaterRenderer)} > Simulations > {_Lod.Name} > {nameof(_Lod.MaximumQueryCount)}</i> to support a higher number of queries.", _Water);
_Wrapper = new(water.SimulationBuffer, shader, Kernel);
}
/// <summary>
@@ -301,7 +288,7 @@ namespace WaveHarmonic.Crest
{
if (queryPoints.Length + _SegmentRegistrarRingBuffer.Current._QueryCount > _MaximumQueryCount)
{
LogMaximumQueryCountExceededError();
Debug.LogError($"Crest: Max query count ({_MaximumQueryCount}) exceeded, increase the max query count in the Animated Waves Settings to support a higher number of queries.");
return false;
}
@@ -361,7 +348,7 @@ namespace WaveHarmonic.Crest
if (countPts + segment.x > _QueryPositionXZ_MinimumGridSize.Length)
{
LogMaximumQueryCountExceededError();
Debug.LogError("Crest: Too many wave height queries. Increase Max Query Count in the Animated Waves Settings.");
return false;
}
@@ -631,23 +618,7 @@ namespace WaveHarmonic.Crest
_SegmentRegistrarRingBuffer.ClearAll();
}
public virtual void Initialize(WaterRenderer water)
{
}
public int ResultGuidCount => _ResultSegments != null ? _ResultSegments.Count : 0;
public int RequestCount => _Requests != null ? _Requests.Count : 0;
public int QueryCount => _SegmentRegistrarRingBuffer != null ? _SegmentRegistrarRingBuffer.Current._QueryCount : 0;
}
abstract class QueryBaseSimple : QueryBase, IQueryableSimple
{
protected QueryBaseSimple(IQueryableLod<IQueryProvider> lod) : base(lod) { }
public virtual int Query(int ownerHash, float minSpatialLength, Vector3[] queryPoints, Vector3[] results, Vector3? center)
public virtual int Query(int ownerHash, float minSpatialLength, Vector3[] queryPoints, Vector3[] results)
{
var result = (int)QueryStatus.OK;
@@ -663,159 +634,12 @@ namespace WaveHarmonic.Crest
return result;
}
}
abstract class QueryPerCamera<T> : IQueryable where T : IQueryable, new()
{
internal readonly WaterRenderer _Water;
internal readonly Dictionary<Camera, T> _Providers = new();
public int ResultGuidCount => _ResultSegments != null ? _ResultSegments.Count : 0;
public QueryPerCamera(WaterRenderer water)
{
_Water = water;
Initialize(water);
}
public int RequestCount => _Requests != null ? _Requests.Count : 0;
public int ResultGuidCount
{
get
{
var total = 0;
foreach (var (camera, provider) in _Providers)
{
if (_Water.ShouldExecuteQueries(camera)) total += provider.ResultGuidCount;
}
return total;
}
}
public int RequestCount
{
get
{
var total = 0;
foreach (var (camera, provider) in _Providers)
{
if (_Water.ShouldExecuteQueries(camera)) total += provider.RequestCount;
}
return total;
}
}
public int QueryCount
{
get
{
var total = 0;
foreach (var (camera, provider) in _Providers)
{
if (_Water.ShouldExecuteQueries(camera)) total += provider.QueryCount;
}
return total;
}
}
public void CleanUp()
{
foreach (var provider in _Providers.Values)
{
provider?.CleanUp();
}
}
public void Initialize(WaterRenderer water)
{
var camera = water.CurrentCamera;
if (camera == null)
{
camera = water.Viewer;
}
if (camera == null)
{
return;
}
if (!_Providers.ContainsKey(camera))
{
// Cannot use parameters. We could use System.Activator.CreateInstance to get
// around that, but instead we just use WaterRenderer.Instance.
_Providers.Add(camera, new());
}
}
public void SendReadBack(WaterRenderer water)
{
_Providers[water.CurrentCamera].SendReadBack(water);
}
public void UpdateQueries(WaterRenderer water)
{
_Providers[water.CurrentCamera].UpdateQueries(water);
}
public Vector2 FindCenter(Vector3[] queries, Vector3? center)
{
if (center != null)
{
return ((Vector3)center).XZ();
}
// Calculate center if none provided.
var sum = Vector2.zero;
foreach (var point in queries)
{
sum += point.XZ();
}
return new(sum.x / queries.Length, sum.y / queries.Length);
}
}
abstract class QueryPerCameraSimple<T> : QueryPerCamera<T>, IQueryableSimple where T : IQueryableSimple, new()
{
protected QueryPerCameraSimple(WaterRenderer water) : base(water) { }
public int Query(int id, float length, Vector3[] queries, Vector3[] results, Vector3? center = null)
{
if (_Water._InCameraLoop)
{
return _Providers[_Water.CurrentCamera].Query(id, length, queries, results, center);
}
var lastStatus = -1;
var lastDistance = Mathf.Infinity;
var newCenter = FindCenter(queries, center);
foreach (var provider in _Providers)
{
var camera = provider.Key;
if (!_Water.ShouldExecuteQueries(camera))
{
continue;
}
var distance = (newCenter - camera.transform.position.XZ()).sqrMagnitude;
if (lastStatus == (int)QueryBase.QueryStatus.OK && lastDistance < distance)
{
continue;
}
var status = provider.Value.Query(id, length, queries, results, center);
if (lastStatus < 0 || status == (int)QueryBase.QueryStatus.OK)
{
lastStatus = status;
lastDistance = distance;
}
}
return lastStatus;
}
public int QueryCount => _SegmentRegistrarRingBuffer != null ? _SegmentRegistrarRingBuffer.Current._QueryCount : 0;
}
static partial class Extensions

View File

@@ -40,13 +40,6 @@ namespace WaveHarmonic.Crest
[@DecoratedField, SerializeField]
QuerySource _Source;
[Tooltip("The viewer as the source of the queries.\n\nOnly needs to be set if using multiple viewpoints on the Water Renderer.")]
[@Predicated(nameof(_Source), inverted: false, nameof(QuerySource.Viewer), hide: true)]
[@GenerateAPI]
[@DecoratedField]
[SerializeField]
Camera _Viewer;
[Tooltip(ICollisionProvider.k_LayerTooltip)]
[@GenerateAPI]
[@DecoratedField, SerializeField]
@@ -181,11 +174,6 @@ namespace WaveHarmonic.Crest
var distance = water.ViewerHeightAboveWater;
if (water.MultipleViewpoints && (_Viewer == null || !water.GetViewerHeightAboveWater(_Viewer, out distance)))
{
return;
}
if (_Source == QuerySource.Transform)
{
if (!_SampleHeightHelper.SampleHeight(transform.position, out var height, minimumLength: 2f * _MinimumWavelength, _Layer)) return;
@@ -245,11 +233,6 @@ namespace WaveHarmonic.Crest
var distance = water.ViewerDistanceToShoreline;
if (water.MultipleViewpoints && (_Viewer == null || !water.GetViewerDistanceToShoreline(_Viewer, out distance)))
{
return;
}
if (_Source == QuerySource.Transform)
{
if (!_SampleDepthHelper.SampleDistanceToWaterEdge(transform.position, out distance))

View File

@@ -1,7 +1,6 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using System.Collections.Generic;
using UnityEngine;
// Linter does not support mixing inheritdoc plus defining own parameters.
@@ -21,7 +20,7 @@ namespace WaveHarmonic.Crest.Internal
private protected readonly Vector3[] _QueryPosition;
private protected readonly Vector3[] _QueryResult;
readonly Dictionary<int, int> _LastFrame = new();
int _LastFrame = -1;
private protected SampleHelper(int queryCount = 1)
{
@@ -30,29 +29,15 @@ namespace WaveHarmonic.Crest.Internal
}
[System.Diagnostics.Conditional("UNITY_EDITOR")]
private protected void Validate(bool allowMultipleCallsPerFrame, int id)
private protected void Validate(bool allowMultipleCallsPerFrame)
{
if (!_LastFrame.ContainsKey(id))
{
_LastFrame.Add(id, -1);
}
#if UNITY_EDITOR
// Prevent false positives spamming the console.
if (!UnityEditor.EditorApplication.isFocused || (Application.isPlaying && UnityEditor.EditorApplication.isPaused))
{
_LastFrame[id] = Time.frameCount;
return;
}
#endif
if (!Time.inFixedTimeStep && !allowMultipleCallsPerFrame && _LastFrame[id] == Time.frameCount)
if (Application.isPlaying && !Time.inFixedTimeStep && !allowMultipleCallsPerFrame && _LastFrame == Time.frameCount)
{
var type = GetType().Name;
Debug.LogWarning($"Crest: {type} sample called multiple times in one frame which is not expected. Each {type} object services a single sample per frame. To perform multiple queries, create multiple {type} objects or use the query provider API directly.");
}
_LastFrame[id] = Time.frameCount;
_LastFrame = Time.frameCount;
}
// The first method is there just to get inheritdoc to work as it does not like
@@ -118,7 +103,7 @@ namespace WaveHarmonic.Crest
var isVelocity = (options & QueryOptions.Velocity) == QueryOptions.Velocity;
var isNormal = (options & QueryOptions.Normal) == QueryOptions.Normal;
Validate(allowMultipleCallsPerFrame, id);
Validate(allowMultipleCallsPerFrame);
_QueryPosition[0] = position;
@@ -130,8 +115,7 @@ namespace WaveHarmonic.Crest
_QueryResult,
isNormal ? _QueryResultNormal : null,
isVelocity ? _QueryResultVelocity : null,
layer,
position
layer
);
if (!provider.RetrieveSucceeded(status))
@@ -250,13 +234,11 @@ namespace WaveHarmonic.Crest
return false;
}
var id = GetHashCode();
Validate(false, id);
Validate(false);
_QueryPosition[0] = position;
var status = flowProvider.Query(id, minimumLength, _QueryPosition, _QueryResult, position);
var status = flowProvider.Query(GetHashCode(), minimumLength, _QueryPosition, _QueryResult);
if (!flowProvider.RetrieveSucceeded(status))
{
@@ -277,25 +259,25 @@ namespace WaveHarmonic.Crest
/// </summary>
public sealed class SampleDepthHelper : Internal.SampleHelper
{
internal bool Sample(int id, Vector3 position, out Vector2 result, bool allowMultipleCallsPerFrame = false)
bool Sample(Vector3 position, out Vector2 result)
{
var water = WaterRenderer.Instance;
var depthProvider = water == null ? null : water.DepthLod.Provider;
if (depthProvider == null)
{
result = new(Mathf.Infinity, Mathf.Infinity);
result = Vector2.zero;
return false;
}
Validate(allowMultipleCallsPerFrame, id);
Validate(false);
_QueryPosition[0] = position;
var status = depthProvider.Query(id, minimumLength: 0, _QueryPosition, _QueryResult, position);
var status = depthProvider.Query(GetHashCode(), minimumLength: 0, _QueryPosition, _QueryResult);
if (!depthProvider.RetrieveSucceeded(status))
{
result = new(Mathf.Infinity, Mathf.Infinity);
result = Vector2.zero;
return false;
}
@@ -303,18 +285,13 @@ namespace WaveHarmonic.Crest
return true;
}
bool Sample(Vector3 position, out Vector2 result)
{
return Sample(GetHashCode(), position, out result);
}
/// <summary>
/// Sample both the water depth and water edge distance.
/// </summary>
/// <param name="depth">Filled by the water depth at the query position.</param>
/// <param name="distance">Filled by the distance to water edge at the query position.</param>
/// <inheritdoc cref="Internal.SampleHelper.Sample" />
internal bool Sample(Vector3 position, out float depth, out float distance)
bool Sample(Vector3 position, out float depth, out float distance)
{
var success = Sample(position, out var result);
depth = result.x;
@@ -335,12 +312,7 @@ namespace WaveHarmonic.Crest
/// <inheritdoc cref="Sample(Vector3, out float, out float)"/>
public bool SampleDistanceToWaterEdge(Vector3 position, out float distance)
{
return SampleDistanceToWaterEdge(GetHashCode(), position, out distance);
}
internal bool SampleDistanceToWaterEdge(int id, Vector3 position, out float distance)
{
var success = Sample(id, position, out var result);
var success = Sample(position, out var result);
distance = result.y;
return success;
}

View File

@@ -24,6 +24,13 @@ namespace WaveHarmonic.Crest
return;
}
#if UNITY_EDITOR
if (!WaterRenderer.IsWithinEditorUpdate)
{
return;
}
#endif
var camera = context.hdCamera.camera;
// Custom passes execute for every camera. We only support one camera for now.

View File

@@ -45,7 +45,6 @@ namespace WaveHarmonic.Crest
}
}
#if URP_COMPATIBILITY_MODE
[System.Obsolete]
public override void Execute(ScriptableRenderContext context, ref RenderingData data)
{
@@ -55,7 +54,6 @@ namespace WaveHarmonic.Crest
context.ExecuteCommandBuffer(buffer);
CommandBufferPool.Release(buffer);
}
#endif
}
}

View File

@@ -41,6 +41,13 @@ namespace WaveHarmonic.Crest
return;
}
#if UNITY_EDITOR
if (!WaterRenderer.IsWithinEditorUpdate)
{
return;
}
#endif
// Only sample shadows for the main camera.
if (!ReferenceEquals(water.Viewer, camera))
{

View File

@@ -30,6 +30,12 @@ namespace WaveHarmonic.Crest
CopyShadowMapBuffer?.Clear();
return;
}
if (!WaterRenderer.IsWithinEditorUpdate)
{
CopyShadowMapBuffer?.Clear();
return;
}
#endif
var water = _Water;
@@ -44,12 +50,7 @@ namespace WaveHarmonic.Crest
return;
}
if (_Water.Reflections.ReflectionCamera == camera)
{
return;
}
if (CopyShadowMapBuffer != null)
if (camera == water.Viewer && CopyShadowMapBuffer != null)
{
if (_Light != null)
{
@@ -82,15 +83,36 @@ namespace WaveHarmonic.Crest
CopyShadowMapBuffer?.Clear();
return;
}
if (!WaterRenderer.IsWithinEditorUpdate)
{
CopyShadowMapBuffer?.Clear();
return;
}
#endif
// CBs added to a light are executed for every camera, but the LOD data is only
// supports a single camera. Removing the CB after the camera renders restricts the
// CB to one camera. Careful of recursive rendering for planar reflections, as it
// executes a camera within this camera's frame.
if (_Light != null && CopyShadowMapBuffer != null)
var water = _Water;
if (water == null)
{
_Light.RemoveCommandBuffer(LightEvent.BeforeScreenspaceMask, CopyShadowMapBuffer);
return;
}
if (!WaterRenderer.ShouldRender(camera, water.Surface.Layer))
{
return;
}
if (camera == water.Viewer)
{
// CBs added to a light are executed for every camera, but the LOD data is only
// supports a single camera. Removing the CB after the camera renders restricts the
// CB to one camera. Careful of recursive rendering for planar reflections, as it
// executes a camera within this camera's frame.
if (_Light != null && CopyShadowMapBuffer != null)
{
_Light.RemoveCommandBuffer(LightEvent.BeforeScreenspaceMask, CopyShadowMapBuffer);
}
}
}
}

View File

@@ -96,7 +96,6 @@ namespace WaveHarmonic.Crest
private protected override Color ClearColor => Color.black;
private protected override bool NeedToReadWriteTextureData => true;
internal override int BufferCount => 2;
internal override bool SkipEndOfFrame => true;
private protected override GraphicsFormat RequestedTextureFormat => _TextureFormatMode switch
{
@@ -151,9 +150,17 @@ namespace WaveHarmonic.Crest
#if d_UnityURP
var asset = GraphicsSettings.currentRenderPipeline as UniversalRenderPipelineAsset;
// TODO: Support single casacades as it is possible.
if (asset && asset.shadowCascadeCount < 2)
{
Debug.LogError("Crest shadowing requires shadow cascades to be enabled on the pipeline asset.", asset);
_Valid = false;
return;
}
if (asset.mainLightRenderingMode == LightRenderingMode.Disabled)
{
Debug.LogWarning("Crest: Main Light must be enabled to enable water shadowing.", _Water);
Debug.LogError("Crest: Main Light must be enabled to enable water shadowing.", _Water);
_Valid = false;
return;
}
@@ -164,7 +171,7 @@ namespace WaveHarmonic.Crest
if (isShadowsDisabled)
{
Debug.LogWarning("Crest: Shadows must be enabled in the quality settings to enable water shadowing.", _Water);
Debug.LogError("Crest: Shadows must be enabled in the quality settings to enable water shadowing.", _Water);
_Valid = false;
return;
}
@@ -229,6 +236,8 @@ namespace WaveHarmonic.Crest
{
base.Allocate();
_Targets.RunLambda(buffer => Clear(buffer));
{
_RenderMaterial = new PropertyWrapperMaterial[Slices];
var shader = WaterResources.Instance.Shaders._UpdateShadow;
@@ -254,6 +263,12 @@ namespace WaveHarmonic.Crest
}
}
internal override void ClearLodData()
{
base.ClearLodData();
_Targets.RunLambda(buffer => Clear(buffer));
}
/// <summary>
/// Validates the primary light.
/// </summary>
@@ -294,7 +309,7 @@ namespace WaveHarmonic.Crest
{
if (_Error != Error.IncorrectLightType)
{
Debug.LogWarning("Crest: Primary light must be of type Directional.", _Light);
Debug.LogError("Crest: Primary light must be of type Directional.", _Light);
_Error = Error.IncorrectLightType;
}
return false;
@@ -383,7 +398,7 @@ namespace WaveHarmonic.Crest
CopyShadowMapBuffer ??= new() { name = WaterRenderer.k_DrawLodData };
CopyShadowMapBuffer.Clear();
FlipBuffers(buffer);
FlipBuffers();
// clear the shadow collection. it will be overwritten with shadow values IF the shadows render,
// which only happens if there are (nontransparent) shadow receivers around. this is only reliable
@@ -506,7 +521,6 @@ namespace WaveHarmonic.Crest
{
_Enabled = true;
_TextureFormat = GraphicsFormat.R8G8_UNorm;
_Blur = true;
}
internal static SortedList<int, ILodInput> s_Inputs = new(Helpers.DuplicateComparison);

View File

@@ -23,9 +23,13 @@ namespace WaveHarmonic.Crest
/// <summary>
/// Where to obtain water shape on CPU for physics / gameplay.
/// </summary>
[System.Obsolete("Please use QuerySource instead.")]
public CollisionSource CollisionSource { get => _CollisionSource; internal set => _CollisionSource = value; }
/// <summary>
/// Maximum number of wave queries that can be performed when using GPU queries.
/// </summary>
public int MaximumQueryCount => _MaximumQueryCount;
/// <summary>
/// Any water deeper than this will receive full wave strength.
/// </summary>
@@ -40,12 +44,7 @@ namespace WaveHarmonic.Crest
/// <remarks>
/// Set 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.
/// </remarks>
public float WaveResolutionMultiplier { get => GetWaveResolutionMultiplier(); set => _WaveResolutionMultiplier = value; }
/// <summary>
/// The wave sampling method to determine quality and performance.
/// </summary>
public WaveSampling WaveSampling { get => _WaveSampling; set => _WaveSampling = value; }
public float WaveResolutionMultiplier { get => _WaveResolutionMultiplier; set => _WaveResolutionMultiplier = value; }
}
}
@@ -662,23 +661,6 @@ namespace WaveHarmonic.Crest
{
partial class Lod
{
/// <summary>
/// Blurs the output.
/// </summary>
/// <remarks>
/// Enable if blurring is desired or intolerable artifacts are present.
/// The blur is optimized to only run on inner LODs and at lower scales.
/// </remarks>
public bool Blur { get => _Blur; set => _Blur = value; }
/// <summary>
/// Number of blur iterations.
/// </summary>
/// <remarks>
/// Blur iterations are optimized to only run maximum iterations on the inner LODs.
/// </remarks>
public int BlurIterations { get => _BlurIterations; set => _BlurIterations = value; }
/// <summary>
/// Whether the simulation is enabled.
/// </summary>
@@ -715,22 +697,6 @@ namespace WaveHarmonic.Crest
}
}
namespace WaveHarmonic.Crest
{
partial class Lod<T>
{
/// <summary>
/// Maximum number of queries that can be performed when using GPU queries.
/// </summary>
public int MaximumQueryCount => _MaximumQueryCount;
/// <summary>
/// Where to obtain water data on CPU for physics / gameplay.
/// </summary>
public LodQuerySource QuerySource { get => _QuerySource; internal set => _QuerySource = value; }
}
}
namespace WaveHarmonic.Crest
{
partial class LodInput
@@ -786,14 +752,6 @@ namespace WaveHarmonic.Crest
{
partial class Meniscus
{
/// <summary>
/// Rules to exclude cameras from rendering the meniscus.
/// </summary>
/// <remarks>
/// These are exclusion rules, so for all cameras, select Nothing. These rules are applied on top of the Layer rules.
/// </remarks>
public WaterCameraExclusion CameraExclusions { get => _CameraExclusions; set => _CameraExclusions = value; }
/// <summary>
/// Whether the meniscus is enabled.
/// </summary>
@@ -984,14 +942,6 @@ namespace WaveHarmonic.Crest
/// "Viewer" will reuse queries already performed by the Water Renderer
/// </remarks>
public QuerySource Source { get => _Source; set => _Source = value; }
/// <summary>
/// The viewer as the source of the queries.
/// </summary>
/// <remarks>
/// Only needs to be set if using multiple viewpoints on the Water Renderer.
/// </remarks>
public UnityEngine.Camera Viewer { get => _Viewer; set => _Viewer = value; }
}
}
@@ -1092,11 +1042,6 @@ namespace WaveHarmonic.Crest
/// </remarks>
public float MaximumVerticalDisplacement { get => _MaximumVerticalDisplacement; set => _MaximumVerticalDisplacement = value; }
/// <summary>
/// Whether to override automatic culling based on heuristics.
/// </summary>
public bool OverrideCulling { get => _OverrideCulling; set => _OverrideCulling = value; }
/// <summary>
/// Whether to use the wind turbulence on this component rather than the global wind turbulence.
/// </summary>
@@ -1171,14 +1116,6 @@ namespace WaveHarmonic.Crest
/// </remarks>
public bool EvaluateSpectrumAtRunTimeEveryFrame { get => _EvaluateSpectrumAtRunTimeEveryFrame; set => _EvaluateSpectrumAtRunTimeEveryFrame = value; }
/// <summary>
/// Whether the maximum possible vertical displacement is used for the Drop Detail Height Based On Waves calculation.
/// </summary>
/// <remarks>
/// This setting is ignored for global waves, as they always contribute. For local waves, only enable for large areas that are treated like global waves (eg a storm).
/// </remarks>
public bool IncludeInDropDetailHeightBasedOnWaves { get => _IncludeInDropDetailHeightBasedOnWaves; set => _IncludeInDropDetailHeightBasedOnWaves = value; }
/// <summary>
/// Whether to use the wind direction on this component rather than the global wind direction.
/// </summary>
@@ -1201,7 +1138,7 @@ namespace WaveHarmonic.Crest
/// <remarks>
/// Low resolutions are more efficient but can result in noticeable patterns in the shape.
/// </remarks>
public int Resolution { get => GetResolution(); set => _Resolution = value; }
public int Resolution { get => _Resolution; set => _Resolution = value; }
/// <summary>
/// How much these waves respect the shallow water attenuation.
@@ -1211,14 +1148,6 @@ namespace WaveHarmonic.Crest
/// </remarks>
public float RespectShallowWaterAttenuation { get => _RespectShallowWaterAttenuation; set => _RespectShallowWaterAttenuation = value; }
/// <summary>
/// Whether global waves is applied above or below sea level.
/// </summary>
/// <remarks>
/// Waves are faded out to avoid hard transitionds. They are fully faded by 1m from sea level.
/// </remarks>
public bool SeaLevelOnly { get => _SeaLevelOnly; set => _SeaLevelOnly = value; }
/// <summary>
/// The spectrum that defines the water surface shape.
/// </summary>
@@ -1344,14 +1273,6 @@ namespace WaveHarmonic.Crest
/// </remarks>
public bool AllowRenderQueueSorting { get => _AllowRenderQueueSorting; set => _AllowRenderQueueSorting = value; }
/// <summary>
/// Rules to exclude cameras from surface rendering.
/// </summary>
/// <remarks>
/// These are exclusion rules, so for all cameras, select Nothing. These rules are applied on top of the Layer rules.
/// </remarks>
public WaterCameraExclusion CameraExclusions { get => _CameraExclusions; set => _CameraExclusions = value; }
/// <summary>
/// Have the water surface cast shadows for albedo (both foam and custom).
/// </summary>
@@ -1430,17 +1351,8 @@ namespace WaveHarmonic.Crest
/// <remarks>
/// If disabled, then additionally ignore any camera that is not the view camera or our reflection camera. It will require managing culling masks of all cameras.
/// </remarks>
[System.Obsolete("Please use Camera Exclusion instead.")]
public bool AllCameras { get => _AllCameras; set => _AllCameras = value; }
/// <summary>
/// Rules to exclude cameras from rendering underwater.
/// </summary>
/// <remarks>
/// These are exclusion rules, so for all cameras, select Nothing. These rules are applied on top of the Layer rules.
/// </remarks>
public WaterCameraExclusion CameraExclusions { get => _CameraExclusions; set => _CameraExclusions = value; }
/// <summary>
/// Copying parameters each frame ensures underwater appearance stays consistent with the water surface.
/// </summary>
@@ -1594,7 +1506,6 @@ namespace WaveHarmonic.Crest
/// <summary>
/// Whether to allow MSAA.
/// </summary>
[System.Obsolete("MSAA for the planar reflection camera is no longer supported. This setting will be ignored.")]
public bool AllowMSAA { get => _AllowMSAA; set => _AllowMSAA = value; }
/// <summary>
@@ -1661,14 +1572,6 @@ namespace WaveHarmonic.Crest
/// </summary>
public float NonObliqueNearSurfaceThreshold { get => _NonObliqueNearSurfaceThreshold; set => _NonObliqueNearSurfaceThreshold = value; }
/// <summary>
/// Overscan amount to capture off-screen content.
/// </summary>
/// <remarks>
/// Renders the reflections at a larger viewport size to capture off-screen content when the surface reflects off-screen. This avoids a category of artifacts - especially when looking down. This can be expensive, as the value is a multiplier to the capture size.
/// </remarks>
public float Overscan { get => _Overscan; set => _Overscan = value; }
/// <summary>
/// Overrides global quality settings.
/// </summary>
@@ -1746,14 +1649,6 @@ namespace WaveHarmonic.Crest
/// </remarks>
public UnityEngine.Camera Viewer { get => GetViewer(); set => _Camera = value; }
/// <summary>
/// Rules to exclude cameras from being a center-of-detail.
/// </summary>
/// <remarks>
/// These are exclusion rules, so for all cameras, select Nothing.
/// </remarks>
public WaterCameraExclusion CameraExclusions { get => _CameraExclusions; set => _CameraExclusions = value; }
/// <summary>
/// Have the water surface cast shadows for albedo (both foam and custom).
/// </summary>
@@ -1773,14 +1668,6 @@ namespace WaveHarmonic.Crest
/// </summary>
public ClipLod ClipLod => _ClipLod;
/// <summary>
/// The background rendering mode when a camera does not render.
/// </summary>
/// <remarks>
/// When switching between multiple cameras, simulations will not progress for cameras which do not render for that frame. This setting tells the system when it should continue to progress the simulations.
/// </remarks>
public WaterDataBackgroundMode DataBackgroundMode { get => _DataBackgroundMode; set => _DataBackgroundMode = value; }
/// <summary>
/// Water depth information used for shallow water, shoreline foam, wave attenuation, among others.
/// </summary>

View File

@@ -29,6 +29,11 @@ namespace WaveHarmonic.Crest.Generated
{
enum CollisionLayers
{
/// <summary>
/// All layers.
/// </summary>
Everything,
/// <summary>
/// No extra layers (ie single layer).
/// </summary>
@@ -46,11 +51,6 @@ namespace WaveHarmonic.Crest.Generated
/// Extra displacement layer for visual displacement.
/// </summary>
Displacement,
/// <summary>
/// All layers.
/// </summary>
Everything,
}
}
@@ -288,31 +288,6 @@ namespace WaveHarmonic.Crest.Generated
}
namespace WaveHarmonic.Crest.Generated
{
enum LodQuerySource
{
/// <summary>
/// No query source.
/// </summary>
None,
/// <summary>
/// Uses AsyncGPUReadback to retrieve data from GPU to CPU.
/// </summary>
/// <remarks>
/// This is the most optimal approach.
/// </remarks>
GPU,
/// <summary>
/// Computes data entirely on the CPU.
/// </summary>
CPU,
}
}
namespace WaveHarmonic.Crest.Generated
{
enum LodTextureFormatMode
@@ -410,68 +385,6 @@ namespace WaveHarmonic.Crest.Generated
}
namespace WaveHarmonic.Crest.Generated
{
enum WaterCameraExclusion
{
/// <summary>
/// No exclusion rules applied.
/// </summary>
Nothing,
/// <summary>
/// Exclude hidden cameras.
/// </summary>
/// <remarks>
/// Does not affect reflection cameras, as they are typically always hidden. Use the Reflection flag for them.
/// </remarks>
Hidden,
/// <summary>
/// Exclude reflection cameras.
/// </summary>
Reflection,
/// <summary>
/// Exclude cameras not tagged as MainCamera.
/// </summary>
NonMainCamera,
/// <summary>
/// Apply all exclusion rules.
/// </summary>
Everything,
}
}
namespace WaveHarmonic.Crest.Generated
{
enum WaterDataBackgroundMode
{
/// <summary>
/// Always progress simulations in the background when camera does not render.
/// </summary>
Always,
/// <summary>
/// Progress simulations in the background when camera is inactive (ie !isActiveAndEnabled).
/// </summary>
Inactive,
/// <summary>
/// Progress simulations in the background when camera is disabled (ie !enabled).
/// </summary>
Disabled,
/// <summary>
/// Never progress simulations in the background.
/// </summary>
Never,
}
}
namespace WaveHarmonic.Crest.Generated
{
enum WaterInjectionPoint
@@ -538,31 +451,3 @@ namespace WaveHarmonic.Crest.Generated
Clip,
}
}
namespace WaveHarmonic.Crest.Generated
{
enum WaveSampling
{
/// <summary>
/// Automatically chooses the other options as needed (512+ resolution needs precision).
/// </summary>
Automatic,
/// <summary>
/// Reduces samples by copying waves from higher LODs to lower LODs.
/// </summary>
/// <remarks>
/// Best for resolutions lower than 512.
/// </remarks>
Performance,
/// <summary>
/// Samples directly from the wave buffers to preserve wave quality.
/// </summary>
/// <remarks>
/// Needed for higher resolutions (512+). Higher LOD counts can also benefit with this enabled.
/// </remarks>
Precision,
}
}

View File

@@ -108,9 +108,7 @@ namespace WaveHarmonic.Crest
[UnityEngine.Space(10)]
[Tooltip("Query points for buoyancy.\n\nOnly applicable to Probes model.")]
[@Predicated(nameof(_Model), true, nameof(FloatingObjectModel.Probes), hide: true)]
[@GenerateAPI]
[@DecoratedField]
[SerializeField]
internal FloatingObjectProbe[] _Probes = new FloatingObjectProbe[] { };

View File

@@ -85,10 +85,7 @@ namespace WaveHarmonic.Crest
});
}
}
#endif
#if URP_COMPATIBILITY_MODE
#if UNITY_6000_0_OR_NEWER
[System.Obsolete]
#endif
public override void Execute(ScriptableRenderContext context, ref RenderingData data)
@@ -99,7 +96,6 @@ namespace WaveHarmonic.Crest
context.ExecuteCommandBuffer(buffer);
CommandBufferPool.Release(buffer);
}
#endif
}
}
}

View File

@@ -21,7 +21,6 @@ namespace WaveHarmonic.Crest
public static MaskRenderer Instantiate(WaterRenderer water)
{
#pragma warning disable format
#if d_UnityHDRP
if (RenderPipelineHelper.IsHighDefinition)
{
@@ -41,7 +40,6 @@ namespace WaveHarmonic.Crest
{
return new MaskRendererBIRP(water);
}
#pragma warning restore format
}
// For PortalRenderer.
@@ -237,7 +235,7 @@ namespace WaveHarmonic.Crest
slices: TextureXR.slices,
dimension: TextureXR.dimension,
depthBufferBits: DepthBits.None,
colorFormat: Helpers.GetCompatibleTextureFormat(GraphicsFormat.R16_SFloat, randomWrite: true),
colorFormat: GraphicsFormat.R16_SFloat,
enableRandomWrite: true,
useDynamicScale: true,
name: k_MaskColor
@@ -253,7 +251,7 @@ namespace WaveHarmonic.Crest
scaleFactor: Vector2.one,
slices: TextureXR.slices,
dimension: TextureXR.dimension,
depthBufferBits: Rendering.GetDefaultDepthBufferBits(),
depthBufferBits: Helpers.k_DepthBits,
colorFormat: GraphicsFormat.None,
enableRandomWrite: false,
useDynamicScale: true,
@@ -279,7 +277,7 @@ namespace WaveHarmonic.Crest
if (_Inputs.HasFlag(MaskInput.Depth))
{
descriptor.graphicsFormat = GraphicsFormat.None;
descriptor.depthBufferBits = (int)Rendering.GetDefaultDepthBufferBits();
descriptor.depthBufferBits = Helpers.k_DepthBufferBits;
if (RenderPipelineCompatibilityHelper.ReAllocateIfNeeded(ref _DepthRTH, descriptor, name: k_MaskDepth))
{
@@ -290,8 +288,7 @@ namespace WaveHarmonic.Crest
if (_Inputs.HasFlag(MaskInput.Color))
{
// NOTE: Intel iGPU for Metal and DirectX both had issues with R16 (2021.11.18).
descriptor.graphicsFormat = Helpers.GetCompatibleTextureFormat(GraphicsFormat.R16_SFloat, randomWrite: true);
descriptor.depthStencilFormat = GraphicsFormat.None;
descriptor.graphicsFormat = GraphicsFormat.R16_SFloat;
descriptor.depthBufferBits = 0;
descriptor.enableRandomWrite = true;

View File

@@ -100,10 +100,7 @@ namespace WaveHarmonic.Crest
});
}
}
#endif
#if URP_COMPATIBILITY_MODE
#if UNITY_6000_0_OR_NEWER
[System.Obsolete]
#endif
public override void Execute(ScriptableRenderContext context, ref RenderingData data)
@@ -113,7 +110,6 @@ namespace WaveHarmonic.Crest
context.ExecuteCommandBuffer(buffer);
CommandBufferPool.Release(buffer);
}
#endif
}
}
}

View File

@@ -33,14 +33,6 @@ namespace WaveHarmonic.Crest
internal Material _Material;
[@Heading("Advanced")]
[Tooltip("Rules to exclude cameras from rendering the meniscus.\n\nThese are exclusion rules, so for all cameras, select Nothing. These rules are applied on top of the Layer rules.")]
[@DecoratedField]
[@GenerateAPI]
[SerializeField]
WaterCameraExclusion _CameraExclusions = WaterCameraExclusion.Hidden | WaterCameraExclusion.Reflection;
WaterRenderer _Water;
internal MeniscusRenderer Renderer { get; private set; }
@@ -84,7 +76,6 @@ namespace WaveHarmonic.Crest
return;
}
#pragma warning disable format
#if d_UnityHDRP
if (RenderPipelineHelper.IsHighDefinition)
{
@@ -105,7 +96,6 @@ namespace WaveHarmonic.Crest
{
Renderer ??= new MeniscusRendererBIRP(water, this);
}
#pragma warning restore format
}
}
@@ -141,6 +131,11 @@ namespace WaveHarmonic.Crest
private protected readonly WaterRenderer _Water;
internal readonly Meniscus _Meniscus;
static partial class ShaderIDs
{
public static readonly int s_HorizonNormal = Shader.PropertyToID("_Crest_HorizonNormal");
}
public abstract void OnBeginCameraRendering(Camera camera);
public abstract void OnEndCameraRendering(Camera camera);
@@ -179,18 +174,18 @@ namespace WaveHarmonic.Crest
return false;
}
// Meniscus is a product of the water surface.
if (!_Water.Surface.Enabled)
{
return false;
}
if (camera.cameraType is not CameraType.Game and not CameraType.SceneView)
{
return false;
}
if (!WaterRenderer.ShouldRender(camera, _Meniscus.Layer, _Meniscus._CameraExclusions))
{
return false;
}
// Meniscus depends on both the surface and volume.
if (!_Water.Surface.ShouldRender(camera) || !_Water.Underwater.ShouldRender(camera))
if (!WaterRenderer.ShouldRender(camera, _Meniscus.Layer))
{
return false;
}
@@ -216,6 +211,13 @@ namespace WaveHarmonic.Crest
internal void Execute<T>(Camera camera, T commands) where T : ICommandWrapper
{
// Project water normal onto camera plane.
_Meniscus.Material.SetVector(ShaderIDs.s_HorizonNormal, new Vector2
(
Vector3.Dot(Vector3.up, camera.transform.right),
Vector3.Dot(Vector3.up, camera.transform.up)
));
var isFullScreenRequired = true;
var isMasked = false;
var passOffset = 1;

View File

@@ -22,7 +22,6 @@ namespace WaveHarmonic.Crest
public static int s_WaterLineSnappedPosition = Shader.PropertyToID("_Crest_WaterLineSnappedPosition");
public static int s_WaterLineResolution = Shader.PropertyToID("_Crest_WaterLineResolution");
public static int s_WaterLineTexel = Shader.PropertyToID("_Crest_WaterLineTexel");
public static int s_WaterLineFlatWater = Shader.PropertyToID("_Crest_WaterLineFlatWater");
}
RenderTexture _HeightRT;
@@ -51,13 +50,6 @@ namespace WaveHarmonic.Crest
internal void UpdateDisplacedSurfaceData(Camera camera)
{
Helpers.SetGlobalBoolean(ShaderIDs.s_WaterLineFlatWater, IsQuadMesh);
if (IsQuadMesh)
{
return;
}
// World size of the texture. Formula should effectively cover the camera.
var size = 1f + (camera.nearClipPlane * 2f);
@@ -92,11 +84,9 @@ namespace WaveHarmonic.Crest
BindDisplacedSurfaceData(wrapper);
var lod = (int)Builder.PatchType.Interior;
var mpb = PerCascadeMPB[lod];
var mpb = _PerCascadeMPB.Current[lod];
var viewpoint = _Water.Viewpoint;
if (viewpoint == null || (viewpoint != camera.transform && Vector3.Distance(viewpoint.position, camera.transform.position) > 0.01f))
if (_Water.Viewpoint != camera.transform && Vector3.Distance(_Water.Viewpoint.position, camera.transform.position) > 0.01f)
{
foreach (var chunk in _Water.Surface.Chunks)
{

View File

@@ -27,9 +27,6 @@ namespace WaveHarmonic.Crest
case nameof(_Layer):
SetLayer((int)previous, _Layer);
break;
case nameof(_MeshType):
_Water.Rebuild();
break;
case nameof(_ChunkTemplate):
// We have to rebuild, as we instantiate entire GO. If we restricted it to just a
// MeshRenderer, then we could just replace those.

View File

@@ -68,7 +68,7 @@ namespace WaveHarmonic.Crest
}
// Our reflections do not need them.
if (camera == _Water.Reflections.ReflectionCamera)
if (camera == WaterReflections.CurrentCamera)
{
return;
}
@@ -114,7 +114,6 @@ namespace WaveHarmonic.Crest
var rld = new RendererListDesc(_ShaderTagID, context.cullingResults, camera)
{
layerMask = 1 << _Water.Surface.Layer,
// Required to set the pass. Use shader to keep WB material overrides.
overrideShader = _Water.Surface.Material.shader,
overrideShaderPassIndex = _Water.Surface.Material.FindPass("Forward"),
renderQueueRange = RenderQueueRange.transparent,

View File

@@ -22,7 +22,6 @@ namespace WaveHarmonic.Crest
}
CommandBuffer _DrawWaterSurfaceBuffer;
MaterialPropertyBlock _QuadMeshMPB;
void OnBeginCameraRenderingLegacy(Camera camera)
{
@@ -41,6 +40,8 @@ namespace WaveHarmonic.Crest
return;
}
camera.depthTextureMode |= DepthTextureMode.Depth;
_DrawWaterSurfaceBuffer ??= new() { name = WaterRenderer.k_DrawWater };
_DrawWaterSurfaceBuffer.Clear();
@@ -110,15 +111,6 @@ namespace WaveHarmonic.Crest
// Always enabled.
commands.SetShaderKeyword("LIGHTPROBE_SH", true);
if (IsQuadMesh)
{
_QuadMeshMPB ??= new();
new PropertyWrapperMPB(_QuadMeshMPB).SetSHCoefficients(Root.position);
Render(camera, commands, Material, pass: 0, mpb: _QuadMeshMPB);
commands.EndSample(k_DrawWaterSurface);
return;
}
UpdateChunkVisibility(camera);
foreach (var chunk in Chunks)
@@ -145,7 +137,9 @@ namespace WaveHarmonic.Crest
chunk.Bind();
}
commands.DrawRenderer(chunk.Rend, renderer.sharedMaterial, 0, 0);
var mpb = new PropertyWrapperMPB(chunk._MaterialPropertyBlock);
mpb.SetSHCoefficients(chunk.transform.position);
commands.DrawMesh(chunk._Mesh, chunk.transform.localToWorldMatrix, renderer.sharedMaterial, 0, 0, chunk._MaterialPropertyBlock);
}
commands.EndSample(k_DrawWaterSurface);

View File

@@ -1,7 +1,6 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
#if UNITY_EDITOR
#if d_UnityHDRP
using UnityEngine;
@@ -57,7 +56,7 @@ namespace WaveHarmonic.Crest
return;
}
if (camera.cameraType != CameraType.SceneView || (_Water.SingleViewpoint && camera != _Water.Viewer))
if (camera.cameraType != CameraType.SceneView || camera != _Water.Viewer)
{
return;
}
@@ -69,4 +68,3 @@ namespace WaveHarmonic.Crest
}
#endif // d_UnityHDRP
#endif

View File

@@ -1,8 +1,6 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
#if UNITY_EDITOR
using UnityEngine;
using UnityEngine.Rendering;
@@ -14,7 +12,7 @@ namespace WaveHarmonic.Crest
void OnPreRenderWaterLevelDepthTexture(Camera camera)
{
if (camera.cameraType != CameraType.SceneView || (_Water.SingleViewpoint && camera != _Water.Viewer))
if (camera.cameraType != CameraType.SceneView || camera != _Water.Viewer)
{
return;
}
@@ -40,5 +38,3 @@ namespace WaveHarmonic.Crest
}
}
}
#endif

View File

@@ -1,8 +1,6 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
#if UNITY_EDITOR
#if d_UnityURP
using UnityEngine;
@@ -39,7 +37,7 @@ namespace WaveHarmonic.Crest
return;
}
if (camera.cameraType != CameraType.SceneView || (_Water.SingleViewpoint && camera != _Water.Viewer))
if (camera.cameraType != CameraType.SceneView || camera != _Water.Viewer)
{
return;
}
@@ -71,10 +69,7 @@ namespace WaveHarmonic.Crest
});
}
}
#endif
#if URP_COMPATIBILITY_MODE
#if UNITY_6000_0_OR_NEWER
[System.Obsolete]
#endif
public override void Execute(ScriptableRenderContext context, ref RenderingData data)
@@ -84,10 +79,8 @@ namespace WaveHarmonic.Crest
context.ExecuteCommandBuffer(buffer);
CommandBufferPool.Release(buffer);
}
#endif
}
}
}
#endif // d_UnityURP
#endif

View File

@@ -1,9 +1,8 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
#if UNITY_EDITOR
// FIXME: Broken for BIRP on MacOS. Either platform specific problem or bug in Unity.
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
@@ -12,38 +11,16 @@ namespace WaveHarmonic.Crest
partial class SurfaceRenderer
{
RenderTexture _WaterLevelDepthTexture;
internal RenderTexture WaterLevelDepthTexture { get; private set; }
internal RenderTexture WaterLevelDepthTexture => _WaterLevelDepthTexture;
RenderTargetIdentifier _WaterLevelDepthTarget;
Material _WaterLevelDepthMaterial;
internal RenderTexture GetWaterLevelDepthTexture(Camera camera)
{
// Do not use SeparateViewpoint here, as this is called outside the camera loop.
if (_Water.SingleViewpoint)
{
return WaterLevelDepthTexture;
}
else if (_PerCameraLevelDepthTexture.ContainsKey(camera))
{
return _PerCameraLevelDepthTexture[camera];
}
return null;
}
const string k_WaterLevelDepthTextureName = "Crest Water Level Depth Texture";
void ExecuteWaterLevelDepthTexture(Camera camera, CommandBuffer buffer)
{
if (_Water.SingleViewpoint && _WaterLevelDepthTexture == null)
{
_WaterLevelDepthTexture = new(0, 0, 0);
WaterLevelDepthTexture = _WaterLevelDepthTexture;
}
LoadCameraDataLDT(camera);
WaterLevelDepthTexture.name = k_WaterLevelDepthTextureName;
Helpers.CreateRenderTargetTextureReference(ref _WaterLevelDepthTexture, ref _WaterLevelDepthTarget);
_WaterLevelDepthTexture.name = k_WaterLevelDepthTextureName;
if (_WaterLevelDepthMaterial == null)
{
@@ -64,19 +41,11 @@ namespace WaveHarmonic.Crest
// Depth texture.
// Always release to handle screen size changes.
WaterLevelDepthTexture.Release();
_WaterLevelDepthTexture.Release();
descriptor.graphicsFormat = UnityEngine.Experimental.Rendering.GraphicsFormat.R32_SFloat;
descriptor.depthBufferBits = 0;
WaterLevelDepthTexture.descriptor = descriptor;
WaterLevelDepthTexture.Create();
_WaterLevelDepthTarget = new
(
WaterLevelDepthTexture,
mipLevel: 0,
CubemapFace.Unknown,
depthSlice: -1 // Bind all XR slices.
);
Helpers.SafeCreateRenderTexture(ref _WaterLevelDepthTexture, descriptor);
_WaterLevelDepthTexture.Create();
// Convert.
Helpers.Blit(buffer, _WaterLevelDepthTarget, Rendering.BIRP.UtilityMaterial, (int)Rendering.BIRP.UtilityPass.Copy);
@@ -112,48 +81,4 @@ namespace WaveHarmonic.Crest
#endif
}
}
// Multiple Viewpoints
// Technically, this should always store an RT per camera, as it is screen-space.
// But having it opt-in is not a bad idea.
partial class SurfaceRenderer
{
internal Dictionary<Camera, RenderTexture> _PerCameraLevelDepthTexture = new();
void LoadCameraDataLDT(Camera camera)
{
if (_Water.SingleViewpoint)
{
return;
}
if (!_PerCameraLevelDepthTexture.ContainsKey(camera))
{
var descriptor = new RenderTextureDescriptor(camera.pixelWidth, camera.pixelHeight)
{
graphicsFormat = UnityEngine.Experimental.Rendering.GraphicsFormat.None,
depthBufferBits = 32,
};
WaterLevelDepthTexture = new(descriptor);
_PerCameraLevelDepthTexture.Add(camera, WaterLevelDepthTexture);
}
else
{
WaterLevelDepthTexture = _PerCameraLevelDepthTexture[camera];
}
}
internal void RemoveCameraDataLDT(Camera camera)
{
if (_PerCameraLevelDepthTexture.ContainsKey(camera))
{
_PerCameraLevelDepthTexture[camera].Release();
Helpers.Destroy(_PerCameraLevelDepthTexture[camera]);
_PerCameraLevelDepthTexture.Remove(camera);
}
}
}
}
#endif

View File

@@ -24,7 +24,9 @@ namespace WaveHarmonic.Crest
{
_Water = water;
renderPassEvent = RenderPassEvent.BeforeRenderingTransparents;
ConfigureInput(ScriptableRenderPassInput.None);
// Copy color happens between "after skybox" and "before transparency".
ConfigureInput(ScriptableRenderPassInput.Color | ScriptableRenderPassInput.Depth);
}
public static void Enable(WaterRenderer water)
@@ -56,14 +58,12 @@ namespace WaveHarmonic.Crest
}
// Our reflections do not need them.
if (camera == _Water.Reflections.ReflectionCamera)
if (camera == WaterReflections.CurrentCamera)
{
return;
}
var material = _Water.Surface.Material;
if (material == null)
if (Instance._Water.Surface.Material == null)
{
return;
}
@@ -73,15 +73,6 @@ namespace WaveHarmonic.Crest
return;
}
{
// Copy color happens between "after skybox" and "before transparency".
var pass = ScriptableRenderPassInput.Color | ScriptableRenderPassInput.Depth;
#if d_Crest_SimpleTransparency
pass = ScriptableRenderPassInput.None;
#endif
ConfigureInput(pass);
}
camera.GetUniversalAdditionalCameraData().scriptableRenderer.EnqueuePass(Instance);
}
@@ -122,7 +113,6 @@ namespace WaveHarmonic.Crest
var rld = new RendererListDesc(_ShaderTagID, renderingData.cullResults, cameraData.camera)
{
layerMask = 1 << _Water.Surface.Layer,
// Required to set the pass. Use shader to keep WB material overrides.
overrideShader = _Water.Surface.Material.shader,
overrideShaderPassIndex = 0, // UniversalForward
renderQueueRange = RenderQueueRange.transparent,
@@ -139,10 +129,7 @@ namespace WaveHarmonic.Crest
});
}
}
#endif
#if URP_COMPATIBILITY_MODE
#if UNITY_6000_0_OR_NEWER
[System.Obsolete]
#endif
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
@@ -157,7 +144,6 @@ namespace WaveHarmonic.Crest
var rld = new RendererListDesc(_ShaderTagID, renderingData.cullResults, renderingData.cameraData.camera)
{
layerMask = 1 << _Water.Surface.Layer,
// Required to set the pass. Use shader to keep WB material overrides.
overrideShader = _Water.Surface.Material.shader,
overrideShaderPassIndex = 0, // UniversalForward
renderQueueRange = RenderQueueRange.transparent,
@@ -170,7 +156,6 @@ namespace WaveHarmonic.Crest
context.ExecuteCommandBuffer(buffer);
CommandBufferPool.Release(buffer);
}
#endif
}
}
}

View File

@@ -5,19 +5,10 @@ using System.Buffers;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using WaveHarmonic.Crest.Internal;
using WaveHarmonic.Crest.Utility;
namespace WaveHarmonic.Crest
{
enum WaterMeshType
{
[Tooltip("Chunks implemented as a clip-map.")]
Chunks,
[Tooltip("A single quad.\n\nOptimal for demanding platforms like mobile. Displacement will only contribute to normals.")]
Quad,
}
/// <summary>
/// Renders the water surface.
/// </summary>
@@ -36,12 +27,6 @@ namespace WaveHarmonic.Crest
[@DecoratedField, SerializeField]
internal bool _Enabled = true;
[@Label("Mesh")]
[Tooltip("The meshing solution for the water surface.")]
[@DecoratedField]
[@SerializeField]
WaterMeshType _MeshType;
[Tooltip("The water chunk renderers will have this layer.")]
[@Layer]
[@GenerateAPI]
@@ -89,12 +74,6 @@ namespace WaveHarmonic.Crest
[@Heading("Advanced")]
[Tooltip("Rules to exclude cameras from surface rendering.\n\nThese are exclusion rules, so for all cameras, select Nothing. These rules are applied on top of the Layer rules.")]
[@DecoratedField]
[@GenerateAPI]
[SerializeField]
internal WaterCameraExclusion _CameraExclusions = WaterCameraExclusion.Hidden | WaterCameraExclusion.Reflection;
[Tooltip("How to handle self-intersections of the water surface.\n\nThey can be caused by choppy waves which can cause a flipped underwater effect. When not using the portals/volumes, this fix is only applied when within 2 metres of the water surface. Automatic will disable the fix if portals/volumes are used which is the recommend setting.")]
[@DecoratedField, SerializeField]
internal SurfaceSelfIntersectionFixMode _SurfaceSelfIntersectionFixMode = SurfaceSelfIntersectionFixMode.Automatic;
@@ -143,8 +122,8 @@ namespace WaveHarmonic.Crest
// Level of Detail
//
readonly MaterialPropertyBlock[] _PerCascadeMPB = new MaterialPropertyBlock[Lod.k_MaximumSlices];
internal MaterialPropertyBlock[] PerCascadeMPB { get; private set; }
// Extra frame is for motion vectors.
internal BufferedData<MaterialPropertyBlock[]> _PerCascadeMPB = new(2, () => new MaterialPropertyBlock[Lod.k_MaximumSlices]);
// We are computing these values to be optimal based on the base mesh vertex density.
float _LodAlphaBlackPointFade;
@@ -176,7 +155,6 @@ namespace WaveHarmonic.Crest
internal Material _MotionVectorMaterial;
internal Material AboveOrBelowSurfaceMaterial => _VolumeMaterial == null ? _Material : _VolumeMaterial;
internal bool IsQuadMesh => _MeshType == WaterMeshType.Quad;
//
@@ -222,23 +200,8 @@ namespace WaveHarmonic.Crest
public static readonly int s_ChunkGeometryGridWidth = Shader.PropertyToID("_Crest_ChunkGeometryGridWidth");
public static readonly int s_ChunkFarNormalsWeight = Shader.PropertyToID("_Crest_ChunkFarNormalsWeight");
public static readonly int s_ChunkNormalScrollSpeed = Shader.PropertyToID("_Crest_ChunkNormalScrollSpeed");
public static readonly int s_NormalMapParameters = Shader.PropertyToID("_Crest_NormalMapParameters");
}
bool _ForceRenderingOff;
internal bool ForceRenderingOff
{
get => _ForceRenderingOff;
set
{
_ForceRenderingOff = value;
if (_Enabled)
{
Root.gameObject.SetActive(!_ForceRenderingOff && !IsQuadMesh);
}
}
public static readonly int s_ChunkMeshScaleAlphaSource = Shader.PropertyToID("_Crest_ChunkMeshScaleAlphaSource");
public static readonly int s_ChunkGeometryGridWidthSource = Shader.PropertyToID("_Crest_ChunkGeometryGridWidthSource");
}
internal void Initialize()
@@ -248,10 +211,19 @@ namespace WaveHarmonic.Crest
Root.position = _Water.Position;
Root.localScale = new(_Water.Scale, 1f, _Water.Scale);
// Populate MPBs with defaults. Protects against null exceptions etc.
PerCascadeMPB = _PerCascadeMPB;
NormalMapParameters = _NormalMapParameters;
InitializeProperties();
// Populate MPBs with defaults.
for (var index = 0; index < _Water.LodLevels; index++)
{
for (var frame = 0; frame < 2; frame++)
{
var mpb = new MaterialPropertyBlock();
mpb.SetInteger(Lod.ShaderIDs.s_LodIndex, index);
mpb.SetFloat(ShaderIDs.s_ChunkFarNormalsWeight, 1f);
mpb.SetFloat(ShaderIDs.s_ChunkMeshScaleAlpha, 0f);
mpb.SetFloat(ShaderIDs.s_ChunkMeshScaleAlphaSource, 0f);
_PerCascadeMPB.Previous(frame)[index] = mpb;
}
}
// Resolution is 4 tiles across.
var baseMeshDensity = _Water.LodResolution * 0.25f / _Water._GeometryDownSampleFactor;
@@ -325,11 +297,6 @@ namespace WaveHarmonic.Crest
return;
}
if (IsQuadMesh)
{
return;
}
GeometryUtility.CalculateFrustumPlanes(camera, _CameraFrustumPlanes);
foreach (var chunk in Chunks)
@@ -380,43 +347,20 @@ namespace WaveHarmonic.Crest
_Rebuild = false;
}
internal void DisableChunks()
internal void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera)
{
foreach (var chunk in Chunks)
if (!WaterRenderer.ShouldRender(camera, Layer))
{
if (chunk.Rend != null) chunk.Rend.enabled = false;
}
}
internal bool ShouldRender(Camera camera)
{
if (!_Enabled)
{
return false;
}
if (!WaterRenderer.ShouldRender(camera, Layer, _CameraExclusions))
{
return false;
return;
}
// Our planar reflection camera must never render the surface.
if (camera == _Water.Reflections.ReflectionCamera)
if (camera == WaterReflections.CurrentCamera)
{
return false;
return;
}
if (Material == null)
{
return false;
}
return true;
}
internal void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera)
{
if (!ShouldRender(camera))
{
return;
}
@@ -424,7 +368,7 @@ namespace WaveHarmonic.Crest
WritePerCameraMaterialParameters(camera);
// Motion Vectors.
if (ShouldRenderMotionVectors(camera) && QueueMotionVectors)
if (ShouldRenderMotionVectors(camera) && _QueueMotionVectors)
{
UpdateChunkVisibility(camera);
@@ -434,7 +378,6 @@ namespace WaveHarmonic.Crest
}
}
#pragma warning disable format
#if d_UnityURP
if (RenderPipelineHelper.IsUniversal)
{
@@ -450,26 +393,19 @@ namespace WaveHarmonic.Crest
{
OnBeginCameraRenderingLegacy(camera);
}
#pragma warning restore format
}
internal void OnEndCameraRendering(Camera camera)
{
_DoneChunkVisibility = false;
// Restore in case exclusion culling ran.
foreach (var chunk in Chunks)
{
if (chunk.Rend != null && !chunk._Culled) chunk.Rend.enabled = true;
}
if (!WaterRenderer.ShouldRender(camera, Layer))
{
return;
}
// Our planar reflection camera must never render the surface.
if (camera == _Water.Reflections.ReflectionCamera)
if (camera == WaterReflections.CurrentCamera)
{
return;
}
@@ -480,20 +416,6 @@ namespace WaveHarmonic.Crest
}
}
void InitializeProperties()
{
System.Array.Fill(NormalMapParameters, new Vector4(0, 0, 1, 0));
// Populate MPBs with defaults.
for (var index = 0; index < PerCascadeMPB.Length; index++)
{
var block = new MaterialPropertyBlock();
block.SetInteger(Lod.ShaderIDs.s_LodIndex, index);
block.SetFloat(ShaderIDs.s_ChunkFarNormalsWeight, 1f);
PerCascadeMPB[index] = block;
}
}
void WritePerCameraMaterialParameters(Camera camera)
{
if (Material == null)
@@ -520,16 +442,14 @@ namespace WaveHarmonic.Crest
var value = _SurfaceSelfIntersectionFixMode switch
{
SurfaceSelfIntersectionFixMode.On =>
!_Water._PerCameraHeightReady
? ForceFacing.None
: height < -2f
height < -2f
? ForceFacing.BelowWater
: height > 2f
? ForceFacing.AboveWater
: ForceFacing.None,
// Skip for portals as it is possible to see both sides of the surface at any position.
SurfaceSelfIntersectionFixMode.Automatic =>
_Water.Portaled || !_Water._PerCameraHeightReady
_Water.Portaled
? ForceFacing.None
: height < -2f
? ForceFacing.BelowWater
@@ -551,27 +471,12 @@ namespace WaveHarmonic.Crest
Rebuild();
}
if (_ForceRenderingOff)
{
return;
}
LoadCameraData(_Water.CurrentCamera);
Root.position = _Water.Position;
Root.localScale = new(_Water.Scale, 1f, _Water.Scale);
Root.gameObject.SetActive(!IsQuadMesh);
Material.SetKeyword(new(Material.shader, "_CREST_CUSTOM_MESH"), IsQuadMesh);
_PerCascadeMPB.Flip();
WritePerCascadeInstanceData();
if (IsQuadMesh)
{
LateUpdateQuadMesh();
return;
}
foreach (var chunk in Chunks)
{
chunk.UpdateMeshBounds(_Water, this);
@@ -601,48 +506,60 @@ namespace WaveHarmonic.Crest
{
var levels = _Water.LodLevels;
var texel = _Water.LodResolution * 0.25f / _Water._GeometryDownSampleFactor;
var mpbsCurrent = _PerCascadeMPB.Current;
var mpbsPrevious = _PerCascadeMPB.Previous(1);
// LOD 0
{
var mpb = mpbsCurrent[0];
if (_Water.WriteMotionVectors)
{
// NOTE: it may be more optimal to store in an array than fetching from MPB.
mpb.SetFloat(ShaderIDs.s_ChunkMeshScaleAlphaSource, mpbsPrevious[0].GetFloat(ShaderIDs.s_ChunkMeshScaleAlpha));
}
// Blend LOD 0 shape in/out to avoid pop, if scale could increase.
PerCascadeMPB[0].SetFloat(ShaderIDs.s_ChunkMeshScaleAlpha, _Water.ScaleCouldIncrease ? _Water.ViewerAltitudeLevelAlpha : 0f);
mpb.SetFloat(ShaderIDs.s_ChunkMeshScaleAlpha, _Water.ScaleCouldIncrease ? _Water.ViewerAltitudeLevelAlpha : 0f);
}
// LOD N
{
var mpb = mpbsCurrent[levels - 1];
// Blend furthest normals scale in/out to avoid pop, if scale could reduce.
var weight = _Water.ScaleCouldDecrease ? _Water.ViewerAltitudeLevelAlpha : 1f;
PerCascadeMPB[levels - 1].SetFloat(ShaderIDs.s_ChunkFarNormalsWeight, weight);
NormalMapParameters[levels - 1] = new(0, 0, weight, 0);
mpb.SetFloat(ShaderIDs.s_ChunkFarNormalsWeight, _Water.ScaleCouldDecrease ? _Water.ViewerAltitudeLevelAlpha : 1f);
}
for (var index = 0; index < levels; index++)
{
var mpb = PerCascadeMPB[index];
var mpbCurrent = mpbsCurrent[index];
var mpbPrevious = mpbsPrevious[index];
// geometry data
// compute grid size of geometry. take the long way to get there - make sure we land exactly on a power of two
// and not inherit any of the lossy-ness from lossyScale.
var scale = _Water.CascadeData.Current[index].x;
var scale = _Water._CascadeData.Current[index].x;
var width = scale / texel;
mpb.SetFloat(ShaderIDs.s_ChunkGeometryGridWidth, width);
if (_Water.WriteMotionVectors)
{
// NOTE: it may be more optimal to store in an array than fetching from MPB.
mpbPrevious.SetFloat(ShaderIDs.s_ChunkGeometryGridWidthSource, mpbCurrent.GetFloat(ShaderIDs.s_ChunkGeometryGridWidth));
}
mpbCurrent.SetFloat(ShaderIDs.s_ChunkGeometryGridWidth, width);
var mul = 1.875f; // fudge 1
var pow = 1.4f; // fudge 2
var texelWidth = width / _Water._GeometryDownSampleFactor;
var speed = new Vector2
mpbCurrent.SetVector(ShaderIDs.s_ChunkNormalScrollSpeed, new
(
Mathf.Pow(Mathf.Log(1f + 2f * texelWidth) * mul, pow),
Mathf.Pow(Mathf.Log(1f + 4f * texelWidth) * mul, pow)
);
mpb.SetVector(ShaderIDs.s_ChunkNormalScrollSpeed, speed);
var normals = NormalMapParameters[index];
normals.x = speed.x;
normals.y = speed.y;
NormalMapParameters[index] = normals;
Mathf.Pow(Mathf.Log(1f + 4f * texelWidth) * mul, pow),
0,
0
));
}
}
@@ -732,7 +649,7 @@ namespace WaveHarmonic.Crest
_CanSkipCulling = WaterBody.WaterBodies.Count == 0;
}
internal void Render(Camera camera, CommandBuffer buffer, Material material = null, int pass = 0, bool culled = false, MaterialPropertyBlock mpb = null)
internal void Render(Camera camera, CommandBuffer buffer, Material material = null, int pass = 0, bool culled = false)
{
var noMaterial = material == null;
@@ -741,12 +658,6 @@ namespace WaveHarmonic.Crest
return;
}
if (IsQuadMesh)
{
buffer.DrawMesh(Helpers.QuadMesh, Matrix4x4.TRS(Root.position, Quaternion.Euler(90f, 0, 0), new(10000, 10000, 1)), noMaterial ? Material : material, 0, shaderPass: pass, mpb);
return;
}
UpdateChunkVisibility(camera);
// Spends approx 0.2-0.3ms here on 2018 Dell XPS 15.
@@ -841,7 +752,6 @@ namespace WaveHarmonic.Crest
partial class SurfaceRenderer
{
bool _QueueMotionVectors;
bool QueueMotionVectors => _QueueMotionVectors && !IsQuadMesh;
bool ShouldRenderMotionVectors(Camera camera)
{
@@ -907,7 +817,7 @@ namespace WaveHarmonic.Crest
void UpdateMotionVectorsMaterial(Material surface, ref Material motion)
{
if (!QueueMotionVectors)
if (!_QueueMotionVectors)
{
return;
}
@@ -945,80 +855,4 @@ namespace WaveHarmonic.Crest
motion.SetFloat(ShaderIDs.s_BuiltShadowCasterZTest, 1); // ZTest Never
}
}
partial class SurfaceRenderer
{
internal Dictionary<Camera, MaterialPropertyBlock[]> _PerCameraPerCascadeMPB = new();
internal Dictionary<Camera, Vector4[]> _PerCameraNormalMapParameters = new();
void LoadCameraData(Camera camera)
{
if (!_Water.SeparateViewpoint)
{
return;
}
if (!_PerCameraPerCascadeMPB.ContainsKey(camera))
{
PerCascadeMPB = new MaterialPropertyBlock[Lod.k_MaximumSlices];
_PerCameraPerCascadeMPB.Add(camera, PerCascadeMPB);
NormalMapParameters = new Vector4[Lod.k_MaximumSlices];
_PerCameraNormalMapParameters.Add(camera, NormalMapParameters);
InitializeProperties();
}
else
{
PerCascadeMPB = _PerCameraPerCascadeMPB[camera];
NormalMapParameters = _PerCameraNormalMapParameters[camera];
}
}
internal void RemoveCameraData(Camera camera)
{
if (_PerCameraPerCascadeMPB.ContainsKey(camera))
{
_PerCameraPerCascadeMPB.Remove(camera);
_PerCameraNormalMapParameters.Remove(camera);
}
#if UNITY_EDITOR
RemoveCameraDataLDT(camera);
#endif
}
}
// Quad
partial class SurfaceRenderer
{
readonly Vector4[] _NormalMapParameters = new Vector4[Lod.k_MaximumSlices];
Vector4[] NormalMapParameters { get; set; }
void LateUpdateQuadMesh()
{
Shader.SetGlobalVectorArray(ShaderIDs.s_NormalMapParameters, NormalMapParameters);
var scale = new Vector3(10000 * _Water.Scale, 10000 * _Water.Scale, 1);
var bounds = Helpers.QuadMesh.bounds;
bounds.Expand(scale);
Graphics.RenderMesh
(
new()
{
motionVectorMode = MotionVectorGenerationMode.Camera,
material = Material,
worldBounds = Root.TransformBounds(bounds),
layer = Layer,
shadowCastingMode = CastShadows ? ShadowCastingMode.On : ShadowCastingMode.Off,
lightProbeUsage = LightProbeUsage.Off,
reflectionProbeUsage = ReflectionProbeUsage.BlendProbesAndSkybox,
renderingLayerMask = (uint)Layer,
},
Helpers.QuadMesh,
submeshIndex: 0,
Matrix4x4.TRS(Root.position, Quaternion.Euler(90f, 0, 0), scale)
);
UpdateMaterial(_Material, ref _MotionVectorMaterial);
}
}
}

View File

@@ -9,12 +9,12 @@ namespace WaveHarmonic.Crest
{
interface IReportsHeight
{
bool ReportHeight(WaterRenderer water, ref Rect bounds, ref float minimum, ref float maximum);
bool ReportHeight(ref Rect bounds, ref float minimum, ref float maximum);
}
interface IReportsDisplacement
{
bool ReportDisplacement(WaterRenderer water, ref Rect bounds, ref float horizontal, ref float vertical);
bool ReportDisplacement(ref Rect bounds, ref float horizontal, ref float vertical);
}
/// <summary>
@@ -24,7 +24,7 @@ namespace WaveHarmonic.Crest
[AddComponentMenu("")]
#endif
[@ExecuteDuringEditMode]
sealed partial class WaterChunkRenderer : ManagedBehaviour<WaterRenderer>
sealed class WaterChunkRenderer : ManagedBehaviour<WaterRenderer>
{
[SerializeField]
internal bool _DrawRenderBounds = false;
@@ -61,6 +61,8 @@ namespace WaveHarmonic.Crest
int _LodIndex = -1;
public static List<IReportsHeight> HeightReporters { get; } = new();
public static List<IReportsDisplacement> DisplacementReporters { get; } = new();
// There is a 1-frame delay with Initialized in edit mode due to setting
// enableInEditMode in EditorApplication.update. This only really affect this
@@ -73,12 +75,6 @@ namespace WaveHarmonic.Crest
_Mesh = mesh;
_PreviousObjectToWorld = _CurrentObjectToWorld = transform.localToWorldMatrix;
_Transform = transform;
WaterRenderer.s_OnLoadCameraData -= LoadCameraData;
WaterRenderer.s_OnLoadCameraData += LoadCameraData;
WaterRenderer.s_OnStoreCameraData -= StoreCameraData;
WaterRenderer.s_OnStoreCameraData += StoreCameraData;
WaterRenderer.s_OnRemoveCameraData -= RemoveCameraData;
WaterRenderer.s_OnRemoveCameraData += RemoveCameraData;
}
private protected override void OnStart()
@@ -148,7 +144,6 @@ namespace WaveHarmonic.Crest
matProps = _MaterialPropertyBlock,
worldBounds = Rend.bounds,
layer = surface.Layer,
renderingLayerMask = (uint)surface.Layer,
receiveShadows = false,
shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off,
lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off,
@@ -189,10 +184,11 @@ namespace WaveHarmonic.Crest
};
}
// Used by the water mask system if we need to render the water mask in situations
// where the water itself doesn't need to be rendered or has otherwise been disabled
internal void Bind()
{
_MaterialPropertyBlock = _Water.Surface.PerCascadeMPB[_LodIndex];
new PropertyWrapperMPB(_MaterialPropertyBlock).SetSHCoefficients(_Transform.position);
_MaterialPropertyBlock = _Water.Surface._PerCascadeMPB.Current[_LodIndex];
Rend.SetPropertyBlock(_MaterialPropertyBlock);
_WaterDataHasBeenBound = true;
@@ -238,12 +234,21 @@ namespace WaveHarmonic.Crest
var scale = transform.lossyScale;
var rotation = transform.rotation;
var boundsPadding = _Water.MaximumHorizontalDisplacement;
var expandXZ = boundsPadding / scale.x;
var boundsY = _Water.MaximumVerticalDisplacement;
// Extend the kinematic bounds slightly to give room for dynamic waves.
if (_Water._DynamicWavesLod.Enabled)
{
extents.y += 5f;
boundsY += 5f;
}
// Extend bounds by global waves.
extents.x += expandXZ;
extents.y += boundsY;
extents.z += expandXZ;
// Get XZ bounds. Doing this manually bypasses updating render bounds call.
Rect rect;
{
@@ -263,15 +268,23 @@ namespace WaveHarmonic.Crest
var totalHorizontal = 0f;
var totalVertical = 0f;
foreach (var (key, input) in AnimatedWavesLod.s_Inputs)
foreach (var reporter in DisplacementReporters)
{
input.DisplacementReporter?.ReportDisplacement(_Water, ref rect, ref totalHorizontal, ref totalVertical);
var horizontal = 0f;
var vertical = 0f;
if (reporter.ReportDisplacement(ref rect, ref horizontal, ref vertical))
{
totalHorizontal += horizontal;
totalVertical += vertical;
}
}
var expandXZ = totalHorizontal / scale.x;
boundsPadding = totalHorizontal;
expandXZ = boundsPadding / scale.x;
boundsY = totalVertical;
extents.x += expandXZ;
extents.y += totalVertical;
extents.y += boundsY;
extents.z += expandXZ;
}
@@ -280,59 +293,37 @@ namespace WaveHarmonic.Crest
var minimumWaterLevelBounds = 0f;
var maximumWaterLevelBounds = 0f;
foreach (var (key, input) in LevelLod.s_Inputs)
foreach (var reporter in HeightReporters)
{
input.HeightReporter?.ReportHeight(_Water, ref rect, ref minimumWaterLevelBounds, ref maximumWaterLevelBounds);
var minimum = 0f;
var maximum = 0f;
if (reporter.ReportHeight(ref rect, ref minimum, ref maximum))
{
minimumWaterLevelBounds = Mathf.Max(minimumWaterLevelBounds, Mathf.Abs(Mathf.Min(minimum, _Water.SeaLevel) - _Water.SeaLevel));
maximumWaterLevelBounds = Mathf.Max(maximumWaterLevelBounds, Mathf.Abs(Mathf.Max(maximum, _Water.SeaLevel) - _Water.SeaLevel));
}
}
extents.y += Mathf.Abs((minimumWaterLevelBounds - maximumWaterLevelBounds) * 0.5f);
minimumWaterLevelBounds *= 0.5f;
maximumWaterLevelBounds *= 0.5f;
boundsY = minimumWaterLevelBounds + maximumWaterLevelBounds;
extents.y += boundsY;
bounds.extents = extents;
var offset = Mathf.Lerp(minimumWaterLevelBounds, maximumWaterLevelBounds, 0.5f);
var offset = maximumWaterLevelBounds - minimumWaterLevelBounds;
center.y += offset;
bounds.center = center;
}
return bounds;
}
}
partial class WaterChunkRenderer
{
class AdditionalCameraData
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
static void InitStatics()
{
public Matrix4x4 _CurrentObjectToWorld;
public Matrix4x4 _PreviousObjectToWorld;
}
readonly Dictionary<Camera, AdditionalCameraData> _CameraData = new();
AdditionalCameraData _AdditionalCameraData;
void LoadCameraData(Camera camera)
{
if (!_CameraData.ContainsKey(camera))
{
_CameraData[camera] = new();
}
_AdditionalCameraData = _CameraData[camera];
_CurrentObjectToWorld = _AdditionalCameraData._CurrentObjectToWorld;
_PreviousObjectToWorld = _AdditionalCameraData._PreviousObjectToWorld;
}
void StoreCameraData(Camera camera)
{
if (_AdditionalCameraData == null) return;
_AdditionalCameraData._CurrentObjectToWorld = _CurrentObjectToWorld;
_AdditionalCameraData._PreviousObjectToWorld = _PreviousObjectToWorld;
}
void RemoveCameraData(Camera camera)
{
if (_CameraData.ContainsKey(camera))
{
_CameraData.Remove(camera);
}
HeightReporters.Clear();
DisplacementReporters.Clear();
}
}

View File

@@ -1,117 +0,0 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
#if d_UnityURP
#if UNITY_6000_0_OR_NEWER
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.Universal;
namespace WaveHarmonic.Crest
{
partial class WaterReflections
{
CopyDepthRenderPass _CopyTargetsRenderPass;
void CaptureTargetDepth(ScriptableRenderContext context, Camera camera)
{
if (camera != ReflectionCamera)
{
return;
}
if (!RenderPipelineHelper.IsUniversal)
{
return;
}
#if URP_COMPATIBILITY_MODE
#if !UNITY_6000_4_OR_NEWER
if (GraphicsSettings.GetRenderPipelineSettings<RenderGraphSettings>().enableRenderCompatibilityMode)
{
return;
}
#endif
#endif
_CopyTargetsRenderPass ??= new(this);
var renderer = camera.GetUniversalAdditionalCameraData().scriptableRenderer;
renderer.EnqueuePass(_CopyTargetsRenderPass);
}
sealed class CopyDepthRenderPass : ScriptableRenderPass
{
readonly WaterReflections _Renderer;
RTHandle _Wrapper;
class CopyPassData
{
public TextureHandle _Source;
public TextureHandle _Target;
public int _Slice;
}
public CopyDepthRenderPass(WaterReflections renderer)
{
_Renderer = renderer;
renderPassEvent = RenderPassEvent.AfterRendering;
}
public void Dispose()
{
_Wrapper?.Release();
_Wrapper = null;
}
public override void RecordRenderGraph(RenderGraph graph, ContextContainer frame)
{
var resources = frame.Get<UniversalResourceData>();
var source = resources.cameraDepth;
if (!source.IsValid())
{
return;
}
// Create a wrapper. Does not appear to be anything heavy in here.
_Wrapper ??= RTHandles.Alloc(_Renderer._DepthTexture);
_Wrapper.SetRenderTexture(_Renderer._DepthTexture);
var texture = graph.ImportTexture(_Wrapper);
using var builder = graph.AddUnsafePass<CopyPassData>("Crest.CopyDepth", out var data);
data._Source = source;
data._Target = graph.ImportTexture(_Wrapper);
data._Slice = _Renderer._ActiveSlice;
builder.UseTexture(data._Source, AccessFlags.Read);
builder.UseTexture(data._Target, AccessFlags.Write);
// Unity's AddCopyPass cannot handle this it seems.
builder.SetRenderFunc((CopyPassData data, UnsafeGraphContext context) =>
{
RTHandle source = data._Source;
RTHandle target = data._Target;
// Just in case. Planar Reflections will work mostly without it.
if (source.rt == null)
{
return;
}
if (source.rt.graphicsFormat == target.rt.graphicsFormat && source.rt.depthStencilFormat == target.rt.depthStencilFormat)
{
context.cmd.m_WrappedCommandBuffer.CopyTexture(source.rt, 0, 0, target.rt, data._Slice, 0);
}
});
}
}
}
}
#endif
#endif

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 79f370973399f49f38382da0814a25cb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 205
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -10,7 +10,6 @@ using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;
using UnityEngine.Rendering.Universal;
using WaveHarmonic.Crest.Internal;
namespace WaveHarmonic.Crest
{
@@ -70,12 +69,6 @@ namespace WaveHarmonic.Crest
[@Delayed, SerializeField]
int _Resolution = 256;
[Tooltip("Overscan amount to capture off-screen content.\n\nRenders the reflections at a larger viewport size to capture off-screen content when the surface reflects off-screen. This avoids a category of artifacts - especially when looking down. This can be expensive, as the value is a multiplier to the capture size.")]
[@Range(1, 2)]
[@GenerateAPI]
[SerializeField]
float _Overscan = 1.5f;
[Tooltip("Whether to render to the viewer camera only.\n\nWhen disabled, reflections will render for all cameras rendering the water layer, which currently this prevents Refresh Rate from working. Enabling will unlock the Refresh Rate heading.")]
[@GenerateAPI]
[@DecoratedField, SerializeField]
@@ -110,6 +103,11 @@ namespace WaveHarmonic.Crest
[@DecoratedField, SerializeField]
bool _Stencil = false;
[Tooltip("Whether to allow MSAA.")]
[@GenerateAPI]
[@DecoratedField, SerializeField]
bool _AllowMSAA = false;
[@Space(10)]
[Tooltip("Overrides global quality settings.")]
@@ -191,12 +189,6 @@ namespace WaveHarmonic.Crest
[Tooltip("Rendering reflections per-camera requires recursive rendering. Check this toggle if experiencing issues. The other downside without it is a one-frame delay.")]
[@DecoratedField, SerializeField]
internal bool _DisableRecursiveRendering;
[Tooltip("Whether to create a context more compatible for planar reflections camera. Try enabling this if you are getting exceptions.")]
[@Predicated(RenderPipeline.Universal, hide: true)]
[@DecoratedField]
[SerializeField]
internal bool _ForceCompatibility;
}
@@ -208,81 +200,55 @@ namespace WaveHarmonic.Crest
static class ShaderIDs
{
public static int s_ReflectionColorTexture = Shader.PropertyToID("_Crest_ReflectionColorTexture");
public static int s_ReflectionDepthTexture = Shader.PropertyToID("_Crest_ReflectionDepthTexture");
public static int s_ReflectionTexture = Shader.PropertyToID("_Crest_ReflectionTexture");
public static int s_ReflectionPositionNormal = Shader.PropertyToID("_Crest_ReflectionPositionNormal");
public static readonly int s_ReflectionMatrixIVP = Shader.PropertyToID("_Crest_ReflectionMatrixIVP");
public static readonly int s_ReflectionMatrixV = Shader.PropertyToID("_Crest_ReflectionMatrixV");
public static readonly int s_Crest_ReflectionOverscan = Shader.PropertyToID("_Crest_ReflectionOverscan");
public static readonly int s_PlanarReflectionsApplySmoothness = Shader.PropertyToID("_Crest_PlanarReflectionsApplySmoothness");
}
// Checked in underwater to filter cameras.
internal static Camera CurrentCamera { get; private set; }
internal WaterRenderer _Water;
internal UnderwaterRenderer _UnderWater;
bool _ApplySmoothness;
RenderTexture _ColorTexture;
RenderTexture _DepthTexture;
internal RenderTexture ColorTexture => _ColorTexture;
internal RenderTexture DepthTexture => _DepthTexture;
RenderTexture _ReflectionTexture;
internal RenderTexture ReflectionTexture => _ReflectionTexture;
readonly Vector4[] _ReflectionPositionNormal = new Vector4[2];
readonly Matrix4x4[] _ReflectionMatrixIVP = new Matrix4x4[2];
readonly Matrix4x4[] _ReflectionMatrixV = new Matrix4x4[2];
internal int _ActiveSlice;
Camera _CameraViewpoint;
Skybox _CameraViewpointSkybox;
Camera _CameraReflections;
Skybox _CameraReflectionsSkybox;
internal Camera ReflectionCamera => _CameraReflections;
int RefreshPerFrames => _RenderOnlySingleCamera ? _RefreshPerFrames : 1;
long _LastRefreshOnFrame = -1;
internal bool SupportsRecursiveRendering => _Water.SupportsRecursiveRendering && !_Debug._DisableRecursiveRendering;
internal bool SupportsRecursiveRendering =>
#if !UNITY_6000_0_OR_NEWER
// HDRP cannot recursive render for 2022.
!RenderPipelineHelper.IsHighDefinition &&
#endif
!_Debug._DisableRecursiveRendering;
readonly float[] _CullDistances = new float[32];
Texture _CameraDepthTexture;
/// <summary>
/// Invoked when the reflection camera is created.
/// </summary>
public static Action<Camera> OnCameraAdded { get; set; }
bool RequireTemporaryTargets =>
#if UNITY_6000_0_OR_NEWER && d_UnityURP
// As of Unity 6 we can write directly to a slice for URP.
!RenderPipelineHelper.IsUniversal &&
#endif
true;
internal void OnEnable()
{
// We initialized here previously to fix the first frame being black, but could not
// replicate anymore.
_CameraViewpoint = _Water.Viewer;
_CameraViewpointSkybox = _CameraViewpoint.GetComponent<Skybox>();
#if d_UnityURP
#if UNITY_6000_0_OR_NEWER
RenderPipelineManager.beginCameraRendering -= CaptureTargetDepth;
RenderPipelineManager.beginCameraRendering += CaptureTargetDepth;
#endif
#endif
// This is called also called every frame, but was required here as there was a
// black reflection for a frame without this earlier setup call.
CreateWaterObjects(_CameraViewpoint);
}
internal void OnDisable()
{
Shader.SetGlobalTexture(ShaderIDs.s_ReflectionColorTexture, Texture2D.blackTexture);
Shader.SetGlobalTexture(ShaderIDs.s_ReflectionDepthTexture, Texture2D.blackTexture);
#if d_UnityURP
#if UNITY_6000_0_OR_NEWER
RenderPipelineManager.beginCameraRendering -= CaptureTargetDepth;
#endif
#endif
Shader.SetGlobalTexture(ShaderIDs.s_ReflectionTexture, Texture2D.blackTexture);
}
internal void OnDestroy()
@@ -293,18 +259,11 @@ namespace WaveHarmonic.Crest
_CameraReflections = null;
}
if (_ColorTexture)
if (_ReflectionTexture)
{
_ColorTexture.Release();
Helpers.Destroy(_ColorTexture);
_ColorTexture = null;
}
if (_DepthTexture)
{
_DepthTexture.Release();
Helpers.Destroy(_DepthTexture);
_DepthTexture = null;
_ReflectionTexture.Release();
Helpers.Destroy(_ReflectionTexture);
_ReflectionTexture = null;
}
}
@@ -319,7 +278,7 @@ namespace WaveHarmonic.Crest
// This method could be executed twice: once by the camera rendering the surface,
// and once again by the planar reflection camera. For the latter, we do not want
// to proceed or infinite recursion. For safety.
if (camera == _CameraReflections)
if (camera == CurrentCamera)
{
return false;
}
@@ -355,46 +314,18 @@ namespace WaveHarmonic.Crest
if (camera == _CameraViewpoint)
{
// TODO: Emit an event instead so WBs can listen.
Shader.SetGlobalTexture(ShaderIDs.s_ReflectionColorTexture, _ColorTexture);
Shader.SetGlobalTexture(ShaderIDs.s_ReflectionDepthTexture, _DepthTexture);
Shader.SetGlobalTexture(ShaderIDs.s_ReflectionTexture, _ReflectionTexture);
}
}
internal void OnEndCameraRendering(Camera camera)
{
if (camera == ReflectionCamera)
{
// Appears to be the only reasonable way to get camera depth separately for SRPs.
_CameraDepthTexture = Shader.GetGlobalTexture(Crest.ShaderIDs.Unity.s_CameraDepthTexture);
}
if (!ShouldRender(camera))
{
return;
}
Shader.SetGlobalTexture(ShaderIDs.s_ReflectionColorTexture, Texture2D.blackTexture);
}
internal void LateUpdate()
{
// Check if enabled for at least one material every frame.
_ApplySmoothness = false;
CheckSurfaceMaterial(_Water.Surface.Material);
foreach (var wb in WaterBody.WaterBodies)
{
CheckSurfaceMaterial(wb._Material);
}
if (SupportsRecursiveRendering)
{
return;
}
// Passing a struct.
LateUpdate(new());
Shader.SetGlobalTexture(ShaderIDs.s_ReflectionTexture, Texture2D.blackTexture);
}
internal void LateUpdate(ScriptableRenderContext context)
@@ -445,18 +376,19 @@ namespace WaveHarmonic.Crest
UpdateCameraModes();
ForceDistanceCulling(_FarClipPlane);
_CameraReflections.targetTexture = _ReflectionTexture;
// TODO: Do not do this every frame.
if (_Mode != WaterReflectionSide.Both)
{
Helpers.ClearRenderTexture(_ColorTexture, Color.clear, depth: true);
Helpers.ClearRenderTexture(_DepthTexture, Color.clear, depth: true);
Helpers.ClearRenderTexture(_ReflectionTexture, Color.clear, depth: false);
}
var isActive = _Water.Surface.Root.gameObject.activeSelf;
// We do not want the water plane when rendering planar reflections.
_Water.Surface.Root.gameObject.SetActive(false);
CurrentCamera = _CameraReflections;
// Optionally disable pixel lights for reflection/refraction
var oldPixelLightCount = QualitySettings.pixelLightCount;
if (_DisablePixelLights)
@@ -504,7 +436,8 @@ namespace WaveHarmonic.Crest
_QualitySettingsOverride.Restore();
_Water.Surface.Root.gameObject.SetActive(isActive);
CurrentCamera = null;
_Water.Surface.Root.gameObject.SetActive(true);
// Remember this frame as last refreshed.
_LastRefreshOnFrame = Time.renderedFrameCount;
@@ -513,60 +446,41 @@ namespace WaveHarmonic.Crest
void Render(ScriptableRenderContext context)
{
var colorTarget = _ColorTexture;
var depthTarget = _DepthTexture;
if (RequireTemporaryTargets)
{
var descriptor = _ColorTexture.descriptor;
descriptor.dimension = TextureDimension.Tex2D;
descriptor.volumeDepth = 1;
descriptor.useMipMap = false;
// No need to clear, as camera clears using the skybox.
colorTarget = RenderTexture.GetTemporary(descriptor);
if (RenderPipelineHelper.IsLegacy)
{
descriptor = _DepthTexture.descriptor;
descriptor.dimension = TextureDimension.Tex2D;
descriptor.volumeDepth = 1;
descriptor.useMipMap = false;
// No need to clear, as camera clears using the skybox.
depthTarget = RenderTexture.GetTemporary(descriptor);
}
}
if (RenderPipelineHelper.IsLegacy)
{
// Not documented, but does not work for SRPs.
_CameraReflections.SetTargetBuffers(colorTarget.colorBuffer, depthTarget.depthBuffer);
}
else
{
_CameraReflections.targetTexture = colorTarget;
}
#if UNITY_6000_0_OR_NEWER && d_UnityURP
_CameraReflections.targetTexture = _ReflectionTexture;
#else
var descriptor = _ReflectionTexture.descriptor;
descriptor.dimension = TextureDimension.Tex2D;
descriptor.volumeDepth = 1;
descriptor.useMipMap = false;
// No need to clear, as camera clears using the skybox.
var target = RenderTexture.GetTemporary(descriptor);
_CameraReflections.targetTexture = target;
#endif
if (_Mode != WaterReflectionSide.Below)
{
_ReflectionPositionNormal[0] = ComputeHorizonPositionAndNormal(_CameraReflections, _Water.SeaLevel, 0.05f, false);
if (_UnderWater._Enabled)
{
// Disable underwater layer. It is the only way to exclude probes.
_CameraReflections.cullingMask = _Layers & ~(1 << _UnderWater.Layer);
}
_ActiveSlice = 0;
RenderCamera(context, _CameraReflections, Vector3.up, false, 0);
CopyTargets(colorTarget, depthTarget, 0);
_ReflectionPositionNormal[0] = ComputeHorizonPositionAndNormal(_CameraReflections, _Water.SeaLevel, 0.05f, false);
#if !(UNITY_6000_0_OR_NEWER && d_UnityURP)
Graphics.CopyTexture(target, 0, 0, _ReflectionTexture, 0, 0);
#endif
_CameraReflections.ResetProjectionMatrix();
}
if (_Mode != WaterReflectionSide.Above)
{
_ReflectionPositionNormal[1] = ComputeHorizonPositionAndNormal(_CameraReflections, _Water.SeaLevel, -0.05f, true);
if (_UnderWater._Enabled)
{
// Enable underwater layer.
@@ -575,32 +489,22 @@ namespace WaveHarmonic.Crest
_CameraReflections.depthTextureMode = DepthTextureMode.Depth;
}
_ActiveSlice = 1;
RenderCamera(context, _CameraReflections, Vector3.down, _NonObliqueNearSurface, 1);
CopyTargets(colorTarget, depthTarget, 1);
_ReflectionPositionNormal[1] = ComputeHorizonPositionAndNormal(_CameraReflections, _Water.SeaLevel, -0.05f, true);
#if !(UNITY_6000_0_OR_NEWER && d_UnityURP)
Graphics.CopyTexture(target, 0, 0, _ReflectionTexture, 1, 0);
#endif
_CameraReflections.ResetProjectionMatrix();
}
if (RequireTemporaryTargets)
{
RenderTexture.ReleaseTemporary(colorTarget);
if (RenderPipelineHelper.IsLegacy) RenderTexture.ReleaseTemporary(depthTarget);
}
#if !(UNITY_6000_0_OR_NEWER && d_UnityURP)
RenderTexture.ReleaseTemporary(target);
#endif
if (_ApplySmoothness)
{
// We are only using mip-maps if applying smoothness/roughness.
_ColorTexture.GenerateMips();
}
_ReflectionTexture.GenerateMips();
Shader.SetGlobalVectorArray(ShaderIDs.s_ReflectionPositionNormal, _ReflectionPositionNormal);
Shader.SetGlobalMatrixArray(ShaderIDs.s_ReflectionMatrixIVP, _ReflectionMatrixIVP);
Shader.SetGlobalMatrixArray(ShaderIDs.s_ReflectionMatrixV, _ReflectionMatrixV);
}
void RenderCamera(ScriptableRenderContext context, Camera camera, Vector3 planeNormal, bool nonObliqueNearSurface, int slice)
@@ -613,9 +517,10 @@ namespace WaveHarmonic.Crest
var viewpoint = _CameraViewpoint.transform;
if (offset == 0f && viewpoint.position.y == planePosition.y)
{
// Minor offset to prevent "Screen position out of view frustum". Needs to scale
// with distance from center.
offset = viewpoint.position.magnitude >= 15000f ? 0.01f : 0.001f;
// Minor offset to prevent "Screen position out of view frustum". Smallest number
// to work with both above and below. Smallest number to work with both above and
// below. Could be BIRP only.
offset = 0.00001f;
}
}
@@ -634,12 +539,7 @@ namespace WaveHarmonic.Crest
if (_UseObliqueMatrix && (!nonObliqueNearSurface || Mathf.Abs(_CameraViewpoint.transform.position.y - planePosition.y) > _NonObliqueNearSurfaceThreshold))
{
var matrix = _CameraViewpoint.CalculateObliqueMatrix(clipPlane);
// Overscan.
var overscan = 1f - (_Overscan - 1f) * 0.5f;
matrix[0, 0] *= overscan;
matrix[1, 1] *= overscan;
camera.projectionMatrix = matrix;
camera.projectionMatrix = _CameraViewpoint.CalculateObliqueMatrix(clipPlane);
}
// Set custom culling matrix from the current camera
@@ -650,12 +550,9 @@ namespace WaveHarmonic.Crest
camera.transform.eulerAngles = new(-euler.x, euler.y, euler.z);
camera.cullingMatrix = camera.projectionMatrix * camera.worldToCameraMatrix;
_ReflectionMatrixV[slice] = camera.worldToCameraMatrix;
_ReflectionMatrixIVP[slice] = (GL.GetGPUProjectionMatrix(camera.projectionMatrix, true) * camera.worldToCameraMatrix).inverse;
if (SupportsRecursiveRendering)
{
Helpers.RenderCamera(camera, context, slice, _Debug._ForceCompatibility);
Helpers.RenderCamera(camera, context, slice);
}
else
{
@@ -663,35 +560,6 @@ namespace WaveHarmonic.Crest
}
}
void CopyTargets(Texture color, Texture depth, int slice)
{
if (RequireTemporaryTargets)
{
Graphics.CopyTexture(color, 0, 0, 0, 0, _Resolution, _Resolution, _ColorTexture, slice, 0, 0, 0);
}
if (!RenderPipelineHelper.IsLegacy)
{
depth = _CameraDepthTexture;
}
if (Rendering.IsRenderGraph)
{
return;
}
// This can change between depth and R32 based on settings.
if (depth != null && depth.graphicsFormat != _DepthTexture.graphicsFormat)
{
RecreateDepth(depth);
}
if (depth != null && depth.width >= _Resolution)
{
Graphics.CopyTexture(depth, 0, 0, 0, 0, _Resolution, _Resolution, _DepthTexture, slice, 0, 0, 0);
}
}
/// <summary>
/// Limit render distance for reflection camera for first 32 layers
/// </summary>
@@ -755,101 +623,38 @@ namespace WaveHarmonic.Crest
_CameraReflections.orthographic = _CameraViewpoint.orthographic;
_CameraReflections.fieldOfView = _CameraViewpoint.fieldOfView;
_CameraReflections.orthographicSize = _CameraViewpoint.orthographicSize;
_CameraReflections.allowMSAA = false;
_CameraReflections.allowMSAA = _AllowMSAA;
_CameraReflections.aspect = _CameraViewpoint.aspect;
_CameraReflections.useOcclusionCulling = !_DisableOcclusionCulling && _CameraViewpoint.useOcclusionCulling;
_CameraReflections.depthTextureMode = _CameraViewpoint.depthTextureMode;
// Overscan
{
_CameraReflections.usePhysicalProperties = _Overscan > 1f;
var baseSensor = new Vector2(36f, 24f);
var focal = (baseSensor.y * 0.5f) / Mathf.Tan(_CameraViewpoint.fieldOfView * 0.5f * Mathf.Deg2Rad);
var overscan = 1f - (_Overscan - 1f) * 0.5f;
_CameraReflections.sensorSize = baseSensor / overscan;
_CameraReflections.focalLength = focal;
Shader.SetGlobalFloat(ShaderIDs.s_Crest_ReflectionOverscan, overscan);
}
}
void RecreateDepth(Texture depth)
{
if (_DepthTexture != null && _DepthTexture.IsCreated())
{
_DepthTexture.Release();
_DepthTexture.descriptor = depth.GetDescriptor();
}
else
{
_DepthTexture = new(depth.GetDescriptor());
}
_DepthTexture.name = "_Crest_ReflectionDepth";
_DepthTexture.width = _DepthTexture.height = _Resolution;
_DepthTexture.isPowerOfTwo = true;
_DepthTexture.useMipMap = false;
_DepthTexture.autoGenerateMips = false;
_DepthTexture.filterMode = FilterMode.Point;
_DepthTexture.volumeDepth = 2;
_DepthTexture.dimension = TextureDimension.Tex2DArray;
_DepthTexture.Create();
}
// On-demand create any objects we need for water
void CreateWaterObjects(Camera currentCamera)
{
// We cannot exclude stencil for URP, as the depth texture format always has it.
var colorFormat = Rendering.GetDefaultColorFormat(_HDR);
var depthFormat = Rendering.GetDefaultDepthFormat(_Stencil || RenderPipelineHelper.IsUniversal);
var format = _HDR ? RenderTextureFormat.ARGBHalf : RenderTextureFormat.ARGB32;
var stencil = _Stencil ? 24 : 16;
// Reflection render texture
if (!_ColorTexture || _ColorTexture.width != _Resolution || _ColorTexture.graphicsFormat != colorFormat || _ColorTexture.depthStencilFormat != depthFormat)
if (!_ReflectionTexture || _ReflectionTexture.width != _Resolution || _ReflectionTexture.format != format || _ReflectionTexture.depth != stencil)
{
if (_ColorTexture)
if (_ReflectionTexture)
{
Helpers.Destroy(_ColorTexture);
Helpers.Destroy(_DepthTexture);
Helpers.Destroy(_ReflectionTexture);
}
var descriptor = new RenderTextureDescriptor(_Resolution, _Resolution)
Debug.Assert(SystemInfo.SupportsRenderTextureFormat(format), "Crest: The graphics device does not support the render texture format " + format.ToString());
_ReflectionTexture = new(_Resolution, _Resolution, stencil, format)
{
name = "_Crest_WaterReflection",
isPowerOfTwo = true,
dimension = TextureDimension.Tex2DArray,
volumeDepth = 2,
depthStencilFormat = depthFormat,
msaaSamples = 1,
};
_ColorTexture = new(descriptor)
{
name = "_Crest_ReflectionColor",
graphicsFormat = colorFormat,
isPowerOfTwo = true,
useMipMap = true,
autoGenerateMips = false,
filterMode = FilterMode.Trilinear,
};
_ColorTexture.Create();
_DepthTexture = new(descriptor)
{
name = "_Crest_ReflectionDepth",
graphicsFormat = UnityEngine.Experimental.Rendering.GraphicsFormat.None,
isPowerOfTwo = true,
useMipMap = false,
autoGenerateMips = false,
filterMode = FilterMode.Point,
};
if (RenderPipelineHelper.IsHighDefinition)
{
_DepthTexture.graphicsFormat = UnityEngine.Experimental.Rendering.GraphicsFormat.R32_SFloat;
_DepthTexture.depthStencilFormat = UnityEngine.Experimental.Rendering.GraphicsFormat.None;
}
_DepthTexture.Create();
_ReflectionTexture.Create();
}
// Camera for reflection
@@ -886,8 +691,8 @@ namespace WaveHarmonic.Crest
{
var additionalCameraData = _CameraReflections.gameObject.AddComponent<UniversalAdditionalCameraData>();
additionalCameraData.renderShadows = !_DisableShadows;
additionalCameraData.requiresColorTexture = _Mode != WaterReflectionSide.Above; // or incur assertions
additionalCameraData.requiresDepthTexture = true;
additionalCameraData.requiresColorTexture = false;
additionalCameraData.requiresDepthTexture = false;
}
#endif
OnCameraAdded?.Invoke(_CameraReflections);
@@ -1059,19 +864,6 @@ namespace WaveHarmonic.Crest
return new(position.x, position.y, normal.x, normal.y);
}
void CheckSurfaceMaterial(Material material)
{
if (material == null)
{
return;
}
if (!_ApplySmoothness)
{
_ApplySmoothness = material.GetBoolean(ShaderIDs.s_PlanarReflectionsApplySmoothness);
}
}
void SetEnabled(bool previous, bool current)
{
if (previous == current) return;
@@ -1092,15 +884,4 @@ namespace WaveHarmonic.Crest
}
#endif
}
partial class WaterReflections
{
// MSAA would require separate textures to resolve to. Not worth the expense.
[HideInInspector]
[Obsolete("MSAA for the planar reflection camera is no longer supported. This setting will be ignored.")]
[Tooltip("Whether to allow MSAA.")]
[@GenerateAPI]
[@DecoratedField, SerializeField]
bool _AllowMSAA;
}
}

View File

@@ -8,7 +8,44 @@ namespace WaveHarmonic.Crest
/// </summary>
sealed class DefaultTimeProvider : ITimeProvider
{
public float Time => UnityEngine.Time.time;
public float Delta => UnityEngine.Time.deltaTime;
public float Time
{
get
{
#if UNITY_EDITOR
if (UnityEngine.Application.isPlaying)
{
return UnityEngine.Time.time;
}
else
{
return WaterRenderer.EditorTime;
}
#else
return UnityEngine.Time.time;
#endif
}
}
public float Delta
{
get
{
#if UNITY_EDITOR
if (UnityEngine.Application.isPlaying)
{
return UnityEngine.Time.deltaTime;
}
else
{
return WaterRenderer.EditorDeltaTime;
}
#else
return UnityEngine.Time.deltaTime;
#endif
;
}
}
}
}

View File

@@ -21,7 +21,6 @@ namespace WaveHarmonic.Crest
public static readonly int s_TextureSize = Shader.PropertyToID("_Crest_TextureSize");
public static readonly int s_TexturePosition = Shader.PropertyToID("_Crest_TexturePosition");
public static readonly int s_TextureRotation = Shader.PropertyToID("_Crest_TextureRotation");
public static readonly int s_TextureResolution = Shader.PropertyToID("_Crest_TextureResolution");
public static readonly int s_Multiplier = Shader.PropertyToID("_Crest_Multiplier");
public static readonly int s_FeatherWidth = Shader.PropertyToID("_Crest_FeatherWidth");
public static readonly int s_NegativeValues = Shader.PropertyToID("_Crest_NegativeValues");

View File

@@ -10,7 +10,6 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Samples.Editor")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Samples.Examples")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Samples.Ripples")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Samples.Waterfall")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Samples.Submarine")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.CPUQueries")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.CPUQueries.Editor")]

View File

@@ -103,11 +103,7 @@ namespace WaveHarmonic.Crest
sealed class Layer : Decorator { }
[Conditional(Symbols.k_UnityEditor)]
sealed class Stripped : Decorator
{
public enum Style { None, PlatformTab, }
public Stripped(Style style = Style.None, bool indent = false) { }
}
sealed class Stripped : Decorator { }
[Conditional(Symbols.k_UnityEditor)]
sealed class Delayed : Decorator { }
@@ -115,12 +111,6 @@ namespace WaveHarmonic.Crest
[Conditional(Symbols.k_UnityEditor)]
sealed class Disabled : Decorator { }
[Conditional(Symbols.k_UnityEditor)]
sealed class Required : Attribute { }
[Conditional(Symbols.k_UnityEditor)]
sealed class PlatformTabs : Attribute { }
[Conditional(Symbols.k_UnityEditor)]
sealed class AttachMaterialEditor : Attribute
{
@@ -163,25 +153,25 @@ namespace WaveHarmonic.Crest
{
[Flags]
public enum Clamp { None = 0, Minimum = 1, Maximum = 2, Both = Minimum | Maximum }
public Range(float minimum, float maximum, Clamp clamp = Clamp.Both, float scale = 1f, bool delayed = false, int step = 0, bool power = false) { }
public Range(float minimum, float maximum, Clamp clamp = Clamp.Both, float scale = 1f, bool delayed = false, int step = 0, bool power = false) {}
}
[Conditional(Symbols.k_UnityEditor)]
sealed class Minimum : Decorator
{
public Minimum(float minimum) { }
public Minimum(float minimum) {}
}
[Conditional(Symbols.k_UnityEditor)]
sealed class Maximum : Decorator
{
public Maximum(float maximum) { }
public Maximum(float maximum) {}
}
[Conditional(Symbols.k_UnityEditor)]
sealed class WarnIfAbove : Decorator
{
public WarnIfAbove(float maximum) { }
public WarnIfAbove(float maximum) {}
}
[Conditional(Symbols.k_UnityEditor)]

View File

@@ -10,7 +10,7 @@ using UnityEngine.Rendering.HighDefinition;
using UnityEngine.Rendering.Universal;
using WaveHarmonic.Crest.Internal;
#if !UNITY_6000_0_OR_NEWER
#if !UNITY_2023_2_OR_NEWER
using GraphicsFormatUsage = UnityEngine.Experimental.Rendering.FormatUsage;
#endif
@@ -21,6 +21,18 @@ namespace WaveHarmonic.Crest
/// </summary>
static partial class Helpers
{
// Adapted from:
// Packages/com.unity.render-pipelines.universal/Runtime/UniversalRenderer.cs
#if UNITY_SWITCH || UNITY_ANDROID || UNITY_EMBEDDED_LINUX || UNITY_QNX
internal const GraphicsFormat k_DepthStencilFormat = GraphicsFormat.D24_UNorm_S8_UInt;
internal const int k_DepthBufferBits = 24;
internal const DepthBits k_DepthBits = DepthBits.Depth24;
#else
internal const GraphicsFormat k_DepthStencilFormat = GraphicsFormat.D32_SFloat_S8_UInt;
internal const int k_DepthBufferBits = 32;
internal const DepthBits k_DepthBits = DepthBits.Depth32;
#endif
public static class ShaderIDs
{
public static readonly int s_MainTexture = Shader.PropertyToID("_Utility_MainTexture");
@@ -362,14 +374,6 @@ namespace WaveHarmonic.Crest
return mask == (mask | (1 << layer));
}
// https://docs.unity3d.com/6000.3/Documentation/Manual/WebGPU-limitations.html
public static bool IsWebGPU =>
#if UNITY_6000_0_OR_NEWER
SystemInfo.graphicsDeviceType is GraphicsDeviceType.WebGPU;
#else
false;
#endif
// R16G16B16A16_SFloat appears to be the most compatible format.
// https://docs.unity3d.com/Manual/class-TextureImporterOverride.html#texture-compression-support-platforms
// https://learn.microsoft.com/en-us/windows/win32/direct3d12/typed-unordered-access-view-loads#supported-formats-and-api-calls
@@ -382,16 +386,6 @@ namespace WaveHarmonic.Crest
&& SystemInfo.SupportsRandomWriteOnRenderTextureFormat(rtFormat);
}
static GraphicsFormat GetWebGPUTextureFormat(GraphicsFormat format)
{
// WebGPU is very limited in format usage - especially read-write.
return GraphicsFormatUtility.GetComponentCount(format) switch
{
1 => GraphicsFormat.R32_SFloat,
_ => GraphicsFormat.R32G32B32A32_SFloat,
};
}
internal static GraphicsFormat GetCompatibleTextureFormat(GraphicsFormat format, GraphicsFormatUsage usage, string label, bool randomWrite = false)
{
var result = SystemInfo.GetCompatibleFormat(format, usage);
@@ -428,26 +422,9 @@ namespace WaveHarmonic.Crest
result = s_FallbackGraphicsFormat;
}
if (randomWrite && IsWebGPU)
{
// Pass the requested format otherwise GetCompatibleFormat may change the component
// count which we need to pick the right format.
result = GetWebGPUTextureFormat(format);
}
return result;
}
public static GraphicsFormat GetCompatibleTextureFormat(GraphicsFormat format, bool randomWrite)
{
if (randomWrite && IsWebGPU)
{
format = GetWebGPUTextureFormat(format);
}
return format;
}
public static void SetGlobalKeyword(string keyword, bool enabled)
{
if (enabled)
@@ -783,58 +760,6 @@ namespace WaveHarmonic.Crest
return false;
}
internal static void UniversalRenderCamera(ScriptableRenderContext context, Camera camera, int slice)
{
#if UNITY_6000_0_OR_NEWER
// SingleCameraRequest does not render the full camera stack, thus should exclude
// overlays which is likely desirable. Alternative approach worth investigating:
// https://docs.unity3d.com/6000.0/Documentation/Manual/urp/User-Render-Requests.html
// Setting destination silences a warning if Opaque Texture enabled.
s_RenderSingleCameraRequest.destination = camera.targetTexture;
s_RenderSingleCameraRequest.slice = slice;
UnityEngine.Rendering.RenderPipeline.SubmitRenderRequest(camera, s_RenderSingleCameraRequest);
#else
#pragma warning disable CS0618 // Type or member is obsolete
UniversalRenderPipeline.RenderSingleCamera(context, camera);
#pragma warning restore CS0618 // Type or member is obsolete
#endif
}
internal static void UniversalRenderCamera(ScriptableRenderContext context, Camera camera, int slice, bool noRenderFeatures)
{
// Get this every time as it could change.
var renderers = (ScriptableRendererData[])s_RenderDataListField.GetValue(UniversalRenderPipeline.asset);
var renderer = (UniversalRendererData)renderers[GetRendererIndex(camera)];
// On Unity 6.3, got exceptions if this was auto with SSAO enabled.
var safe = !noRenderFeatures && renderer.intermediateTextureMode == IntermediateTextureMode.Always;
if (!safe)
{
foreach (var feature in renderer.rendererFeatures)
{
// Null exception reported here. Might be null due to missing render features
if (feature == null) continue;
s_RenderFeatureActiveStates.Add(feature.isActive);
feature.SetActive(false);
}
}
UniversalRenderCamera(context, camera, slice);
if (!safe)
{
var index = 0;
foreach (var feature in renderer.rendererFeatures)
{
if (feature == null) continue;
feature.SetActive(s_RenderFeatureActiveStates[index++]);
}
s_RenderFeatureActiveStates.Clear();
}
}
internal static void RenderCameraWithoutCustomPasses(Camera camera)
{
// Get this every time as it could change.
@@ -866,12 +791,24 @@ namespace WaveHarmonic.Crest
static readonly UnityEngine.Rendering.RenderPipeline.StandardRequest s_RenderStandardRequest = new();
public static void RenderCamera(Camera camera, ScriptableRenderContext context, int slice, bool noRenderFeatures = false)
public static void RenderCamera(Camera camera, ScriptableRenderContext context, int slice)
{
#if d_UnityURP
if (RenderPipelineHelper.IsUniversal)
{
UniversalRenderCamera(context, camera, slice, noRenderFeatures);
#if UNITY_6000_0_OR_NEWER
// SingleCameraRequest does not render the full camera stack, thus should exclude
// overlays which is likely desirable. Alternative approach worth investigating:
// https://docs.unity3d.com/6000.0/Documentation/Manual/urp/User-Render-Requests.html
// Setting destination silences a warning if Opaque Texture enabled.
s_RenderSingleCameraRequest.destination = camera.targetTexture;
s_RenderSingleCameraRequest.slice = slice;
UnityEngine.Rendering.RenderPipeline.SubmitRenderRequest(camera, s_RenderSingleCameraRequest);
#else
#pragma warning disable CS0618 // Type or member is obsolete
UniversalRenderPipeline.RenderSingleCamera(context, camera);
#pragma warning restore CS0618 // Type or member is obsolete
#endif
return;
}
#endif
@@ -1175,33 +1112,6 @@ namespace WaveHarmonic.Crest
{
return @event.GetPersistentEventCount() == 0;
}
public static bool Encapsulates(this Rect r1, Rect r2)
{
return r1.Contains(r2.min) && r1.Contains(r2.max);
}
}
namespace Compatibility
{
static partial class Extensions
{
#if !UNITY_6000_3_OR_NEWER
internal static int GetEntityId(this Object @this)
{
return @this.GetInstanceID();
}
#endif
internal static ulong GetRawSceneHandle(this UnityEngine.SceneManagement.Scene @this)
{
#if UNITY_6000_4_OR_NEWER
return @this.handle.GetRawData();
#else
return (ulong)@this.handle;
#endif
}
}
}
}

View File

@@ -23,11 +23,6 @@ namespace WaveHarmonic.Crest
void SetBlock();
}
interface IPropertyWrapperVariants : IPropertyWrapper
{
void SetKeyword(in LocalKeyword keyword, bool value);
}
static class PropertyWrapperConstants
{
internal const string k_NoShaderMessage = "Cannot create required material because shader <i>{0}</i> could not be found or loaded."
@@ -77,7 +72,7 @@ namespace WaveHarmonic.Crest
}
[System.Serializable]
readonly struct PropertyWrapperMaterial : IPropertyWrapperVariants
readonly struct PropertyWrapperMaterial : IPropertyWrapper
{
public Material Material { get; }
@@ -129,7 +124,7 @@ namespace WaveHarmonic.Crest
}
[System.Serializable]
readonly struct PropertyWrapperCompute : IPropertyWrapperVariants
readonly struct PropertyWrapperCompute : IPropertyWrapper
{
readonly CommandBuffer _Buffer;
readonly ComputeShader _Shader;
@@ -162,7 +157,7 @@ namespace WaveHarmonic.Crest
}
[System.Serializable]
readonly struct PropertyWrapperComputeStandalone : IPropertyWrapperVariants
readonly struct PropertyWrapperComputeStandalone : IPropertyWrapper
{
readonly ComputeShader _Shader;
readonly int _Kernel;

View File

@@ -61,7 +61,6 @@ namespace WaveHarmonic.Crest
cameraData = frameData.Get<UniversalCameraData>();
renderingData = frameData.Get<UniversalRenderingData>();
#if URP_COMPATIBILITY_MODE
if (builder == null)
{
#pragma warning disable CS0618 // Type or member is obsolete
@@ -70,7 +69,6 @@ namespace WaveHarmonic.Crest
#pragma warning restore CS0618 // Type or member is obsolete
}
else
#endif
{
colorTargetHandle = resources.activeColorTexture;
depthTargetHandle = resources.activeDepthTexture;

View File

@@ -93,7 +93,6 @@ namespace WaveHarmonic.Crest
return false;
}
// Modified from:
// https://github.com/Unity-Technologies/Graphics/blob/19ec161f3f752db865597374b3ad1b3eaf110097/Packages/com.unity.render-pipelines.universal/Runtime/RenderingUtils.cs#L697-L729
/// <summary>
@@ -109,8 +108,7 @@ namespace WaveHarmonic.Crest
/// <param name="mipMapBias">Bias applied to mipmaps during filtering.</param>
/// <param name="name">Name of the RTHandle.</param>
/// <returns>If the RTHandle should be re-allocated</returns>
public static bool ReAllocateIfNeeded
(
public static bool ReAllocateIfNeeded(
ref RTHandle handle,
Vector2 scaleFactor,
in RenderTextureDescriptor descriptor,
@@ -119,28 +117,50 @@ namespace WaveHarmonic.Crest
bool isShadowMap = false,
int anisoLevel = 1,
float mipMapBias = 0,
string name = ""
)
string name = "")
{
// Only HDRP seems to use this?
if (RenderPipelineHelper.IsHighDefinition)
var usingConstantScale = handle != null && handle.useScaling && handle.scaleFactor == scaleFactor;
if (!usingConstantScale || RTHandleNeedsReAlloc(handle, descriptor, filterMode, wrapMode, isShadowMap, anisoLevel, mipMapBias, name, true))
{
var usingConstantScale = handle != null && handle.useScaling && handle.scaleFactor == scaleFactor;
if (!usingConstantScale || RTHandleNeedsReAlloc(handle, descriptor, filterMode, wrapMode, isShadowMap, anisoLevel, mipMapBias, name, true))
{
handle?.Release();
handle = RTHandles.Alloc(scaleFactor, descriptor, filterMode, wrapMode, isShadowMap, anisoLevel, mipMapBias, name);
return true;
}
handle?.Release();
handle = RTHandles.Alloc(scaleFactor, descriptor, filterMode, wrapMode, isShadowMap, anisoLevel, mipMapBias, name);
return true;
}
else
return false;
}
// https://github.com/Unity-Technologies/Graphics/blob/19ec161f3f752db865597374b3ad1b3eaf110097/Packages/com.unity.render-pipelines.universal/Runtime/RenderingUtils.cs#L731-L764
/// <summary>
/// Re-allocate dynamically resized RTHandle if it is not allocated or doesn't match the descriptor
/// </summary>
/// <param name="handle">RTHandle to check (can be null)</param>
/// <param name="scaleFunc">Function used for the RTHandle size computation.</param>
/// <param name="descriptor">Descriptor for the RTHandle to match</param>
/// <param name="filterMode">Filtering mode of the RTHandle.</param>
/// <param name="wrapMode">Addressing mode of the RTHandle.</param>
/// <param name="isShadowMap">Set to true if the depth buffer should be used as a shadow map.</param>
/// <param name="anisoLevel">Anisotropic filtering level.</param>
/// <param name="mipMapBias">Bias applied to mipmaps during filtering.</param>
/// <param name="name">Name of the RTHandle.</param>
/// <returns>If an allocation was done</returns>
public static bool ReAllocateIfNeeded(
ref RTHandle handle,
ScaleFunc scaleFunc,
in RenderTextureDescriptor descriptor,
FilterMode filterMode = FilterMode.Point,
TextureWrapMode wrapMode = TextureWrapMode.Repeat,
bool isShadowMap = false,
int anisoLevel = 1,
float mipMapBias = 0,
string name = "")
{
var usingScaleFunction = handle != null && handle.useScaling && handle.scaleFactor == Vector2.zero;
if (!usingScaleFunction || RTHandleNeedsReAlloc(handle, descriptor, filterMode, wrapMode, isShadowMap, anisoLevel, mipMapBias, name, true))
{
if (RTHandleNeedsReAlloc(handle, descriptor, filterMode, wrapMode, isShadowMap, anisoLevel, mipMapBias, name, false))
{
handle?.Release();
handle = RTHandles.Alloc(descriptor, filterMode, wrapMode, isShadowMap, anisoLevel, mipMapBias, name);
return true;
}
handle?.Release();
handle = RTHandles.Alloc(scaleFunc, descriptor, filterMode, wrapMode, isShadowMap, anisoLevel, mipMapBias, name);
return true;
}
return false;

View File

@@ -5,91 +5,14 @@
#define _XR_ENABLED
#endif
using System.Runtime.CompilerServices;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using UnityEngine.XR;
namespace WaveHarmonic.Crest
{
static partial class Rendering
{
// Taken from Unity 6.5:
// Packages/com.unity.render-pipelines.core/Runtime/Utilities/CoreUtils.cs
/// <summary>
/// Return the GraphicsFormat of DepthStencil RenderTarget preferred for the current platform.
/// </summary>
/// <returns>The GraphicsFormat of DepthStencil RenderTarget preferred for the current platform.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static GraphicsFormat GetDefaultDepthStencilFormat()
{
#if UNITY_SWITCH || UNITY_SWITCH2 || UNITY_EMBEDDED_LINUX || UNITY_QNX || UNITY_ANDROID
return GraphicsFormat.D24_UNorm_S8_UInt;
#else
return GraphicsFormat.D32_SFloat_S8_UInt;
#endif
}
// Taken from Unity 6.5:
// Packages/com.unity.render-pipelines.core/Runtime/Utilities/CoreUtils.cs
/// <summary>
/// Return the GraphicsFormat of Depth-only RenderTarget preferred for the current platform.
/// </summary>
/// <returns>The GraphicsFormat of Depth-only RenderTarget preferred for the current platform.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static GraphicsFormat GetDefaultDepthOnlyFormat()
{
#if UNITY_SWITCH || UNITY_SWITCH2 || UNITY_EMBEDDED_LINUX || UNITY_QNX || UNITY_ANDROID
return GraphicsFormatUtility.GetDepthStencilFormat(24, 0); // returns GraphicsFormat.D24_UNorm when hardware supports it
#else
return GraphicsFormat.D32_SFloat;
#endif
}
// Taken from Unity 6.5:
// Packages/com.unity.render-pipelines.core/Runtime/Utilities/CoreUtils.cs
/// <summary>
/// Return the number of DepthStencil RenderTarget depth bits preferred for the current platform.
/// </summary>
/// <returns>The number of DepthStencil RenderTarget depth bits preferred for the current platform.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static DepthBits GetDefaultDepthBufferBits()
{
#if UNITY_SWITCH || UNITY_SWITCH2 || UNITY_EMBEDDED_LINUX || UNITY_QNX || UNITY_ANDROID
return DepthBits.Depth24;
#else
return DepthBits.Depth32;
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static GraphicsFormat GetDefaultColorFormat(bool hdr)
{
return SystemInfo.GetGraphicsFormat(hdr ? DefaultFormat.HDR : DefaultFormat.LDR);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static GraphicsFormat GetDefaultDepthFormat(bool stencil)
{
return stencil ? GetDefaultDepthStencilFormat() : GetDefaultDepthOnlyFormat();
}
// URP_COMPATIBILITY_MODE = URP + UE < 6.4
public static bool IsRenderGraph => RenderPipelineHelper.IsUniversal
#if URP_COMPATIBILITY_MODE
#if !UNITY_6000_0_OR_NEWER
&& false
#else
&& !GraphicsSettings.GetRenderPipelineSettings<RenderGraphSettings>().enableRenderCompatibilityMode
#endif
#endif
;
public static partial class BIRP
{
static partial class ShaderIDs

View File

@@ -35,11 +35,6 @@
"name": "com.unity.render-pipelines.universal",
"expression": "",
"define": "d_UnityURP"
},
{
"name": "com.unity.render-pipelines.universal",
"expression": "(,17.3.0)",
"define": "URP_COMPATIBILITY_MODE"
}
],
"noEngineReferences": false

View File

@@ -20,9 +20,6 @@ namespace WaveHarmonic.Crest
readonly System.Action<CommandBuffer> _CopyColorTexture;
readonly System.Action<CommandBuffer> _SetRenderTargetToBackBuffers;
bool _AllocatedColor;
GraphicsFormat _GraphicsFormat;
public UnderwaterEffectPass(UnderwaterRenderer renderer)
{
_Renderer = renderer;
@@ -43,8 +40,6 @@ namespace WaveHarmonic.Crest
public void Allocate(GraphicsFormat format)
{
_GraphicsFormat = format;
if (_Renderer.RenderBeforeTransparency && !_Renderer._NeedsColorTexture)
{
return;
@@ -62,8 +57,6 @@ namespace WaveHarmonic.Crest
wrapMode: TextureWrapMode.Clamp,
name: "_Crest_UnderwaterCameraColorTexture"
);
_AllocatedColor = true;
}
public void ReAllocate(RenderTextureDescriptor descriptor)
@@ -92,11 +85,6 @@ namespace WaveHarmonic.Crest
if (!_Renderer.RenderBeforeTransparency || _Renderer._NeedsColorTexture)
{
if (!_AllocatedColor)
{
Allocate(_GraphicsFormat);
}
buffer.SetGlobalTexture(UnderwaterRenderer.ShaderIDs.s_CameraColorTexture, _ColorTexture);
}

View File

@@ -30,7 +30,6 @@ namespace WaveHarmonic.Crest
}
}
#if URP_COMPATIBILITY_MODE
[System.Obsolete]
public override void OnCameraSetup(CommandBuffer buffer, ref RenderingData data)
{
@@ -47,7 +46,6 @@ namespace WaveHarmonic.Crest
context.ExecuteCommandBuffer(buffer);
CommandBufferPool.Release(buffer);
}
#endif
}
partial class CopyDepthBufferPassURP
@@ -65,7 +63,6 @@ namespace WaveHarmonic.Crest
var resources = frameData.Get<UniversalResourceData>();
cameraData = frameData.Get<UniversalCameraData>();
#if URP_COMPATIBILITY_MODE
if (builder == null)
{
#pragma warning disable CS0618 // Type or member is obsolete
@@ -74,7 +71,6 @@ namespace WaveHarmonic.Crest
#pragma warning restore CS0618 // Type or member is obsolete
}
else
#endif
{
// We need reset render targets to these before the next pass, but we do not read
// or write to the color target.
@@ -103,7 +99,6 @@ namespace WaveHarmonic.Crest
}
}
#if URP_COMPATIBILITY_MODE
[System.Obsolete]
public override void OnCameraSetup(CommandBuffer buffer, ref RenderingData data)
{
@@ -120,7 +115,6 @@ namespace WaveHarmonic.Crest
context.ExecuteCommandBuffer(buffer);
CommandBufferPool.Release(buffer);
}
#endif
}
}

View File

@@ -140,7 +140,7 @@ namespace WaveHarmonic.Crest
if (UseStencilBuffer)
{
descriptor.colorFormat = RenderTextureFormat.Depth;
descriptor.depthBufferBits = (int)Rendering.GetDefaultDepthBufferBits();
descriptor.depthBufferBits = (int)Helpers.k_DepthBits;
// bindMS is necessary in this case for depth.
descriptor.SetMSAASamples(camera);
descriptor.bindMS = descriptor.msaaSamples > 1;
@@ -272,9 +272,9 @@ namespace WaveHarmonic.Crest
if (camera.cameraType != CameraType.Reflection)
{
// Skip work if camera is far enough below the surface.
var forceFullShader = !_MaskRead || (_Water._PerCameraHeightReady && _Water._ViewerHeightAboveWaterPerCamera < -8f && !Portaled);
var forceFullShader = !_Water.Surface.Enabled || (_Water._ViewerHeightAboveWaterPerCamera < -8f && !Portaled);
_VolumeMaterial.SetKeyword("d_Crest_NoMaskColor", forceFullShader);
_VolumeMaterial.SetKeyword("d_Crest_NoMaskDepth", !_MaskRead || RenderBeforeTransparency);
_VolumeMaterial.SetKeyword("d_Crest_NoMaskDepth", !_Water.Surface.Enabled || RenderBeforeTransparency);
}
// Compute ambient lighting SH.

View File

@@ -87,7 +87,7 @@ namespace WaveHarmonic.Crest
#if UNITY_EDITOR
// Only repaint, otherwise changes might persist.
if (Event.current?.type != EventType.Repaint)
if (Event.current.type != EventType.Repaint)
{
return;
}

View File

@@ -91,14 +91,12 @@ namespace WaveHarmonic.Crest
}
// Portals have their own fitted to the portal bounds.
else
#pragma warning disable format
#if d_CrestPortals
if (!Portaled || _Water.Portals.RequiresFullScreenMask)
#endif
{
RenderLineMask(commands, camera, mask.ColorRT.descriptor, mask._ColorRTI);
}
#pragma warning restore format
}
internal void RenderLineMask(CommandBuffer buffer, Camera camera, RenderTextureDescriptor descriptor, RenderTargetIdentifier target)
@@ -201,12 +199,12 @@ namespace WaveHarmonic.Crest
MaskRenderer.MaskInput MaskRenderer.IMaskProvider.Allocate()
{
return UseLegacyMask || UseStencilBuffer ? MaskRenderer.MaskInput.Both : MaskRenderer.MaskInput.Color;
return MaskRenderer.MaskInput.Both;
}
MaskRenderer.MaskInput MaskRenderer.IMaskReceiver.Allocate()
{
return UseLegacyMask || UseStencilBuffer ? MaskRenderer.MaskInput.Both : MaskRenderer.MaskInput.Color;
return MaskRenderer.MaskInput.Both;
}
MaskRenderer.MaskInput MaskRenderer.IMaskProvider.Write(Camera camera)
@@ -217,7 +215,7 @@ namespace WaveHarmonic.Crest
_DoneMaskRead = true;
}
return _MaskRead ? (UseLegacyMask || UseStencilBuffer ? MaskRenderer.MaskInput.Both : MaskRenderer.MaskInput.Color) : MaskRenderer.MaskInput.None;
return _MaskRead ? _Water.Surface.Enabled ? MaskRenderer.MaskInput.Both : MaskRenderer.MaskInput.Color : MaskRenderer.MaskInput.None;
}
}
}

View File

@@ -13,6 +13,11 @@ namespace WaveHarmonic.Crest
[System.Serializable]
public sealed partial class UnderwaterRenderer
{
[SerializeField, HideInInspector]
#pragma warning disable 414
int _Version = 0;
#pragma warning restore 414
internal const float k_CullLimitMinimum = 0.000001f;
internal const float k_CullLimitMaximum = 0.01f;
@@ -67,11 +72,10 @@ namespace WaveHarmonic.Crest
[@Heading("Advanced")]
[Tooltip("Rules to exclude cameras from rendering underwater.\n\nThese are exclusion rules, so for all cameras, select Nothing. These rules are applied on top of the Layer rules.")]
[@DecoratedField]
[Tooltip("Whether to execute for all cameras.\n\nIf disabled, then additionally ignore any camera that is not the view camera or our reflection camera. It will require managing culling masks of all cameras.")]
[@GenerateAPI]
[SerializeField]
internal WaterCameraExclusion _CameraExclusions = WaterCameraExclusion.Hidden | WaterCameraExclusion.Reflection;
[@DecoratedField, SerializeField]
bool _AllCameras;
[Tooltip("Copying parameters each frame ensures underwater appearance stays consistent with the water surface.\n\nHas a small overhead so should be disabled if not needed.")]
[@GenerateAPI]
@@ -128,7 +132,7 @@ namespace WaveHarmonic.Crest
internal bool RenderBeforeTransparency => false;
#else
// Legacy mask works except for negative volumes. Not officially supported.
internal bool UseLegacyMask => false;
internal bool UseLegacyMask => _AllCameras;
internal bool RenderBeforeTransparency => true;
#endif
@@ -249,11 +253,6 @@ namespace WaveHarmonic.Crest
_HorizonMaskMaterial = null;
}
internal bool ShouldRender(Camera camera)
{
return ShouldRender(camera, Pass.Effect);
}
internal bool ShouldRender(Camera camera, Pass pass)
{
if (!_Enabled || _Material == null)
@@ -272,7 +271,7 @@ namespace WaveHarmonic.Crest
}
// Skip entire mask pass if possible.
if (pass == Pass.Mask && !_Water.Surface.ShouldRender(camera))
if (pass == Pass.Mask && !_Water.Surface.Enabled)
{
return false;
}
@@ -291,23 +290,29 @@ namespace WaveHarmonic.Crest
}
#endif
if (_Debug._OnlyReflectionCameras && camera.cameraType != CameraType.Reflection)
var isReflectionCamera = camera.cameraType == CameraType.Reflection;
// Mask or culling is not needed for reflections.
if (isReflectionCamera && pass != Pass.Effect)
{
return false;
}
var isPlanarReflectionCamera = camera == _Water.Reflections.ReflectionCamera;
if (_Debug._OnlyReflectionCameras && !isReflectionCamera)
{
return false;
}
// Option to exclude cameras that is not the view camera or our reflection camera.
// Otherwise, filtering depends on the camera's culling mask which is not always
// accessible like with the global "Reflection Probes Camera". But whether those
// cameras triggering camera events is a bug is TBD as it is intermittent.
if (camera != _Water.Reflections.ReflectionCamera && !WaterRenderer.ShouldRender(camera, _CameraExclusions))
if (!_AllCameras && camera != _Water.GetViewer(includeSceneCamera: false) && camera.cameraType != CameraType.SceneView && camera != WaterReflections.CurrentCamera)
{
return false;
}
if (!_Debug._DisableHeightAboveWaterOptimization && !Portaled && _Water.Surface.ShouldRender(camera))
if (!_Debug._DisableHeightAboveWaterOptimization && !Portaled)
{
_Water.UpdatePerCameraHeight(camera);
_ViewerWaterHeight = _Water._ViewerHeightAboveWaterPerCamera;
@@ -340,11 +345,6 @@ namespace WaveHarmonic.Crest
OnBeginCameraRendering(camera);
#if UNITY_EDITOR
if (_VolumeMaterial == null)
{
return;
}
// Populated by this point.
if (_VolumeMaterial.shader != WaterResources.Instance.Shaders._UnderwaterEffect)
{
@@ -352,7 +352,6 @@ namespace WaveHarmonic.Crest
}
#endif
#pragma warning disable format
#if d_UnityURP
if (RenderPipelineHelper.IsUniversal)
{
@@ -372,7 +371,6 @@ namespace WaveHarmonic.Crest
{
OnBeforeLegacyRender(camera);
}
#pragma warning restore format
}
internal void OnBeginCameraRendering(Camera camera)
@@ -382,6 +380,12 @@ namespace WaveHarmonic.Crest
return;
}
// Only one camera supported due to LOD center dependency.
if (!UseLegacyMask && ShouldRender(camera, Pass.Mask) && camera == _Water.Viewer)
{
_Water.Surface.UpdateDisplacedSurfaceData(camera);
}
#if d_UnityHDRP
if (RenderPipelineHelper.IsHighDefinition)
{
@@ -462,13 +466,6 @@ namespace WaveHarmonic.Crest
UpdateEnvironmentalLighting(camera, extinction, _ViewerWaterHeight);
}
// Only relevant to cameras rendering the surface from here.
if (!_Water.Surface.ShouldRender(camera))
{
return;
}
// Redo culling. Culling is per camera. But chunks are shared.
if (Portaled || _ViewerWaterHeight > -5f)
{
RevertCulling();
@@ -510,28 +507,6 @@ namespace WaveHarmonic.Crest
}
}
internal void ExecuteHeightField(Camera camera)
{
if (UseLegacyMask)
{
return;
}
#if d_CrestPortals
if (Portaled && !_Water.Portals.RequiresFullScreenMask)
{
return;
}
#endif
if (!ShouldRender(camera, Pass.Mask))
{
return;
}
_Water.Surface.UpdateDisplacedSurfaceData(camera);
}
void SetEnabled(bool previous, bool current)
{
if (previous == current) return;
@@ -562,21 +537,4 @@ namespace WaveHarmonic.Crest
}
#endif
}
// Obsolete / Migration
partial class UnderwaterRenderer
{
[SerializeField, HideInInspector]
#pragma warning disable 414
int _Version = 0;
#pragma warning restore 414
// No migration as default value for new control is far more effective than this toggle.
[System.Obsolete("Please use Camera Exclusion instead.")]
[Tooltip("Whether to execute for all cameras.\n\nIf disabled, then additionally ignore any camera that is not the view camera or our reflection camera. It will require managing culling masks of all cameras.")]
[@GenerateAPI]
[@DecoratedField, SerializeField]
[HideInInspector]
bool _AllCameras;
}
}

View File

@@ -1,20 +0,0 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
using WaveHarmonic.Crest.Internal;
namespace WaveHarmonic.Crest
{
/// <summary>
/// Marks a camera to render water regardless of exclusions.
/// </summary>
/// <remarks>
/// This is only necessary when using <see cref="WaterCameraExclusion"/>.
/// </remarks>
[AddComponentMenu(Constants.k_MenuPrefixScripts + "Water Camera")]
public sealed class WaterCamera : ManagedBehaviour<WaterRenderer>
{
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 719569e3358694628a8edfc67eb3ab83
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -17,7 +17,19 @@ namespace WaveHarmonic.Crest
internal const string k_ProxyShader = "Hidden/Crest/Editor/WaterProxy";
internal GameObject _ProxyPlane;
private protected override bool CanRunInEditMode => !_ShowWaterProxyPlane;
internal static float EditorTime { get; set; }
internal static float EditorDeltaTime { get; set; }
static int s_EditorFrames = 0;
// Useful for rate limiting processes called outside of RunUpdate like camera events.
static int s_EditorFramesSinceUpdate = 0;
static int EditorFramesSinceUpdate => Application.isPlaying ? 0 : s_EditorFramesSinceUpdate;
internal static bool IsWithinEditorUpdate => EditorFramesSinceUpdate == 0;
internal bool IsSceneViewActive { get; set; }
int _LastFrameSceneCamera;
private protected override void Reset()
@@ -45,9 +57,37 @@ namespace WaveHarmonic.Crest
}
}
static void EditorUpdate()
{
// Do not execute when editor is not active to conserve power and prevent possible leaks.
if (!UnityEditorInternal.InternalEditorUtility.isApplicationActive)
{
return;
}
s_EditorFramesSinceUpdate++;
if (Instance == null) return;
if (!Application.isPlaying)
{
if ((Time.time - EditorTime) >= (1f / Mathf.Clamp(Instance._EditModeFrameRate, 0.01f, 120f)))
{
s_EditorFrames++;
s_EditorFramesSinceUpdate = 0;
EditorDeltaTime = Time.time - EditorTime;
EditorTime = Time.time;
Instance.RunUpdate();
}
}
}
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
static void OnDomainReload()
{
s_EditorFramesSinceUpdate = 0;
AfterRuntimeLoad();
}
@@ -62,10 +102,6 @@ namespace WaveHarmonic.Crest
{
switch (path)
{
case nameof(_MultipleViewpoints):
case nameof(_EditorMultipleViewpoints):
Rebuild();
break;
case nameof(_ExtentsSizeMultiplier):
case nameof(_GeometryDownSampleFactor):
Surface._Rebuild = true;

View File

@@ -229,7 +229,7 @@ namespace WaveHarmonic.Crest
}
// Our reflections do not need them.
if (camera == _Water.Reflections.ReflectionCamera)
if (camera == WaterReflections.CurrentCamera)
{
return;
}

View File

@@ -18,6 +18,11 @@ namespace WaveHarmonic.Crest
public static readonly int s_ScreenSpaceShadowTexture = Shader.PropertyToID("_Crest_ScreenSpaceShadowTexture");
public static readonly int s_TemporaryDepthTexture = Shader.PropertyToID("_Crest_TemporaryDepthTexture");
public static readonly int s_PrimaryLightHasCookie = Shader.PropertyToID("g_Crest_PrimaryLightHasCookie");
public static class Unity
{
public static readonly int s_CameraOpaqueTexture = Shader.PropertyToID("_CameraOpaqueTexture");
}
}
bool _DoneMatrices;
@@ -32,11 +37,7 @@ namespace WaveHarmonic.Crest
? Rendering.BIRP.FrameBufferFormatOverride.HDR
: Rendering.BIRP.FrameBufferFormatOverride.LDR;
#if UNITY_EDITOR
[UnityEditor.InitializeOnLoadMethod]
#else
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
#endif
static void InitializeOnLoad()
{
// Fixes error on first frame.
@@ -98,11 +99,10 @@ namespace WaveHarmonic.Crest
}
}
Shader.SetGlobalTexture(Crest.ShaderIDs.Unity.s_CameraOpaqueTexture, Texture2D.grayTexture);
Shader.SetGlobalTexture(ShaderIDs.Unity.s_CameraOpaqueTexture, Texture2D.grayTexture);
}
// Needs to be separate, as it needs to run after the water volume pass.
[System.Diagnostics.Conditional(Constants.Symbols.k_Refraction)]
void OnLegacyCopyPass(Camera camera)
{
if (!ShouldRender(camera, Surface.Layer))
@@ -110,20 +110,18 @@ namespace WaveHarmonic.Crest
return;
}
var material = Surface.Material;
if (material == null)
if (Surface.Material == null)
{
return;
}
if (!SurfaceRenderer.IsTransparent(material))
if (!SurfaceRenderer.IsTransparent(Surface.Material))
{
return;
}
// Our reflections do not need them.
if (camera == _Reflections.ReflectionCamera)
if (camera == WaterReflections.CurrentCamera)
{
return;
}
@@ -178,7 +176,7 @@ namespace WaveHarmonic.Crest
partial class WaterRenderer
{
bool _DoneCameraOpaqueTexture;
RTHandle _CameraOpaqueTexture;
RenderTexture _CameraOpaqueTexture;
CommandBuffer _CameraOpaqueTextureCommands;
internal void UpdateCameraOpaqueTexture(Camera camera, CommandBuffer commands)
@@ -194,7 +192,6 @@ namespace WaveHarmonic.Crest
commands.EndSample(k_DrawCopyColor);
}
[System.Diagnostics.Conditional(Constants.Symbols.k_Refraction)]
internal void OnBeginCameraOpaqueTexture(Camera camera)
{
if (_DoneCameraOpaqueTexture)
@@ -202,20 +199,28 @@ namespace WaveHarmonic.Crest
return;
}
camera.depthTextureMode |= DepthTextureMode.Depth;
var descriptor = Rendering.BIRP.GetCameraTargetDescriptor(camera, FrameBufferFormatOverride);
// Required, or depth/stencil format will be chosen.
descriptor.depthStencilFormat = UnityEngine.Experimental.Rendering.GraphicsFormat.None;
// Occurred in a build and caused a black screen.
if (descriptor.width <= 0)
{
return;
}
RenderPipelineCompatibilityHelper.ReAllocateIfNeeded(ref _CameraOpaqueTexture, descriptor);
if (_CameraOpaqueTexture == null)
{
_CameraOpaqueTexture = new(descriptor)
{
name = "_CameraOpaqueTexture",
};
}
else
{
_CameraOpaqueTexture.Release();
_CameraOpaqueTexture.descriptor = descriptor;
}
_CameraOpaqueTexture.Create();
_CameraOpaqueTextureCommands ??= new()
{

View File

@@ -9,7 +9,7 @@ namespace WaveHarmonic.Crest
{
[SerializeField, HideInInspector]
#pragma warning disable 414
int _Version = 2;
int _Version = 1;
#pragma warning restore 414
/// <inheritdoc/>
@@ -33,15 +33,6 @@ namespace WaveHarmonic.Crest
_Version = 1;
}
if (_Version < 2)
{
#pragma warning disable CS0618 // Type or member is obsolete
AnimatedWavesLod.QuerySource = (LodQuerySource)Mathf.Max(0, (int)AnimatedWavesLod.CollisionSource - 1);
#pragma warning restore CS0618 // Type or member is obsolete
_Version = 2;
}
}
/// <inheritdoc/>

View File

@@ -29,57 +29,6 @@ namespace WaveHarmonic.Crest
BeforeTransparent,
}
/// <summary>
/// Rules to exclude cameras.
/// </summary>
[@GenerateDoc]
[System.Flags]
public enum WaterCameraExclusion
{
/// <inheritdoc cref="Generated.WaterCameraExclusion.Nothing" />
[Tooltip("No exclusion rules applied.")]
Nothing = 0,
/// <inheritdoc cref="Generated.WaterCameraExclusion.Hidden" />
[Tooltip("Exclude hidden cameras.\n\nDoes not affect reflection cameras, as they are typically always hidden. Use the Reflection flag for them.")]
Hidden = 1 << 1,
/// <inheritdoc cref="Generated.WaterCameraExclusion.Reflection" />
[Tooltip("Exclude reflection cameras.")]
Reflection = 1 << 2,
/// <inheritdoc cref="Generated.WaterCameraExclusion.NonMainCamera" />
[Tooltip("Exclude cameras not tagged as MainCamera.")]
NonMainCamera = 1 << 3,
/// <inheritdoc cref="Generated.WaterCameraExclusion.Everything" />
[Tooltip("Apply all exclusion rules.")]
Everything = ~0,
}
/// <summary>
/// The background processing mode for when cameras do not render.
/// </summary>
[@GenerateDoc]
public enum WaterDataBackgroundMode
{
/// <inheritdoc cref="Generated.WaterDataBackgroundMode.Always" />
[Tooltip("Always progress simulations in the background when camera does not render.")]
Always,
/// <inheritdoc cref="Generated.WaterDataBackgroundMode.Inactive" />
[Tooltip("Progress simulations in the background when camera is inactive (ie !isActiveAndEnabled).")]
Inactive,
/// <inheritdoc cref="Generated.WaterDataBackgroundMode.Disabled" />
[Tooltip("Progress simulations in the background when camera is disabled (ie !enabled).")]
Disabled,
/// <inheritdoc cref="Generated.WaterDataBackgroundMode.Never" />
[Tooltip("Never progress simulations in the background.")]
Never,
}
partial class WaterRenderer
{
internal const float k_MaximumWindSpeedKPH = 150f;
@@ -279,36 +228,13 @@ namespace WaveHarmonic.Crest
[SerializeField]
internal float _ExtentsSizeMultiplier = 100f;
[@Heading("Center of Detail", alwaysEnabled: true)]
[Tooltip("Whether to support multiple center-of-detail (per camera).")]
#if !UNITY_6000_0_OR_NEWER
[@Predicated(RenderPipeline.HighDefinition, inverted: true, hide: true)]
#endif
[@DecoratedField]
[SerializeField]
bool _MultipleViewpoints;
[@Heading("Center of Detail")]
[Tooltip("The viewpoint which drives the water detail - the center of the LOD system.\n\nSetting this is optional. Defaults to the camera.")]
[@Predicated(nameof(_MultipleViewpoints), inverted: true)]
[@GenerateAPI(Getter.Custom)]
[@DecoratedField, SerializeField]
Transform _Viewpoint;
[Tooltip("Rules to exclude cameras from being a center-of-detail.\n\nThese are exclusion rules, so for all cameras, select Nothing.")]
[@Predicated(nameof(_MultipleViewpoints))]
[@DecoratedField]
[@GenerateAPI]
[SerializeField]
WaterCameraExclusion _CameraExclusions = WaterCameraExclusion.Everything;
[Tooltip("The background rendering mode when a camera does not render.\n\nWhen switching between multiple cameras, simulations will not progress for cameras which do not render for that frame. This setting tells the system when it should continue to progress the simulations.")]
[@Predicated(nameof(_MultipleViewpoints))]
[@GenerateAPI]
[@DecoratedField]
[SerializeField]
internal WaterDataBackgroundMode _DataBackgroundMode = WaterDataBackgroundMode.Never;
[@Label("Displacement Correction")]
[Tooltip("Keep the center of detail from drifting from the viewpoint.\n\nLarge horizontal displacement can displace the center of detail. This uses queries to keep the center of detail aligned.")]
[@GenerateAPI]
@@ -445,20 +371,17 @@ namespace WaveHarmonic.Crest
[@DecoratedField, SerializeField]
internal bool _ShowWaterProxyPlane;
[Tooltip("Sets the update rate of the water system when in edit mode.\n\nCan be reduced to save power.")]
[@Range(0f, 120f, Range.Clamp.Minimum)]
[SerializeField]
float _EditModeFrameRate = 30f;
[Tooltip("Move water with Scene view camera if Scene window is focused.")]
[@Predicated(nameof(_ShowWaterProxyPlane), true)]
[@DecoratedField, SerializeField]
internal bool _FollowSceneCamera = true;
[Tooltip("Each scene view will have its own viewpoint.")]
#if !UNITY_6000_0_OR_NEWER
[@Predicated(RenderPipeline.HighDefinition, inverted: true, hide: true)]
#endif
[@DecoratedField]
[SerializeField]
bool _EditorMultipleViewpoints = true;
[Tooltip("Whether height queries are enabled in edit mode.\n\nQueries force enable \"Always Refresh\" scene view option.")]
[Tooltip("Whether height queries are enabled in edit mode.")]
[@DecoratedField, SerializeField]
internal bool _HeightQueries = true;
#pragma warning restore 414
@@ -474,15 +397,6 @@ namespace WaveHarmonic.Crest
{
[@Space(10)]
#if !CREST_DEBUG
[HideInInspector]
#endif
[Tooltip("Simulates cameras not rendering.")]
[@DecoratedField, SerializeField]
public bool _SimulatePaused;
[@Space(10)]
[Tooltip("Attach debug GUI that adds some controls and allows to visualize the water data.")]
[@DecoratedField, SerializeField]
public bool _AttachDebugGUI;
@@ -515,39 +429,24 @@ namespace WaveHarmonic.Crest
#if !CREST_DEBUG
[HideInInspector]
#endif
[Tooltip("Log scale changes to the console.")]
[Tooltip("Water will not move with viewpoint.")]
[@DecoratedField, SerializeField]
public bool _LogScaleChange;
#if !CREST_DEBUG
[HideInInspector]
#endif
[Tooltip("Pause the editor when the scale changes.")]
[Tooltip("Water will not move with viewpoint.")]
[@DecoratedField, SerializeField]
public bool _PauseOnScaleChange;
#if !CREST_DEBUG
[HideInInspector]
#endif
[Tooltip("Ignore waves when calculation scale.")]
[Tooltip("Water will not move with viewpoint.")]
[@DecoratedField, SerializeField]
public bool _IgnoreWavesForScaleChange;
#if !CREST_DEBUG
[HideInInspector]
#endif
[@InlineToggle]
[SerializeField]
public bool _OverrideScale;
#if !CREST_DEBUG
[HideInInspector]
#endif
[@Predicated(nameof(_OverrideScale))]
[@Range(1, 256, Range.Clamp.Minimum, power: true, step: 2)]
[SerializeField]
public int _ScaleOverride;
[@Heading("Server")]
[Tooltip("Emulate running on a client without a GPU. Equivalent to running standalone with -nographics argument.")]

View File

@@ -3,6 +3,10 @@
#if d_UnityURP
#if !UNITY_6000_3_OR_NEWER
#define URP_COMPATIBILITY_MODE
#endif
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
@@ -75,7 +79,6 @@ namespace WaveHarmonic.Crest
Instance = new(water);
}
[System.Diagnostics.Conditional(Constants.Symbols.k_Refraction)]
internal void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera)
{
var water = _Water;
@@ -86,7 +89,7 @@ namespace WaveHarmonic.Crest
}
// Our reflections do not need them.
if (camera == _Water.Reflections.ReflectionCamera)
if (camera == WaterReflections.CurrentCamera)
{
return;
}
@@ -97,15 +100,13 @@ namespace WaveHarmonic.Crest
return;
}
var material = water.Surface.Material;
if (material == null)
if (water.Surface.Material == null)
{
return;
}
// TODO: Could also check RenderType. Which is better?
if (!SurfaceRenderer.IsTransparent(material))
if (!SurfaceRenderer.IsTransparent(water.Surface.Material))
{
return;
}
@@ -118,18 +119,14 @@ namespace WaveHarmonic.Crest
// Needed for OnCameraSetup.
renderer.EnqueuePass(this);
#if URP_COMPATIBILITY_MODE
#if UNITY_6000_0_OR_NEWER
#if !UNITY_6000_4_OR_NEWER
// Copy depth pass does not support RG directly.
if (GraphicsSettings.GetRenderPipelineSettings<RenderGraphSettings>().enableRenderCompatibilityMode)
#endif
#endif
{
if (water.WriteToColorTexture) renderer.EnqueuePass(_CopyColorPass);
if (water.WriteToDepthTexture) renderer.EnqueuePass(_CopyDepthPass);
}
#endif
}
#if UNITY_6000_0_OR_NEWER
@@ -146,16 +143,11 @@ namespace WaveHarmonic.Crest
if (_Water.WriteToDepthTexture)
{
// Whether we a writing to color or depth format.
#if !UNITY_6000_5_OR_NEWER
_CopyDepthPass.CopyToDepth = descriptor.colorFormat == UnityEngine.Experimental.Rendering.GraphicsFormat.None;
#endif
_CopyDepthPass.Render(graph, frame, resources.cameraDepthTexture, resources.cameraDepth);
}
}
#endif
#if URP_COMPATIBILITY_MODE
#if UNITY_6000_0_OR_NEWER
[System.Obsolete]
#endif
public override void OnCameraSetup(CommandBuffer buffer, ref RenderingData data)
@@ -174,6 +166,7 @@ namespace WaveHarmonic.Crest
queue.Remove(_CopyColorPass);
}
#if URP_COMPATIBILITY_MODE
// Also check internal RT because it can be null on Vulkan for some reason.
if (renderer.cameraDepthTargetHandle?.rt != null && renderer.m_DepthTexture?.rt != null)
{
@@ -183,6 +176,7 @@ namespace WaveHarmonic.Crest
_CopyDepthPass.Setup(renderer.cameraDepthTargetHandle, renderer.m_DepthTexture);
}
else
#endif
{
var queue = s_ActiveRenderPassQueue.GetValue(renderer) as List<ScriptableRenderPass>;
queue.Remove(_CopyDepthPass);
@@ -196,7 +190,6 @@ namespace WaveHarmonic.Crest
{
// Blank
}
#endif // URP_COMPATIBILITY_MODE
}
}
}

View File

@@ -1,120 +0,0 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
namespace WaveHarmonic.Crest
{
partial class WaterResources
{
public ComputeLibrary _ComputeLibrary;
public sealed class ComputeLibrary
{
public BlitCompute _BlitCompute;
public BlurCompute _BlurCompute;
public ClearCompute _ClearCompute;
public ShapeCombineCompute _ShapeCombineCompute;
public ComputeLibrary(WaterResources resources)
{
_BlitCompute = new(resources.Compute._Blit);
_BlurCompute = new(resources.Compute._Blur);
_ClearCompute = new(resources.Compute._Clear);
_ShapeCombineCompute = new(resources.Compute._ShapeCombine);
}
}
public abstract class UtilityCompute
{
public readonly ComputeShader _Shader;
public readonly LocalKeyword _Float1Keyword;
public readonly LocalKeyword _Float2Keyword;
public readonly LocalKeyword _Float3Keyword;
public readonly LocalKeyword _Float4Keyword;
public UtilityCompute(ComputeShader shader)
{
_Shader = shader;
var keywords = shader.keywordSpace;
_Float1Keyword = keywords.FindKeyword("d_Float1");
_Float2Keyword = keywords.FindKeyword("d_Float2");
_Float3Keyword = keywords.FindKeyword("d_Float3");
_Float4Keyword = keywords.FindKeyword("d_Float4");
}
public void SetVariantForFormat<T>(T wrapper, GraphicsFormat format) where T : IPropertyWrapperVariants
{
var count = GraphicsFormatUtility.GetComponentCount(format);
wrapper.SetKeyword(_Float1Keyword, count == 1);
wrapper.SetKeyword(_Float2Keyword, count == 2);
wrapper.SetKeyword(_Float3Keyword, count == 3);
wrapper.SetKeyword(_Float4Keyword, count == 4);
}
}
public sealed class ClearCompute : UtilityCompute
{
public readonly int _KernelClearTarget;
public readonly int _KernelClearTargetBoundaryX;
public readonly int _KernelClearTargetBoundaryY;
public ClearCompute(ComputeShader shader) : base(shader)
{
// Using FindKernel can fail if upgrading Crest, and is quite tricky to get right.
_KernelClearTarget = 0;
_KernelClearTargetBoundaryX = 1;
_KernelClearTargetBoundaryY = 2;
}
}
public sealed class BlitCompute : UtilityCompute
{
public readonly int _KernelAdd;
public BlitCompute(ComputeShader shader) : base(shader)
{
_KernelAdd = 0;
}
}
public sealed class BlurCompute : UtilityCompute
{
public readonly int _KernelHorizontal;
public readonly int _KernelVertical;
public BlurCompute(ComputeShader shader) : base(shader)
{
_KernelHorizontal = 0;
_KernelVertical = 1;
}
}
public sealed class ShapeCombineCompute
{
public readonly ComputeShader _Shader;
public readonly LocalKeyword _CombineKeyword;
public readonly LocalKeyword _DynamicWavesKeyword;
public readonly LocalKeyword _FlowKeyword;
public readonly int _CopyAnimatedWavesKernel;
public readonly int _CombineAnimatedWavesKernel;
public readonly int _CombineDynamicWavesKernel;
public ShapeCombineCompute(ComputeShader shader)
{
_Shader = shader;
var keywords = shader.keywordSpace;
_CombineKeyword = keywords.FindKeyword("d_Combine");
_DynamicWavesKeyword = keywords.FindKeyword("d_DynamicWaves");
_FlowKeyword = keywords.FindKeyword("d_Flow");
_CombineAnimatedWavesKernel = 0;
_CopyAnimatedWavesKernel = 1;
_CombineDynamicWavesKernel = 2;
}
}
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: dcf1e593838654a4193ec161a5f37702
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -13,7 +13,7 @@ namespace WaveHarmonic.Crest
[ExecuteAlways, Utility.FilePath("Packages/com.waveharmonic.crest/Runtime/Settings/Resources.asset")]
[@HelpURL("Manual/Scripting.html#resources")]
sealed partial class WaterResources : Utility.ScriptableSingleton<WaterResources>
sealed class WaterResources : Utility.ScriptableSingleton<WaterResources>
{
[Serializable]
public sealed class ShaderResources
@@ -88,8 +88,6 @@ namespace WaveHarmonic.Crest
public ComputeShader _Whirlpool;
public ComputeShader _Clear;
public ComputeShader _Blit;
public ComputeShader _Blur;
}
#pragma warning disable IDE0032 // Use auto property
@@ -177,14 +175,8 @@ namespace WaveHarmonic.Crest
#if !CREST_DEBUG
hideFlags = HideFlags.NotEditable;
#endif
Initialize();
}
void Initialize()
{
Keywords.Initialize(this);
_ComputeLibrary = new(this);
AfterEnabled?.Invoke();
}
@@ -207,7 +199,7 @@ namespace WaveHarmonic.Crest
if (path.StartsWith("Packages/com.waveharmonic.crest") && path.EndsWith(".compute"))
{
// Unity loses these if the compute shader is recompiled.
Instance.Initialize();
Instance.Keywords.Initialize(Instance);
}
}
}

View File

@@ -56,11 +56,6 @@
"expression": "",
"define": "d_UnityURP"
},
{
"name": "com.unity.render-pipelines.universal",
"expression": "(,17.3.0)",
"define": "URP_COMPATIBILITY_MODE"
},
{
"name": "com.waveharmonic.crest.paint",
"expression": "",

View File

@@ -42,9 +42,8 @@ namespace WaveHarmonic.Crest
float _GenerationTime = -1f;
// WebGPU claims it does but does not.
static readonly bool s_SupportsRandomWriteRGFloat =
!Helpers.IsWebGPU && SystemInfo.SupportsRandomWriteOnRenderTextureFormat(RenderTextureFormat.RGFloat);
SystemInfo.SupportsRandomWriteOnRenderTextureFormat(RenderTextureFormat.RGFloat);
public static class ShaderIDs
{
@@ -208,7 +207,7 @@ namespace WaveHarmonic.Crest
rtd.enableRandomWrite = true;
rtd.depthBufferBits = 0;
rtd.volumeDepth = k_CascadeCount;
rtd.graphicsFormat = GraphicsFormat.R32G32B32A32_SFloat;
rtd.colorFormat = RenderTextureFormat.ARGBFloat;
rtd.msaaSamples = 1;
Helpers.SafeCreateRenderTexture(ref _SpectrumInitial, rtd);
@@ -216,7 +215,7 @@ namespace WaveHarmonic.Crest
_SpectrumInitial.Create();
// Raw wave data buffer
WaveBuffers = new(resolution, resolution, 0, Helpers.GetCompatibleTextureFormat(GraphicsFormat.R16G16B16A16_SFloat, randomWrite: true))
WaveBuffers = new(resolution, resolution, 0, GraphicsFormat.R16G16B16A16_SFloat)
{
wrapMode = TextureWrapMode.Repeat,
antiAliasing = 1,
@@ -264,7 +263,7 @@ namespace WaveHarmonic.Crest
offset <<= 1;
}
var texture = new Texture2D(resolution, Mathf.RoundToInt(Mathf.Log(resolution, 2)), TextureFormat.RGFloat, false, true);
var texture = new Texture2D(resolution, Mathf.RoundToInt(Mathf.Log(resolution, 2)), TextureFormat.RGBAFloat, false, true);
texture.SetPixels(colors);
texture.Apply();
s_ButterflyTextures.Add(resolution, texture);
@@ -301,7 +300,7 @@ namespace WaveHarmonic.Crest
if (s_SupportsRandomWriteRGFloat)
{
descriptor.graphicsFormat = GraphicsFormat.R32G32_SFloat;
descriptor.colorFormat = RenderTextureFormat.RGFloat;
}
// No need to clear as overwritten.

View File

@@ -67,8 +67,6 @@ namespace WaveHarmonic.Crest
internal SpectrumModel _Model;
#pragma warning restore 414
internal float[] _PowerLinearScales = new float[k_NumberOfOctaves];
internal enum SpectrumModel
{
None,
@@ -223,7 +221,6 @@ namespace WaveHarmonic.Crest
// we store power on logarithmic scale. this does not include 0, we represent 0 as min value
pow = Mathf.Max(pow, Mathf.Pow(10f, s_MinimumPowerLog));
_PowerLinearScales[octave] = pow;
_PowerLogarithmicScales[octave] = Mathf.Log10(pow);
}
}
@@ -282,7 +279,6 @@ namespace WaveHarmonic.Crest
{
var power = _PowerDisabled[i] ? 0f : Mathf.Pow(10f, _PowerLogarithmicScales[i]);
power *= _Multiplier * _Multiplier;
_PowerLinearScales[i] = power;
_ScratchData[i] = power * Color.white;
}