还原水插件

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

View File

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