还原水插件
This commit is contained in:
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user