升级6.4.升级水,升级天气

This commit is contained in:
2026-04-05 00:26:54 +08:00
parent 63bc9b5536
commit 5f7cbfb713
635 changed files with 34718 additions and 22567 deletions

View File

@@ -1,19 +1,16 @@
// 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_2023_2_OR_NEWER
using GraphicsFormatUsage = UnityEngine.Experimental.Rendering.FormatUsage;
#endif
namespace WaveHarmonic.Crest
{
using Inputs = SortedList<int, ILodInput>;
using Inputs = Utility.SortedList<int, ILodInput>;
/// <summary>
/// Texture format preset.
@@ -42,7 +39,7 @@ namespace WaveHarmonic.Crest
/// Base class for data/behaviours created on each LOD.
/// </summary>
[System.Serializable]
public abstract partial class Lod
public abstract partial class Lod : Versioned
{
[Tooltip("Whether the simulation is enabled.")]
[@GenerateAPI(Getter.Custom, Setter.Custom)]
@@ -50,14 +47,13 @@ namespace WaveHarmonic.Crest
internal bool _Enabled;
[Tooltip("Whether to override the resolution.\n\nIf not enabled, then the simulation will use the resolution defined on the Water Renderer.")]
[@Predicated(typeof(AnimatedWavesLod), inverted: true, hide: true)]
[@Hide(typeof(AnimatedWavesLod))]
[@GenerateAPI(Setter.Dirty)]
[@InlineToggle(fix: true), SerializeField]
[@InlineToggle, SerializeField]
internal bool _OverrideResolution = true;
[Tooltip("The resolution of the simulation data.\n\nSet higher for sharper results at the cost of higher memory usage.")]
[@Predicated(typeof(AnimatedWavesLod), inverted: true, hide: true)]
[@Predicated(nameof(_OverrideResolution), hide: true)]
[@Show(nameof(_OverrideResolution))]
[@ShowComputedProperty(nameof(Resolution))]
[@Delayed]
[@GenerateAPI(Getter.Custom, Setter.Dirty)]
@@ -72,25 +68,45 @@ namespace WaveHarmonic.Crest
[Tooltip("The render texture format used for this simulation data.\n\nIt will be overriden if the format is incompatible with the platform.")]
[@ShowComputedProperty(nameof(RequestedTextureFormat))]
[@Predicated(nameof(_TextureFormatMode), inverted: true, nameof(LodTextureFormatMode.Manual), hide: true)]
[@Show(nameof(_TextureFormatMode), nameof(LodTextureFormatMode.Manual))]
[@GenerateAPI(Setter.Dirty)]
[@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.")]
[@Hide(typeof(AnimatedWavesLod))]
[@Hide(typeof(DynamicWavesLod))]
[@GenerateAPI(Setter.Dirty)]
[@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.")]
[@Hide(typeof(AnimatedWavesLod))]
[@Hide(typeof(DynamicWavesLod))]
[@Enable(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;
internal const string k_BlurField = nameof(_Blur);
internal const string k_TextureFormatModeField = nameof(_TextureFormatMode);
internal static class ShaderIDs
{
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.
@@ -136,17 +152,10 @@ namespace WaveHarmonic.Crest
}
}
static readonly GraphicsFormatUsage s_GraphicsFormatUsage =
// Ensures a non compressed format is returned.
GraphicsFormatUsage.LoadStore |
// All these textures are sampled at some point.
GraphicsFormatUsage.Sample |
// Always use linear filtering.
GraphicsFormatUsage.Linear;
private protected 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);
internal RenderTexture DataTexture { get; private protected set; }
private protected Matrix4x4[] _ViewMatrices = new Matrix4x4[k_MaximumSlices];
private protected Cascade[] _Cascades = new Cascade[k_MaximumSlices];
@@ -161,7 +170,7 @@ namespace WaveHarmonic.Crest
internal WaterRenderer _Water;
internal WaterRenderer Water => _Water;
private protected int _TargetsToClear;
private protected bool _TargetsToClear;
private protected readonly int _TextureShaderID;
private protected readonly int _TextureSourceShaderID;
@@ -184,8 +193,10 @@ namespace WaveHarmonic.Crest
_TextureName = $"_Crest_{ID}Lod";
}
private protected RenderTexture CreateLodDataTextures()
private protected RenderTexture CreateLodDataTextures(string postfix = null)
{
postfix ??= string.Empty;
RenderTexture result = new(Resolution, Resolution, 0, CompatibleTextureFormat)
{
wrapMode = TextureWrapMode.Clamp,
@@ -193,7 +204,7 @@ namespace WaveHarmonic.Crest
filterMode = FilterMode.Bilinear,
anisoLevel = 0,
useMipMap = false,
name = _TextureName,
name = _TextureName + postfix,
dimension = TextureDimension.Tex2DArray,
volumeDepth = Slices,
enableRandomWrite = NeedToReadWriteTextureData
@@ -202,7 +213,7 @@ namespace WaveHarmonic.Crest
return result;
}
private protected void FlipBuffers()
private protected void FlipBuffers(CommandBuffer commands)
{
if (_ReAllocateTexture)
{
@@ -214,11 +225,10 @@ namespace WaveHarmonic.Crest
if (!UnityEditor.EditorApplication.isPaused || Time.deltaTime > 0)
#endif
{
_Targets.Flip();
_SamplingParameters.Flip();
}
UpdateSamplingParameters();
UpdateSamplingParameters(commands);
}
private protected void Clear(RenderTexture target)
@@ -226,29 +236,36 @@ 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();
FlipBuffers(buffer);
buffer.BeginSample(ID);
if (_TargetsToClear > 0 || AlwaysClear)
if (_TargetsToClear || AlwaysClear)
{
// IMPORTANT:
// We need both clears otherwise problems on PS5.
// With only native clear, flow would clear to non black (ie constant flow).
// With only custom clear, absorption/scattering inputs were not clearing.
CoreUtils.SetRenderTarget(buffer, DataTexture, ClearFlag.Color, ClearColor);
_TargetsToClear--;
// Custom clear because clear not working.
if (Helpers.RequiresCustomClear && 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 = false;
}
if (Inputs.Count > 0)
@@ -256,9 +273,11 @@ namespace WaveHarmonic.Crest
SubmitDraws(buffer, Inputs, DataTexture);
// Ensure all targets clear when there are no inputs.
_TargetsToClear = _Targets.Size;
_TargetsToClear = true;
}
TryBlur(buffer);
if (RequiresClearBorder)
{
ClearBorder(buffer);
@@ -354,14 +373,18 @@ namespace WaveHarmonic.Crest
{
var size = Resolution / 8;
var wrapper = new PropertyWrapperCompute(buffer, WaterResources.Instance.Compute._Clear, 1);
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);
wrapper.SetTexture(Crest.ShaderIDs.s_Target, DataTexture);
wrapper.SetVector(Crest.ShaderIDs.s_ClearColor, ClearColor);
wrapper.SetInteger(Crest.ShaderIDs.s_Resolution, Resolution);
wrapper.SetInteger(Crest.ShaderIDs.s_TargetSlice, Slices - 1);
wrapper.Dispatch(size, 1, 1);
wrapper = new PropertyWrapperCompute(buffer, WaterResources.Instance.Compute._Clear, 2);
wrapper = new(buffer, compute._Shader, compute._KernelClearTargetBoundaryY);
wrapper.SetTexture(Crest.ShaderIDs.s_Target, DataTexture);
wrapper.SetVector(Crest.ShaderIDs.s_ClearColor, ClearColor);
wrapper.SetInteger(Crest.ShaderIDs.s_Resolution, Resolution);
@@ -369,7 +392,7 @@ namespace WaveHarmonic.Crest
wrapper.Dispatch(1, size, 1);
}
void UpdateSamplingParameters(bool initialize = false)
void UpdateSamplingParameters(CommandBuffer commands, bool initialize = false)
{
var position = _Water.Position;
var resolution = _Enabled ? Resolution : TextureArrayHelpers.k_SmallTextureSize;
@@ -380,7 +403,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));
@@ -392,11 +415,20 @@ namespace WaveHarmonic.Crest
_ViewMatrices[slice] = WaterRenderer.CalculateViewMatrixFromSnappedPositionRHS(snapped);
}
if (initialize)
if (!initialize)
{
Shader.SetGlobalVector(_SamplingParametersShaderID, new(levels, resolution, 1f / resolution, 0));
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.SetGlobalVectorArray(_SamplingParametersCascadeShaderID, parameters);
if (BufferCount > 1)
@@ -468,7 +500,7 @@ namespace WaveHarmonic.Crest
// The smallest wavelengths should repeat no more than twice across the smaller
// spatial length. Unless we're in the last LOD - then this is the best we can do.
var minimumWavelength = _Water.MaximumWavelength(index) / 2f;
var minimumWavelength = _Water.MaximumWavelength(index, Resolution) / 2f;
if (minimumWavelength < minimumSpatialLength / 2f && index < count - 1)
{
continue;
@@ -480,6 +512,48 @@ 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);
// Applies to both.
compute.SetVariantForFormat(horizontal, rt.graphicsFormat);
horizontal.SetInteger(Crest.ShaderIDs.s_Resolution, rt.width);
horizontal.SetTexture(Crest.ShaderIDs.s_Source, rt);
horizontal.SetTexture(Crest.ShaderIDs.s_Target, temporary);
vertical.SetTexture(Crest.ShaderIDs.s_Source, temporary);
vertical.SetTexture(Crest.ShaderIDs.s_Target, rt);
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.ReleaseTemporaryRT(temporary);
}
/// <summary>
/// Bind data needed to load or compute from this simulation.
/// </summary>
@@ -500,7 +574,7 @@ namespace WaveHarmonic.Crest
// Validate textures.
{
// Find a compatible texture format.
CompatibleTextureFormat = Helpers.GetCompatibleTextureFormat(RequestedTextureFormat, s_GraphicsFormatUsage, Name, NeedToReadWriteTextureData);
CompatibleTextureFormat = Helpers.GetCompatibleTextureFormat(RequestedTextureFormat, Helpers.s_DataGraphicsFormatUsage, Name, NeedToReadWriteTextureData);
if (CompatibleTextureFormat == GraphicsFormat.None)
{
@@ -523,20 +597,15 @@ namespace WaveHarmonic.Crest
// Bind/unbind data texture for all shaders.
Shader.SetGlobalTexture(_TextureShaderID, enable && Enabled ? DataTexture : NullTexture);
if (BufferCount > 1)
{
Shader.SetGlobalTexture(_TextureSourceShaderID, enable && Enabled ? GetDataTexture(1) : NullTexture);
}
if (_SamplingParameters == null || _SamplingParameters.Size != BufferCount)
{
_SamplingParameters = new(BufferCount, () => new Vector4[k_MaximumSlices]);
}
// For safety. Disable to see if we are sampling outside of LOD chain.
_SamplingParameters.RunLambda(x => System.Array.Fill(x, Vector4.zero));
_SamplingParameters.RunLambda(x => System.Array.Fill(x, new(0, 0, 1, 0)));
UpdateSamplingParameters(initialize: true);
UpdateSamplingParameters(null, initialize: true);
}
internal virtual void Enable()
@@ -553,11 +622,10 @@ namespace WaveHarmonic.Crest
internal virtual void Destroy()
{
// Release resources and destroy object to avoid reference leak.
_Targets?.RunLambda(x =>
{
if (x != null) x.Release();
Helpers.Destroy(x);
});
if (DataTexture != null) DataTexture.Release();
Helpers.Destroy(DataTexture);
_AdditionalCameraData.Clear();
}
internal virtual void AfterExecute()
@@ -567,10 +635,11 @@ namespace WaveHarmonic.Crest
private protected virtual void Allocate()
{
_Targets = new(BufferCount, CreateLodDataTextures);
_Targets.RunLambda(Clear);
// Use per-camera data.
DataTexture = CreateLodDataTextures();
Clear(DataTexture);
// Bind globally once here on init, which will bind to all graphics shaders (not compute)
// Bind globally once here on init, which will bind to all shaders.
Shader.SetGlobalTexture(_TextureShaderID, DataTexture);
_ReAllocateTexture = false;
@@ -600,23 +669,21 @@ namespace WaveHarmonic.Crest
int GetResolution() => _OverrideResolution || Water == null ? _Resolution : Water.LodResolution;
private protected void ReAllocate()
private protected virtual void ReAllocate()
{
if (!Enabled) return;
CompatibleTextureFormat = Helpers.GetCompatibleTextureFormat(RequestedTextureFormat, s_GraphicsFormatUsage, Name, NeedToReadWriteTextureData);
var descriptor = _Targets.Current.descriptor;
CompatibleTextureFormat = Helpers.GetCompatibleTextureFormat(RequestedTextureFormat, Helpers.s_DataGraphicsFormatUsage, Name, NeedToReadWriteTextureData);
var descriptor = DataTexture.descriptor;
descriptor.height = descriptor.width = Resolution;
descriptor.graphicsFormat = CompatibleTextureFormat;
_Targets.RunLambda(texture =>
{
texture.Release();
texture.descriptor = descriptor;
texture.Create();
});
descriptor.enableRandomWrite = NeedToReadWriteTextureData;
DataTexture.Release();
DataTexture.descriptor = descriptor;
DataTexture.Create();
_ReAllocateTexture = false;
UpdateSamplingParameters(initialize: true);
UpdateSamplingParameters(null, initialize: true);
}
#if UNITY_EDITOR
@@ -628,6 +695,7 @@ namespace WaveHarmonic.Crest
case nameof(_Enabled):
SetEnabled((bool)previousValue, _Enabled);
break;
case nameof(_Blur):
case nameof(_Resolution):
case nameof(_OverrideResolution):
case nameof(_TextureFormat):
@@ -639,6 +707,45 @@ namespace WaveHarmonic.Crest
#endif
}
partial class Lod
{
readonly Dictionary<Camera, BufferedData<Vector4[]>> _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 (!Persistent)
{
return;
}
if (!_AdditionalCameraData.ContainsKey(camera))
{
_SamplingParameters = new(BufferCount, () => new Vector4[k_MaximumSlices]);
_AdditionalCameraData.Add(camera, _SamplingParameters);
}
else
{
_SamplingParameters = _AdditionalCameraData[camera];
}
}
internal virtual void StoreCameraData(Camera camera)
{
}
internal virtual void RemoveCameraData(Camera camera)
{
if (_AdditionalCameraData.ContainsKey(camera))
{
_AdditionalCameraData.Remove(camera);
}
}
}
// API
partial class Lod
{
@@ -657,29 +764,78 @@ namespace WaveHarmonic.Crest
}
}
interface IQueryableLod<out T> where T : IQueryProvider
{
string Name { get; }
bool Enabled { get; }
WaterRenderer Water { get; }
int MaximumQueryCount { get; }
float Texel { 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 class Lod<T> : Lod where T : IQueryProvider
public abstract partial class Lod<T> : Lod, IQueryableLod<T> 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.")]
[@Show(nameof(_QuerySource), 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; }
private protected abstract T CreateProvider(bool enable);
internal override void SetGlobals(bool enable)
WaterRenderer IQueryableLod<T>.Water => Water;
string IQueryableLod<T>.Name => Name;
float IQueryableLod<T>.Texel => _Cascades[0]._Texel;
private protected abstract T CreateProvider(bool onEnable);
internal override void SetGlobals(bool onEnable)
{
base.SetGlobals(enable);
base.SetGlobals(onEnable);
// We should always have a provider (null provider if disabled).
InitializeProvider(enable);
InitializeProvider(onEnable);
}
private protected void InitializeProvider(bool enable)
private protected void InitializeProvider(bool onEnable)
{
Provider = CreateProvider(enable);
Provider = CreateProvider(onEnable);
// None providers are not IQueryable.
Queryable = Provider as IQueryable;
}
@@ -689,4 +845,30 @@ 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
}